From 49056295ef7763745876c05ae8c04b5a7317d323 Mon Sep 17 00:00:00 2001 From: Matteo Bertozzi Date: Sat, 18 Oct 2014 21:10:23 +0100 Subject: [PATCH] HBASE-12161 Add support for grant/revoke on namespaces in AccessControlClient (Srikanth Srungarapu) --- .../security/access/AccessControlClient.java | 180 +++++++----------- ...rationTestBigLinkedListWithVisibility.java | 6 +- .../hbase/security/access/SecureTestUtil.java | 84 ++++++++ .../security/access/TestAccessController.java | 84 +++++++- .../hadoop/hbase/util/LoadTestTool.java | 8 +- hbase-shell/src/main/ruby/hbase/admin.rb | 1 + 6 files changed, 247 insertions(+), 116 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java index cefe48ca332..eb637517916 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java @@ -20,40 +20,26 @@ package org.apache.hadoop.hbase.security.access; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.regex.Pattern; -import org.apache.hadoop.hbase.classification.InterfaceAudience; -import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.HBaseIOException; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.MasterNotRunningException; import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.ZooKeeperConnectionException; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Table; -import org.apache.hadoop.hbase.client.coprocessor.Batch; -import org.apache.hadoop.hbase.ipc.BlockingRpcCallback; import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel; -import org.apache.hadoop.hbase.ipc.ServerRpcController; import org.apache.hadoop.hbase.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos; -import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService.BlockingInterface; -import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantRequest; -import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantResponse; -import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeRequest; -import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeResponse; -import org.apache.hadoop.hbase.util.ByteStringer; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.hadoop.security.authorize.AccessControlList; - -import com.google.protobuf.ByteString; /** * Utility client for doing access control admin operations. @@ -61,6 +47,22 @@ import com.google.protobuf.ByteString; @InterfaceAudience.Public @InterfaceStability.Evolving public class AccessControlClient { + + private static HTable getAclTable(Configuration conf) throws IOException { + TableName aclTableName = + TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, + AccessControlConstants.OP_ATTRIBUTE_ACL); + return new HTable(conf, aclTableName.getName()); + } + + private static BlockingInterface getAccessControlServiceStub(HTable ht) + throws IOException { + CoprocessorRpcChannel service = ht.coprocessorService(HConstants.EMPTY_START_ROW); + BlockingInterface protocol = + AccessControlProtos.AccessControlService.newBlockingStub(service); + return protocol; + } + /** * Grants permission on the specified table for the specified user * @param conf @@ -69,56 +71,37 @@ public class AccessControlClient { * @param family * @param qual * @param actions - * @return GrantResponse * @throws Throwable */ - public static GrantResponse grant(Configuration conf, final TableName tableName, + public static void grant(Configuration conf, final TableName tableName, final String userName, final byte[] family, final byte[] qual, - final AccessControlProtos.Permission.Action... actions) throws Throwable { - Table ht = null; + final Permission.Action... actions) throws Throwable { + HTable ht = null; try { - TableName aclTableName = - TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "acl"); - ht = new HTable(conf, aclTableName); - Batch.Call callable = - new Batch.Call() { - ServerRpcController controller = new ServerRpcController(); - BlockingRpcCallback rpcCallback = - new BlockingRpcCallback(); + ht = getAclTable(conf); + ProtobufUtil.grant(getAccessControlServiceStub(ht), userName, tableName, family, qual, + actions); + } finally { + if (ht != null) { + ht.close(); + } + } + } - @Override - public GrantResponse call(AccessControlService service) throws IOException { - GrantRequest.Builder builder = GrantRequest.newBuilder(); - AccessControlProtos.Permission.Builder ret = - AccessControlProtos.Permission.newBuilder(); - AccessControlProtos.TablePermission.Builder permissionBuilder = - AccessControlProtos.TablePermission - .newBuilder(); - for (AccessControlProtos.Permission.Action a : actions) { - permissionBuilder.addAction(a); - } - permissionBuilder.setTableName(ProtobufUtil.toProtoTableName(tableName)); - - if (family != null) { - permissionBuilder.setFamily(ByteStringer.wrap(family)); - } - if (qual != null) { - permissionBuilder.setQualifier(ByteStringer.wrap(qual)); - } - ret.setType(AccessControlProtos.Permission.Type.Table).setTablePermission( - permissionBuilder); - builder.setUserPermission(AccessControlProtos.UserPermission.newBuilder() - .setUser(ByteString.copyFromUtf8(userName)).setPermission(ret)); - service.grant(controller, builder.build(), rpcCallback); - return rpcCallback.get(); - } - }; - Map result = ht.coprocessorService(AccessControlService.class, - HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY, callable); - return result.values().iterator().next(); // There will be exactly one - // region for labels - // table and so one entry in - // result Map. + /** + * Grants permission on the specified namespace for the specified user. + * @param conf + * @param namespace + * @param userName + * @param actions + * @throws Throwable + */ + public static void grant(Configuration conf, final String namespace, + final String userName, final Permission.Action... actions) throws Throwable { + HTable ht = null; + try { + ht = getAclTable(conf); + ProtobufUtil.grant(getAccessControlServiceStub(ht), userName, namespace, actions); } finally { if (ht != null) { ht.close(); @@ -144,61 +127,42 @@ public class AccessControlClient { /** * Revokes the permission on the table * @param conf - * @param username * @param tableName + * @param username * @param family * @param qualifier * @param actions - * @return RevokeResponse * @throws Throwable */ - public static RevokeResponse revoke(Configuration conf, final String username, - final TableName tableName, final byte[] family, final byte[] qualifier, - final AccessControlProtos.Permission.Action... actions) throws Throwable { - Table ht = null; + public static void revoke(Configuration conf, final TableName tableName, + final String username, final byte[] family, final byte[] qualifier, + final Permission.Action... actions) throws Throwable { + HTable ht = null; try { - TableName aclTableName = TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, - "acl"); - ht = new HTable(conf, aclTableName); - Batch.Call callable = - new Batch.Call() { - ServerRpcController controller = new ServerRpcController(); - BlockingRpcCallback rpcCallback = - new BlockingRpcCallback(); - - @Override - public RevokeResponse call(AccessControlService service) throws IOException { - AccessControlProtos.Permission.Builder ret = - AccessControlProtos.Permission.newBuilder(); - AccessControlProtos.TablePermission.Builder permissionBuilder = - AccessControlProtos.TablePermission.newBuilder(); - for (AccessControlProtos.Permission.Action a : actions) { - permissionBuilder.addAction(a); - } - if (tableName != null) { - permissionBuilder.setTableName(ProtobufUtil.toProtoTableName(tableName)); - } - if (family != null) { - permissionBuilder.setFamily(ByteStringer.wrap(family)); - } - if (qualifier != null) { - permissionBuilder.setQualifier(ByteStringer.wrap(qualifier)); - } - ret.setType(AccessControlProtos.Permission.Type.Table).setTablePermission( - permissionBuilder); - RevokeRequest builder = AccessControlProtos.RevokeRequest - .newBuilder() - .setUserPermission( - AccessControlProtos.UserPermission.newBuilder() - .setUser(ByteString.copyFromUtf8(username)).setPermission(ret)).build(); - service.revoke(controller, builder, rpcCallback); - return rpcCallback.get(); - } - }; - Map result = ht.coprocessorService(AccessControlService.class, - HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY, callable); - return result.values().iterator().next(); + ht = getAclTable(conf); + ProtobufUtil.revoke(getAccessControlServiceStub(ht), username, tableName, family, qualifier, + actions); + } finally { + if (ht != null) { + ht.close(); + } + } + } + /** + * Revokes the permission on the table for the specified user. + * @param conf + * @param namespace + * @param userName + * @param actions + * @throws Throwable + */ + public static void revoke(Configuration conf, final String namespace, + final String userName, final Permission.Action... actions) throws Throwable { + HTable ht = null; + try { + ht = getAclTable(conf); + ProtobufUtil.revoke(getAccessControlServiceStub(ht), userName, namespace, actions); } finally { if (ht != null) { ht.close(); diff --git a/hbase-it/src/test/java/org/apache/hadoop/hbase/test/IntegrationTestBigLinkedListWithVisibility.java b/hbase-it/src/test/java/org/apache/hadoop/hbase/test/IntegrationTestBigLinkedListWithVisibility.java index 8b847800a83..30ca60d3a01 100644 --- a/hbase-it/src/test/java/org/apache/hadoop/hbase/test/IntegrationTestBigLinkedListWithVisibility.java +++ b/hbase-it/src/test/java/org/apache/hadoop/hbase/test/IntegrationTestBigLinkedListWithVisibility.java @@ -35,7 +35,6 @@ import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.IntegrationTestingUtility; -import org.apache.hadoop.hbase.testclassification.IntegrationTests; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.chaos.factories.MonkeyFactory; import org.apache.hadoop.hbase.client.Admin; @@ -52,13 +51,14 @@ import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.io.hfile.HFile; import org.apache.hadoop.hbase.mapreduce.Import; import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; -import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.access.AccessControlClient; +import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.security.visibility.Authorizations; import org.apache.hadoop.hbase.security.visibility.CellVisibility; import org.apache.hadoop.hbase.security.visibility.VisibilityClient; import org.apache.hadoop.hbase.security.visibility.VisibilityController; +import org.apache.hadoop.hbase.testclassification.IntegrationTests; import org.apache.hadoop.hbase.util.AbstractHBaseTool; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.io.BytesWritable; @@ -154,7 +154,7 @@ public class IntegrationTestBigLinkedListWithVisibility extends IntegrationTestB admin.createTable(htd); if (acl) { LOG.info("Granting permissions for user " + USER.getShortName()); - AccessControlProtos.Permission.Action[] actions = { AccessControlProtos.Permission.Action.READ }; + Permission.Action[] actions = { Permission.Action.READ }; try { AccessControlClient.grant(getConf(), tableName, USER.getShortName(), null, null, actions); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java index 1141c9fa2b5..8002e451cce 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java @@ -390,6 +390,48 @@ public class SecureTestUtil { }); } + /** + * Grant permissions on a namespace to the given user using AccessControl Client. + * Will wait until all active AccessController instances have updated their permissions caches + * or will throw an exception upon timeout (10 seconds). + */ + public static void grantOnNamespaceUsingAccessControlClient(final HBaseTestingUtility util, + final Configuration conf, final String user, final String namespace, + final Permission.Action... actions) throws Exception { + SecureTestUtil.updateACLs(util, new Callable() { + @Override + public Void call() throws Exception { + try { + AccessControlClient.grant(conf, namespace, user, actions); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + }); + } + + /** + * Revoke permissions on a namespace from the given user using AccessControl Client. + * Will wait until all active AccessController instances have updated their permissions caches + * or will throw an exception upon timeout (10 seconds). + */ + public static void revokeFromNamespaceUsingAccessControlClient(final HBaseTestingUtility util, + final Configuration conf, final String user, final String namespace, + final Permission.Action... actions) throws Exception { + SecureTestUtil.updateACLs(util, new Callable() { + @Override + public Void call() throws Exception { + try { + AccessControlClient.revoke(conf, namespace, user, actions); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + }); + } + /** * Revoke permissions on a namespace from the given user. Will wait until all active * AccessController instances have updated their permissions caches or will @@ -439,6 +481,27 @@ public class SecureTestUtil { }); } + /** + * Grant permissions on a table to the given user using AccessControlClient. Will wait until all + * active AccessController instances have updated their permissions caches or will + * throw an exception upon timeout (10 seconds). + */ + public static void grantOnTableUsingAccessControlClient(final HBaseTestingUtility util, + final Configuration conf, final String user, final TableName table, final byte[] family, + final byte[] qualifier, final Permission.Action... actions) throws Exception { + SecureTestUtil.updateACLs(util, new Callable() { + @Override + public Void call() throws Exception { + try { + AccessControlClient.grant(conf, table, user, family, qualifier, actions); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + }); + } + /** * Revoke permissions on a table from the given user. Will wait until all active * AccessController instances have updated their permissions caches or will @@ -463,4 +526,25 @@ public class SecureTestUtil { } }); } + + /** + * Revoke permissions on a table from the given user using AccessControlClient. Will wait until + * all active AccessController instances have updated their permissions caches or will + * throw an exception upon timeout (10 seconds). + */ + public static void revokeFromTableUsingAccessControlClient(final HBaseTestingUtility util, + final Configuration conf, final String user, final TableName table, final byte[] family, + final byte[] qualifier, final Permission.Action... actions) throws Exception { + SecureTestUtil.updateACLs(util, new Callable() { + @Override + public Void call() throws Exception { + try { + AccessControlClient.revoke(conf, table, user, family, qualifier, actions); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + }); + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java index 44a800f0169..7e9eaa72cb2 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java @@ -45,7 +45,6 @@ import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.MiniHBaseCluster; import org.apache.hadoop.hbase.NamespaceDescriptor; -import org.apache.hadoop.hbase.NamespaceDescriptor.Builder; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotFoundException; @@ -2126,6 +2125,89 @@ public class TestAccessController extends SecureTestUtil { verifyAllowed(getAction, USER_NONE); } + @Test + public void testAccessControlClientGrantRevoke() throws Exception { + // Create user for testing, who has no READ privileges by default. + User testGrantRevoke = User.createUserForTesting(conf, "testGrantRevoke", new String[0]); + AccessTestAction getAction = new AccessTestAction() { + @Override + public Object run() throws Exception { + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + return t.get(new Get(TEST_ROW)); + } finally { + t.close(); + } + } + }; + + verifyDenied(getAction, testGrantRevoke); + + // Grant table READ permissions to testGrantRevoke. + try { + grantOnTableUsingAccessControlClient(TEST_UTIL, conf, testGrantRevoke.getShortName(), + TEST_TABLE.getTableName(), null, null, Permission.Action.READ); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. " + e.getStackTrace()); + } + + // Now testGrantRevoke should be able to read also + verifyAllowed(getAction, testGrantRevoke); + + // Revoke table READ permission to testGrantRevoke. + try { + revokeFromTableUsingAccessControlClient(TEST_UTIL, conf, testGrantRevoke.getShortName(), + TEST_TABLE.getTableName(), null, null, Permission.Action.READ); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.revoke " + e.getStackTrace()); + } + + // Now testGrantRevoke shouldn't be able read + verifyDenied(getAction, testGrantRevoke); + } + + @Test + public void testAccessControlClientGrantRevokeOnNamespace() throws Exception { + // Create user for testing, who has no READ privileges by default. + User testNS = User.createUserForTesting(conf, "testNS", new String[0]); + AccessTestAction getAction = new AccessTestAction() { + @Override + public Object run() throws Exception { + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + return t.get(new Get(TEST_ROW)); + } finally { + t.close(); + } + } + }; + + verifyDenied(getAction, testNS); + + // Grant namespace READ to testNS, this should supersede any table permissions + try { + grantOnNamespaceUsingAccessControlClient(TEST_UTIL, conf, testNS.getShortName(), + TEST_TABLE.getTableName().getNamespaceAsString(), Permission.Action.READ); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.grant. " + e.getStackTrace()); + } + + // Now testNS should be able to read also + verifyAllowed(getAction, testNS); + + // Revoke namespace READ to testNS, this should supersede any table permissions + try { + revokeFromNamespaceUsingAccessControlClient(TEST_UTIL, conf, testNS.getShortName(), + TEST_TABLE.getTableName().getNamespaceAsString(), Permission.Action.READ); + } catch (Throwable e) { + LOG.error("error during call of AccessControlClient.revoke " + e.getStackTrace()); + } + + // Now testNS shouldn't be able read + verifyDenied(getAction, testNS); + } + + public static class PingCoprocessor extends PingService implements Coprocessor, CoprocessorService { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/LoadTestTool.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/LoadTestTool.java index ce634029ad3..a3a78000d96 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/LoadTestTool.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/LoadTestTool.java @@ -47,11 +47,11 @@ import org.apache.hadoop.hbase.io.compress.Compression; import org.apache.hadoop.hbase.io.crypto.Cipher; import org.apache.hadoop.hbase.io.crypto.Encryption; import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; -import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos; import org.apache.hadoop.hbase.regionserver.BloomType; import org.apache.hadoop.hbase.security.EncryptionUtil; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.access.AccessControlClient; +import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.util.test.LoadTestDataGenerator; import org.apache.hadoop.hbase.util.test.LoadTestDataGeneratorWithACL; import org.apache.hadoop.security.SecurityUtil; @@ -572,9 +572,9 @@ public class LoadTestTool extends AbstractHBaseTool { if (userOwner != null) { LOG.info("Granting permissions for user " + userOwner.getShortName()); - AccessControlProtos.Permission.Action[] actions = { - AccessControlProtos.Permission.Action.ADMIN, AccessControlProtos.Permission.Action.CREATE, - AccessControlProtos.Permission.Action.READ, AccessControlProtos.Permission.Action.WRITE }; + Permission.Action[] actions = { + Permission.Action.ADMIN, Permission.Action.CREATE, + Permission.Action.READ, Permission.Action.WRITE }; try { AccessControlClient.grant(conf, tableName, userOwner.getShortName(), null, null, actions); } catch (Throwable e) { diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb index d74d229e36a..77c27598687 100644 --- a/hbase-shell/src/main/ruby/hbase/admin.rb +++ b/hbase-shell/src/main/ruby/hbase/admin.rb @@ -23,6 +23,7 @@ java_import org.apache.hadoop.hbase.util.Pair java_import org.apache.hadoop.hbase.util.RegionSplitter java_import org.apache.hadoop.hbase.util.Bytes java_import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos::SnapshotDescription +java_import org.apache.commons.collections.MapUtils # Wrapper for org.apache.hadoop.hbase.client.HBaseAdmin