HDDS-391. Simplify Audit Framework to make audit logging easier to use.

Contributed by Dinesh Chitlangia.
This commit is contained in:
Anu Engineer 2018-09-28 14:00:24 -07:00
parent 35c7351f4b
commit 0da03f8b14
7 changed files with 210 additions and 137 deletions

View File

@ -56,53 +56,21 @@ public ExtendedLogger getLogger() {
}
public void logWriteSuccess(AuditMessage msg) {
logWriteSuccess(Level.INFO, msg);
}
public void logWriteSuccess(Level level, AuditMessage msg) {
this.logger.logIfEnabled(FQCN, level, WRITE_MARKER, msg, null);
this.logger.logIfEnabled(FQCN, Level.INFO, WRITE_MARKER, msg, null);
}
public void logWriteFailure(AuditMessage msg) {
logWriteFailure(Level.ERROR, msg);
}
public void logWriteFailure(Level level, AuditMessage msg) {
logWriteFailure(level, msg, null);
}
public void logWriteFailure(AuditMessage msg, Throwable exception) {
logWriteFailure(Level.ERROR, msg, exception);
}
public void logWriteFailure(Level level, AuditMessage msg,
Throwable exception) {
this.logger.logIfEnabled(FQCN, level, WRITE_MARKER, msg, exception);
this.logger.logIfEnabled(FQCN, Level.ERROR, WRITE_MARKER, msg,
msg.getThrowable());
}
public void logReadSuccess(AuditMessage msg) {
logReadSuccess(Level.INFO, msg);
}
public void logReadSuccess(Level level, AuditMessage msg) {
this.logger.logIfEnabled(FQCN, level, READ_MARKER, msg, null);
this.logger.logIfEnabled(FQCN, Level.INFO, READ_MARKER, msg, null);
}
public void logReadFailure(AuditMessage msg) {
logReadFailure(Level.ERROR, msg);
}
public void logReadFailure(Level level, AuditMessage msg) {
logReadFailure(level, msg, null);
}
public void logReadFailure(AuditMessage msg, Throwable exception) {
logReadFailure(Level.ERROR, msg, exception);
}
public void logReadFailure(Level level, AuditMessage msg,
Throwable exception) {
this.logger.logIfEnabled(FQCN, level, READ_MARKER, msg, exception);
this.logger.logIfEnabled(FQCN, Level.ERROR, READ_MARKER, msg,
msg.getThrowable());
}
}

View File

@ -26,12 +26,13 @@
public class AuditMessage implements Message {
private String message;
private Throwable throwable;
public AuditMessage(String user, String ip, String op,
Map<String, String> params, String ret){
private static final String MSG_PATTERN =
"user=%s | ip=%s | op=%s %s | ret=%s";
public AuditMessage(){
this.message = String.format("user=%s ip=%s op=%s %s ret=%s",
user, ip, op, params, ret);
}
@Override
@ -51,7 +52,7 @@ public Object[] getParameters() {
@Override
public Throwable getThrowable() {
return null;
return throwable;
}
/**
@ -61,4 +62,70 @@ public Throwable getThrowable() {
private void appendMessage(String customMessage) {
this.message += customMessage;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}
/**
* Builder class for AuditMessage.
*/
public static class Builder {
private Throwable throwable;
private String user;
private String ip;
private String op;
private Map<String, String> params;
private String ret;
public Builder(){
}
public Builder setUser(String usr){
this.user = usr;
return this;
}
public Builder atIp(String ipAddr){
this.ip = ipAddr;
return this;
}
public Builder forOperation(String operation){
this.op = operation;
return this;
}
public Builder withParams(Map<String, String> args){
this.params = args;
return this;
}
public Builder withResult(String result){
this.ret = result;
return this;
}
public Builder withException(Throwable ex){
this.throwable = ex;
return this;
}
public AuditMessage build(){
AuditMessage auditMessage = new AuditMessage();
auditMessage.message = String.format(MSG_PATTERN,
this.user, this.ip, this.op, this.params, this.ret);
auditMessage.throwable = this.throwable;
return auditMessage;
}
}
}

View File

@ -0,0 +1,33 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.hadoop.ozone.audit;
import java.util.Map;
/**
* Interface to mark an actor as Auditor.
*/
public interface Auditor {
AuditMessage buildAuditMessageForSuccess(
AuditAction op, Map<String, String> auditMap);
AuditMessage buildAuditMessageForFailure(
AuditAction op, Map<String, String> auditMap, Throwable throwable);
}

View File

@ -88,6 +88,12 @@
* It will generate a message formatted as:
* user=xxx ip=xxx op=XXXX_XXXX {key=val, key1=val1..} ret=XXXXXX
*
* *** Auditor ***
* Interface to mark an actor class as Auditor
* Must be implemented by class where we want to log audit events
* Implementing class must override and implement methods
* buildAuditMessageForSuccess and buildAuditMessageForFailure.
*
* ****************************************************************************
* Usage
* ****************************************************************************
@ -99,12 +105,12 @@
*
* 3. Log Read/Write and Success/Failure event as needed.
* Example
* AUDIT.logWriteSuccess(Level level, AuditMessage msg)
* AUDIT.logWriteSuccess(buildAuditMessageForSuccess(params))
*
* If logging is done without specifying Level, then Level implicitly
* defaults to INFO for xxxxSuccess() and ERROR for xxxxFailure()
* AUDIT.logWriteSuccess(AuditMessage msg)
* AUDIT.logWriteFailure(AuditMessage msg)
* 4. Log Level implicitly defaults to INFO for xxxxSuccess() and ERROR for
* xxxxFailure()
* AUDIT.logWriteSuccess(buildAuditMessageForSuccess(params))
* AUDIT.logWriteFailure(buildAuditMessageForSuccess(params))
*
* See sample invocations in src/test in the following class:
* org.apache.hadoop.ozone.audit.TestOzoneAuditLogger

View File

@ -18,7 +18,6 @@
package org.apache.hadoop.ozone.audit;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.Level;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@ -49,17 +48,41 @@ public class TestOzoneAuditLogger {
private static final Map<String, String> PARAMS =
new DummyEntity().toAuditMap();
private static final AuditMessage WRITE_FAIL_MSG = new AuditMessage("john",
"192.168.0.1", DummyAction.CREATE_VOLUME.name(), PARAMS, FAILURE);
private static final AuditMessage WRITE_FAIL_MSG =
new AuditMessage.Builder()
.setUser("john")
.atIp("192.168.0.1")
.forOperation(DummyAction.CREATE_VOLUME.name())
.withParams(PARAMS)
.withResult(FAILURE)
.withException(null).build();
private static final AuditMessage WRITE_SUCCESS_MSG = new AuditMessage("john",
"192.168.0.1", DummyAction.CREATE_VOLUME.name(), PARAMS, SUCCESS);
private static final AuditMessage WRITE_SUCCESS_MSG =
new AuditMessage.Builder()
.setUser("john")
.atIp("192.168.0.1")
.forOperation(DummyAction.CREATE_VOLUME.name())
.withParams(PARAMS)
.withResult(SUCCESS)
.withException(null).build();
private static final AuditMessage READ_FAIL_MSG = new AuditMessage("john",
"192.168.0.1", DummyAction.READ_VOLUME.name(), PARAMS, FAILURE);
private static final AuditMessage READ_FAIL_MSG =
new AuditMessage.Builder()
.setUser("john")
.atIp("192.168.0.1")
.forOperation(DummyAction.READ_VOLUME.name())
.withParams(PARAMS)
.withResult(FAILURE)
.withException(null).build();
private static final AuditMessage READ_SUCCESS_MSG = new AuditMessage("john",
"192.168.0.1", DummyAction.READ_VOLUME.name(), PARAMS, SUCCESS);
private static final AuditMessage READ_SUCCESS_MSG =
new AuditMessage.Builder()
.setUser("john")
.atIp("192.168.0.1")
.forOperation(DummyAction.READ_VOLUME.name())
.withParams(PARAMS)
.withResult(SUCCESS)
.withException(null).build();
@BeforeClass
public static void setUp(){
@ -77,17 +100,6 @@ public static void tearDown() {
}
}
/**
* Ensures WriteSuccess events are logged @ INFO and above.
*/
@Test
public void logInfoWriteSuccess() throws IOException {
AUDIT.logWriteSuccess(Level.INFO, WRITE_SUCCESS_MSG);
String expected =
"[INFO ] OMAudit - " + WRITE_SUCCESS_MSG.getFormattedMessage();
verifyLog(expected);
}
/**
* Test to verify default log level is INFO when logging success events.
*/
@ -95,7 +107,7 @@ public void logInfoWriteSuccess() throws IOException {
public void verifyDefaultLogLevelForSuccess() throws IOException {
AUDIT.logWriteSuccess(WRITE_SUCCESS_MSG);
String expected =
"[INFO ] OMAudit - " + WRITE_SUCCESS_MSG.getFormattedMessage();
"INFO | OMAudit | " + WRITE_SUCCESS_MSG.getFormattedMessage();
verifyLog(expected);
}
@ -106,18 +118,7 @@ public void verifyDefaultLogLevelForSuccess() throws IOException {
public void verifyDefaultLogLevelForFailure() throws IOException {
AUDIT.logWriteFailure(WRITE_FAIL_MSG);
String expected =
"[ERROR] OMAudit - " + WRITE_FAIL_MSG.getFormattedMessage();
verifyLog(expected);
}
/**
* Test to verify WriteFailure events are logged as ERROR.
*/
@Test
public void logErrorWriteFailure() throws IOException {
AUDIT.logWriteFailure(Level.ERROR, WRITE_FAIL_MSG);
String expected =
"[ERROR] OMAudit - " + WRITE_FAIL_MSG.getFormattedMessage();
"ERROR | OMAudit | " + WRITE_FAIL_MSG.getFormattedMessage();
verifyLog(expected);
}
@ -126,20 +127,8 @@ public void logErrorWriteFailure() throws IOException {
*/
@Test
public void notLogReadEvents() throws IOException {
AUDIT.logReadSuccess(Level.INFO, READ_SUCCESS_MSG);
AUDIT.logReadFailure(Level.INFO, READ_FAIL_MSG);
AUDIT.logReadFailure(Level.ERROR, READ_FAIL_MSG);
AUDIT.logReadFailure(Level.ERROR, READ_FAIL_MSG, new Exception("test"));
verifyNoLog();
}
/**
* Test to ensure DEBUG level messages are not logged when INFO is enabled.
*/
@Test
public void notLogDebugEvents() throws IOException {
AUDIT.logWriteSuccess(Level.DEBUG, WRITE_SUCCESS_MSG);
AUDIT.logReadSuccess(Level.DEBUG, READ_SUCCESS_MSG);
AUDIT.logReadSuccess(READ_SUCCESS_MSG);
AUDIT.logReadFailure(READ_FAIL_MSG);
verifyNoLog();
}

View File

@ -56,13 +56,13 @@ appenders = console, audit
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%-5level] %c{1} - %msg%n
appender.console.layout.pattern = %-5level | %c{1} | %msg%n
appender.audit.type = File
appender.audit.name = AUDITLOG
appender.audit.fileName=audit.log
appender.audit.layout.type=PatternLayout
appender.audit.layout.pattern= [%-5level] %c{1} - %msg%n
appender.audit.layout.pattern= %-5level | %c{1} | %msg%n
loggers=audit
logger.audit.type=AsyncLogger

View File

@ -46,6 +46,7 @@
import org.apache.hadoop.ozone.audit.AuditLogger;
import org.apache.hadoop.ozone.audit.AuditLoggerType;
import org.apache.hadoop.ozone.audit.AuditMessage;
import org.apache.hadoop.ozone.audit.Auditor;
import org.apache.hadoop.ozone.audit.OMAction;
import org.apache.hadoop.ozone.common.Storage.StorageState;
import org.apache.hadoop.ozone.om.exceptions.OMException;
@ -66,7 +67,6 @@
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.util.StringUtils;
import org.apache.logging.log4j.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -98,7 +98,7 @@
*/
@InterfaceAudience.LimitedPrivate({"HDFS", "CBLOCK", "OZONE", "HBASE"})
public final class OzoneManager extends ServiceRuntimeInfoImpl
implements OzoneManagerProtocol, OMMXBean {
implements OzoneManagerProtocol, OMMXBean, Auditor {
private static final Logger LOG =
LoggerFactory.getLogger(OzoneManager.class);
@ -498,9 +498,10 @@ public void createVolume(OmVolumeArgs args) throws IOException {
(args == null) ? null : args.toAuditMap()));
} catch (Exception ex) {
metrics.incNumVolumeCreateFails();
AUDIT.logWriteFailure(Level.ERROR,
AUDIT.logWriteFailure(
buildAuditMessageForFailure(OMAction.CREATE_VOLUME,
(args == null) ? null : args.toAuditMap()), ex);
(args == null) ? null : args.toAuditMap(), ex)
);
throw ex;
}
}
@ -524,7 +525,8 @@ public void setOwner(String volume, String owner) throws IOException {
} catch (Exception ex) {
metrics.incNumVolumeUpdateFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.SET_OWNER,
auditMap), ex);
auditMap, ex)
);
throw ex;
}
}
@ -548,7 +550,7 @@ public void setQuota(String volume, long quota) throws IOException {
} catch (Exception ex) {
metrics.incNumVolumeUpdateFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.SET_QUOTA,
auditMap), ex);
auditMap, ex));
throw ex;
}
}
@ -576,7 +578,7 @@ public boolean checkVolumeAccess(String volume, OzoneAclInfo userAcl)
metrics.incNumVolumeCheckAccessFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(
OMAction.CHECK_VOLUME_ACCESS, auditMap), ex);
OMAction.CHECK_VOLUME_ACCESS, auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -604,7 +606,7 @@ public OmVolumeArgs getVolumeInfo(String volume) throws IOException {
metrics.incNumVolumeInfoFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.READ_VOLUME,
auditMap), ex);
auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -630,7 +632,7 @@ public void deleteVolume(String volume) throws IOException {
} catch (Exception ex) {
metrics.incNumVolumeDeleteFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.DELETE_VOLUME,
buildAuditMap(volume)), ex);
buildAuditMap(volume), ex));
throw ex;
}
}
@ -662,7 +664,7 @@ public List<OmVolumeArgs> listVolumeByUser(String userName, String prefix,
metrics.incNumVolumeListFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.LIST_VOLUMES,
auditMap), ex);
auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -698,7 +700,7 @@ public List<OmVolumeArgs> listAllVolumes(String prefix, String prevKey, int
metrics.incNumVolumeListFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.LIST_VOLUMES,
auditMap), ex);
auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -724,7 +726,7 @@ public void createBucket(OmBucketInfo bucketInfo) throws IOException {
} catch (Exception ex) {
metrics.incNumBucketCreateFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.CREATE_BUCKET,
(bucketInfo == null) ? null : bucketInfo.toAuditMap()), ex);
(bucketInfo == null) ? null : bucketInfo.toAuditMap(), ex));
throw ex;
}
}
@ -750,7 +752,7 @@ public List<OmBucketInfo> listBuckets(String volumeName,
metrics.incNumBucketListFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.LIST_BUCKETS,
auditMap), ex);
auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -781,7 +783,7 @@ public OmBucketInfo getBucketInfo(String volume, String bucket)
metrics.incNumBucketInfoFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.READ_BUCKET,
auditMap), ex);
auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -808,7 +810,7 @@ public OpenKeySession openKey(OmKeyArgs args) throws IOException {
metrics.incNumKeyAllocateFails();
auditSuccess = false;
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.ALLOCATE_KEY,
(args == null) ? null : args.toAuditMap()), ex);
(args == null) ? null : args.toAuditMap(), ex));
throw ex;
} finally {
if(auditSuccess){
@ -832,7 +834,7 @@ public void commitKey(OmKeyArgs args, long clientID)
} catch (Exception ex) {
metrics.incNumKeyCommitFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.COMMIT_KEY,
auditMap), ex);
auditMap, ex));
throw ex;
}
}
@ -851,7 +853,7 @@ public OmKeyLocationInfo allocateBlock(OmKeyArgs args, long clientID)
metrics.incNumBlockAllocateCallFails();
auditSuccess = false;
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.ALLOCATE_BLOCK,
auditMap), ex);
auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -878,7 +880,7 @@ public OmKeyInfo lookupKey(OmKeyArgs args) throws IOException {
metrics.incNumKeyLookupFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.READ_KEY,
(args == null) ? null : args.toAuditMap()), ex);
(args == null) ? null : args.toAuditMap(), ex));
throw ex;
} finally {
if(auditSuccess){
@ -901,7 +903,7 @@ public void renameKey(OmKeyArgs args, String toKeyName) throws IOException {
} catch (IOException e) {
metrics.incNumKeyRenameFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.RENAME_KEY,
auditMap), e);
auditMap, e));
throw e;
}
}
@ -922,7 +924,7 @@ public void deleteKey(OmKeyArgs args) throws IOException {
} catch (Exception ex) {
metrics.incNumKeyDeleteFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.DELETE_KEY,
(args == null) ? null : args.toAuditMap()), ex);
(args == null) ? null : args.toAuditMap(), ex));
throw ex;
}
}
@ -944,7 +946,7 @@ public List<OmKeyInfo> listKeys(String volumeName, String bucketName,
metrics.incNumKeyListFails();
auditSuccess = false;
AUDIT.logReadFailure(buildAuditMessageForFailure(OMAction.LIST_KEYS,
auditMap), ex);
auditMap, ex));
throw ex;
} finally {
if(auditSuccess){
@ -971,7 +973,7 @@ public void setBucketProperty(OmBucketArgs args)
} catch (Exception ex) {
metrics.incNumBucketUpdateFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.UPDATE_BUCKET,
(args == null) ? null : args.toAuditMap()), ex);
(args == null) ? null : args.toAuditMap(), ex));
throw ex;
}
}
@ -994,7 +996,7 @@ public void deleteBucket(String volume, String bucket) throws IOException {
} catch (Exception ex) {
metrics.incNumBucketDeleteFails();
AUDIT.logWriteFailure(buildAuditMessageForFailure(OMAction.DELETE_BUCKET,
auditMap), ex);
auditMap, ex));
throw ex;
}
}
@ -1005,26 +1007,34 @@ private Map<String, String> buildAuditMap(String volume){
return auditMap;
}
// TODO: Temporary method until AuditMessage is simplified
private AuditMessage buildAuditMessageForSuccess(AuditAction op,
@Override
public AuditMessage buildAuditMessageForSuccess(AuditAction op,
Map<String, String> auditMap) {
return new AuditMessage(
(Server.getRemoteUser() == null) ? null :
Server.getRemoteUser().getUserName(),
(Server.getRemoteIp() == null) ? null :
Server.getRemoteIp().getHostAddress(), op.toString(), auditMap,
AuditEventStatus.SUCCESS.toString());
return new AuditMessage.Builder()
.setUser((Server.getRemoteUser() == null) ? null :
Server.getRemoteUser().getUserName())
.atIp((Server.getRemoteIp() == null) ? null :
Server.getRemoteIp().getHostAddress())
.forOperation(op.getAction())
.withParams(auditMap)
.withResult(AuditEventStatus.SUCCESS.toString())
.withException(null)
.build();
}
// TODO: Temporary method until AuditMessage is simplified
private AuditMessage buildAuditMessageForFailure(AuditAction op,
Map<String, String> auditMap) {
return new AuditMessage(
(Server.getRemoteUser() == null) ? null :
Server.getRemoteUser().getUserName(),
(Server.getRemoteIp() == null) ? null :
Server.getRemoteIp().getHostAddress(), op.toString(), auditMap,
AuditEventStatus.FAILURE.toString());
@Override
public AuditMessage buildAuditMessageForFailure(AuditAction op,
Map<String, String> auditMap, Throwable throwable) {
return new AuditMessage.Builder()
.setUser((Server.getRemoteUser() == null) ? null :
Server.getRemoteUser().getUserName())
.atIp((Server.getRemoteIp() == null) ? null :
Server.getRemoteIp().getHostAddress())
.forOperation(op.getAction())
.withParams(auditMap)
.withResult(AuditEventStatus.FAILURE.toString())
.withException(throwable)
.build();
}
private void registerMXBean() {