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.forgerock.opendj.ldap.DN; 066import org.forgerock.opendj.ldap.schema.ObjectClass; 067import org.forgerock.opendj.server.config.server.CsvFileAccessLogPublisherCfg; 068import org.forgerock.opendj.server.config.server.CsvFileHTTPAccessLogPublisherCfg; 069import org.forgerock.opendj.server.config.server.ExternalAccessLogPublisherCfg; 070import org.forgerock.opendj.server.config.server.ExternalHTTPAccessLogPublisherCfg; 071import org.forgerock.opendj.server.config.server.FileCountLogRetentionPolicyCfg; 072import org.forgerock.opendj.server.config.server.FixedTimeLogRotationPolicyCfg; 073import org.forgerock.opendj.server.config.server.FreeDiskSpaceLogRetentionPolicyCfg; 074import org.forgerock.opendj.server.config.server.LogPublisherCfg; 075import org.forgerock.opendj.server.config.server.LogRetentionPolicyCfg; 076import org.forgerock.opendj.server.config.server.LogRotationPolicyCfg; 077import org.forgerock.opendj.server.config.server.SizeLimitLogRetentionPolicyCfg; 078import org.forgerock.opendj.server.config.server.SizeLimitLogRotationPolicyCfg; 079import org.forgerock.opendj.server.config.server.TimeLimitLogRotationPolicyCfg; 080import org.opends.server.core.DirectoryServer; 081import org.opends.server.core.ServerContext; 082import org.opends.server.types.Entry; 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 private final ServerContext serverContext; 124 125 /** 126 * Creates the common audit. 127 * 128 * @param serverContext 129 * The server context. 130 * 131 * @throws ConfigException 132 * If an error occurs. 133 */ 134 public CommonAudit(ServerContext serverContext) throws ConfigException 135 { 136 this.serverContext = serverContext; 137 configurationFramework = ConfigurationFramework.getInstance(); 138 this.dependencyProvider = new CommonAuditDependencyProvider(); 139 this.httpAccessAuditService = createAuditServiceWithoutHandlers(); 140 } 141 142 /** 143 * Indicates if transactionIds received from requests should be trusted. 144 * 145 * @return {@code true} if transactionIds should be trusted, {@code false} otherwise 146 */ 147 public boolean shouldTrustTransactionIds() 148 { 149 return trustTransactionIds.get(); 150 } 151 152 /** 153 * Sets the indicator for transactionIds trusting. 154 * 155 * @param shouldTrust 156 * {@code true} if transactionIds should be trusted, {@code false} 157 * otherwise 158 */ 159 public void setTrustTransactionIds(boolean shouldTrust) 160 { 161 trustTransactionIds.set(shouldTrust); 162 } 163 164 private AuditServiceProxy createAuditServiceWithoutHandlers() throws ConfigException 165 { 166 try 167 { 168 return buildAuditService(new AuditServiceSetup() 169 { 170 @Override 171 public void addHandlers(AuditServiceBuilder builder) 172 { 173 // no handler to add 174 } 175 }); 176 } 177 catch (IOException | ConfigException | AuditException e) 178 { 179 throw new ConfigException(ERR_COMMON_AUDIT_CREATE.get(e), e); 180 } 181 } 182 183 /** 184 * Returns the Common Audit request handler for the provided configuration. 185 * 186 * @param config 187 * The log publisher configuration 188 * @return the request handler associated to the log publisher 189 * @throws ConfigException 190 * If an error occurs 191 */ 192 public RequestHandler getRequestHandler(LogPublisherCfg config) throws ConfigException 193 { 194 if (new PublisherConfig(serverContext, config).isHttpAccessLog()) 195 { 196 return httpAccessAuditService; 197 } 198 return auditServiceCache.get(getConfigNormalizedName(config)); 199 } 200 201 /** 202 * Adds or updates the publisher corresponding to the provided configuration to common audit. 203 * 204 * @param newConfig 205 * Configuration of the publisher 206 * @throws ConfigException 207 * If an error occurs. 208 */ 209 public void addOrUpdatePublisher(final LogPublisherCfg newConfig) throws ConfigException 210 { 211 if (newConfig.isEnabled()) 212 { 213 logger.trace(String.format("Setting up common audit for configuration entry: %s", newConfig.dn())); 214 try 215 { 216 final PublisherConfig newPublisher = new PublisherConfig(serverContext, newConfig); 217 String normalizedName = getConfigNormalizedName(newConfig); 218 if (newPublisher.isHttpAccessLog()) 219 { 220 // if an old version exists, it is replaced by the new one 221 httpAccessPublishers.put(normalizedName, newPublisher); 222 buildAuditService(httpAccessAuditServiceSetup()); 223 } 224 else // all other logs 225 { 226 final AuditServiceProxy existingService = auditServiceCache.get(normalizedName); 227 AuditServiceProxy auditService = buildAuditService(new AuditServiceSetup(existingService) 228 { 229 @Override 230 public void addHandlers(AuditServiceBuilder builder) throws ConfigException 231 { 232 registerHandlerName(newPublisher.getName()); 233 addHandlerToBuilder(newPublisher, builder); 234 } 235 }); 236 auditServiceCache.put(normalizedName, auditService); 237 accessPublishers.put(normalizedName, newPublisher); 238 } 239 } 240 catch (Exception e) 241 { 242 throw new ConfigException(ERR_COMMON_AUDIT_ADD_OR_UPDATE_LOG_PUBLISHER.get(newConfig.dn(), e), e); 243 } 244 } 245 } 246 247 /** 248 * Removes the publisher corresponding to the provided configuration from common audit. 249 * 250 * @param config 251 * Configuration of publisher to remove 252 * @throws ConfigException 253 * If an error occurs. 254 */ 255 public void removePublisher(LogPublisherCfg config) throws ConfigException 256 { 257 logger.trace(String.format("Shutting down common audit for configuration entry:", config.dn())); 258 String normalizedName = getConfigNormalizedName(config); 259 try 260 { 261 if (httpAccessPublishers.containsKey(normalizedName)) 262 { 263 httpAccessPublishers.remove(normalizedName); 264 buildAuditService(httpAccessAuditServiceSetup()); 265 } 266 else if (accessPublishers.containsKey(normalizedName)) 267 { 268 accessPublishers.remove(normalizedName); 269 AuditServiceProxy auditService = auditServiceCache.remove(normalizedName); 270 if (auditService != null) 271 { 272 auditService.shutdown(); 273 } 274 } 275 // else it is not a registered publisher, nothing to do 276 } 277 catch (Exception e) 278 { 279 throw new ConfigException(ERR_COMMON_AUDIT_REMOVE_LOG_PUBLISHER.get(config.dn(), e), e); 280 } 281 } 282 283 /** Shutdown common audit. */ 284 public void shutdown() 285 { 286 httpAccessAuditService.shutdown(); 287 for (AuditServiceProxy service : auditServiceCache.values()) 288 { 289 service.shutdown(); 290 } 291 } 292 293 private AuditServiceSetup httpAccessAuditServiceSetup() 294 { 295 return new AuditServiceSetup(httpAccessAuditService) 296 { 297 @Override 298 public void addHandlers(AuditServiceBuilder builder) throws ConfigException 299 { 300 for (PublisherConfig publisher : httpAccessPublishers.values()) 301 { 302 registerHandlerName(publisher.getName()); 303 addHandlerToBuilder(publisher, builder); 304 } 305 } 306 }; 307 } 308 309 /** 310 * Strategy for the setup of AuditService. 311 * <p> 312 * Unless no handler must be added, this class should be extended and 313 * implementations should override the {@code addHandlers()} method. 314 */ 315 static abstract class AuditServiceSetup 316 { 317 private final AuditServiceProxy existingAuditServiceProxy; 318 private final List<String> names = new ArrayList<>(); 319 320 /** Creation with no existing audit service. */ 321 AuditServiceSetup() 322 { 323 this.existingAuditServiceProxy = null; 324 } 325 326 /** Creation with an existing audit service. */ 327 AuditServiceSetup(AuditServiceProxy existingAuditService) 328 { 329 this.existingAuditServiceProxy = existingAuditService; 330 } 331 332 abstract void addHandlers(AuditServiceBuilder builder) throws ConfigException; 333 334 void registerHandlerName(String name) 335 { 336 names.add(name); 337 } 338 339 List<String> getHandlerNames() 340 { 341 return names; 342 } 343 344 boolean mustCreateAuditServiceProxy() 345 { 346 return existingAuditServiceProxy == null; 347 } 348 349 AuditServiceProxy getExistingAuditServiceProxy() 350 { 351 return existingAuditServiceProxy; 352 } 353 354 } 355 356 private AuditServiceProxy buildAuditService(AuditServiceSetup setup) 357 throws IOException, AuditException, ConfigException 358 { 359 final JsonValue jsonConfig; 360 try (InputStream input = getClass().getResourceAsStream(AUDIT_SERVICE_JSON_CONFIGURATION_FILE)) 361 { 362 jsonConfig = AuditJsonConfig.getJson(input); 363 } 364 365 EventTopicsMetaData eventTopicsMetaData = coreTopicSchemas() 366 .withCoreTopicSchemaExtensions(jsonConfig.get("extensions")) 367 .withAdditionalTopicSchemas(jsonConfig.get("additionalTopics")) 368 .build(); 369 AuditServiceBuilder builder = newAuditService() 370 .withEventTopicsMetaData(eventTopicsMetaData) 371 .withDependencyProvider(dependencyProvider); 372 373 setup.addHandlers(builder); 374 375 AuditServiceConfiguration auditConfig = new AuditServiceConfiguration(); 376 auditConfig.setAvailableAuditEventHandlers(setup.getHandlerNames()); 377 auditConfig.setFilterPolicies(getFilterPoliciesToPreventHttpHeadersLogging()); 378 builder.withConfiguration(auditConfig); 379 AuditService audit = builder.build(); 380 381 final AuditServiceProxy proxy; 382 if (setup.mustCreateAuditServiceProxy()) 383 { 384 proxy = new AuditServiceProxy(audit); 385 logger.trace("Starting up new common audit service"); 386 proxy.startup(); 387 } 388 else 389 { 390 proxy = setup.getExistingAuditServiceProxy(); 391 proxy.setDelegate(audit); 392 logger.trace("Starting up existing updated common audit service"); 393 } 394 return proxy; 395 } 396 397 /** 398 * Build filter policies at the AuditService level to prevent logging of the headers for HTTP requests. 399 * <p> 400 * HTTP Headers may contains authentication information. 401 */ 402 private Map<String, FilterPolicy> getFilterPoliciesToPreventHttpHeadersLogging() 403 { 404 Map<String, FilterPolicy> filterPolicies = new HashMap<>(); 405 FilterPolicy policy = new FilterPolicy(); 406 policy.setExcludeIf(asList("/http-access/http/request/headers")); 407 filterPolicies.put("field", policy); 408 return filterPolicies; 409 } 410 411 private void addHandlerToBuilder(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException 412 { 413 if (publisher.isCsv()) 414 { 415 addCsvHandler(publisher, builder); 416 } 417 else if (publisher.isExternal()) 418 { 419 addExternalHandler(publisher, builder); 420 } 421 else 422 { 423 throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_HANDLER_TYPE.get(publisher.getDn())); 424 } 425 } 426 427 /** Add a handler defined externally in a JSON configuration file. */ 428 private void addExternalHandler(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException 429 { 430 ExternalConfigData config = publisher.getExternalConfig(); 431 File configFile = getFileForPath(config.getConfigurationFile()); 432 try (InputStream input = new BufferedInputStream(new FileInputStream(configFile))) 433 { 434 JsonValue jsonConfig = AuditJsonConfig.getJson(input); 435 registerHandlerToService(jsonConfig, builder, configurationFramework.getClassLoader()); 436 } 437 catch (IOException e) 438 { 439 throw new ConfigException(ERR_COMMON_AUDIT_EXTERNAL_HANDLER_JSON_FILE.get(configFile, publisher.getDn(), e), e); 440 } 441 catch (Exception e) 442 { 443 throw new ConfigException(ERR_COMMON_AUDIT_EXTERNAL_HANDLER_CREATION.get(publisher.getDn(), e), e); 444 } 445 } 446 447 private void addCsvHandler(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException 448 { 449 String name = publisher.getName(); 450 try 451 { 452 CsvConfigData config = publisher.getCsvConfig(); 453 CsvAuditEventHandlerConfiguration csvConfig = new CsvAuditEventHandlerConfiguration(); 454 File logDirectory = getFileForPath(config.getLogDirectory()); 455 csvConfig.setLogDirectory(logDirectory.getAbsolutePath()); 456 csvConfig.setName(name); 457 csvConfig.setTopics(Collections.singleton(publisher.getCommonAuditTopic())); 458 459 addCsvHandlerFormattingConfig(config, csvConfig); 460 addCsvHandlerBufferingConfig(config, csvConfig); 461 addCsvHandlerSecureConfig(publisher, config, csvConfig); 462 addCsvHandlerRotationConfig(publisher, config, csvConfig); 463 addCsvHandlerRetentionConfig(publisher, config, csvConfig); 464 465 builder.withAuditEventHandler(CsvAuditEventHandler.class, csvConfig); 466 } 467 catch (Exception e) 468 { 469 throw new ConfigException(ERR_COMMON_AUDIT_CSV_HANDLER_CREATION.get(publisher.getDn(), e), e); 470 } 471 } 472 473 private void addCsvHandlerFormattingConfig(CsvConfigData config, CsvAuditEventHandlerConfiguration auditConfig) 474 throws ConfigException 475 { 476 CsvFormatting formatting = new CsvFormatting(); 477 formatting.setQuoteChar(config.getQuoteChar()); 478 formatting.setDelimiterChar(config.getDelimiterChar()); 479 String endOfLineSymbols = config.getEndOfLineSymbols(); 480 if (endOfLineSymbols != null && !endOfLineSymbols.isEmpty()) 481 { 482 formatting.setEndOfLineSymbols(endOfLineSymbols); 483 } 484 auditConfig.setFormatting(formatting); 485 } 486 487 private void addCsvHandlerBufferingConfig(CsvConfigData config, CsvAuditEventHandlerConfiguration auditConfig) 488 { 489 EventBufferingConfiguration bufferingConfig = new EventBufferingConfiguration(); 490 bufferingConfig.setEnabled(config.isAsynchronous()); 491 bufferingConfig.setAutoFlush(config.isAutoFlush()); 492 auditConfig.setBufferingConfiguration(bufferingConfig); 493 } 494 495 private void addCsvHandlerSecureConfig(PublisherConfig publisher, CsvConfigData config, 496 CsvAuditEventHandlerConfiguration auditConfig) 497 { 498 if (config.isTamperEvident()) 499 { 500 CsvSecurity security = new CsvSecurity(); 501 security.setSignatureInterval(config.getSignatureTimeInterval() + "ms"); 502 security.setEnabled(true); 503 String keyStoreFile = config.getKeystoreFile(); 504 security.setFilename(getFileForPath(keyStoreFile).getPath()); 505 security.setPassword(getSecurePassword(publisher, config)); 506 auditConfig.setSecurity(security); 507 } 508 } 509 510 private void addCsvHandlerRotationConfig(PublisherConfig publisher, CsvConfigData config, 511 CsvAuditEventHandlerConfiguration auditConfig) throws ConfigException 512 { 513 SortedSet<String> rotationPolicies = config.getRotationPolicies(); 514 if (rotationPolicies.isEmpty()) 515 { 516 return; 517 } 518 519 FileRotation fileRotation = new FileRotation(); 520 fileRotation.setRotationEnabled(true); 521 for (final String policy : rotationPolicies) 522 { 523 LogRotationPolicyCfg policyConfig = serverContext.getRootConfig().getLogRotationPolicy(policy); 524 if (policyConfig instanceof FixedTimeLogRotationPolicyCfg) 525 { 526 List<String> times = convertTimesOfDay(publisher, (FixedTimeLogRotationPolicyCfg) policyConfig); 527 fileRotation.setRotationTimes(times); 528 } 529 else if (policyConfig instanceof SizeLimitLogRotationPolicyCfg) 530 { 531 fileRotation.setMaxFileSize(((SizeLimitLogRotationPolicyCfg) policyConfig).getFileSizeLimit()); 532 } 533 else if (policyConfig instanceof TimeLimitLogRotationPolicyCfg) 534 { 535 long rotationInterval = ((TimeLimitLogRotationPolicyCfg) policyConfig).getRotationInterval(); 536 fileRotation.setRotationInterval(String.valueOf(rotationInterval) + " ms"); 537 } 538 else 539 { 540 throw new ConfigException( 541 ERR_COMMON_AUDIT_UNSUPPORTED_LOG_ROTATION_POLICY.get(publisher.getDn(), policyConfig.dn())); 542 } 543 } 544 auditConfig.setFileRotation(fileRotation); 545 } 546 547 private void addCsvHandlerRetentionConfig(PublisherConfig publisher, CsvConfigData config, 548 CsvAuditEventHandlerConfiguration auditConfig) throws ConfigException 549 { 550 SortedSet<String> retentionPolicies = config.getRetentionPolicies(); 551 if (retentionPolicies.isEmpty()) 552 { 553 return; 554 } 555 556 FileRetention fileRetention = new FileRetention(); 557 for (final String policy : retentionPolicies) 558 { 559 LogRetentionPolicyCfg policyConfig = serverContext.getRootConfig().getLogRetentionPolicy(policy); 560 if (policyConfig instanceof FileCountLogRetentionPolicyCfg) 561 { 562 fileRetention.setMaxNumberOfHistoryFiles(((FileCountLogRetentionPolicyCfg) policyConfig).getNumberOfFiles()); 563 } 564 else if (policyConfig instanceof FreeDiskSpaceLogRetentionPolicyCfg) 565 { 566 fileRetention.setMinFreeSpaceRequired(((FreeDiskSpaceLogRetentionPolicyCfg) policyConfig).getFreeDiskSpace()); 567 } 568 else if (policyConfig instanceof SizeLimitLogRetentionPolicyCfg) 569 { 570 fileRetention.setMaxDiskSpaceToUse(((SizeLimitLogRetentionPolicyCfg) policyConfig).getDiskSpaceUsed()); 571 } 572 else 573 { 574 throw new ConfigException( 575 ERR_COMMON_AUDIT_UNSUPPORTED_LOG_RETENTION_POLICY.get(publisher.getDn(), policyConfig.dn())); 576 } 577 } 578 auditConfig.setFileRetention(fileRetention); 579 } 580 581 /** 582 * Convert the set of provided times of day using 24-hour format "HHmm" to a list of 583 * times of day using duration in minutes, e.g "20 minutes". 584 * <p> 585 * Example: "0230" => "150 minutes" 586 */ 587 private List<String> convertTimesOfDay(PublisherConfig publisher, FixedTimeLogRotationPolicyCfg policyConfig) 588 throws ConfigException 589 { 590 SortedSet<String> timesOfDay = policyConfig.getTimeOfDay(); 591 List<String> times = new ArrayList<>(); 592 for (String timeOfDay : timesOfDay) 593 { 594 try 595 { 596 int time = Integer.valueOf(timeOfDay.substring(0, 2)) * 60 + Integer.valueOf(timeOfDay.substring(2, 4)); 597 times.add(String.valueOf(time) + " minutes"); 598 } 599 catch (NumberFormatException | IndexOutOfBoundsException e) 600 { 601 throw new ConfigException(ERR_COMMON_AUDIT_INVALID_TIME_OF_DAY.get(publisher.getDn(), timeOfDay, 602 StaticUtils.stackTraceToSingleLineString(e))); 603 } 604 } 605 return times; 606 } 607 608 private String getSecurePassword(PublisherConfig publisher, CsvConfigData config) 609 { 610 String fileName = config.getKeystorePinFile(); 611 File pinFile = getFileForPath(fileName); 612 613 if (!pinFile.exists()) 614 { 615 logger.warn(ERR_COMMON_AUDIT_KEYSTORE_PIN_FILE_MISSING.get(publisher.getDn(), pinFile)); 616 return ""; 617 } 618 619 try (BufferedReader br = new BufferedReader(new FileReader(pinFile))) 620 { 621 String pinStr = br.readLine(); 622 if (pinStr == null) 623 { 624 logger.warn(ERR_COMMON_AUDIT_KEYSTORE_PIN_FILE_CONTAINS_EMPTY_PIN.get(publisher.getDn(), pinFile)); 625 return ""; 626 } 627 return pinStr; 628 } 629 catch (IOException ioe) 630 { 631 logger.warn(ERR_COMMON_AUDIT_ERROR_READING_KEYSTORE_PIN_FILE.get(publisher.getDn(), pinFile, 632 stackTraceToSingleLineString(ioe)), ioe); 633 return ""; 634 } 635 } 636 637 /** 638 * Indicates if the provided log publisher configuration corresponds to a common audit publisher. 639 * <p> 640 * The common audit publisher may not already exist. 641 * <p> 642 * This method must not be used when the corresponding configuration is deleted, because it 643 * implies checking the corresponding configuration entry in the server. 644 * 645 * @param config 646 * The log publisher configuration. 647 * @return {@code true} if publisher corresponds to a common audit publisher 648 * @throws ConfigException 649 * If an error occurs 650 */ 651 public boolean isCommonAuditConfig(LogPublisherCfg config) throws ConfigException 652 { 653 return new PublisherConfig(serverContext, config).isCommonAudit(); 654 } 655 656 /** 657 * Indicates if the provided log publisher configuration corresponds to a common audit publisher. 658 * 659 * @param config 660 * The log publisher configuration. 661 * @return {@code true} if publisher is defined for common audit, {@code false} otherwise 662 * @throws ConfigException 663 * If an error occurs 664 */ 665 public boolean isExistingCommonAuditConfig(LogPublisherCfg config) throws ConfigException 666 { 667 String name = getConfigNormalizedName(config); 668 return accessPublishers.containsKey(name) || httpAccessPublishers.containsKey(name); 669 } 670 671 /** 672 * Indicates if HTTP access logging is enabled for common audit. 673 * 674 * @return {@code true} if there is at least one HTTP access logger enabled for common audit. 675 */ 676 public boolean isHttpAccessLogEnabled() 677 { 678 return !httpAccessPublishers.isEmpty(); 679 } 680 681 private String getConfigNormalizedName(LogPublisherCfg config) 682 { 683 return config.dn().toNormalizedUrlSafeString(); 684 } 685 686 /** 687 * Returns the audit service that manages HTTP Access logging. 688 * 689 * @return the request handler that accepts audit events 690 */ 691 public RequestHandler getAuditServiceForHttpAccessLog() 692 { 693 return httpAccessAuditService; 694 } 695 696 /** 697 * This class hides all ugly code needed to determine which type of publisher and audit event handler is needed. 698 * <p> 699 * In particular, it allows to retrieve a common configuration that can be used for log publishers that 700 * publish to the same kind of handler. 701 * For example: for CSV handler, DJ configurations for the log publishers contain the same methods but 702 * do not have a common interface (CsvFileAccessLogPublisherCfg vs CsvFileHTTPAccessLogPublisherCfg). 703 */ 704 private static class PublisherConfig 705 { 706 private final LogPublisherCfg config; 707 private final boolean isCommonAudit; 708 private LogType logType; 709 private AuditType auditType; 710 711 PublisherConfig(ServerContext serverContext, LogPublisherCfg config) throws ConfigException 712 { 713 this.config = config; 714 Entry configEntry = DirectoryServer.getConfigEntry(config.dn()); 715 if (hasObjectClass(serverContext,configEntry, "ds-cfg-csv-file-access-log-publisher")) 716 { 717 auditType = AuditType.CSV; 718 logType = LogType.ACCESS; 719 } 720 else if (hasObjectClass(serverContext,configEntry, "ds-cfg-csv-file-http-access-log-publisher")) 721 { 722 auditType = AuditType.CSV; 723 logType = LogType.HTTP_ACCESS; 724 } 725 else if (hasObjectClass(serverContext,configEntry, "ds-cfg-external-access-log-publisher")) 726 { 727 auditType = AuditType.EXTERNAL; 728 logType = LogType.ACCESS; 729 } 730 else if (hasObjectClass(serverContext,configEntry, "ds-cfg-external-http-access-log-publisher")) 731 { 732 auditType = AuditType.EXTERNAL; 733 logType = LogType.HTTP_ACCESS; 734 } 735 isCommonAudit = auditType != null; 736 } 737 738 private boolean hasObjectClass(ServerContext serverContext, Entry entry, String objectClassName) 739 { 740 ObjectClass objectClass = serverContext.getSchema().getObjectClass(objectClassName); 741 return !objectClass.isPlaceHolder() && entry.hasObjectClass(objectClass); 742 } 743 744 DN getDn() 745 { 746 return config.dn(); 747 } 748 749 String getName() 750 { 751 return config.dn().rdn().getFirstAVA().getAttributeValue().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}