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