001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2015-2016 ForgeRock AS. 015 */ 016package org.opends.server.loggers; 017 018import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; 019 020import static java.util.Arrays.asList; 021import static org.opends.messages.LoggerMessages.*; 022import static org.forgerock.audit.AuditServiceBuilder.newAuditService; 023import static org.forgerock.audit.events.EventTopicsMetaDataBuilder.coreTopicSchemas; 024import static org.forgerock.audit.json.AuditJsonConfig.registerHandlerToService; 025import static org.opends.server.util.StaticUtils.getFileForPath; 026 027import java.io.BufferedInputStream; 028import java.io.BufferedReader; 029import java.io.File; 030import java.io.FileInputStream; 031import java.io.FileReader; 032import java.io.IOException; 033import java.io.InputStream; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.HashMap; 037import java.util.List; 038import java.util.Map; 039import java.util.SortedSet; 040import java.util.concurrent.ConcurrentHashMap; 041import java.util.concurrent.atomic.AtomicBoolean; 042import java.util.regex.Pattern; 043 044import org.forgerock.audit.AuditException; 045import org.forgerock.audit.AuditService; 046import org.forgerock.audit.AuditServiceBuilder; 047import org.forgerock.audit.AuditServiceConfiguration; 048import org.forgerock.audit.AuditServiceProxy; 049import org.forgerock.audit.DependencyProvider; 050import org.forgerock.audit.events.EventTopicsMetaData; 051import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration.FileRetention; 052import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration.FileRotation; 053import org.forgerock.audit.filter.FilterPolicy; 054import org.forgerock.audit.handlers.csv.CsvAuditEventHandler; 055import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration; 056import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.CsvFormatting; 057import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.CsvSecurity; 058import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.EventBufferingConfiguration; 059import org.forgerock.audit.json.AuditJsonConfig; 060import org.forgerock.i18n.slf4j.LocalizedLogger; 061import org.forgerock.json.JsonValue; 062import org.forgerock.json.resource.RequestHandler; 063import org.forgerock.opendj.config.ConfigurationFramework; 064import org.forgerock.opendj.config.server.ConfigException; 065import org.opends.server.admin.server.ServerManagementContext; 066import org.opends.server.admin.std.server.CsvFileAccessLogPublisherCfg; 067import org.opends.server.admin.std.server.CsvFileHTTPAccessLogPublisherCfg; 068import org.opends.server.admin.std.server.ExternalAccessLogPublisherCfg; 069import org.opends.server.admin.std.server.ExternalHTTPAccessLogPublisherCfg; 070import org.opends.server.admin.std.server.FileCountLogRetentionPolicyCfg; 071import org.opends.server.admin.std.server.FixedTimeLogRotationPolicyCfg; 072import org.opends.server.admin.std.server.FreeDiskSpaceLogRetentionPolicyCfg; 073import org.opends.server.admin.std.server.LogPublisherCfg; 074import org.opends.server.admin.std.server.LogRetentionPolicyCfg; 075import org.opends.server.admin.std.server.LogRotationPolicyCfg; 076import org.opends.server.admin.std.server.RootCfg; 077import org.opends.server.admin.std.server.SizeLimitLogRetentionPolicyCfg; 078import org.opends.server.admin.std.server.SizeLimitLogRotationPolicyCfg; 079import org.opends.server.admin.std.server.TimeLimitLogRotationPolicyCfg; 080import org.opends.server.config.ConfigEntry; 081import org.opends.server.core.DirectoryServer; 082import org.forgerock.opendj.ldap.DN; 083import org.opends.server.util.StaticUtils; 084 085/** 086 * Entry point for the common audit facility. 087 * <p> 088 * This class manages the AuditService instances and Audit Event Handlers that correspond to the 089 * publishers defined in OpenDJ configuration. 090 * <p> 091 * In theory there should be only one instance of AuditService for all the event handlers but 092 * defining one service per handler allow to perform filtering at the DJ server level. 093 */ 094public class CommonAudit 095{ 096 /** Transaction id used when the incoming request does not contain a transaction id. */ 097 public static final String DEFAULT_TRANSACTION_ID = "0"; 098 099 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 100 101 private static final String AUDIT_SERVICE_JSON_CONFIGURATION_FILE = "audit-config.json"; 102 103 /** Dependency provider used to instantiate the handlers. */ 104 private final DependencyProvider dependencyProvider; 105 106 /** Configuration framework is used to get an up-to-date class loader with any external library available. */ 107 private final ConfigurationFramework configurationFramework; 108 109 /** Cache of audit services per configuration entry normalized name. */ 110 private final Map<String, AuditServiceProxy> auditServiceCache = new ConcurrentHashMap<>(10); 111 112 /** Cache of PublisherConfig per http access configuration entry normalized name. */ 113 private final Map<String, PublisherConfig> httpAccessPublishers = new ConcurrentHashMap<>(5); 114 115 /** Cache of PublisherConfig per access configuration entry normalized name. */ 116 private final Map<String, PublisherConfig> accessPublishers = new ConcurrentHashMap<>(5); 117 118 /** Audit service shared by all HTTP access publishers. */ 119 private final AuditServiceProxy httpAccessAuditService; 120 121 private final AtomicBoolean trustTransactionIds = new AtomicBoolean(false); 122 123 /** 124 * Creates the common audit. 125 * 126 * @throws ConfigException 127 * If an error occurs. 128 */ 129 public CommonAudit() throws ConfigException 130 { 131 configurationFramework = ConfigurationFramework.getInstance(); 132 this.dependencyProvider = new CommonAuditDependencyProvider(); 133 this.httpAccessAuditService = createAuditServiceWithoutHandlers(); 134 } 135 136 /** 137 * Indicates if transactionIds received from requests should be trusted. 138 * 139 * @return {@code true} if transactionIds should be trusted, {@code false} otherwise 140 */ 141 public boolean shouldTrustTransactionIds() 142 { 143 return trustTransactionIds.get(); 144 } 145 146 /** 147 * Sets the indicator for transactionIds trusting. 148 * 149 * @param shouldTrust 150 * {@code true} if transactionIds should be trusted, {@code false} 151 * otherwise 152 */ 153 public void setTrustTransactionIds(boolean shouldTrust) 154 { 155 trustTransactionIds.set(shouldTrust); 156 } 157 158 private AuditServiceProxy createAuditServiceWithoutHandlers() throws ConfigException 159 { 160 try 161 { 162 return buildAuditService(new AuditServiceSetup() 163 { 164 @Override 165 public void addHandlers(AuditServiceBuilder builder) 166 { 167 // no handler to add 168 } 169 }); 170 } 171 catch (IOException | ConfigException | AuditException e) 172 { 173 throw new ConfigException(ERR_COMMON_AUDIT_CREATE.get(e), e); 174 } 175 } 176 177 /** 178 * Returns the Common Audit request handler for the provided configuration. 179 * 180 * @param config 181 * The log publisher configuration 182 * @return the request handler associated to the log publisher 183 * @throws ConfigException 184 * If an error occurs 185 */ 186 public RequestHandler getRequestHandler(LogPublisherCfg config) throws ConfigException 187 { 188 if (new PublisherConfig(config).isHttpAccessLog()) 189 { 190 return httpAccessAuditService; 191 } 192 return auditServiceCache.get(getConfigNormalizedName(config)); 193 } 194 195 /** 196 * Adds or updates the publisher corresponding to the provided configuration to common audit. 197 * 198 * @param newConfig 199 * Configuration of the publisher 200 * @throws ConfigException 201 * If an error occurs. 202 */ 203 public void addOrUpdatePublisher(final LogPublisherCfg newConfig) throws ConfigException 204 { 205 if (newConfig.isEnabled()) 206 { 207 logger.trace(String.format("Setting up common audit for configuration entry: %s", newConfig.dn())); 208 try 209 { 210 final PublisherConfig newPublisher = new PublisherConfig(newConfig); 211 String normalizedName = getConfigNormalizedName(newConfig); 212 if (newPublisher.isHttpAccessLog()) 213 { 214 // if an old version exists, it is replaced by the new one 215 httpAccessPublishers.put(normalizedName, newPublisher); 216 buildAuditService(httpAccessAuditServiceSetup()); 217 } 218 else // all other logs 219 { 220 final AuditServiceProxy existingService = auditServiceCache.get(normalizedName); 221 AuditServiceProxy auditService = buildAuditService(new AuditServiceSetup(existingService) 222 { 223 @Override 224 public void addHandlers(AuditServiceBuilder builder) throws ConfigException 225 { 226 registerHandlerName(newPublisher.getName()); 227 addHandlerToBuilder(newPublisher, builder); 228 } 229 }); 230 auditServiceCache.put(normalizedName, auditService); 231 accessPublishers.put(normalizedName, newPublisher); 232 } 233 } 234 catch (Exception e) 235 { 236 throw new ConfigException(ERR_COMMON_AUDIT_ADD_OR_UPDATE_LOG_PUBLISHER.get(newConfig.dn(), e), e); 237 } 238 } 239 } 240 241 /** 242 * Removes the publisher corresponding to the provided configuration from common audit. 243 * 244 * @param config 245 * Configuration of publisher to remove 246 * @throws ConfigException 247 * If an error occurs. 248 */ 249 public void removePublisher(LogPublisherCfg config) throws ConfigException 250 { 251 logger.trace(String.format("Shutting down common audit for configuration entry:", config.dn())); 252 String normalizedName = getConfigNormalizedName(config); 253 try 254 { 255 if (httpAccessPublishers.containsKey(normalizedName)) 256 { 257 httpAccessPublishers.remove(normalizedName); 258 buildAuditService(httpAccessAuditServiceSetup()); 259 } 260 else if (accessPublishers.containsKey(normalizedName)) 261 { 262 accessPublishers.remove(normalizedName); 263 AuditServiceProxy auditService = auditServiceCache.remove(normalizedName); 264 if (auditService != null) 265 { 266 auditService.shutdown(); 267 } 268 } 269 // else it is not a registered publisher, nothing to do 270 } 271 catch (Exception e) 272 { 273 throw new ConfigException(ERR_COMMON_AUDIT_REMOVE_LOG_PUBLISHER.get(config.dn(), e), e); 274 } 275 } 276 277 /** Shutdown common audit. */ 278 public void shutdown() 279 { 280 httpAccessAuditService.shutdown(); 281 for (AuditServiceProxy service : auditServiceCache.values()) 282 { 283 service.shutdown(); 284 } 285 } 286 287 private AuditServiceSetup httpAccessAuditServiceSetup() 288 { 289 return new AuditServiceSetup(httpAccessAuditService) 290 { 291 @Override 292 public void addHandlers(AuditServiceBuilder builder) throws ConfigException 293 { 294 for (PublisherConfig publisher : httpAccessPublishers.values()) 295 { 296 registerHandlerName(publisher.getName()); 297 addHandlerToBuilder(publisher, builder); 298 } 299 } 300 }; 301 } 302 303 /** 304 * Strategy for the setup of AuditService. 305 * <p> 306 * Unless no handler must be added, this class should be extended and 307 * implementations should override the {@code addHandlers()} method. 308 */ 309 static abstract class AuditServiceSetup 310 { 311 private final AuditServiceProxy existingAuditServiceProxy; 312 private final List<String> names = new ArrayList<>(); 313 314 /** Creation with no existing audit service. */ 315 AuditServiceSetup() 316 { 317 this.existingAuditServiceProxy = null; 318 } 319 320 /** Creation with an existing audit service. */ 321 AuditServiceSetup(AuditServiceProxy existingAuditService) 322 { 323 this.existingAuditServiceProxy = existingAuditService; 324 } 325 326 abstract void addHandlers(AuditServiceBuilder builder) throws ConfigException; 327 328 void registerHandlerName(String name) 329 { 330 names.add(name); 331 } 332 333 List<String> getHandlerNames() 334 { 335 return names; 336 } 337 338 boolean mustCreateAuditServiceProxy() 339 { 340 return existingAuditServiceProxy == null; 341 } 342 343 AuditServiceProxy getExistingAuditServiceProxy() 344 { 345 return existingAuditServiceProxy; 346 } 347 348 } 349 350 private AuditServiceProxy buildAuditService(AuditServiceSetup setup) 351 throws IOException, AuditException, ConfigException 352 { 353 final JsonValue jsonConfig; 354 try (InputStream input = getClass().getResourceAsStream(AUDIT_SERVICE_JSON_CONFIGURATION_FILE)) 355 { 356 jsonConfig = AuditJsonConfig.getJson(input); 357 } 358 359 EventTopicsMetaData eventTopicsMetaData = coreTopicSchemas() 360 .withCoreTopicSchemaExtensions(jsonConfig.get("extensions")) 361 .withAdditionalTopicSchemas(jsonConfig.get("additionalTopics")) 362 .build(); 363 AuditServiceBuilder builder = newAuditService() 364 .withEventTopicsMetaData(eventTopicsMetaData) 365 .withDependencyProvider(dependencyProvider); 366 367 setup.addHandlers(builder); 368 369 AuditServiceConfiguration auditConfig = new AuditServiceConfiguration(); 370 auditConfig.setAvailableAuditEventHandlers(setup.getHandlerNames()); 371 auditConfig.setFilterPolicies(getFilterPoliciesToPreventHttpHeadersLogging()); 372 builder.withConfiguration(auditConfig); 373 AuditService audit = builder.build(); 374 375 final AuditServiceProxy proxy; 376 if (setup.mustCreateAuditServiceProxy()) 377 { 378 proxy = new AuditServiceProxy(audit); 379 logger.trace("Starting up new common audit service"); 380 proxy.startup(); 381 } 382 else 383 { 384 proxy = setup.getExistingAuditServiceProxy(); 385 proxy.setDelegate(audit); 386 logger.trace("Starting up existing updated common audit service"); 387 } 388 return proxy; 389 } 390 391 /** 392 * Build filter policies at the AuditService level to prevent logging of the headers for HTTP requests. 393 * <p> 394 * HTTP Headers may contains authentication information. 395 */ 396 private Map<String, FilterPolicy> getFilterPoliciesToPreventHttpHeadersLogging() 397 { 398 Map<String, FilterPolicy> filterPolicies = new HashMap<>(); 399 FilterPolicy policy = new FilterPolicy(); 400 policy.setExcludeIf(asList("/http-access/http/request/headers")); 401 filterPolicies.put("field", policy); 402 return filterPolicies; 403 } 404 405 private void addHandlerToBuilder(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException 406 { 407 if (publisher.isCsv()) 408 { 409 addCsvHandler(publisher, builder); 410 } 411 else if (publisher.isExternal()) 412 { 413 addExternalHandler(publisher, builder); 414 } 415 else 416 { 417 throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_HANDLER_TYPE.get(publisher.getDn())); 418 } 419 } 420 421 /** Add a handler defined externally in a JSON configuration file. */ 422 private void addExternalHandler(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException 423 { 424 ExternalConfigData config = publisher.getExternalConfig(); 425 File configFile = getFileForPath(config.getConfigurationFile()); 426 try (InputStream input = new BufferedInputStream(new FileInputStream(configFile))) 427 { 428 JsonValue jsonConfig = AuditJsonConfig.getJson(input); 429 registerHandlerToService(jsonConfig, builder, configurationFramework.getClassLoader()); 430 } 431 catch (IOException e) 432 { 433 throw new ConfigException(ERR_COMMON_AUDIT_EXTERNAL_HANDLER_JSON_FILE.get(configFile, publisher.getDn(), e), e); 434 } 435 catch (Exception e) 436 { 437 throw new ConfigException(ERR_COMMON_AUDIT_EXTERNAL_HANDLER_CREATION.get(publisher.getDn(), e), e); 438 } 439 } 440 441 private void addCsvHandler(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException 442 { 443 String name = publisher.getName(); 444 try 445 { 446 CsvConfigData config = publisher.getCsvConfig(); 447 CsvAuditEventHandlerConfiguration csvConfig = new CsvAuditEventHandlerConfiguration(); 448 File logDirectory = getFileForPath(config.getLogDirectory()); 449 csvConfig.setLogDirectory(logDirectory.getAbsolutePath()); 450 csvConfig.setName(name); 451 csvConfig.setTopics(Collections.singleton(publisher.getCommonAuditTopic())); 452 453 addCsvHandlerFormattingConfig(config, csvConfig); 454 addCsvHandlerBufferingConfig(config, csvConfig); 455 addCsvHandlerSecureConfig(publisher, config, csvConfig); 456 addCsvHandlerRotationConfig(publisher, config, csvConfig); 457 addCsvHandlerRetentionConfig(publisher, config, csvConfig); 458 459 builder.withAuditEventHandler(CsvAuditEventHandler.class, csvConfig); 460 } 461 catch (Exception e) 462 { 463 throw new ConfigException(ERR_COMMON_AUDIT_CSV_HANDLER_CREATION.get(publisher.getDn(), e), e); 464 } 465 } 466 467 private void addCsvHandlerFormattingConfig(CsvConfigData config, CsvAuditEventHandlerConfiguration auditConfig) 468 throws ConfigException 469 { 470 CsvFormatting formatting = new CsvFormatting(); 471 formatting.setQuoteChar(config.getQuoteChar()); 472 formatting.setDelimiterChar(config.getDelimiterChar()); 473 String endOfLineSymbols = config.getEndOfLineSymbols(); 474 if (endOfLineSymbols != null && !endOfLineSymbols.isEmpty()) 475 { 476 formatting.setEndOfLineSymbols(endOfLineSymbols); 477 } 478 auditConfig.setFormatting(formatting); 479 } 480 481 private void addCsvHandlerBufferingConfig(CsvConfigData config, CsvAuditEventHandlerConfiguration auditConfig) 482 { 483 EventBufferingConfiguration bufferingConfig = new EventBufferingConfiguration(); 484 bufferingConfig.setEnabled(config.isAsynchronous()); 485 bufferingConfig.setAutoFlush(config.isAutoFlush()); 486 auditConfig.setBufferingConfiguration(bufferingConfig); 487 } 488 489 private void addCsvHandlerSecureConfig(PublisherConfig publisher, CsvConfigData config, 490 CsvAuditEventHandlerConfiguration auditConfig) 491 { 492 if (config.isTamperEvident()) 493 { 494 CsvSecurity security = new CsvSecurity(); 495 security.setSignatureInterval(config.getSignatureTimeInterval() + "ms"); 496 security.setEnabled(true); 497 String keyStoreFile = config.getKeystoreFile(); 498 security.setFilename(getFileForPath(keyStoreFile).getPath()); 499 security.setPassword(getSecurePassword(publisher, config)); 500 auditConfig.setSecurity(security); 501 } 502 } 503 504 private void addCsvHandlerRotationConfig(PublisherConfig publisher, CsvConfigData config, 505 CsvAuditEventHandlerConfiguration auditConfig) throws ConfigException 506 { 507 RootCfg root = ServerManagementContext.getInstance().getRootConfiguration(); 508 SortedSet<String> rotationPolicies = config.getRotationPolicies(); 509 if (rotationPolicies.isEmpty()) 510 { 511 return; 512 } 513 514 FileRotation fileRotation = new FileRotation(); 515 fileRotation.setRotationEnabled(true); 516 for (final String policy : rotationPolicies) 517 { 518 LogRotationPolicyCfg policyConfig = root.getLogRotationPolicy(policy); 519 if (policyConfig instanceof FixedTimeLogRotationPolicyCfg) 520 { 521 List<String> times = convertTimesOfDay(publisher, (FixedTimeLogRotationPolicyCfg) policyConfig); 522 fileRotation.setRotationTimes(times); 523 } 524 else if (policyConfig instanceof SizeLimitLogRotationPolicyCfg) 525 { 526 fileRotation.setMaxFileSize(((SizeLimitLogRotationPolicyCfg) policyConfig).getFileSizeLimit()); 527 } 528 else if (policyConfig instanceof TimeLimitLogRotationPolicyCfg) 529 { 530 long rotationInterval = ((TimeLimitLogRotationPolicyCfg) policyConfig).getRotationInterval(); 531 fileRotation.setRotationInterval(String.valueOf(rotationInterval) + " ms"); 532 } 533 else 534 { 535 throw new ConfigException( 536 ERR_COMMON_AUDIT_UNSUPPORTED_LOG_ROTATION_POLICY.get(publisher.getDn(), policyConfig.dn())); 537 } 538 } 539 auditConfig.setFileRotation(fileRotation); 540 } 541 542 private void addCsvHandlerRetentionConfig(PublisherConfig publisher, CsvConfigData config, 543 CsvAuditEventHandlerConfiguration auditConfig) throws ConfigException 544 { 545 RootCfg root = ServerManagementContext.getInstance().getRootConfiguration(); 546 SortedSet<String> retentionPolicies = config.getRetentionPolicies(); 547 if (retentionPolicies.isEmpty()) 548 { 549 return; 550 } 551 552 FileRetention fileRetention = new FileRetention(); 553 for (final String policy : retentionPolicies) 554 { 555 LogRetentionPolicyCfg policyConfig = root.getLogRetentionPolicy(policy); 556 if (policyConfig instanceof FileCountLogRetentionPolicyCfg) 557 { 558 fileRetention.setMaxNumberOfHistoryFiles(((FileCountLogRetentionPolicyCfg) policyConfig).getNumberOfFiles()); 559 } 560 else if (policyConfig instanceof FreeDiskSpaceLogRetentionPolicyCfg) 561 { 562 fileRetention.setMinFreeSpaceRequired(((FreeDiskSpaceLogRetentionPolicyCfg) policyConfig).getFreeDiskSpace()); 563 } 564 else if (policyConfig instanceof SizeLimitLogRetentionPolicyCfg) 565 { 566 fileRetention.setMaxDiskSpaceToUse(((SizeLimitLogRetentionPolicyCfg) policyConfig).getDiskSpaceUsed()); 567 } 568 else 569 { 570 throw new ConfigException( 571 ERR_COMMON_AUDIT_UNSUPPORTED_LOG_RETENTION_POLICY.get(publisher.getDn(), policyConfig.dn())); 572 } 573 } 574 auditConfig.setFileRetention(fileRetention); 575 } 576 577 /** 578 * Convert the set of provided times of day using 24-hour format "HHmm" to a list of 579 * times of day using duration in minutes, e.g "20 minutes". 580 * <p> 581 * Example: "0230" => "150 minutes" 582 */ 583 private List<String> convertTimesOfDay(PublisherConfig publisher, FixedTimeLogRotationPolicyCfg policyConfig) 584 throws ConfigException 585 { 586 SortedSet<String> timesOfDay = policyConfig.getTimeOfDay(); 587 List<String> times = new ArrayList<>(); 588 for (String timeOfDay : timesOfDay) 589 { 590 try 591 { 592 int time = Integer.valueOf(timeOfDay.substring(0, 2)) * 60 + Integer.valueOf(timeOfDay.substring(2, 4)); 593 times.add(String.valueOf(time) + " minutes"); 594 } 595 catch (NumberFormatException | IndexOutOfBoundsException e) 596 { 597 throw new ConfigException(ERR_COMMON_AUDIT_INVALID_TIME_OF_DAY.get(publisher.getDn(), timeOfDay, 598 StaticUtils.stackTraceToSingleLineString(e))); 599 } 600 } 601 return times; 602 } 603 604 private String getSecurePassword(PublisherConfig publisher, CsvConfigData config) 605 { 606 String fileName = config.getKeystorePinFile(); 607 File pinFile = getFileForPath(fileName); 608 609 if (!pinFile.exists()) 610 { 611 logger.warn(ERR_COMMON_AUDIT_KEYSTORE_PIN_FILE_MISSING.get(publisher.getDn(), pinFile)); 612 return ""; 613 } 614 615 try (BufferedReader br = new BufferedReader(new FileReader(pinFile))) 616 { 617 String pinStr = br.readLine(); 618 if (pinStr == null) 619 { 620 logger.warn(ERR_COMMON_AUDIT_KEYSTORE_PIN_FILE_CONTAINS_EMPTY_PIN.get(publisher.getDn(), pinFile)); 621 return ""; 622 } 623 return pinStr; 624 } 625 catch (IOException ioe) 626 { 627 logger.warn(ERR_COMMON_AUDIT_ERROR_READING_KEYSTORE_PIN_FILE.get(publisher.getDn(), pinFile, 628 stackTraceToSingleLineString(ioe)), ioe); 629 return ""; 630 } 631 } 632 633 /** 634 * Indicates if the provided log publisher configuration corresponds to a common audit publisher. 635 * <p> 636 * The common audit publisher may not already exist. 637 * <p> 638 * This method must not be used when the corresponding configuration is deleted, because it 639 * implies checking the corresponding configuration entry in the server. 640 * 641 * @param config 642 * The log publisher configuration. 643 * @return {@code true} if publisher corresponds to a common audit publisher 644 * @throws ConfigException 645 * If an error occurs 646 */ 647 public boolean isCommonAuditConfig(LogPublisherCfg config) throws ConfigException 648 { 649 return new PublisherConfig(config).isCommonAudit(); 650 } 651 652 /** 653 * Indicates if the provided log publisher configuration corresponds to a common audit publisher. 654 * 655 * @param config 656 * The log publisher configuration. 657 * @return {@code true} if publisher is defined for common audit, {@code false} otherwise 658 * @throws ConfigException 659 * If an error occurs 660 */ 661 public boolean isExistingCommonAuditConfig(LogPublisherCfg config) throws ConfigException 662 { 663 String name = getConfigNormalizedName(config); 664 return accessPublishers.containsKey(name) || httpAccessPublishers.containsKey(name); 665 } 666 667 /** 668 * Indicates if HTTP access logging is enabled for common audit. 669 * 670 * @return {@code true} if there is at least one HTTP access logger enabled for common audit. 671 */ 672 public boolean isHttpAccessLogEnabled() 673 { 674 return !httpAccessPublishers.isEmpty(); 675 } 676 677 private String getConfigNormalizedName(LogPublisherCfg config) 678 { 679 return config.dn().toNormalizedUrlSafeString(); 680 } 681 682 /** 683 * Returns the audit service that manages HTTP Access logging. 684 * 685 * @return the request handler that accepts audit events 686 */ 687 public RequestHandler getAuditServiceForHttpAccessLog() 688 { 689 return httpAccessAuditService; 690 } 691 692 /** 693 * This class hides all ugly code needed to determine which type of publisher and audit event handler is needed. 694 * <p> 695 * In particular, it allows to retrieve a common configuration that can be used for log publishers that 696 * publish to the same kind of handler. 697 * For example: for CSV handler, DJ configurations for the log publishers contain the same methods but 698 * do not have a common interface (CsvFileAccessLogPublisherCfg vs CsvFileHTTPAccessLogPublisherCfg). 699 */ 700 private static class PublisherConfig 701 { 702 private final LogPublisherCfg config; 703 private final boolean isCommonAudit; 704 private LogType logType; 705 private AuditType auditType; 706 707 PublisherConfig(LogPublisherCfg config) throws ConfigException 708 { 709 this.config = config; 710 ConfigEntry configEntry = DirectoryServer.getConfigEntry(config.dn()); 711 if (configEntry.hasObjectClass("ds-cfg-csv-file-access-log-publisher")) 712 { 713 auditType = AuditType.CSV; 714 logType = LogType.ACCESS; 715 } 716 else if (configEntry.hasObjectClass("ds-cfg-csv-file-http-access-log-publisher")) 717 { 718 auditType = AuditType.CSV; 719 logType = LogType.HTTP_ACCESS; 720 } 721 else if (configEntry.hasObjectClass("ds-cfg-external-access-log-publisher")) 722 { 723 auditType = AuditType.EXTERNAL; 724 logType = LogType.ACCESS; 725 } 726 else if (configEntry.hasObjectClass("ds-cfg-external-http-access-log-publisher")) 727 { 728 auditType = AuditType.EXTERNAL; 729 logType = LogType.HTTP_ACCESS; 730 } 731 isCommonAudit = auditType != null; 732 } 733 734 DN getDn() 735 { 736 return config.dn(); 737 } 738 739 String getName() 740 { 741 return config.dn().rdn().getFirstAVA().getAttributeValue().toString(); 742 } 743 744 String getCommonAuditTopic() throws ConfigException 745 { 746 if (isAccessLog()) 747 { 748 return "ldap-access"; 749 } 750 else if (isHttpAccessLog()) 751 { 752 return "http-access"; 753 } 754 throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn())); 755 } 756 757 boolean isExternal() 758 { 759 return AuditType.EXTERNAL == auditType; 760 } 761 762 boolean isCsv() 763 { 764 return AuditType.CSV == auditType; 765 } 766 767 boolean isAccessLog() 768 { 769 return LogType.ACCESS == logType; 770 } 771 772 boolean isHttpAccessLog() 773 { 774 return LogType.HTTP_ACCESS == logType; 775 } 776 777 boolean isCommonAudit() 778 { 779 return isCommonAudit; 780 } 781 782 CsvConfigData getCsvConfig() throws ConfigException 783 { 784 if (isAccessLog()) 785 { 786 CsvFileAccessLogPublisherCfg conf = (CsvFileAccessLogPublisherCfg) config; 787 return new CsvConfigData(conf.getLogDirectory(), conf.getCsvQuoteChar(), conf.getCsvDelimiterChar(), conf 788 .getCsvEolSymbols(), conf.isAsynchronous(), conf.isAutoFlush(), conf.isTamperEvident(), conf 789 .getSignatureTimeInterval(), conf.getKeyStoreFile(), conf.getKeyStorePinFile(), conf.getRotationPolicy(), 790 conf.getRetentionPolicy()); 791 } 792 if (isHttpAccessLog()) 793 { 794 CsvFileHTTPAccessLogPublisherCfg conf = (CsvFileHTTPAccessLogPublisherCfg) config; 795 return new CsvConfigData(conf.getLogDirectory(), conf.getCsvQuoteChar(), conf.getCsvDelimiterChar(), conf 796 .getCsvEolSymbols(), conf.isAsynchronous(), conf.isAutoFlush(), conf.isTamperEvident(), conf 797 .getSignatureTimeInterval(), conf.getKeyStoreFile(), conf.getKeyStorePinFile(), conf.getRotationPolicy(), 798 conf.getRetentionPolicy()); 799 } 800 throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn())); 801 } 802 803 ExternalConfigData getExternalConfig() throws ConfigException 804 { 805 if (isAccessLog()) 806 { 807 ExternalAccessLogPublisherCfg conf = (ExternalAccessLogPublisherCfg) config; 808 return new ExternalConfigData(conf.getConfigFile()); 809 } 810 if (isHttpAccessLog()) 811 { 812 ExternalHTTPAccessLogPublisherCfg conf = (ExternalHTTPAccessLogPublisherCfg) config; 813 return new ExternalConfigData(conf.getConfigFile()); 814 } 815 throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn())); 816 } 817 818 @Override 819 public boolean equals(Object obj) 820 { 821 if (this == obj) 822 { 823 return true; 824 } 825 if (!(obj instanceof PublisherConfig)) 826 { 827 return false; 828 } 829 PublisherConfig other = (PublisherConfig) obj; 830 return config.dn().equals(other.config.dn()); 831 } 832 833 @Override 834 public int hashCode() 835 { 836 return config.dn().hashCode(); 837 } 838 839 } 840 841 /** Types of audit handlers managed. */ 842 private enum AuditType 843 { 844 CSV, EXTERNAL 845 } 846 847 /** Types of log managed. */ 848 private enum LogType 849 { 850 ACCESS, HTTP_ACCESS 851 } 852 853 /** 854 * Contains the parameters for a CSV handler. 855 * <p> 856 * OpenDJ log publishers that logs to a CSV handler have the same parameters but do not share 857 * a common ancestor with all the parameters (e.g Access Log, HTTP Access Log, ...), hence this class 858 * is necessary to avoid duplicating code that setup the configuration of the CSV handler. 859 */ 860 private static class CsvConfigData 861 { 862 private final String logDirectory; 863 private final String eolSymbols; 864 private final String delimiterChar; 865 private final String quoteChar; 866 private final boolean asynchronous; 867 private final boolean autoFlush; 868 private final boolean tamperEvident; 869 private final long signatureTimeInterval; 870 private final String keystoreFile; 871 private final String keystorePinFile; 872 private final SortedSet<String> rotationPolicies; 873 private final SortedSet<String> retentionPolicies; 874 875 CsvConfigData(String logDirectory, String quoteChar, String delimiterChar, String eolSymbols, boolean asynchronous, 876 boolean autoFlush, boolean tamperEvident, long signatureTimeInterval, String keystoreFile, 877 String keystorePinFile, SortedSet<String> rotationPolicies, SortedSet<String> retentionPolicies) 878 { 879 this.logDirectory = logDirectory; 880 this.quoteChar = quoteChar; 881 this.delimiterChar = delimiterChar; 882 this.eolSymbols = eolSymbols; 883 this.asynchronous = asynchronous; 884 this.autoFlush = autoFlush; 885 this.tamperEvident = tamperEvident; 886 this.signatureTimeInterval = signatureTimeInterval; 887 this.keystoreFile = keystoreFile; 888 this.keystorePinFile = keystorePinFile; 889 this.rotationPolicies = rotationPolicies; 890 this.retentionPolicies = retentionPolicies; 891 } 892 893 String getEndOfLineSymbols() 894 { 895 return eolSymbols; 896 } 897 898 char getDelimiterChar() throws ConfigException 899 { 900 String filtered = delimiterChar.replaceAll(Pattern.quote("\\"), ""); 901 if (filtered.length() != 1) 902 { 903 throw new ConfigException(ERR_COMMON_AUDIT_CSV_HANDLER_DELIMITER_CHAR.get("", filtered)); 904 } 905 return filtered.charAt(0); 906 } 907 908 public char getQuoteChar() throws ConfigException 909 { 910 String filtered = quoteChar.replaceAll(Pattern.quote("\\"), ""); 911 if (filtered.length() != 1) 912 { 913 throw new ConfigException(ERR_COMMON_AUDIT_CSV_HANDLER_QUOTE_CHAR.get("", filtered)); 914 } 915 return filtered.charAt(0); 916 } 917 918 String getLogDirectory() 919 { 920 return logDirectory; 921 } 922 923 boolean isAsynchronous() 924 { 925 return asynchronous; 926 } 927 928 boolean isAutoFlush() 929 { 930 return autoFlush; 931 } 932 933 boolean isTamperEvident() 934 { 935 return tamperEvident; 936 } 937 938 long getSignatureTimeInterval() 939 { 940 return signatureTimeInterval; 941 } 942 943 String getKeystoreFile() 944 { 945 return keystoreFile; 946 } 947 948 String getKeystorePinFile() 949 { 950 return keystorePinFile; 951 } 952 953 SortedSet<String> getRotationPolicies() 954 { 955 return rotationPolicies; 956 } 957 958 SortedSet<String> getRetentionPolicies() 959 { 960 return retentionPolicies; 961 } 962 } 963 964 /** 965 * Contains the parameters for an external handler. 966 * <p> 967 * OpenDJ log publishers that logs to an external handler have the same 968 * parameters but do not share a common ancestor with all the parameters (e.g 969 * Access Log, HTTP Access Log, ...), hence this class is necessary to avoid 970 * duplicating code that setup the configuration of an external handler. 971 */ 972 private static class ExternalConfigData 973 { 974 private final String configurationFile; 975 976 ExternalConfigData(String configurationFile) 977 { 978 this.configurationFile = configurationFile; 979 } 980 981 String getConfigurationFile() 982 { 983 return configurationFile; 984 } 985 } 986 987}