CommonAuditAccessLogPublisher.java revision e433824f46514e6ed863eb6b95c70038331c8c24
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2015 ForgeRock AS
*/
package org.opends.server.loggers;
import static org.opends.messages.LoggerMessages.*;
import static org.forgerock.json.JsonValue.json;
import static org.forgerock.json.resource.Requests.newCreateRequest;
import static org.forgerock.json.resource.ResourcePath.resourcePath;
import static org.opends.server.loggers.OpenDJAccessAuditEventBuilder.openDJAccessEvent;
import static org.opends.server.types.AuthenticationType.SASL;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.forgerock.audit.events.AccessAuditEventBuilder.ResponseStatus;
import org.forgerock.audit.events.AuditEvent;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.json.resource.CreateRequest;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.services.context.RootContext;
import org.forgerock.util.Pair;
import org.forgerock.util.promise.ExceptionHandler;
import org.forgerock.util.promise.RuntimeExceptionHandler;
import org.opends.server.admin.std.server.AccessLogPublisherCfg;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ExtendedOperationHandler;
import org.opends.server.controls.TransactionIdControl;
import org.opends.server.core.AbandonOperation;
import org.opends.server.core.AddOperation;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ExtendedOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.ServerContext;
import org.opends.server.core.UnbindOperation;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Operation;
import org.opends.server.util.ServerConstants;
import org.opends.server.util.StaticUtils;
/**
* Publishes access events to Common Audit.
*
* @param <T> the type of configuration
*/
abstract class CommonAuditAccessLogPublisher<T extends AccessLogPublisherCfg>
extends AbstractTextAccessLogPublisher<T>
implements CommonAuditLogPublisher
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** Transaction id used when the incoming request does not contain a transaction id. */
private static final String DEFAULT_TRANSACTION_ID = "0";
/** Audit service handler. */
private RequestHandler requestHandler;
/** Current configuration for this publisher. */
private T config;
@Override
public void setRequestHandler(RequestHandler handler)
{
this.requestHandler = handler;
}
abstract boolean shouldLogControlOids();
T getConfig()
{
return config;
}
void setConfig(T config)
{
this.config = config;
}
@Override
public void initializeLogPublisher(final T cfg, ServerContext serverContext)
throws ConfigException, InitializationException
{
initializeFilters(cfg);
config = cfg;
}
@Override
public boolean isConfigurationAcceptable(final T configuration, final List<LocalizableMessage> unacceptableReasons)
{
return isFilterConfigurationAcceptable(configuration, unacceptableReasons);
}
@Override
public void logAbandonResult(final AbandonOperation abandonOperation)
{
if (!isResponseLoggable(abandonOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(abandonOperation, "ABANDON");
appendAbandonRequest(abandonOperation, builder);
appendResultCodeAndMessage(abandonOperation, builder);
sendEvent(builder.toEvent());
}
@Override
public void logAddResponse(final AddOperation addOperation)
{
if (!isResponseLoggable(addOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(addOperation, "ADD");
appendAddRequest(addOperation, builder);
appendResultCodeAndMessage(addOperation, builder);
DN proxiedAuthorizationDN = addOperation.getProxiedAuthorizationDN();
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
sendEvent(builder.toEvent());
}
@Override
public void logBindResponse(final BindOperation bindOperation)
{
if (!isResponseLoggable(bindOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(bindOperation, "BIND");
appendBindRequest(bindOperation, builder);
appendResultCodeAndMessage(bindOperation, builder);
final LocalizableMessage failureMessage = bindOperation.getAuthFailureReason();
if (failureMessage != null)
{
// this code path is mutually exclusive with the if result code is success
// down below
builder.ldapFailureMessage(failureMessage.toString());
if (bindOperation.getSASLMechanism() != null && bindOperation.getSASLAuthUserEntry() != null)
{ // SASL bind and we have successfully found a user entry for auth
builder.userId(bindOperation.getSASLAuthUserEntry().getName().toString());
}
else
{ // SASL bind failed to find user entry for auth or simple bind
builder.userId(bindOperation.getRawBindDN().toString());
}
}
if (bindOperation.getResultCode() == ResultCode.SUCCESS)
{
// this code path is mutually exclusive with the if failure message exist
// just above
final AuthenticationInfo authInfo = bindOperation.getAuthenticationInfo();
if (authInfo != null)
{
final DN authDN = authInfo.getAuthenticationDN();
if (authDN != null)
{
builder.userId(authDN.toString());
final DN authzDN = authInfo.getAuthorizationDN();
if (!authDN.equals(authzDN))
{
builder.runAs(authzDN.toString());
}
}
else
{
builder.userId("");
}
}
}
sendEvent(builder.toEvent());
}
@Override
public void logCompareResponse(final CompareOperation compareOperation)
{
if (!isResponseLoggable(compareOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(compareOperation, "COMPARE");
appendCompareRequest(compareOperation, builder);
appendResultCodeAndMessage(compareOperation, builder);
DN proxiedAuthorizationDN = compareOperation.getProxiedAuthorizationDN();
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
sendEvent(builder.toEvent());
}
private void appendProxiedAuthorizationDNIfNeeded(OpenDJAccessAuditEventBuilder<?> builder, DN proxiedAuthorizationDN)
{
if (proxiedAuthorizationDN != null)
{
builder.runAs(proxiedAuthorizationDN.toString());
}
}
@Override
public void logConnect(final ClientConnection clientConnection)
{
if (!isConnectLoggable(clientConnection))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = openDJAccessEvent()
.eventName("DJ-" + clientConnection.getProtocol() + "-" + "CONNECT")
.client(clientConnection.getClientAddress(), clientConnection.getClientPort())
.server(clientConnection.getServerAddress(), clientConnection.getServerPort())
.request(clientConnection.getProtocol(), "CONNECT")
.transactionId(DEFAULT_TRANSACTION_ID)
.response(ResponseStatus.SUCCESSFUL, String.valueOf(ResultCode.SUCCESS.intValue()), 0, TimeUnit.MILLISECONDS)
.ldapConnectionId(clientConnection.getConnectionID());
sendEvent(builder.toEvent());
}
@Override
public void logDeleteResponse(final DeleteOperation deleteOperation)
{
if (!isResponseLoggable(deleteOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(deleteOperation, "DELETE");
appendDeleteRequest(deleteOperation, builder);
appendResultCodeAndMessage(deleteOperation, builder);
DN proxiedAuthorizationDN = deleteOperation.getProxiedAuthorizationDN();
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
sendEvent(builder.toEvent());
}
@Override
public void logDisconnect(final ClientConnection clientConnection, final DisconnectReason disconnectReason,
final LocalizableMessage message)
{
if (!isDisconnectLoggable(clientConnection))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = openDJAccessEvent()
.eventName("DJ-" + clientConnection.getProtocol() + "-" + "DISCONNECT")
.client(clientConnection.getClientAddress(), clientConnection.getClientPort())
.server(clientConnection.getServerAddress(), clientConnection.getServerPort())
.request(clientConnection.getProtocol(),"DISCONNECT")
.transactionId(DEFAULT_TRANSACTION_ID)
.response(ResponseStatus.SUCCESSFUL, String.valueOf(ResultCode.SUCCESS.intValue()), 0, TimeUnit.MILLISECONDS)
.ldapConnectionId(clientConnection.getConnectionID())
.ldapReason(disconnectReason.toString())
.ldapMessage(message.toString());
sendEvent(builder.toEvent());
}
@Override
public void logExtendedResponse(final ExtendedOperation extendedOperation)
{
if (!isResponseLoggable(extendedOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(extendedOperation, "EXTENDED");
appendExtendedRequest(extendedOperation, builder);
appendResultCodeAndMessage(extendedOperation, builder);
final String oid = extendedOperation.getResponseOID();
if (oid != null)
{
final ExtendedOperationHandler<?> extOpHandler = DirectoryServer.getExtendedOperationHandler(oid);
if (extOpHandler != null)
{
String name = extOpHandler.getExtendedOperationName();
builder.ldapName(name);
}
builder.ldapOid(oid);
}
sendEvent(builder.toEvent());
}
@Override
public void logModifyDNResponse(final ModifyDNOperation modifyDNOperation)
{
if (!isResponseLoggable(modifyDNOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(modifyDNOperation, "MODIFYDN");
appendModifyDNRequest(modifyDNOperation, builder);
appendResultCodeAndMessage(modifyDNOperation, builder);
DN proxiedAuthorizationDN = modifyDNOperation.getProxiedAuthorizationDN();
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
sendEvent(builder.toEvent());
}
@Override
public void logModifyResponse(final ModifyOperation modifyOperation)
{
if (!isResponseLoggable(modifyOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(modifyOperation, "MODIFY");
appendModifyRequest(modifyOperation, builder);
appendResultCodeAndMessage(modifyOperation, builder);
DN proxiedAuthorizationDN = modifyOperation.getProxiedAuthorizationDN();
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
sendEvent(builder.toEvent());
}
@Override
public void logSearchResultDone(final SearchOperation searchOperation)
{
if (!isResponseLoggable(searchOperation))
{
return;
}
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(searchOperation, "SEARCH");
builder
.ldapSearch(searchOperation)
.ldapNEntries(searchOperation.getEntriesSent());
appendResultCodeAndMessage(searchOperation, builder);
DN proxiedAuthorizationDN = searchOperation.getProxiedAuthorizationDN();
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
sendEvent(builder.toEvent());
}
@Override
public void logUnbind(final UnbindOperation unbindOperation)
{
if (!isRequestLoggable(unbindOperation))
{
return;
}
sendEvent(getEventBuilder(unbindOperation, "UNBIND").toEvent());
}
@Override
protected void close0()
{
// nothing to do because closing is managed in the CommonAudit class
}
private void appendAbandonRequest(final AbandonOperation abandonOperation,
final OpenDJAccessAuditEventBuilder<?> builder)
{
builder.ldapIdToAbandon(abandonOperation.getIDToAbandon());
}
private void appendAddRequest(final AddOperation addOperation, OpenDJAccessAuditEventBuilder<?> builder)
{
builder.ldapDn(addOperation.getRawEntryDN().toString());
}
private void appendBindRequest(final BindOperation bindOperation, final OpenDJAccessAuditEventBuilder<?> builder)
{
builder.ldapProtocolVersion(bindOperation.getProtocolVersion());
final String authType = bindOperation.getAuthenticationType() != SASL ?
bindOperation.getAuthenticationType().toString() : "SASL mechanism=" + bindOperation.getSASLMechanism();
builder.ldapAuthType(authType);
builder.ldapDn(bindOperation.getRawBindDN().toString());
}
private void appendCompareRequest(final CompareOperation compareOperation,
final OpenDJAccessAuditEventBuilder<?> builder)
{
builder.ldapDn(compareOperation.getRawEntryDN().toString());
builder.ldapAttr(compareOperation.getAttributeType().getNameOrOID());
}
private void appendDeleteRequest(final DeleteOperation deleteOperation,
final OpenDJAccessAuditEventBuilder<?> builder)
{
builder.ldapDn(deleteOperation.getRawEntryDN().toString());
}
private void appendExtendedRequest(final ExtendedOperation extendedOperation,
final OpenDJAccessAuditEventBuilder<?> builder)
{
final String oid = extendedOperation.getRequestOID();
final ExtendedOperationHandler<?> extOpHandler = DirectoryServer.getExtendedOperationHandler(oid);
if (extOpHandler != null)
{
final String name = extOpHandler.getExtendedOperationName();
builder.ldapName(name);
}
builder.ldapOid(oid);
}
private void appendModifyDNRequest(final ModifyDNOperation modifyDNOperation,
final OpenDJAccessAuditEventBuilder<?> builder)
{
builder.ldapDn(modifyDNOperation.getRawEntryDN().toString());
builder.ldapModifyDN(modifyDNOperation);
}
private void appendModifyRequest(final ModifyOperation modifyOperation,
final OpenDJAccessAuditEventBuilder<?> builder)
{
builder.ldapDn(modifyOperation.getRawEntryDN().toString());
}
private OpenDJAccessAuditEventBuilder<?> appendResultCodeAndMessage(
Operation operation, OpenDJAccessAuditEventBuilder<?> builder)
{
final LocalizableMessageBuilder message = operation.getErrorMessage();
int resultCode = operation.getResultCode().intValue();
ResponseStatus status = resultCode == 0 ? ResponseStatus.SUCCESSFUL : ResponseStatus.FAILED;
Pair<Long, TimeUnit> executionTime = getExecutionTime(operation);
if (message != null && message.length() > 0)
{
builder.responseWithDetail(status, String.valueOf(resultCode), executionTime.getFirst(),
executionTime.getSecond(), json(message.toString()));
}
else
{
builder.response(status, String.valueOf(resultCode), executionTime.getFirst(), executionTime.getSecond());
}
builder.ldapMaskedResultAndMessage(operation);
return builder;
}
/** Returns an event builder with all common fields filled. */
private OpenDJAccessAuditEventBuilder<?> getEventBuilder(final Operation operation, final String opType)
{
ClientConnection clientConn = operation.getClientConnection();
OpenDJAccessAuditEventBuilder<?> builder = openDJAccessEvent()
.eventName("DJ-" + clientConn.getProtocol() + "-" + opType)
.client(clientConn.getClientAddress(), clientConn.getClientPort())
.server(clientConn.getServerAddress(), clientConn.getServerPort())
.request(clientConn.getProtocol(), opType)
.ldapAdditionalItems(operation)
.ldapSync(operation)
.ldapIds(operation)
.transactionId(getTransactionId(operation));
if (shouldLogControlOids())
{
builder.ldapControls(operation);
}
return builder;
}
private String getTransactionId(Operation operation)
{
String transactionId = getTransactionIdFromControl(operation);
if (transactionId == null)
{
// use a default value because transaction id has no usage in this case
transactionId = DEFAULT_TRANSACTION_ID;
}
return transactionId;
}
private String getTransactionIdFromControl(Operation operation)
{
for (Control control : operation.getRequestControls())
{
if (control.getOID().equals(ServerConstants.OID_TRANSACTION_ID_CONTROL))
{
try
{
return operation.getRequestControl(TransactionIdControl.DECODER).getTransactionId();
}
catch (DirectoryException e)
{
logger.error(ERR_COMMON_AUDIT_INVALID_TRANSACTION_ID.get(StaticUtils.stackTraceToSingleLineString(e)));
}
}
}
return null;
}
private Pair<Long,TimeUnit> getExecutionTime(final Operation operation)
{
Long etime = operation.getProcessingNanoTime();
// if not configured for nanos, use millis
return etime <= -1 ?
Pair.of(operation.getProcessingTime(), TimeUnit.MILLISECONDS) :
Pair.of(etime, TimeUnit.NANOSECONDS);
}
/** Sends an JSON-encoded event to the audit service. */
private void sendEvent(AuditEvent event)
{
CreateRequest request = newCreateRequest(resourcePath("/ldap-access"), event.getValue());
requestHandler
.handleCreate(new RootContext(), request)
.thenOnException(new ExceptionHandler<ResourceException>()
{
@Override
public void handleException(ResourceException e)
{
logger.error(ERR_COMMON_AUDIT_UNABLE_TO_PROCESS_LOG_EVENT.get(StaticUtils.stackTraceToSingleLineString(e)));
}
})
.thenOnRuntimeException(new RuntimeExceptionHandler()
{
@Override
public void handleRuntimeException(RuntimeException e)
{
logger.error(ERR_COMMON_AUDIT_UNABLE_TO_PROCESS_LOG_EVENT.get(StaticUtils.stackTraceToSingleLineString(e)));
}
});
}
}