/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2015-2016 ForgeRock AS. */ package org.opends.server.loggers; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static java.util.Arrays.asList; import static org.opends.messages.LoggerMessages.*; import static org.forgerock.audit.AuditServiceBuilder.newAuditService; import static org.forgerock.audit.events.EventTopicsMetaDataBuilder.coreTopicSchemas; import static org.forgerock.audit.json.AuditJsonConfig.registerHandlerToService; import static org.opends.server.util.StaticUtils.getFileForPath; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import org.forgerock.audit.AuditException; import org.forgerock.audit.AuditService; import org.forgerock.audit.AuditServiceBuilder; import org.forgerock.audit.AuditServiceConfiguration; import org.forgerock.audit.AuditServiceProxy; import org.forgerock.audit.DependencyProvider; import org.forgerock.audit.events.EventTopicsMetaData; import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration; import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration.FileRetention; import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration.FileRotation; import org.forgerock.audit.filter.FilterPolicy; import org.forgerock.audit.handlers.csv.CsvAuditEventHandler; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.CsvFormatting; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.CsvSecurity; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.EventBufferingConfiguration; import org.forgerock.audit.handlers.json.JsonAuditEventHandler; import org.forgerock.audit.handlers.json.JsonAuditEventHandlerConfiguration; import org.forgerock.audit.json.AuditJsonConfig; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.json.JsonValue; import org.forgerock.json.resource.RequestHandler; import org.forgerock.opendj.config.ConfigurationFramework; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.server.config.server.CsvFileAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.CsvFileHTTPAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.ExternalAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.ExternalHTTPAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.FileCountLogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.FixedTimeLogRotationPolicyCfg; import org.forgerock.opendj.server.config.server.FreeDiskSpaceLogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.JsonFileAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.JsonFileHTTPAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.LogPublisherCfg; import org.forgerock.opendj.server.config.server.LogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.LogRotationPolicyCfg; import org.forgerock.opendj.server.config.server.SizeLimitLogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.SizeLimitLogRotationPolicyCfg; import org.forgerock.opendj.server.config.server.TimeLimitLogRotationPolicyCfg; import org.opends.server.core.ServerContext; import org.opends.server.util.StaticUtils; /** * Entry point for the common audit facility. *
* This class manages the AuditService instances and Audit Event Handlers that correspond to the * publishers defined in OpenDJ configuration. *
* In theory there should be only one instance of AuditService for all the event handlers but
* defining one service per handler allow to perform filtering at the DJ server level.
*/
public class CommonAudit
{
/** Transaction id used when the incoming request does not contain a transaction id. */
public static final String DEFAULT_TRANSACTION_ID = "0";
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private static final String AUDIT_SERVICE_JSON_CONFIGURATION_FILE = "audit-config.json";
/** Dependency provider used to instantiate the handlers. */
private final DependencyProvider dependencyProvider;
/** Configuration framework is used to get an up-to-date class loader with any external library available. */
private final ConfigurationFramework configurationFramework;
/** Cache of audit services per configuration entry normalized name. */
private final Map
* Unless no handler must be added, this class should be extended and
* implementations should override the {@code addHandlers()} method.
*/
static abstract class AuditServiceSetup
{
private final AuditServiceProxy existingAuditServiceProxy;
private final List
* HTTP Headers may contain authentication information.
*/
private Map
* Example: "0230" => "150 minutes"
*/
private List
* The common audit publisher may not already exist.
*
* This method must not be used when the corresponding configuration is deleted, because it
* implies checking the corresponding configuration entry in the server.
*
* @param config
* The log publisher configuration.
* @return {@code true} if publisher corresponds to a common audit publisher
* @throws ConfigException
* If an error occurs
*/
public boolean isCommonAuditConfig(LogPublisherCfg config) throws ConfigException
{
return new PublisherConfig(serverContext, config).isCommonAudit;
}
/**
* Indicates if the provided log publisher configuration corresponds to a common audit publisher.
*
* @param config
* The log publisher configuration.
* @return {@code true} if publisher is defined for common audit, {@code false} otherwise
* @throws ConfigException
* If an error occurs
*/
public boolean isExistingCommonAuditConfig(LogPublisherCfg config) throws ConfigException
{
String name = getConfigNormalizedName(config);
return accessPublishers.containsKey(name) || httpAccessPublishers.containsKey(name);
}
/**
* Indicates if HTTP access logging is enabled for common audit.
*
* @return {@code true} if there is at least one HTTP access logger enabled for common audit.
*/
public boolean isHttpAccessLogEnabled()
{
return !httpAccessPublishers.isEmpty();
}
private String getConfigNormalizedName(LogPublisherCfg config)
{
return config.dn().toNormalizedUrlSafeString();
}
/**
* Returns the audit service that manages HTTP Access logging.
*
* @return the request handler that accepts audit events
*/
public RequestHandler getAuditServiceForHttpAccessLog()
{
return httpAccessAuditService;
}
/**
* This class hides all ugly code needed to determine which type of publisher and audit event handler is needed.
*
* In particular, it allows to retrieve a common configuration that can be used for log publishers that
* publish to the same kind of handler.
* For example: for CSV handler, DJ configurations for the log publishers contain the same methods but
* do not have a common interface (CsvFileAccessLogPublisherCfg vs CsvFileHTTPAccessLogPublisherCfg).
*/
private static class PublisherConfig
{
private final LogPublisherCfg config;
private final boolean isCommonAudit;
private LogType logType = LogType.UNCONFIGURED;
private AuditType auditType;
PublisherConfig(ServerContext serverContext, LogPublisherCfg config) throws ConfigException
{
this.config = config;
if (config instanceof JsonFileAccessLogPublisherCfg)
{
auditType = AuditType.JSON;
logType = LogType.ACCESSLOG;
}
else if (config instanceof JsonFileHTTPAccessLogPublisherCfg)
{
auditType = AuditType.JSON;
logType = LogType.HTTPLOG;
}
if (config instanceof CsvFileAccessLogPublisherCfg)
{
auditType = AuditType.CSV;
logType = LogType.ACCESSLOG;
}
else if (config instanceof CsvFileHTTPAccessLogPublisherCfg)
{
auditType = AuditType.CSV;
logType = LogType.HTTPLOG;
}
if (config instanceof ExternalAccessLogPublisherCfg)
{
auditType = AuditType.EXTERNAL;
logType = LogType.ACCESSLOG;
}
else if (config instanceof ExternalHTTPAccessLogPublisherCfg)
{
auditType = AuditType.EXTERNAL;
logType = LogType.HTTPLOG;
}
isCommonAudit = auditType != null;
}
DN getDn()
{
return config.dn();
}
String getName()
{
return config.dn().rdn().getFirstAVA().getAttributeValue().toString();
}
String getCommonAuditTopic() throws ConfigException
{
return logType.getCommonAuditTopic(config);
}
CsvConfigData getCsvConfig() throws ConfigException
{
return logType.getCsvConfig(config);
}
JsonConfigData getJsonConfig() throws ConfigException
{
return logType.getJsonConfig(config);
}
ExternalConfigData getExternalConfig() throws ConfigException
{
return logType.getExternalConfig(config);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (!(obj instanceof PublisherConfig))
{
return false;
}
PublisherConfig other = (PublisherConfig) obj;
return config.dn().equals(other.config.dn());
}
@Override
public int hashCode()
{
return config.dn().hashCode();
}
}
/** Types of audit handlers managed. */
private enum AuditType
{
CSV, JSON, EXTERNAL
}
/** Log types for LDAP or HTTP, to get specific configuration depending on the handler. **/
private enum LogType
{
UNCONFIGURED
{
@Override
String getCommonAuditTopic(LogPublisherCfg config) throws ConfigException
{
throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn()));
}
@Override
ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException
{
throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn()));
}
@Override
CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException
{
throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn()));
}
@Override
JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException
{
throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn()));
}
@Override
boolean isHttp()
{
return false;
}
},
HTTPLOG
{
@Override
public String getCommonAuditTopic(LogPublisherCfg config)
{
return "http-access";
}
@Override
public ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException
{
ExternalHTTPAccessLogPublisherCfg conf = (ExternalHTTPAccessLogPublisherCfg) config;
return new ExternalConfigData(conf.getConfigFile());
}
@Override
public CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException
{
CsvFileHTTPAccessLogPublisherCfg conf = (CsvFileHTTPAccessLogPublisherCfg) config;
return new CsvConfigData(conf.getLogDirectory(), conf.getCsvQuoteChar(), conf.getCsvDelimiterChar(), conf
.getCsvEolSymbols(), conf.isAsynchronous(), conf.isAutoFlush(), conf.isTamperEvident(), conf
.getSignatureTimeInterval(), conf.getKeyStoreFile(), conf.getKeyStorePinFile(), conf.getRotationPolicy(),
conf.getRetentionPolicy());
}
@Override
public JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException
{
JsonFileHTTPAccessLogPublisherCfg conf = (JsonFileHTTPAccessLogPublisherCfg) config;
return new JsonConfigData(conf.getLogDirectory(), conf.getRotationPolicy(), conf.getRetentionPolicy());
}
@Override
public boolean isHttp()
{
return true;
}
},
ACCESSLOG
{
@Override
public String getCommonAuditTopic(LogPublisherCfg config)
{
return "ldap-access";
}
@Override
public ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException
{
ExternalAccessLogPublisherCfg conf = (ExternalAccessLogPublisherCfg) config;
return new ExternalConfigData(conf.getConfigFile());
}
@Override
public CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException
{
CsvFileAccessLogPublisherCfg conf = (CsvFileAccessLogPublisherCfg) config;
return new CsvConfigData(conf.getLogDirectory(), conf.getCsvQuoteChar(), conf.getCsvDelimiterChar(), conf
.getCsvEolSymbols(), conf.isAsynchronous(), conf.isAutoFlush(), conf.isTamperEvident(), conf
.getSignatureTimeInterval(), conf.getKeyStoreFile(), conf.getKeyStorePinFile(), conf.getRotationPolicy(),
conf.getRetentionPolicy());
}
@Override
public JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException
{
JsonFileAccessLogPublisherCfg conf = (JsonFileAccessLogPublisherCfg) config;
return new JsonConfigData(conf.getLogDirectory(), conf.getRotationPolicy(), conf.getRetentionPolicy());
}
@Override
public boolean isHttp()
{
return false;
}
};
abstract String getCommonAuditTopic(LogPublisherCfg config) throws ConfigException;
abstract ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException;
abstract CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException;
abstract JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException;
abstract boolean isHttp();
}
/**
* Contains common parameters for non External CAUD handlers.
*
* OpenDJ log publishers that log to a CAUD handler have the same parameters but do not share
* a common ancestor with all the parameters (e.g Access Log, HTTP Access Log, ...), hence this class
* is necessary to avoid duplicating code that setup configuration of the handler.
*/
private abstract static class HandlerConfigData
{
private final String logDirectory;
private final SortedSet
* Adds CSV specific configuration to a CAUD handler.
*/
private static class CsvConfigData extends HandlerConfigData
{
private final String eolSymbols;
private final String delimiterChar;
private final String quoteChar;
private final boolean asynchronous;
private final boolean autoFlush;
private final boolean tamperEvident;
private final long signatureTimeInterval;
private final String keystoreFile;
private final String keystorePinFile;
CsvConfigData(String logDirectory, String quoteChar, String delimiterChar, String eolSymbols, boolean asynchronous,
boolean autoFlush, boolean tamperEvident, long signatureTimeInterval, String keystoreFile,
String keystorePinFile, SortedSet
* OpenDJ log publishers that logs to an external handler have the same
* parameters but do not share a common ancestor with all the parameters (e.g
* Access Log, HTTP Access Log, ...), hence this class is necessary to avoid
* duplicating code that setup the configuration of an external handler.
*/
private static class ExternalConfigData
{
private final String configurationFile;
ExternalConfigData(String configurationFile)
{
this.configurationFile = configurationFile;
}
String getConfigurationFile()
{
return configurationFile;
}
}
}