diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java index 456f44bac113..654818a1aa5a 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java @@ -218,6 +218,16 @@ public final class OzoneConfigKeys { public static final String OZONE_READONLY_ADMINISTRATORS_GROUPS = "ozone.readonly.administrators.groups"; + public static final String OZONE_BLACKLIST_USERS = + "ozone.blacklist.users"; + public static final String OZONE_BLACKLIST_GROUPS = + "ozone.blacklist.groups"; + + public static final String OZONE_READ_BLACKLIST_USERS = + "ozone.read.blacklist.users"; + public static final String OZONE_READ_BLACKLIST_GROUPS = + "ozone.read.blacklist.groups"; + /** * Used only for testing purpose. Results in making every user an admin. * */ diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 6c4bd26ebe88..a510a84424d1 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -1903,6 +1903,46 @@ + + ozone.blacklist.users + + + Ozone blacklisted users delimited by the comma. + If set, This is the list of users that are not allowed to do any operations even + if the blacklisted user is also under (readonly) admin / admin group, + + + + + ozone.blacklist.groups + + + Ozone blacklisted groups delimited by the comma. + If set, This is the list of groups that are not allowed to do any operations even + if the blacklisted user is also under (readonly) admin / admin group, + + + + + ozone.read.blacklist.users + + + Ozone read blacklist users delimited by the comma. + If set, This is the list of users are not allowed to do any read operations even + if the blacklisted user is also under (readonly) admin / admin group. + + + + + ozone.read.blacklist.groups + + + Ozone read blacklist groups delimited by the comma. + If set, This is the list of groups are not allowed to do any read operations even + if the blacklisted user is also under (readonly) admin / admin group. + + + ozone.s3g.volume.name s3v diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/OzoneBlacklist.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/OzoneBlacklist.java new file mode 100644 index 000000000000..8f48030d9ebb --- /dev/null +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/OzoneBlacklist.java @@ -0,0 +1,219 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.hdds.server; + +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLACKLIST_GROUPS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLACKLIST_USERS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READ_BLACKLIST_GROUPS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READ_BLACKLIST_USERS; + +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.security.AccessControlException; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.util.StringUtils; + +/** + * This class contains the blacklisted user information, username and group + * and is able to check whether the provided {@link UserGroupInformation} + * is blacklisted. + * + * The implementation is similar to {@link OzoneAdmins}. + */ +public class OzoneBlacklist { + + private volatile Set blacklistUsernames; + + private volatile Set blacklistGroups; + + public OzoneBlacklist(Collection blacklistUsernames) { + this(blacklistUsernames, null); + } + + public OzoneBlacklist(Collection blacklistUsernames, + Collection blacklistGroups) { + setBlacklistUsernames(blacklistUsernames); + this.blacklistGroups = blacklistGroups != null + ? Collections.unmodifiableSet(new LinkedHashSet<>(blacklistGroups)) + : Collections.emptySet(); + } + + /** + * Returns an OzoneBlacklist instance configured with blacklisted users + * and groups from the provided configuration. + * + * @param configuration the configuration settings to apply. + * @return a configured OzoneBlacklist instance. + */ + public static OzoneBlacklist getOzoneBlacklist( + OzoneConfiguration configuration) { + Collection blacklistUsernames = + getOzoneBlacklistUsersFromConfig(configuration); + Collection blacklistGroupNames = + getOzoneBlacklistGroupsFromConfig(configuration); + return new OzoneBlacklist(blacklistUsernames, blacklistGroupNames); + } + + /** + * Creates and returns a read-only blacklist object. This object includes the + * read blacklisted users and user groups obtained from the Ozone + * configuration. + * + * @param configuration the configuration settings to apply. + * @return a configured OzoneBlacklist instance. + */ + public static OzoneBlacklist getReadonlyBlacklist( + OzoneConfiguration configuration) { + Collection omReadBlacklistUser = + getOzoneReadBlacklistUsersFromConfig(configuration); + Collection omReadBlacklistGroups = + getOzoneReadBlacklistGroupsFromConfig(configuration); + return new OzoneBlacklist(omReadBlacklistUser, omReadBlacklistGroups); + } + + /** + * Check ozone blacklist, throws exception if user is blacklisted. + */ + public void checkBlacklist(UserGroupInformation ugi) + throws AccessControlException { + if (ugi != null && isBlacklisted(ugi)) { + throw new AccessControlException("Access denied for user " + + ugi.getUserName() + ". User is blacklisted."); + } + } + + private boolean hasBlacklistGroup(Collection userGroups) { + return !Sets.intersection(blacklistGroups, + new LinkedHashSet<>(userGroups)).isEmpty(); + } + + /** + * Check whether the provided {@link UserGroupInformation user} + * is blacklisted. + * + * @param user the {@link UserGroupInformation}. + * @return true if the user is blacklisted, otherwise false. + */ + public boolean isBlacklisted(UserGroupInformation user) { + return user != null + && (blacklistUsernames.contains(user.getShortUserName()) + || hasBlacklistGroup(user.getGroups())); + } + + public Collection getBlacklistGroups() { + return blacklistGroups; + } + + public Set getBlacklistUsernames() { + return blacklistUsernames; + } + + public void setBlacklistUsernames( + Collection blacklistUsernames) { + this.blacklistUsernames = blacklistUsernames != null + ? Collections.unmodifiableSet( + new LinkedHashSet<>(blacklistUsernames)) + : Collections.emptySet(); + } + + /** + * Return list of blacklisted users from config. + * + * @param conf the configuration settings to apply. + */ + public static Collection getOzoneBlacklistUsersFromConfig( + OzoneConfiguration conf) { + return conf.getTrimmedStringCollection(OZONE_BLACKLIST_USERS); + } + + /** + * Return list of blacklisted users from config value. + * @param valueString the configuration value. + */ + public static Collection getOzoneBlacklistUsersFromConfigValue( + String valueString) { + return StringUtils.getTrimmedStringCollection(valueString); + } + + /** + * Return list of blacklist Groups from config. + * + * @param configuration the configuration settings to apply. + */ + public static Collection getOzoneBlacklistGroupsFromConfig( + OzoneConfiguration configuration) { + return configuration.getTrimmedStringCollection(OZONE_BLACKLIST_GROUPS); + } + + /** + * Return list of blacklisted groups from config value. + * @param valueString the configuration value. + */ + public static Collection getOzoneBlacklistGroupsFromConfigValue( + String valueString) { + return StringUtils.getTrimmedStringCollection(valueString); + } + + /** + * Return list of Ozone Read only blacklisted users from config. + * + * @param conf the configuration settings to apply. + */ + public static Collection getOzoneReadBlacklistUsersFromConfig( + OzoneConfiguration conf) { + return conf.getTrimmedStringCollection(OZONE_READ_BLACKLIST_USERS); + } + + /** + * Return list of Ozone Read only blacklisted users from config value. + * @param valueString the configuration value. + */ + public static Collection getOzoneReadBlacklistUsersFromConfigValue( + String valueString) { + return StringUtils.getTrimmedStringCollection(valueString); + } + + /** + * Return list of Ozone Read only blacklisted groups from config. + * + * @param conf the configuration settings to apply. + */ + public static Collection getOzoneReadBlacklistGroupsFromConfig( + OzoneConfiguration conf) { + return conf.getTrimmedStringCollection(OZONE_READ_BLACKLIST_GROUPS); + } + + /** + * Return list of Ozone Read only blacklisted groups from config value. + * @param valueString the configuration value. + */ + public static Collection getOzoneReadBlacklistGroupsFromConfigValue( + String valueString) { + return StringUtils.getTrimmedStringCollection(valueString); + } + + public void setBlacklistGroups(Collection blacklistGroups) { + this.blacklistGroups = blacklistGroups != null + ? Collections.unmodifiableSet(new LinkedHashSet<>(blacklistGroups)) + : Collections.emptySet(); + } +} diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java index fb46edf0bdf0..d37cbd2619c3 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java @@ -19,7 +19,11 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLACKLIST_GROUPS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLACKLIST_USERS; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READ_BLACKLIST_GROUPS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READ_BLACKLIST_USERS; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_DIR_DELETING_SERVICE_INTERVAL; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_KEY_DELETING_LIMIT_PER_TASK; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_SST_FILTERING_SERVICE_INTERVAL; @@ -64,6 +68,10 @@ void reconfigurableProperties() { .add(OZONE_THREAD_NUMBER_DIR_DELETION) .add(OZONE_SNAPSHOT_SST_FILTERING_SERVICE_INTERVAL) .addAll(new OmConfig().reconfigurableProperties()) + .add(OZONE_BLACKLIST_USERS) + .add(OZONE_BLACKLIST_GROUPS) + .add(OZONE_READ_BLACKLIST_USERS) + .add(OZONE_READ_BLACKLIST_GROUPS) .build(); assertProperties(getSubject(), expected); @@ -92,6 +100,70 @@ void readOnlyAdmins() throws ReconfigurationException { cluster().getOzoneManager().getOmReadOnlyAdminUsernames()); } + @Test + void blacklistUsers() throws ReconfigurationException { + final String newValue = RandomStringUtils.secure().nextAlphabetic(10); + + getSubject().reconfigureProperty(OZONE_BLACKLIST_USERS, newValue); + + assertEquals( + ImmutableSet.of(newValue), + cluster().getOzoneManager().getOmBlacklistUsernames()); + } + + @Test + void blacklistGroups() throws ReconfigurationException { + String groupA = "groupA"; + String groupB = "groupB"; + getSubject().reconfigureProperty(OZONE_BLACKLIST_GROUPS, groupA); + assertTrue( + cluster().getOzoneManager().getOmBlacklistGroups().contains(groupA), + groupA + " should be a blacklist group"); + + getSubject().reconfigureProperty(OZONE_BLACKLIST_GROUPS, groupB); + assertFalse( + cluster().getOzoneManager().getOmBlacklistGroups().contains(groupA), + groupA + " should NOT be a blacklist group"); + assertTrue( + cluster().getOzoneManager().getOmBlacklistGroups().contains(groupB), + groupB + " should be a blacklist group"); + } + + @Test + void readBlacklistUsers() throws ReconfigurationException { + final String newValue = RandomStringUtils.secure().nextAlphabetic(10); + + getSubject().reconfigureProperty(OZONE_READ_BLACKLIST_USERS, + newValue); + + assertEquals( + ImmutableSet.of(newValue), + cluster().getOzoneManager().getOmReadOnlyBlacklistUsernames()); + } + + @Test + void readBlacklistGroups() throws ReconfigurationException { + String groupA = "readonlyBlacklistGroupA"; + String groupB = "readonlyBlacklistGroupB"; + getSubject().reconfigureProperty(OZONE_READ_BLACKLIST_GROUPS, + groupA); + assertTrue( + cluster().getOzoneManager().getOmReadonlyBlacklistGroups() + .contains(groupA), + groupA + " should be a readOnly blacklist group"); + + getSubject().reconfigureProperty(OZONE_READ_BLACKLIST_GROUPS, + groupB); + assertFalse( + cluster().getOzoneManager().getOmReadonlyBlacklistGroups() + .contains(groupA), + groupA + " should NOT be a readOnly blacklist group"); + assertTrue( + cluster().getOzoneManager().getOmReadonlyBlacklistGroups() + .contains(groupB), + groupB + " should be a readOnly blacklist group"); + } + @Test public void maxListSize() throws ReconfigurationException { final long initialValue = cluster().getOzoneManager().getConfig().getMaxListSize(); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java index 0587b71f239c..e42c1d76761c 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java @@ -32,11 +32,15 @@ import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED_DEFAULT; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLACKLIST_GROUPS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLACKLIST_USERS; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_FLEXIBLE_FQDN_RESOLUTION_ENABLED; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_FLEXIBLE_FQDN_RESOLUTION_ENABLED_DEFAULT; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_KEY_PREALLOCATION_BLOCKS_MAX; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_KEY_PREALLOCATION_BLOCKS_MAX_DEFAULT; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READ_BLACKLIST_GROUPS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READ_BLACKLIST_USERS; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SCM_BLOCK_SIZE; import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SCM_BLOCK_SIZE_DEFAULT; import static org.apache.hadoop.ozone.OzoneConsts.DB_TRANSIENT_MARKER; @@ -197,6 +201,7 @@ import org.apache.hadoop.hdds.security.token.OzoneBlockTokenSecretManager; import org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient; import org.apache.hadoop.hdds.server.OzoneAdmins; +import org.apache.hadoop.hdds.server.OzoneBlacklist; import org.apache.hadoop.hdds.server.ServiceRuntimeInfoImpl; import org.apache.hadoop.hdds.server.http.RatisDropwizardExports; import org.apache.hadoop.hdds.utils.HAUtils; @@ -412,6 +417,8 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl private final OzoneAdmins omAdmins; private final OzoneAdmins readOnlyAdmins; private final OzoneAdmins s3OzoneAdmins; + private final OzoneBlacklist omBlacklist; + private final OzoneBlacklist readBlacklist; private final OMMetrics metrics; private final OmSnapshotInternalMetrics omSnapshotIntMetrics; @@ -531,7 +538,11 @@ private OzoneManager(OzoneConfiguration conf, StartupOption startupOption) .register(OZONE_KEY_DELETING_LIMIT_PER_TASK, this::reconfOzoneKeyDeletingLimitPerTask) .register(OZONE_DIR_DELETING_SERVICE_INTERVAL, this::reconfOzoneDirDeletingServiceInterval) - .register(OZONE_THREAD_NUMBER_DIR_DELETION, this::reconfOzoneThreadNumberDirDeletion); + .register(OZONE_THREAD_NUMBER_DIR_DELETION, this::reconfOzoneThreadNumberDirDeletion) + .register(OZONE_BLACKLIST_USERS, this::reconfOzoneBlacklistUsers) + .register(OZONE_BLACKLIST_GROUPS, this::reconfOzoneBlacklistGroups) + .register(OZONE_READ_BLACKLIST_USERS, this::reconfOzoneReadBlacklistUsers) + .register(OZONE_READ_BLACKLIST_GROUPS, this::reconfOzoneReadBlacklistGroups); reconfigurationHandler.setReconfigurationCompleteCallback(reconfigurationHandler.defaultLoggingCallback()); @@ -683,9 +694,13 @@ private OzoneManager(OzoneConfiguration conf, StartupOption startupOption) omStarterUser = UserGroupInformation.getCurrentUser().getShortUserName(); omAdmins = OzoneAdmins.getOzoneAdmins(omStarterUser, conf); LOG.info("OM start with adminUsers: {}", omAdmins.getAdminUsernames()); + omBlacklist = OzoneBlacklist.getOzoneBlacklist(conf); + LOG.info("OM start with blacklist users: {}", + omBlacklist.getBlacklistUsernames()); // Get read only admin list readOnlyAdmins = OzoneAdmins.getReadonlyAdmins(conf); + readBlacklist = OzoneBlacklist.getReadonlyBlacklist(conf); s3OzoneAdmins = OzoneAdmins.getS3Admins(conf); instantiateServices(false); @@ -4611,6 +4626,26 @@ public Collection getOmAdminGroups() { return omAdmins.getAdminGroups(); } + public Collection getOmBlacklistUsernames() { + return omBlacklist.getBlacklistUsernames(); + } + + public Collection getOmReadOnlyBlacklistUsernames() { + return readBlacklist.getBlacklistUsernames(); + } + + public Collection getOmBlacklistGroups() { + return omBlacklist.getBlacklistGroups(); + } + + public Collection getOmReadonlyBlacklistGroups() { + return readBlacklist.getBlacklistGroups(); + } + + public OzoneBlacklist getOmBlacklist() { + return omBlacklist; + } + /** * @param callerUgi Caller UserGroupInformation * @return return true if the {@code ugi} is a read-only OM admin @@ -4627,6 +4662,22 @@ public boolean isAdmin(UserGroupInformation callerUgi) { return callerUgi != null && omAdmins.isAdmin(callerUgi); } + /** + * @param callerUgi Caller UserGroupInformation + * @return returns true if the {@code ugi} is under the blacklist. + */ + public boolean isBlacklisted(UserGroupInformation callerUgi) { + return callerUgi != null && omBlacklist.isBlacklisted(callerUgi); + } + + /** + * @param callerUgi Caller UserGroupInformation + * @return returns true if the {@code ugi} is under the read blacklist. + */ + public boolean isReadBlacklisted(UserGroupInformation callerUgi) { + return callerUgi != null && readBlacklist.isBlacklisted(callerUgi); + } + /** * Check ozone admin privilege, throws exception if not admin. * Only checks admin privilege if authorization is enabled. @@ -5348,6 +5399,42 @@ private String reconfOzoneReadOnlyAdmins(String newVal) { return String.valueOf(newVal); } + private String reconfOzoneBlacklistUsers(String newVal) { + Collection blacklistUsers = + OzoneBlacklist.getOzoneBlacklistUsersFromConfigValue(newVal); + omBlacklist.setBlacklistUsernames(blacklistUsers); + LOG.info("Load conf {} : {}, and now blacklist users are: {}", + OZONE_BLACKLIST_USERS, newVal, blacklistUsers); + return String.valueOf(newVal); + } + + private String reconfOzoneBlacklistGroups(String newVal) { + Collection blacklistGroups = + OzoneBlacklist.getOzoneBlacklistGroupsFromConfigValue(newVal); + omBlacklist.setBlacklistGroups(blacklistGroups); + LOG.info("Load conf {} : {}, and now blacklist groups are: {}", + OZONE_BLACKLIST_GROUPS, newVal, blacklistGroups); + return String.valueOf(newVal); + } + + private String reconfOzoneReadBlacklistUsers(String newVal) { + Collection readBlacklistUsers = + OzoneBlacklist.getOzoneReadBlacklistUsersFromConfigValue(newVal); + readBlacklist.setBlacklistUsernames(readBlacklistUsers); + LOG.info("Load conf {} : {}, and now read blacklist users are: {}", + OZONE_READ_BLACKLIST_USERS, newVal, readBlacklistUsers); + return String.valueOf(newVal); + } + + private String reconfOzoneReadBlacklistGroups(String newVal) { + Collection readBlacklistGroups = + OzoneBlacklist.getOzoneReadBlacklistGroupsFromConfigValue(newVal); + readBlacklist.setBlacklistGroups(readBlacklistGroups); + LOG.info("Load conf {} : {}, and now read blacklist groups are: {}", + OZONE_READ_BLACKLIST_GROUPS, newVal, readBlacklistGroups); + return String.valueOf(newVal); + } + private String reconfOzoneKeyDeletingLimitPerTask(String newVal) { Preconditions.checkArgument(Integer.parseInt(newVal) >= 0, OZONE_KEY_DELETING_LIMIT_PER_TASK + " cannot be negative."); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneNativeAuthorizer.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneNativeAuthorizer.java index 264e6de67a73..876b71329107 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneNativeAuthorizer.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneNativeAuthorizer.java @@ -19,12 +19,14 @@ import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_REQUEST; +import com.google.common.annotations.VisibleForTesting; import java.util.Objects; import java.util.function.BooleanSupplier; import java.util.function.Predicate; import org.apache.hadoop.hdds.annotation.InterfaceAudience; import org.apache.hadoop.hdds.annotation.InterfaceStability; import org.apache.hadoop.hdds.server.OzoneAdmins; +import org.apache.hadoop.hdds.server.OzoneBlacklist; import org.apache.hadoop.ozone.OzoneConsts; import org.apache.hadoop.ozone.om.BucketManager; import org.apache.hadoop.ozone.om.KeyManager; @@ -48,6 +50,7 @@ public class OzoneNativeAuthorizer implements OzoneManagerAuthorizer { LoggerFactory.getLogger(OzoneNativeAuthorizer.class); private static final Predicate NO_ADMIN = any -> false; + private static final Predicate NO_BLACKLIST = any -> false; private VolumeManager volumeManager; private BucketManager bucketManager; @@ -55,6 +58,8 @@ public class OzoneNativeAuthorizer implements OzoneManagerAuthorizer { private PrefixManager prefixManager; private Predicate adminCheck = NO_ADMIN; private Predicate readOnlyAdminCheck = NO_ADMIN; + private Predicate blacklistCheck = NO_BLACKLIST; + private Predicate readOnlyBlacklistCheck = NO_BLACKLIST; private BooleanSupplier allowListAllVolumes = () -> false; public OzoneNativeAuthorizer() { @@ -100,6 +105,21 @@ public boolean checkAccess(IOzoneObj ozObject, RequestContext context) "configured to work with OzoneObjInfo type only.", INVALID_REQUEST); } + if (blacklistCheck.test(context.getClientUgi())) { + LOG.debug("Permission DENIED on request {} for object {}. " + + "Reason: user is under the blacklist", context, ozObject); + return false; + } + + if (readOnlyBlacklistCheck.test(context.getClientUgi()) + && (context.getAclRights() == ACLType.READ + || context.getAclRights() == ACLType.READ_ACL + || context.getAclRights() == ACLType.LIST)) { + LOG.debug("Permission DENIED on request {} for object {}. " + + "Reason: user is under a read blacklist", context, ozObject); + return false; + } + // bypass all checks for admin if (adminCheck.test(context.getClientUgi())) { return true; @@ -197,6 +217,8 @@ public OzoneNativeAuthorizer configure(OzoneManager om, KeyManager km, PrefixMan allowListAllVolumes = () -> om.getConfig().isListAllVolumesAllowed(); setAdminCheck(om::isAdmin); setReadOnlyAdminCheck(om::isReadOnlyAdmin); + setBlacklistCheck(om::isBlacklisted); + setReadOnlyBlacklistCheck(om::isReadBlacklisted); keyManager = km; prefixManager = pm; @@ -211,6 +233,31 @@ public void setReadOnlyAdminCheck(Predicate check) { readOnlyAdminCheck = Objects.requireNonNull(check, "read-only admin check"); } + @VisibleForTesting + void setOzoneAdmins(OzoneAdmins ozoneAdmins) { + setAdminCheck(ozoneAdmins::isAdmin); + } + + @VisibleForTesting + void setOzoneBlacklist(OzoneBlacklist ozoneBlacklist) { + setBlacklistCheck(ozoneBlacklist::isBlacklisted); + } + + @VisibleForTesting + void setOzoneReadBlacklist(OzoneBlacklist readBlacklist) { + setReadOnlyBlacklistCheck(readBlacklist::isBlacklisted); + } + + public void setBlacklistCheck(Predicate check) { + blacklistCheck = Objects.requireNonNull(check, "blacklist check"); + } + + public void setReadOnlyBlacklistCheck( + Predicate check) { + readOnlyBlacklistCheck = Objects.requireNonNull(check, + "read-only blacklist check"); + } + public boolean getAllowListAllVolumes() { return allowListAllVolumes.getAsBoolean(); } diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestOzoneBlacklist.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestOzoneBlacklist.java new file mode 100644 index 000000000000..83a97c584ebe --- /dev/null +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestOzoneBlacklist.java @@ -0,0 +1,235 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.security.acl; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import org.apache.hadoop.hdds.server.OzoneAdmins; +import org.apache.hadoop.hdds.server.OzoneBlacklist; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.security.UserGroupInformation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Test Ozone blacklist from OzoneNativeAuthorizer. + */ +public class TestOzoneBlacklist { + + private OzoneNativeAuthorizer nativeAuthorizer; + + @BeforeEach + public void setup() { + nativeAuthorizer = new OzoneNativeAuthorizer(); + } + + @Test + public void testCreateVolume() throws Exception { + UserGroupInformation.createUserForTesting("testuser", + new String[]{"testgroup"}); + try { + OzoneObj obj = getTestVolumeobj("testvolume"); + RequestContext context = getUserRequestContext("testuser", + IAccessAuthorizer.ACLType.CREATE); + testBlacklistedUserOperations(obj, context); + testBlacklistedGroupOperations(obj, context); + } finally { + UserGroupInformation.reset(); + } + } + + @Test + public void testBucketOperation() throws OMException { + UserGroupInformation.createUserForTesting("testuser", + new String[]{"testgroup"}); + try { + OzoneObj obj = getTestBucketobj("testbucket"); + RequestContext context = getUserRequestContext("testuser", + IAccessAuthorizer.ACLType.LIST); + nativeAuthorizer.setOzoneReadBlacklist(new OzoneBlacklist( + Collections.singletonList("testuser"), null)); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching read only blacklisted users are not allowed to" + + " perform read operations"); + + context = getUserRequestContext("testuser", + IAccessAuthorizer.ACLType.READ); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching read blacklisted users are not allowed to" + + " perform read operations"); + + context = getUserRequestContext("testuser", + IAccessAuthorizer.ACLType.READ_ACL); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching read blacklisted users are not allowed to" + + " perform read operations"); + + context = getUserRequestContext("testuser", + IAccessAuthorizer.ACLType.WRITE); + RequestContext finalContext = context; + assertThrows(NullPointerException.class, + () -> nativeAuthorizer.checkAccess(obj, finalContext)); + + nativeAuthorizer.setOzoneReadBlacklist(new OzoneBlacklist( + null, Collections.singletonList("testgroup"))); + context = getUserRequestContext("testuser", + IAccessAuthorizer.ACLType.READ_ACL); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching blacklisted read only users are not allowed to" + + " perform read operations"); + } finally { + UserGroupInformation.reset(); + } + } + + @Test + public void testListAllVolume() throws Exception { + UserGroupInformation.createUserForTesting("testuser", + new String[]{"testgroup"}); + try { + OzoneObj obj = getTestVolumeobj("/"); + RequestContext context = getUserRequestContext("testuser", + IAccessAuthorizer.ACLType.LIST); + testBlacklistedUserOperations(obj, context); + testBlacklistedGroupOperations(obj, context); + } finally { + UserGroupInformation.reset(); + } + } + + private void testBlacklistedUserOperations( + OzoneObj obj, RequestContext context) throws OMException { + nativeAuthorizer.setOzoneAdmins(new OzoneAdmins( + Collections.singletonList("testuser"))); + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "admins are allowed to perform any operations"); + + nativeAuthorizer.setOzoneBlacklist( + new OzoneBlacklist(Collections.singletonList("testuser"))); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching blacklisted users are not allowed to perform" + + " any operations"); + + nativeAuthorizer.setOzoneBlacklist( + new OzoneBlacklist(asList("testuser2", "testuser"))); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching blacklisted users are not allowed to perform" + + " any operations"); + + nativeAuthorizer.setOzoneBlacklist( + new OzoneBlacklist(asList("testuser2", "testuser3"))); + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "non-blacklisted users should be allowed to perform" + + " operations"); + + nativeAuthorizer.setOzoneReadBlacklist(new OzoneBlacklist( + Collections.singletonList("testuser"), null)); + if (context.getAclRights() == IAccessAuthorizer.ACLType.LIST + || context.getAclRights() == IAccessAuthorizer.ACLType.READ + || context.getAclRights() == IAccessAuthorizer.ACLType.READ_ACL) { + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching read blacklisted users are not allowed to perform" + + " read operations"); + } else { + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "non-matched read blacklisted users should still be able" + + " to perform operations"); + } + + nativeAuthorizer.setOzoneReadBlacklist(new OzoneBlacklist( + Collections.singletonList("testuser1"), null)); + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "admins are allowed to perform any operations"); + } + + private void testBlacklistedGroupOperations( + OzoneObj obj, RequestContext context) throws Exception { + nativeAuthorizer.setOzoneAdmins(new OzoneAdmins(null, + asList("testgroup", "anothergroup"))); + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "users in admin groups are allowed to perform any operations"); + + nativeAuthorizer.setOzoneBlacklist( + new OzoneBlacklist(null, + Collections.singletonList("testgroup"))); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "users in matching blacklisted groups are not allowed to" + + " perform any operations"); + + nativeAuthorizer.setOzoneBlacklist( + new OzoneBlacklist(null, + asList("testgroup", "anothergroup2"))); + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "users in matching blacklisted groups are not allowed to" + + " perform any operations"); + + nativeAuthorizer.setOzoneBlacklist( + new OzoneBlacklist(null, + asList("testgroup2", "testgroup3"))); + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "users in admin groups and not in blacklist groups are" + + " allowed to perform any operations"); + + nativeAuthorizer.setOzoneReadBlacklist(new OzoneBlacklist(null, + Collections.singletonList("testgroup"))); + if (context.getAclRights() == IAccessAuthorizer.ACLType.LIST + || context.getAclRights() == IAccessAuthorizer.ACLType.READ + || context.getAclRights() == IAccessAuthorizer.ACLType.READ_ACL) { + assertFalse(nativeAuthorizer.checkAccess(obj, context), + "matching read blacklisted users are not allowed to" + + " perform read operations"); + } else { + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "non-matched read blacklisted users should still be" + + " able to perform operations"); + } + + nativeAuthorizer.setOzoneReadBlacklist(new OzoneBlacklist(null, + Collections.singletonList("testgroup1"))); + assertTrue(nativeAuthorizer.checkAccess(obj, context), + "users in admin groups and not in blacklist groups are" + + " allowed to perform any operations"); + } + + private RequestContext getUserRequestContext(String username, + IAccessAuthorizer.ACLType type) { + return RequestContext.newBuilder() + .setClientUgi(UserGroupInformation.createRemoteUser(username)) + .setAclType(IAccessAuthorizer.ACLIdentityType.USER) + .setAclRights(type) + .build(); + } + + private OzoneObj getTestVolumeobj(String volumename) { + return OzoneObjInfo.Builder.newBuilder() + .setResType(OzoneObj.ResourceType.VOLUME) + .setStoreType(OzoneObj.StoreType.OZONE) + .setVolumeName(volumename).build(); + } + + private OzoneObj getTestBucketobj(String bucketname) { + return OzoneObjInfo.Builder.newBuilder() + .setResType(OzoneObj.ResourceType.BUCKET) + .setStoreType(OzoneObj.StoreType.OZONE) + .setVolumeName(bucketname).build(); + } +}