HDDS-1768. Audit xxxAcl methods in OzoneManager (#1204)

This commit is contained in:
dineshchitlangia 2019-08-15 11:43:47 -04:00 committed by Bharat Viswanadham
parent 22c4f38c4b
commit c801f7a26c
6 changed files with 510 additions and 68 deletions

View File

@ -248,6 +248,7 @@ public final class OzoneConsts {
public static final String MAX_KEYS = "maxKeys";
public static final String PREFIX = "prefix";
public static final String KEY_PREFIX = "keyPrefix";
public static final String ACL = "acl";
public static final String ACLS = "acls";
public static final String USER_ACL = "userAcl";
public static final String ADD_ACLS = "addAcls";
@ -255,6 +256,7 @@ public final class OzoneConsts {
public static final String MAX_NUM_OF_BUCKETS = "maxNumOfBuckets";
public static final String TO_KEY_NAME = "toKeyName";
public static final String STORAGE_TYPE = "storageType";
public static final String RESOURCE_TYPE = "resourceType";
public static final String IS_VERSION_ENABLED = "isVersionEnabled";
public static final String CREATION_TIME = "creationTime";
public static final String DATA_SIZE = "dataSize";

View File

@ -58,6 +58,12 @@ public enum OMAction implements AuditAction {
LIST_MULTIPART_UPLOAD_PARTS,
ABORT_MULTIPART_UPLOAD,
//ACL Actions
ADD_ACL,
GET_ACL,
SET_ACL,
REMOVE_ACL,
//FS Actions
GET_FILE_STATUS,
CREATE_DIRECTORY,

View File

@ -22,6 +22,8 @@ import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OzoneObj.ObjectType;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OzoneObj.StoreType.*;
/**
@ -131,4 +133,15 @@ public abstract class OzoneObj implements IOzoneObj {
value = objType;
}
}
public Map<String, String> toAuditMap() {
Map<String, String> auditMap = new LinkedHashMap<>();
auditMap.put(OzoneConsts.RESOURCE_TYPE, this.getResourceType().value);
auditMap.put(OzoneConsts.STORAGE_TYPE, this.getStoreType().value);
auditMap.put(OzoneConsts.VOLUME, this.getVolumeName());
auditMap.put(OzoneConsts.BUCKET, this.getBucketName());
auditMap.put(OzoneConsts.KEY, this.getKeyName());
return auditMap;
}
}

View File

@ -0,0 +1,284 @@
package org.apache.hadoop.ozone.client.rpc;
import net.jcip.annotations.NotThreadSafe;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.scm.protocolPB.StorageContainerLocationProtocolClientSideTranslatorPB;
import org.apache.hadoop.ozone.MiniOzoneCluster;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.audit.AuditEventStatus;
import org.apache.hadoop.ozone.audit.OMAction;
import org.apache.hadoop.ozone.client.ObjectStore;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneClientFactory;
import org.apache.hadoop.ozone.client.OzoneVolume;
import org.apache.hadoop.ozone.client.VolumeArgs;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
import org.apache.hadoop.ozone.security.acl.OzoneObj;
import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.test.GenericTestUtils;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS_NATIVE;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS_WILDCARD;
import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.VOLUME;
import static org.apache.hadoop.ozone.security.acl.OzoneObj.StoreType.OZONE;
import static org.junit.Assert.assertTrue;
/**
* This class is to test audit logs for xxxACL APIs of Ozone Client.
* It is annotated as NotThreadSafe intentionally since this test reads from
* the generated audit logs to verify the operations. Since the
* maven test plugin will trigger parallel test execution, there is a
* possibility of other audit events being logged and leading to failure of
* all assertion based test in this class.
*/
@NotThreadSafe
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestOzoneRpcClientForAclAuditLog {
private static final Logger LOG =
LoggerFactory.getLogger(TestOzoneRpcClientForAclAuditLog.class);
private static UserGroupInformation ugi;
private static final OzoneAcl USER_ACL =
new OzoneAcl(IAccessAuthorizer.ACLIdentityType.USER,
"johndoe", IAccessAuthorizer.ACLType.ALL, ACCESS);
private static final OzoneAcl USER_ACL_2 =
new OzoneAcl(IAccessAuthorizer.ACLIdentityType.USER,
"jane", IAccessAuthorizer.ACLType.ALL, ACCESS);
private static List<OzoneAcl> aclListToAdd = new ArrayList<>();
private static MiniOzoneCluster cluster = null;
private static OzoneClient ozClient = null;
private static ObjectStore store = null;
private static StorageContainerLocationProtocolClientSideTranslatorPB
storageContainerLocationClient;
private static String scmId = UUID.randomUUID().toString();
/**
* Create a MiniOzoneCluster for testing.
*
* Ozone is made active by setting OZONE_ENABLED = true
*
* @throws IOException
*/
@BeforeClass
public static void init() throws Exception {
System.setProperty("log4j.configurationFile", "log4j2.properties");
ugi = UserGroupInformation.getCurrentUser();
OzoneConfiguration conf = new OzoneConfiguration();
conf.setBoolean(OZONE_ACL_ENABLED, true);
conf.set(OZONE_ADMINISTRATORS, OZONE_ADMINISTRATORS_WILDCARD);
conf.set(OZONE_ACL_AUTHORIZER_CLASS,
OZONE_ACL_AUTHORIZER_CLASS_NATIVE);
startCluster(conf);
aclListToAdd.add(USER_ACL);
aclListToAdd.add(USER_ACL_2);
emptyAuditLog();
}
/**
* Create a MiniOzoneCluster for testing.
* @param conf Configurations to start the cluster.
* @throws Exception
*/
private static void startCluster(OzoneConfiguration conf) throws Exception {
cluster = MiniOzoneCluster.newBuilder(conf)
.setNumDatanodes(3)
.setScmId(scmId)
.build();
cluster.waitForClusterToBeReady();
ozClient = OzoneClientFactory.getRpcClient(conf);
store = ozClient.getObjectStore();
storageContainerLocationClient =
cluster.getStorageContainerLocationClient();
}
/**
* Close OzoneClient and shutdown MiniOzoneCluster.
*/
@AfterClass
public static void teardown() throws IOException {
shutdownCluster();
deleteAuditLog();
}
private static void deleteAuditLog() throws IOException {
File file = new File("audit.log");
if (FileUtils.deleteQuietly(file)) {
LOG.info(file.getName() +
" has been deleted.");
} else {
LOG.info("audit.log could not be deleted.");
}
}
private static void emptyAuditLog() throws IOException {
File file = new File("audit.log");
FileUtils.writeLines(file, new ArrayList<>(), false);
}
/**
* Close OzoneClient and shutdown MiniOzoneCluster.
*/
private static void shutdownCluster() throws IOException {
if(ozClient != null) {
ozClient.close();
}
if (storageContainerLocationClient != null) {
storageContainerLocationClient.close();
}
if (cluster != null) {
cluster.shutdown();
}
}
@Test
public void testXXXAclSuccessAudits() throws Exception {
String userName = ugi.getUserName();
String adminName = ugi.getUserName();
String volumeName = "volume" + RandomStringUtils.randomNumeric(5);
VolumeArgs createVolumeArgs = VolumeArgs.newBuilder()
.setAdmin(adminName)
.setOwner(userName)
.build();
store.createVolume(volumeName, createVolumeArgs);
verifyLog(OMAction.CREATE_VOLUME.name(), volumeName,
AuditEventStatus.SUCCESS.name());
OzoneVolume retVolumeinfo = store.getVolume(volumeName);
verifyLog(OMAction.READ_VOLUME.name(), volumeName,
AuditEventStatus.SUCCESS.name());
Assert.assertTrue(retVolumeinfo.getName().equalsIgnoreCase(volumeName));
OzoneObj volObj = new OzoneObjInfo.Builder()
.setVolumeName(volumeName)
.setResType(VOLUME)
.setStoreType(OZONE)
.build();
//Testing getAcl
List<OzoneAcl> acls = store.getAcl(volObj);
verifyLog(OMAction.GET_ACL.name(), volumeName,
AuditEventStatus.SUCCESS.name());
Assert.assertTrue(acls.size() > 0);
//Testing addAcl
store.addAcl(volObj, USER_ACL);
verifyLog(OMAction.ADD_ACL.name(), volumeName, "johndoe",
AuditEventStatus.SUCCESS.name());
//Testing removeAcl
store.removeAcl(volObj, USER_ACL);
verifyLog(OMAction.REMOVE_ACL.name(), volumeName, "johndoe",
AuditEventStatus.SUCCESS.name());
//Testing setAcl
store.setAcl(volObj, aclListToAdd);
verifyLog(OMAction.SET_ACL.name(), volumeName, "johndoe", "jane",
AuditEventStatus.SUCCESS.name());
}
@Test
public void testXXXAclFailureAudits() throws Exception {
String userName = "bilbo";
String adminName = "bilbo";
String volumeName = "volume" + RandomStringUtils.randomNumeric(5);
VolumeArgs createVolumeArgs = VolumeArgs.newBuilder()
.setAdmin(adminName)
.setOwner(userName)
.build();
store.createVolume(volumeName, createVolumeArgs);
verifyLog(OMAction.CREATE_VOLUME.name(), volumeName,
AuditEventStatus.SUCCESS.name());
OzoneObj volObj = new OzoneObjInfo.Builder()
.setVolumeName(volumeName)
.setResType(VOLUME)
.setStoreType(OZONE)
.build();
// xxxAcl will fail as current ugi user doesn't have the required access
// for volume
try{
List<OzoneAcl> acls = store.getAcl(volObj);
} catch (Exception ex) {
verifyLog(OMAction.GET_ACL.name(), volumeName,
AuditEventStatus.FAILURE.name());
}
try{
store.addAcl(volObj, USER_ACL);
} catch (Exception ex) {
verifyLog(OMAction.ADD_ACL.name(), volumeName,
AuditEventStatus.FAILURE.name());
}
try{
store.removeAcl(volObj, USER_ACL);
} catch (Exception ex) {
verifyLog(OMAction.REMOVE_ACL.name(), volumeName,
AuditEventStatus.FAILURE.name());
}
try{
store.setAcl(volObj, aclListToAdd);
} catch (Exception ex) {
verifyLog(OMAction.SET_ACL.name(), volumeName, "johndoe", "jane",
AuditEventStatus.FAILURE.name());
}
}
private void verifyLog(String... expected) throws Exception {
File file = new File("audit.log");
final List<String> lines = FileUtils.readLines(file, (String)null);
GenericTestUtils.waitFor(() ->
(lines != null) ? true : false, 100, 60000);
try{
// When log entry is expected, the log file will contain one line and
// that must be equal to the expected string
assertTrue(lines.size() != 0);
for(String exp: expected){
assertTrue(lines.get(0).contains(exp));
}
} catch (AssertionError ex){
LOG.error("Error occurred in log verification", ex);
if(lines.size() != 0){
LOG.error("Actual line ::: " + lines.get(0));
LOG.error("Expected tokens ::: " + Arrays.toString(expected));
}
throw ex;
} finally {
emptyAuditLog();
}
}
}

View File

@ -0,0 +1,76 @@
#
# 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.
#
name=PropertiesConfig
# Checks for config change periodically and reloads
monitorInterval=5
filter=read, write
# filter.read.onMatch = DENY avoids logging all READ events
# filter.read.onMatch = ACCEPT permits logging all READ events
# The above two settings ignore the log levels in configuration
# filter.read.onMatch = NEUTRAL permits logging of only those READ events
# which are attempted at log level equal or greater than log level specified
# in the configuration
filter.read.type = MarkerFilter
filter.read.marker = READ
filter.read.onMatch = NEUTRAL
filter.read.onMismatch = NEUTRAL
# filter.write.onMatch = DENY avoids logging all WRITE events
# filter.write.onMatch = ACCEPT permits logging all WRITE events
# The above two settings ignore the log levels in configuration
# filter.write.onMatch = NEUTRAL permits logging of only those WRITE events
# which are attempted at log level equal or greater than log level specified
# in the configuration
filter.write.type = MarkerFilter
filter.write.marker = WRITE
filter.write.onMatch = NEUTRAL
filter.write.onMismatch = NEUTRAL
# Log Levels are organized from most specific to least:
# OFF (most specific, no logging)
# FATAL (most specific, little data)
# ERROR
# WARN
# INFO
# DEBUG
# TRACE (least specific, a lot of data)
# ALL (least specific, all data)
appenders = console, audit
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{DEFAULT} | %-5level | %c{1} | %msg | %throwable{3} %n
appender.audit.type = File
appender.audit.name = AUDITLOG
appender.audit.fileName=audit.log
appender.audit.layout.type=PatternLayout
appender.audit.layout.pattern= %d{DEFAULT} | %-5level | %c{1} | %msg | %throwable{3} %n
loggers=audit
logger.audit.type=AsyncLogger
logger.audit.name=OMAudit
logger.audit.level = INFO
logger.audit.appenderRefs = audit
logger.audit.appenderRef.file.ref = AUDITLOG
rootLogger.level = INFO
rootLogger.appenderRefs = stdout
rootLogger.appenderRef.stdout.ref = STDOUT

View File

@ -30,6 +30,7 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.KeyPair;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
@ -2989,6 +2990,22 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
}
}
private void auditAcl(OzoneObj ozoneObj, List<OzoneAcl> ozoneAcl,
OMAction omAction, Exception ex) {
Map<String, String> auditMap = ozoneObj.toAuditMap();
if(ozoneAcl != null) {
auditMap.put(OzoneConsts.ACL, ozoneAcl.toString());
}
if(ex == null) {
AUDIT.logWriteSuccess(
buildAuditMessageForSuccess(omAction, auditMap));
} else {
AUDIT.logWriteFailure(
buildAuditMessageForFailure(omAction, auditMap, ex));
}
}
/**
* Add acl for Ozone object. Return true if acl is added successfully else
* false.
@ -2999,23 +3016,34 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
*/
@Override
public boolean addAcl(OzoneObj obj, OzoneAcl acl) throws IOException {
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.WRITE_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
// TODO: Audit ACL operation.
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.addAcl(obj, acl);
case BUCKET:
return bucketManager.addAcl(obj, acl);
case KEY:
return keyManager.addAcl(obj, acl);
case PREFIX:
return prefixManager.addAcl(obj, acl);
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
boolean auditSuccess = true;
try{
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.WRITE_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.addAcl(obj, acl);
case BUCKET:
return bucketManager.addAcl(obj, acl);
case KEY:
return keyManager.addAcl(obj, acl);
case PREFIX:
return prefixManager.addAcl(obj, acl);
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
}
} catch(Exception ex) {
auditSuccess = false;
auditAcl(obj, Arrays.asList(acl), OMAction.ADD_ACL, ex);
throw ex;
} finally {
if(auditSuccess){
auditAcl(obj, Arrays.asList(acl), OMAction.ADD_ACL, null);
}
}
}
@ -3029,24 +3057,35 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
*/
@Override
public boolean removeAcl(OzoneObj obj, OzoneAcl acl) throws IOException {
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.WRITE_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
// TODO: Audit ACL operation.
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.removeAcl(obj, acl);
case BUCKET:
return bucketManager.removeAcl(obj, acl);
case KEY:
return keyManager.removeAcl(obj, acl);
case PREFIX:
return prefixManager.removeAcl(obj, acl);
boolean auditSuccess = true;
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
try{
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.WRITE_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.removeAcl(obj, acl);
case BUCKET:
return bucketManager.removeAcl(obj, acl);
case KEY:
return keyManager.removeAcl(obj, acl);
case PREFIX:
return prefixManager.removeAcl(obj, acl);
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
}
} catch(Exception ex) {
auditSuccess = false;
auditAcl(obj, Arrays.asList(acl), OMAction.REMOVE_ACL, ex);
throw ex;
} finally {
if(auditSuccess){
auditAcl(obj, Arrays.asList(acl), OMAction.REMOVE_ACL, null);
}
}
}
@ -3060,23 +3099,34 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
*/
@Override
public boolean setAcl(OzoneObj obj, List<OzoneAcl> acls) throws IOException {
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.WRITE_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
// TODO: Audit ACL operation.
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.setAcl(obj, acls);
case BUCKET:
return bucketManager.setAcl(obj, acls);
case KEY:
return keyManager.setAcl(obj, acls);
case PREFIX:
return prefixManager.setAcl(obj, acls);
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
boolean auditSuccess = true;
try{
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.WRITE_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.setAcl(obj, acls);
case BUCKET:
return bucketManager.setAcl(obj, acls);
case KEY:
return keyManager.setAcl(obj, acls);
case PREFIX:
return prefixManager.setAcl(obj, acls);
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
}
} catch(Exception ex) {
auditSuccess = false;
auditAcl(obj, acls, OMAction.SET_ACL, ex);
throw ex;
} finally {
if(auditSuccess){
auditAcl(obj, acls, OMAction.SET_ACL, null);
}
}
}
@ -3088,24 +3138,35 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
*/
@Override
public List<OzoneAcl> getAcl(OzoneObj obj) throws IOException {
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.READ_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
// TODO: Audit ACL operation.
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.getAcl(obj);
case BUCKET:
return bucketManager.getAcl(obj);
case KEY:
return keyManager.getAcl(obj);
case PREFIX:
return prefixManager.getAcl(obj);
boolean auditSuccess = true;
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
try{
if(isAclEnabled) {
checkAcls(obj.getResourceType(), obj.getStoreType(), ACLType.READ_ACL,
obj.getVolumeName(), obj.getBucketName(), obj.getKeyName());
}
switch (obj.getResourceType()) {
case VOLUME:
return volumeManager.getAcl(obj);
case BUCKET:
return bucketManager.getAcl(obj);
case KEY:
return keyManager.getAcl(obj);
case PREFIX:
return prefixManager.getAcl(obj);
default:
throw new OMException("Unexpected resource type: " +
obj.getResourceType(), INVALID_REQUEST);
}
} catch(Exception ex) {
auditSuccess = false;
auditAcl(obj, null, OMAction.GET_ACL, ex);
throw ex;
} finally {
if(auditSuccess){
auditAcl(obj, null, OMAction.GET_ACL, null);
}
}
}