From fec069df7fa5b655c1fab7d2f23c5f77635cf9b9 Mon Sep 17 00:00:00 2001 From: meiyi Date: Tue, 25 Jun 2019 12:00:39 +0800 Subject: [PATCH] HBASE-22580 Add a table attribute to make user scan snapshot feature configurable for table --- .../apache/hadoop/hbase/HTableDescriptor.java | 10 + .../hadoop/hbase/client/TableDescriptor.java | 7 + .../hbase/client/TableDescriptorBuilder.java | 31 ++++ .../security/access/PermissionStorage.java | 5 + .../SnapshotScannerHDFSAclController.java | 174 +++++++++++------- .../access/SnapshotScannerHDFSAclHelper.java | 74 ++++++-- .../TestSnapshotScannerHDFSAclController.java | 70 ++++++- 7 files changed, 281 insertions(+), 90 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java index 8866eba94fc1..160876cad02a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java @@ -527,6 +527,16 @@ public boolean hasFamily(final byte [] familyName) { return delegatee.hasColumnFamily(familyName); } + /** + * Check if user scan snapshot enable flag of the table is true. If flag is false then + * SnapshotScannerHDFSAclController won't handle HDFS acls for users with table read permission + * @return true if user scan snapshot is enabled for this table + */ + @Override + public boolean isUserScanSnapshotEnabled() { + return delegatee.isUserScanSnapshotEnabled(); + } + /** * @return Name of this table and then a map of all of the column family * descriptors. diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java index fc5e69e88c4a..9a266e0594d2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java @@ -345,4 +345,11 @@ default boolean matchReplicationScope(boolean enabled) { } return !enabled; } + + /** + * Check if user scan snapshot enable flag of the table is true. If flag is false then + * SnapshotScannerHDFSAclController won't handle HDFS acls for users with table read permission + * @return true if user scan snapshot is enabled for this table + */ + boolean isUserScanSnapshotEnabled(); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java index 037a7f860cbf..02a77daf36e8 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java @@ -188,6 +188,11 @@ public class TableDescriptorBuilder { private static final Bytes PRIORITY_KEY = new Bytes(Bytes.toBytes(PRIORITY)); + @InterfaceAudience.Private + public static final String USER_SCAN_SNAPSHOT_ENABLED = "USER_SCAN_SNAPSHOT_ENABLED"; + private static final Bytes USER_SCAN_SNAPSHOT_ENABLED_KEY = + new Bytes(Bytes.toBytes(USER_SCAN_SNAPSHOT_ENABLED)); + /** * Relative priority of the table used for rpc scheduling */ @@ -537,6 +542,11 @@ public TableDescriptorBuilder setReplicationScope(int scope) { return this; } + public TableDescriptorBuilder setUserScanSnapshotEnabled(boolean isEnable) { + desc.setUserScanSnapshotEnabled(isEnable); + return this; + } + public TableDescriptor build() { return new ModifyableTableDescriptor(desc); } @@ -1074,6 +1084,27 @@ public boolean hasColumnFamily(final byte[] familyName) { return families.containsKey(familyName); } + /** + * Setting the user scan snapshot enable flag. + * + * @param isEnabled True if enable user scan snapshot. + * @return the modifyable TD + */ + public ModifyableTableDescriptor setUserScanSnapshotEnabled(boolean isEnabled) { + return setValue(USER_SCAN_SNAPSHOT_ENABLED_KEY, Boolean.toString(isEnabled)); + } + + /** + * Check if user scan snapshot enable flag of the table is true. If flag is false then + * SnapshotScannerHDFSAclController won't handle HDFS acls for users with table read permission + * + * @return true if user scan snapshot is enabled for this table + */ + @Override + public boolean isUserScanSnapshotEnabled() { + return getOrDefault(USER_SCAN_SNAPSHOT_ENABLED_KEY, Boolean::valueOf, false); + } + /** * @return Name of this table and then a map of all of the column family descriptors. */ diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/PermissionStorage.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/PermissionStorage.java index 6d37fe5bf4f8..2a9b195f0f80 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/PermissionStorage.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/PermissionStorage.java @@ -508,6 +508,11 @@ public static ListMultimap getNamespacePermissions(Confi false); } + public static ListMultimap getGlobalPermissions(Configuration conf) + throws IOException { + return getPermissions(conf, null, null, null, null, null, false); + } + /** * Reads user permission assignments stored in the l: column family of the first * table row in _acl_. diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclController.java index c964b67ac5d3..3da8bf01e41a 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclController.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.hadoop.conf.Configuration; @@ -58,7 +59,6 @@ import org.apache.hadoop.hbase.master.MasterServices; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.UserProvider; -import org.apache.hadoop.hbase.security.access.Permission.Action; import org.apache.hadoop.hbase.security.access.SnapshotScannerHDFSAclHelper.PathHelper; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; @@ -66,12 +66,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; +import org.apache.hbase.thirdparty.com.google.common.collect.Sets; + /** * Set HDFS ACLs to hFiles to make HBase granted users have permission to scan snapshot *

* To use this feature, please mask sure HDFS config: *

    - *
  • dfs.permissions.enabled = true
  • + *
  • dfs.namenode.acls.enabled = true
  • *
  • fs.permissions.umask-mode = 027 (or smaller umask than 027)
  • *
*

@@ -103,7 +106,9 @@ public class SnapshotScannerHDFSAclController implements MasterCoprocessor, Mast private SnapshotScannerHDFSAclHelper hdfsAclHelper = null; private PathHelper pathHelper = null; private FileSystem fs = null; + private MasterServices masterServices = null; private volatile boolean initialized = false; + private volatile boolean aclTableInitialized = false; /** Provider for mapping principal names to Users */ private UserProvider userProvider; @@ -121,7 +126,7 @@ public void preMasterInitialization(ObserverContext c) throws IOException { - if (checkInitialized()) { + if (initialized) { try (Admin admin = c.getEnvironment().getConnection().getAdmin()) { if (admin.tableExists(PermissionStorage.ACL_TABLE_NAME)) { // Check if hbase acl table has 'm' CF, if not, add 'm' CF @@ -151,6 +156,8 @@ public void postStartMaster(ObserverContext c) thr .setColumnFamily(ColumnFamilyDescriptorBuilder .newBuilder(SnapshotScannerHDFSAclStorage.HDFS_ACL_FAMILY).build()); admin.modifyTable(builder.build()); + } else { + aclTableInitialized = true; } } else { throw new TableNotFoundException("Table " + PermissionStorage.ACL_TABLE_NAME @@ -163,7 +170,7 @@ public void postStartMaster(ObserverContext c) thr @Override public void preStopMaster(ObserverContext c) { - if (checkInitialized()) { + if (initialized) { hdfsAclHelper.close(); } } @@ -171,18 +178,13 @@ public void preStopMaster(ObserverContext c) { @Override public void postCompletedCreateTableAction(ObserverContext c, TableDescriptor desc, RegionInfo[] regions) throws IOException { - if (!desc.getTableName().isSystemTable() && checkInitialized()) { + if (checkTable(desc, () -> "createTable " + desc.getTableName())) { TableName tableName = desc.getTableName(); - List paths = hdfsAclHelper.getTableRootPaths(tableName, false); - for (Path path : paths) { - if (!fs.exists(path)) { - fs.mkdirs(path); - } - } + hdfsAclHelper.createTableDirectories(tableName); // Add table owner HDFS acls String owner = desc.getOwnerString() == null ? getActiveUser(c).getShortName() : desc.getOwnerString(); - hdfsAclHelper.addTableAcl(tableName, owner); + hdfsAclHelper.addTableAcl(tableName, Sets.newHashSet(owner)); try (Table aclTable = c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(aclTable, owner, tableName); @@ -193,7 +195,7 @@ public void postCompletedCreateTableAction(ObserverContext c, NamespaceDescriptor ns) throws IOException { - if (checkInitialized()) { + if (checkInitialized(() -> "createNamespace " + ns.getName())) { List paths = hdfsAclHelper.getNamespaceRootPaths(ns.getName()); for (Path path : paths) { if (!fs.exists(path)) { @@ -206,7 +208,7 @@ public void postCreateNamespace(ObserverContext c, @Override public void postCompletedSnapshotAction(ObserverContext c, SnapshotDescription snapshot, TableDescriptor tableDescriptor) throws IOException { - if (!tableDescriptor.getTableName().isSystemTable() && checkInitialized()) { + if (checkTable(tableDescriptor, () -> "snapshot " + snapshot.getName())) { hdfsAclHelper.snapshotAcl(snapshot); } } @@ -214,7 +216,7 @@ public void postCompletedSnapshotAction(ObserverContext c, TableName tableName) throws IOException { - if (!tableName.isSystemTable() && checkInitialized()) { + if (checkTable(tableName, () -> "truncateTable " + tableName)) { hdfsAclHelper.resetTableAcl(tableName); } } @@ -222,7 +224,7 @@ public void postCompletedTruncateTableAction(ObserverContext ctx, TableName tableName) throws IOException { - if (!tableName.isSystemTable() && checkInitialized()) { + if (checkTable(tableName, () -> "deleteTable " + tableName)) { /* * remove table user access HDFS acl from namespace directory if the user has no permissions * of global, ns of the table or other tables of the ns, eg: Bob has 'ns1:t1' read permission, @@ -263,10 +265,36 @@ public void postDeleteTable(ObserverContext ctx, } } + @Override + public void postModifyTable(ObserverContext ctx, + TableName tableName, TableDescriptor oldDescriptor, TableDescriptor currentDescriptor) + throws IOException { + if (checkTable(currentDescriptor, () -> "modifyTable " + tableName) + && !oldDescriptor.isUserScanSnapshotEnabled()) { + hdfsAclHelper.createTableDirectories(tableName); + Set users = new HashSet<>(); + users.addAll(hdfsAclHelper.getUsersWithGlobalReadAction()); + users.addAll(hdfsAclHelper.getUsersWithNamespaceReadAction(tableName.getNamespaceAsString())); + Set userWithTableReadPermission = + hdfsAclHelper.getUsersWithTableReadAction(tableName); + users.addAll(userWithTableReadPermission); + hdfsAclHelper.addTableAcl(tableName, users); + try (Table aclTable = + ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { + for (String user : userWithTableReadPermission) { + SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(aclTable, user, tableName); + } + } + } else if (!aclTableInitialized + && Bytes.equals(PermissionStorage.ACL_GLOBAL_NAME, tableName.getName())) { + aclTableInitialized = true; + } + } + @Override public void postDeleteNamespace(ObserverContext ctx, String namespace) throws IOException { - if (checkInitialized()) { + if (checkInitialized(() -> "deleteNamespace " + namespace)) { try (Table aclTable = ctx.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { SnapshotScannerHDFSAclStorage.deleteNamespaceHdfsAcl(aclTable, namespace); @@ -292,7 +320,8 @@ public void postDeleteNamespace(ObserverContext ct @Override public void postGrant(ObserverContext c, UserPermission userPermission, boolean mergeExistingPermissions) throws IOException { - if (!checkInitialized()) { + if (!checkInitialized(() -> "grant " + userPermission + ", merge existing permissions " + + mergeExistingPermissions)) { return; } try (Table aclTable = @@ -302,7 +331,7 @@ public void postGrant(ObserverContext c, switch (userPermission.getAccessScope()) { case GLOBAL: UserPermission perm = getUserGlobalPermission(conf, userName); - if (perm != null && containReadPermission(perm)) { + if (perm != null && hdfsAclHelper.containReadAction(perm)) { if (!isHdfsAclSet(aclTable, userName)) { Pair, Set> namespaceAndTable = SnapshotScannerHDFSAclStorage.getUserNamespaceAndTable(aclTable, userName); @@ -322,7 +351,7 @@ public void postGrant(ObserverContext c, case NAMESPACE: String namespace = ((NamespacePermission) userPermission.getPermission()).getNamespace(); UserPermission nsPerm = getUserNamespacePermission(conf, userName, namespace); - if (nsPerm != null && containReadPermission(nsPerm)) { + if (nsPerm != null && hdfsAclHelper.containReadAction(nsPerm)) { if (!isHdfsAclSet(aclTable, userName, namespace)) { Set skipTables = SnapshotScannerHDFSAclStorage .getUserNamespaceAndTable(aclTable, userName).getSecond(); @@ -336,23 +365,20 @@ public void postGrant(ObserverContext c, } break; case TABLE: - TableName tableName = ((TablePermission) userPermission.getPermission()).getTableName(); - UserPermission tPerm = getUserTablePermission(conf, userName, tableName); - if (tPerm != null) { - TablePermission tablePermission = (TablePermission) tPerm.getPermission(); - if (tablePermission.hasFamily() || tablePermission.hasQualifier()) { - break; - } - } - if (tPerm != null && containReadPermission(tPerm)) { - if (!isHdfsAclSet(aclTable, userName, tableName)) { - hdfsAclHelper.grantAcl(userPermission, new HashSet<>(0), new HashSet<>(0)); + TablePermission tablePerm = (TablePermission) userPermission.getPermission(); + if (checkTableAndPermission(tablePerm)) { + TableName tableName = tablePerm.getTableName(); + UserPermission tPerm = getUserTablePermission(conf, userName, tableName); + if (tPerm != null && hdfsAclHelper.containReadAction(tPerm)) { + if (!isHdfsAclSet(aclTable, userName, tableName)) { + hdfsAclHelper.grantAcl(userPermission, new HashSet<>(0), new HashSet<>(0)); + } + SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(aclTable, userName, tableName); + } else { + // The merged user permission doesn't contain READ, so remove user table HDFS acls if + // it's set + removeUserTableHdfsAcl(aclTable, userName, tableName, userPermission); } - SnapshotScannerHDFSAclStorage.addUserTableHdfsAcl(aclTable, userName, tableName); - } else { - // The merged user permission doesn't contain READ, so remove user table HDFS acls if - // it's set - removeUserTableHdfsAcl(aclTable, userName, tableName, userPermission); } break; default: @@ -365,7 +391,7 @@ public void postGrant(ObserverContext c, @Override public void postRevoke(ObserverContext c, UserPermission userPermission) throws IOException { - if (checkInitialized()) { + if (checkInitialized(() -> "revoke " + userPermission)) { try (Table aclTable = c.getEnvironment().getConnection().getTable(PermissionStorage.ACL_TABLE_NAME)) { String userName = userPermission.getUser(); @@ -373,7 +399,7 @@ public void postRevoke(ObserverContext c, switch (userPermission.getAccessScope()) { case GLOBAL: UserPermission userGlobalPerm = getUserGlobalPermission(conf, userName); - if (userGlobalPerm == null || !containReadPermission(userGlobalPerm)) { + if (userGlobalPerm == null || !hdfsAclHelper.containReadAction(userGlobalPerm)) { removeUserGlobalHdfsAcl(aclTable, userName, userPermission); } break; @@ -381,16 +407,18 @@ public void postRevoke(ObserverContext c, NamespacePermission nsPerm = (NamespacePermission) userPermission.getPermission(); UserPermission userNsPerm = getUserNamespacePermission(conf, userName, nsPerm.getNamespace()); - if (userNsPerm == null || !containReadPermission(userNsPerm)) { + if (userNsPerm == null || !hdfsAclHelper.containReadAction(userNsPerm)) { removeUserNamespaceHdfsAcl(aclTable, userName, nsPerm.getNamespace(), userPermission); } break; case TABLE: TablePermission tPerm = (TablePermission) userPermission.getPermission(); - UserPermission userTablePerm = - getUserTablePermission(conf, userName, tPerm.getTableName()); - if (userTablePerm == null || !containReadPermission(userTablePerm)) { - removeUserTableHdfsAcl(aclTable, userName, tPerm.getTableName(), userPermission); + if (checkTableAndPermission(tPerm)) { + TableName tableName = tPerm.getTableName(); + UserPermission userTablePerm = getUserTablePermission(conf, userName, tableName); + if (userTablePerm == null || !hdfsAclHelper.containReadAction(userTablePerm)) { + removeUserTableHdfsAcl(aclTable, userName, tableName, userPermission); + } } break; default: @@ -442,42 +470,28 @@ private void removeUserTableHdfsAcl(Table aclTable, String userName, TableName t } } - private boolean containReadPermission(UserPermission userPermission) { - if (userPermission != null) { - return Arrays.stream(userPermission.getPermission().getActions()) - .anyMatch(action -> action == Action.READ); - } - return false; - } - private UserPermission getUserGlobalPermission(Configuration conf, String userName) throws IOException { List permissions = PermissionStorage.getUserPermissions(conf, PermissionStorage.ACL_GLOBAL_NAME, null, null, userName, true); - if (permissions != null && permissions.size() > 0) { - return permissions.get(0); - } - return null; + return permissions.size() > 0 ? permissions.get(0) : null; } private UserPermission getUserNamespacePermission(Configuration conf, String userName, String namespace) throws IOException { List permissions = PermissionStorage.getUserNamespacePermissions(conf, namespace, userName, true); - if (permissions != null && permissions.size() > 0) { - return permissions.get(0); - } - return null; + return permissions.size() > 0 ? permissions.get(0) : null; } private UserPermission getUserTablePermission(Configuration conf, String userName, TableName tableName) throws IOException { - List permissions = - PermissionStorage.getUserTablePermissions(conf, tableName, null, null, userName, true); - if (permissions != null && permissions.size() > 0) { - return permissions.get(0); - } - return null; + List permissions = PermissionStorage + .getUserTablePermissions(conf, tableName, null, null, userName, true).stream() + .filter(userPermission -> hdfsAclHelper + .checkTablePermission((TablePermission) userPermission.getPermission())) + .collect(Collectors.toList()); + return permissions.size() > 0 ? permissions.get(0) : null; } private boolean isHdfsAclSet(Table aclTable, String userName) throws IOException { @@ -513,12 +527,32 @@ private boolean isHdfsAclSet(Table aclTable, String userName, String namespace, return isSet; } - private boolean checkInitialized() { + @VisibleForTesting + boolean checkInitialized(Supplier operation) { if (initialized) { - return true; - } else { - return false; + if (aclTableInitialized) { + return true; + } else { + LOG.warn("Skip set HDFS acls because acl table is not initialized when " + operation.get()); + } } + return false; + } + + private boolean checkTableAndPermission(TablePermission tablePermission) throws IOException { + return checkTable(tablePermission.getTableName(), () -> "") + && hdfsAclHelper.checkTablePermission(tablePermission); + } + + private boolean checkTable(TableName tableName, Supplier operation) throws IOException { + return !tableName.isSystemTable() && checkInitialized(operation) + && masterServices.getTableDescriptors().get(tableName).isUserScanSnapshotEnabled(); + } + + private boolean checkTable(TableDescriptor tableDescriptor, Supplier operation) { + TableName tableName = tableDescriptor.getTableName(); + return !tableName.isSystemTable() && checkInitialized(operation) + && tableDescriptor.isUserScanSnapshotEnabled(); } private User getActiveUser(ObserverContext ctx) throws IOException { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclHelper.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclHelper.java index 2a62ff0ab69d..0be52072121c 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclHelper.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/SnapshotScannerHDFSAclHelper.java @@ -23,7 +23,6 @@ import static org.apache.hadoop.fs.permission.AclEntryType.GROUP; import static org.apache.hadoop.fs.permission.AclEntryType.USER; import static org.apache.hadoop.fs.permission.FsAction.READ_EXECUTE; -import static org.apache.hadoop.hbase.security.access.Permission.Action.READ; import java.io.Closeable; import java.io.FileNotFoundException; @@ -47,6 +46,7 @@ import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hbase.AuthUtil; import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Connection; @@ -58,6 +58,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; import org.apache.hbase.thirdparty.com.google.common.collect.Lists; import org.apache.hbase.thirdparty.com.google.common.collect.Sets; import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -260,20 +261,20 @@ public boolean removeNamespaceAcl(TableName tableName, Set removeUsers) } /** - * Set table owner acl when create table + * Set table user acl when create or modify table * @param tableName the table - * @param user the table owner + * @param users the table users with READ permission * @return false if an error occurred, otherwise true */ - public boolean addTableAcl(TableName tableName, String user) { + public boolean addTableAcl(TableName tableName, Set users) { try { long start = System.currentTimeMillis(); - setTableAcl(tableName, Sets.newHashSet(user)); - LOG.info("Set HDFS acl when create table {}, cost {} ms", tableName, + setTableAcl(tableName, users); + LOG.info("Set HDFS acl when create or modify table {}, cost {} ms", tableName, System.currentTimeMillis() - start); return true; } catch (Exception e) { - LOG.error("Set HDFS acl error when create table {}", tableName, e); + LOG.error("Set HDFS acl error when create or modify table {}", tableName, e); return false; } } @@ -323,6 +324,7 @@ private void handleNamespaceAcl(Set namespaces, Set users, HDFSAclOperation.OperationType operationType) throws ExecutionException, InterruptedException, IOException { namespaces.removeAll(skipNamespaces); + namespaces.remove(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR); // handle namespace root directories HDFS acls List hdfsAclOperations = new ArrayList<>(); Set skipTableNamespaces = @@ -350,7 +352,8 @@ private void handleNamespaceAcl(Set namespaces, Set users, Set tables = new HashSet<>(); for (String namespace : namespaces) { tables.addAll(admin.listTableDescriptorsByNamespace(Bytes.toBytes(namespace)).stream() - .map(TableDescriptor::getTableName).collect(Collectors.toSet())); + .filter(TableDescriptor::isUserScanSnapshotEnabled).map(TableDescriptor::getTableName) + .collect(Collectors.toSet())); } handleTableAcl(tables, users, skipNamespaces, skipTables, operationType); } @@ -400,6 +403,15 @@ private void setTableAcl(TableName tableName, Set users) operationType); } + void createTableDirectories(TableName tableName) throws IOException { + List paths = getTableRootPaths(tableName, false); + for (Path path : paths) { + if (!fs.exists(path)) { + fs.mkdirs(path); + } + } + } + /** * return paths that user will global permission will visit * @return the path list @@ -429,7 +441,7 @@ List getNamespaceRootPaths(String namespace) { * @return the path list * @throws IOException if an error occurred */ - List getTableRootPaths(TableName tableName, boolean includeSnapshotPath) + private List getTableRootPaths(TableName tableName, boolean includeSnapshotPath) throws IOException { List paths = Lists.newArrayList(pathHelper.getTmpTableDir(tableName), pathHelper.getDataTableDir(tableName), pathHelper.getMobTableDir(tableName), @@ -447,16 +459,23 @@ private List getTableSnapshotPaths(TableName tableName) throws IOException .collect(Collectors.toList()); } + /** + * Return users with global read permission + * @return users with global read permission + * @throws IOException if an error occurred + */ + Set getUsersWithGlobalReadAction() throws IOException { + return getUsersWithReadAction(PermissionStorage.getGlobalPermissions(conf)); + } + /** * Return users with namespace read permission * @param namespace the namespace * @return users with namespace read permission * @throws IOException if an error occurred */ - private Set getUsersWithNamespaceReadAction(String namespace) throws IOException { - return PermissionStorage.getNamespacePermissions(conf, namespace).entries().stream() - .filter(entry -> entry.getValue().getPermission().implies(READ)) - .map(entry -> entry.getKey()).collect(Collectors.toSet()); + Set getUsersWithNamespaceReadAction(String namespace) throws IOException { + return getUsersWithReadAction(PermissionStorage.getNamespacePermissions(conf, namespace)); } /** @@ -465,10 +484,31 @@ private Set getUsersWithNamespaceReadAction(String namespace) throws IOE * @return users with table read permission * @throws IOException if an error occurred */ - private Set getUsersWithTableReadAction(TableName tableName) throws IOException { - return PermissionStorage.getTablePermissions(conf, tableName).entries().stream() - .filter(entry -> entry.getValue().getPermission().implies(READ)) - .map(entry -> entry.getKey()).collect(Collectors.toSet()); + Set getUsersWithTableReadAction(TableName tableName) throws IOException { + return getUsersWithReadAction(PermissionStorage.getTablePermissions(conf, tableName)); + } + + private Set + getUsersWithReadAction(ListMultimap permissionMultimap) { + return permissionMultimap.entries().stream() + .filter(entry -> checkUserPermission(entry.getValue())).map(entry -> entry.getKey()) + .collect(Collectors.toSet()); + } + + boolean checkUserPermission(UserPermission userPermission) { + boolean result = containReadAction(userPermission); + if (result && userPermission.getPermission() instanceof TablePermission) { + result = checkTablePermission((TablePermission) userPermission.getPermission()); + } + return result; + } + + boolean containReadAction(UserPermission userPermission) { + return userPermission.getPermission().implies(Permission.Action.READ); + } + + boolean checkTablePermission(TablePermission tablePermission) { + return !tablePermission.hasFamily() && !tablePermission.hasQualifier(); } PathHelper getPathHelper() { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestSnapshotScannerHDFSAclController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestSnapshotScannerHDFSAclController.java index e35e3a1b1d99..32f9ff6e2070 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestSnapshotScannerHDFSAclController.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestSnapshotScannerHDFSAclController.java @@ -125,7 +125,9 @@ public static void setupBeforeClass() throws Exception { path = path.getParent(); } - TEST_UTIL.waitTableAvailable(PermissionStorage.ACL_TABLE_NAME); + SnapshotScannerHDFSAclController coprocessor = TEST_UTIL.getHBaseCluster().getMaster() + .getMasterCoprocessorHost().findCoprocessor(SnapshotScannerHDFSAclController.class); + TEST_UTIL.waitFor(1200000, () -> coprocessor.checkInitialized(() -> "check initialized")); } @AfterClass @@ -562,6 +564,53 @@ public void testGrantMobTable() throws Exception { TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, grantUser, snapshot, 6); } } + + @Test + public void testModifyTable() throws Exception { + String namespace = name.getMethodName(); + TableName table = TableName.valueOf(namespace, "t1"); + String snapshot = namespace + "t1"; + + final String grantUserName = name.getMethodName(); + User grantUser = User.createUserForTesting(conf, grantUserName, new String[] {}); + final String grantUserName2 = grantUserName + "2"; + User grantUser2 = User.createUserForTesting(conf, grantUserName2, new String[] {}); + final String grantUserName3 = grantUserName + "3"; + User grantUser3 = User.createUserForTesting(conf, grantUserName3, new String[] {}); + final String nsGrantUserName = grantUserName + "-ns"; + User nsGrantUser = User.createUserForTesting(conf, nsGrantUserName, new String[] {}); + final String globalGrantUserName = grantUserName + "-global"; + User globalGrantUser = User.createUserForTesting(conf, globalGrantUserName, new String[] {}); + final String globalGrantUserName2 = grantUserName + "-global-2"; + User globalGrantUser2 = User.createUserForTesting(conf, globalGrantUserName2, new String[] {}); + + SecureTestUtil.grantGlobal(TEST_UTIL, globalGrantUserName, READ); + SecureTestUtil.grantOnNamespace(TEST_UTIL, nsGrantUserName, namespace, READ); + TableDescriptor td = TestHDFSAclHelper.createUserScanSnapshotDisabledTable(TEST_UTIL, table); + admin.snapshot(snapshot, table); + SecureTestUtil.grantGlobal(TEST_UTIL, globalGrantUserName2, READ); + TestHDFSAclHelper.grantOnTable(TEST_UTIL, grantUserName, table, READ); + SecureTestUtil.grantOnTable(TEST_UTIL, grantUserName2, table, TestHDFSAclHelper.COLUMN1, null, + READ); + TestHDFSAclHelper.grantOnTable(TEST_UTIL, grantUserName3, table, WRITE); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, grantUser, snapshot, -1); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, grantUser2, snapshot, -1); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, grantUser3, snapshot, -1); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, nsGrantUser, snapshot, -1); + // Global permission is set before table is created, the acl is inherited + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, globalGrantUser, snapshot, 6); + // Global permission is set after table is created, the table dir acl is skip + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, globalGrantUser2, snapshot, -1); + + // enable user scan snapshot + admin.modifyTable( + TableDescriptorBuilder.newBuilder(td).setUserScanSnapshotEnabled(true).build()); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, grantUser, snapshot, 6); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, grantUser2, snapshot, -1); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, grantUser3, snapshot, -1); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, nsGrantUser, snapshot, 6); + TestHDFSAclHelper.canUserScanSnapshot(TEST_UTIL, globalGrantUser, snapshot, 6); + } } final class TestHDFSAclHelper { @@ -591,7 +640,7 @@ static Table createTable(HBaseTestingUtility util, TableName tableName) throws I .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN1).build()) .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN2).build()) .setOwner(User.createUserForTesting(util.getConfiguration(), "owner", new String[] {})) - .build(); + .setUserScanSnapshotEnabled(true).build(); byte[][] splits = new byte[][] { Bytes.toBytes("2"), Bytes.toBytes("4") }; return util.createTable(td, splits); } @@ -604,11 +653,26 @@ static Table createMobTable(HBaseTestingUtility util, TableName tableName) throw .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN2).setMobEnabled(true) .setMobThreshold(0).build()) .setOwner(User.createUserForTesting(util.getConfiguration(), "owner", new String[] {})) - .build(); + .setUserScanSnapshotEnabled(true).build(); byte[][] splits = new byte[][] { Bytes.toBytes("2"), Bytes.toBytes("4") }; return util.createTable(td, splits); } + static TableDescriptor createUserScanSnapshotDisabledTable(HBaseTestingUtility util, TableName tableName) + throws IOException { + createNamespace(util, tableName.getNamespaceAsString()); + TableDescriptor td = TableDescriptorBuilder.newBuilder(tableName) + .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN1).build()) + .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN2).build()) + .setOwner(User.createUserForTesting(util.getConfiguration(), "owner", new String[] {})) + .build(); + byte[][] splits = new byte[][] { Bytes.toBytes("2"), Bytes.toBytes("4") }; + try (Table t = util.createTable(td, splits)) { + put(t); + } + return td; + } + static void createTableAndPut(HBaseTestingUtility util, TableName tableNam) throws IOException { try (Table t = createTable(util, tableNam)) { put(t);