diff --git hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java index 7f7138f..085aa2e 100644 --- hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java +++ hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java @@ -37,6 +37,8 @@ import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.NamespaceDescriptor; +import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.catalog.MetaReader; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; @@ -88,7 +90,8 @@ import com.google.protobuf.InvalidProtocolBufferException; */ public class AccessControlLists { /** Internal storage table for access control lists */ - public static final String ACL_TABLE_NAME_STR = "_acl_"; + public static final String ACL_TABLE_NAME_STR = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR + + TableName.NAMESPACE_DELIM + "_acl_"; public static final byte[] ACL_TABLE_NAME = Bytes.toBytes(ACL_TABLE_NAME_STR); public static final byte[] ACL_GLOBAL_NAME = ACL_TABLE_NAME; /** Column family used to store ACL grants */ @@ -576,4 +579,8 @@ public class AccessControlLists { return aclKey.substring(GROUP_PREFIX.length()); } + + public static byte[] getNamespaceEntry(String namespace){ + return Bytes.toBytes("@" + namespace); + } } diff --git hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java index 15f859f..1921318 100644 --- hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java +++ hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java @@ -28,6 +28,8 @@ import java.util.TreeSet; import com.google.protobuf.RpcCallback; import com.google.protobuf.RpcController; import com.google.protobuf.Service; + +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -490,7 +492,14 @@ public class AccessController extends BaseRegionObserver for (byte[] family: families) { familyMap.put(family, null); } - requireGlobalPermission("createTable", Permission.Action.CREATE, desc.getName(), familyMap); + try { + requireGlobalPermission("createTable", Permission.Action.CREATE, desc.getName(), familyMap); + } catch (AccessDeniedException exp) { + LOG.warn("Access denied at HBase admin level, now checking at namespace level.", exp); + requirePermission("create Table", + AccessControlLists.getNamespaceEntry(desc.getTableName().getNamespaceAsString()), null, + null, Permission.Action.WRITE, Permission.Action.ADMIN); + } } @Override @@ -778,27 +787,36 @@ public class AccessController extends BaseRegionObserver @Override public void preCreateNamespace(ObserverContext ctx, - NamespaceDescriptor ns) throws IOException { + NamespaceDescriptor ns) throws IOException { + requirePermission("createNamespace", AccessControlLists.ACL_TABLE_NAME, + ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, Action.ADMIN, Action.CREATE); } @Override public void postCreateNamespace(ObserverContext ctx, - NamespaceDescriptor ns) throws IOException { + NamespaceDescriptor ns) throws IOException { } @Override - public void preDeleteNamespace(ObserverContext ctx, - String namespace) throws IOException { + public void preDeleteNamespace(ObserverContext ctx, String namespace) + throws IOException { + requirePermission("deleteNamespace", AccessControlLists.ACL_TABLE_NAME, + ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, Action.ADMIN, Action.CREATE); } @Override public void postDeleteNamespace(ObserverContext ctx, String namespace) throws IOException { + AccessControlLists.removeTablePermissions(ctx.getEnvironment().getConfiguration(), + AccessControlLists.getNamespaceEntry(namespace)); + LOG.info(namespace + "entry deleted in _acl_ table."); } @Override public void preModifyNamespace(ObserverContext ctx, - NamespaceDescriptor ns) throws IOException { + NamespaceDescriptor ns) throws IOException { + requirePermission("modifyNamespace", AccessControlLists.ACL_TABLE_NAME, + ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, Action.ADMIN, Action.CREATE); } @Override @@ -1371,8 +1389,10 @@ public class AccessController extends BaseRegionObserver private boolean isSpecialTable(HRegionInfo regionInfo) { byte[] tableName = regionInfo.getTableName(); - return Arrays.equals(tableName, AccessControlLists.ACL_TABLE_NAME) - || Arrays.equals(tableName, HConstants.META_TABLE_NAME); + return tableName.equals(AccessControlLists.ACL_TABLE_NAME) + || tableName.equals(HConstants.ROOT_TABLE_NAME) + || tableName.equals(HConstants.META_TABLE_NAME) + || tableName.equals(HConstants.NAMESPACE_TABLE_NAME); } @Override diff --git hbase-server/src/main/ruby/hbase/security.rb hbase-server/src/main/ruby/hbase/security.rb index f471132..a943b49 100644 --- hbase-server/src/main/ruby/hbase/security.rb +++ hbase-server/src/main/ruby/hbase/security.rb @@ -42,18 +42,29 @@ module Hbase end if (table_name != nil) - # Table should exist - raise(ArgumentError, "Can't find a table: #{table_name}") unless exists?(table_name) - - tablebytes=table_name.to_java_bytes - htd = @admin.getTableDescriptor(tablebytes) - - if (family != nil) - raise(ArgumentError, "Can't find a family: #{family}") unless htd.hasFamily(family.to_java_bytes) - end - - fambytes = family.to_java_bytes if (family != nil) - qualbytes = qualifier.to_java_bytes if (qualifier != nil) + #check if the tablename passed is actually a namespace + if (isNamespace?(table_name)) + # Namespace should exist first. + namespace_name = table_name[1...table_name.length] + raise(ArgumentError, "Can't find a namespace: #{namespace_name}") unless namespace_exists?(namespace_name) + + #We pass the namespace name along with "@" so that we can differentiate a namespace from a table. + tablebytes=table_name.to_java_bytes + fambytes = nil + qualbytes = nil + else + # Table should exist + raise(ArgumentError, "Can't find a table: #{table_name}") unless exists?(table_name) + + tablebytes=table_name.to_java_bytes + htd = @admin.getTableDescriptor(tablebytes) + + if (family != nil) + raise(ArgumentError, "Can't find a family: #{family}") unless htd.hasFamily(family.to_java_bytes) + end + + fambytes = family.to_java_bytes if (family != nil) + qualbytes = qualifier.to_java_bytes if (qualifier != nil) end begin @@ -75,26 +86,37 @@ module Hbase meta_table.close() end end + end #---------------------------------------------------------------------------------------------- def revoke(user, table_name=nil, family=nil, qualifier=nil) security_available? # TODO: need to validate user name - if (table_name != nil) - # Table should exist - raise(ArgumentError, "Can't find a table: #{table_name}") unless exists?(table_name) - - tablebytes=table_name.to_java_bytes - htd = @admin.getTableDescriptor(tablebytes) - - if (family != nil) - raise(ArgumentError, "Can't find family: #{family}") unless htd.hasFamily(family.to_java_bytes) - end - - fambytes = family.to_java_bytes if (family != nil) - qualbytes = qualifier.to_java_bytes if (qualifier != nil) + #check if the tablename passed is actually a namespace + if (isNamespace?(table_name)) + # Namespace should exist first. + namespace_name = table_name[1...table_name.length] + raise(ArgumentError, "Can't find a namespace: #{namespace_name}") unless namespace_exists?(namespace_name) + + #We pass the namespace name along with "@" so that we can differentiate a namespace from a table. + tablebytes=table_name.to_java_bytes + fambytes = nil + qualbytes = nil + else + # Table should exist + raise(ArgumentError, "Can't find a table: #{table_name}") unless exists?(table_name) + + tablebytes=table_name.to_java_bytes + htd = @admin.getTableDescriptor(tablebytes) + + if (family != nil) + raise(ArgumentError, "Can't find a family: #{family}") unless htd.hasFamily(family.to_java_bytes) + end + + fambytes = family.to_java_bytes if (family != nil) + qualbytes = qualifier.to_java_bytes if (qualifier != nil) end begin @@ -113,13 +135,21 @@ module Hbase meta_table.close() end end + end #---------------------------------------------------------------------------------------------- def user_permission(table_name=nil) security_available? if (table_name != nil) - raise(ArgumentError, "Can't find table: #{table_name}") unless exists?(table_name) + #check if namespace is passed. + if (isNamespace?(table_name)) + # Namespace should exist first. + namespace_name = table_name[1...table_name.length] + raise(ArgumentError, "Can't find a namespace: #{namespace_name}") unless namespace_exists?(namespace_name) + else + raise(ArgumentError, "Can't find table: #{table_name}") unless exists?(table_name) + end tablebytes=table_name.to_java_bytes end @@ -167,11 +197,24 @@ module Hbase @admin.tableExists(table_name) end + def isNamespace?(table_name) + table_name.start_with?('@') + end + + # Does Namespace exist + def namespace_exists?(namespace_name) + namespaceDesc = @admin.getNamespaceDescriptor(namespace_name) + if(namespaceDesc == nil) + return false + else + return true + end + end + # Make sure that security tables are available def security_available?() raise(ArgumentError, "DISABLED: Security features are not available") \ unless exists?(org.apache.hadoop.hbase.security.access.AccessControlLists::ACL_TABLE_NAME) end - end end diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java index f069766..b7cafe8 100644 --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java @@ -18,11 +18,24 @@ package org.apache.hadoop.hbase.security.access; +import static org.junit.Assert.fail; + import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException; +import org.apache.hadoop.hbase.exceptions.AccessDeniedException; +import org.apache.hadoop.hbase.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.CheckPermissionsRequest; import org.apache.hadoop.hbase.security.User; +import com.google.protobuf.ServiceException; + /** * Utility methods for testing security */ @@ -37,4 +50,110 @@ public class SecureTestUtil { String currentUser = User.getCurrent().getName(); conf.set("hbase.superuser", "admin,"+currentUser); } + + public void verifyAllowed(User user, PrivilegedExceptionAction... actions) throws Exception { + for (PrivilegedExceptionAction action : actions) { + try { + user.runAs(action); + } catch (AccessDeniedException ade) { + fail("Expected action to pass for user '" + user.getShortName() + "' but was denied"); + } + } + } + + public void verifyAllowed(PrivilegedExceptionAction action, User... users) throws Exception { + for (User user : users) { + verifyAllowed(user, action); + } + } + + public void verifyDenied(User user, PrivilegedExceptionAction... actions) throws Exception { + for (PrivilegedExceptionAction action : actions) { + try { + user.runAs(action); + fail("Expected AccessDeniedException for user '" + user.getShortName() + "'"); + } catch (IOException e) { + boolean isAccessDeniedException = false; + if(e instanceof RetriesExhaustedWithDetailsException) { + // in case of batch operations, and put, the client assembles a + // RetriesExhaustedWithDetailsException instead of throwing an + // AccessDeniedException + for(Throwable ex : ((RetriesExhaustedWithDetailsException) e).getCauses()) { + if (ex instanceof AccessDeniedException) { + isAccessDeniedException = true; + break; + } + } + } + else { + // For doBulkLoad calls AccessDeniedException + // is buried in the stack trace + Throwable ex = e; + do { + if (ex instanceof AccessDeniedException) { + isAccessDeniedException = true; + break; + } + } while((ex = ex.getCause()) != null); + } + if (!isAccessDeniedException) { + fail("Not receiving AccessDeniedException for user '" + user.getShortName() + "'"); + } + } catch (UndeclaredThrowableException ute) { + // TODO why we get a PrivilegedActionException, which is unexpected? + Throwable ex = ute.getUndeclaredThrowable(); + if (ex instanceof PrivilegedActionException) { + ex = ((PrivilegedActionException) ex).getException(); + } + if (ex instanceof ServiceException) { + ServiceException se = (ServiceException)ex; + if (se.getCause() != null && se.getCause() instanceof AccessDeniedException) { + // expected result + return; + } + } + fail("Not receiving AccessDeniedException for user '" + user.getShortName() + "'"); + } + } + } + + public void verifyDenied(PrivilegedExceptionAction action, User... users) throws Exception { + for (User user : users) { + verifyDenied(user, action); + } + } + + public void checkTablePerms(Configuration conf, byte[] table, byte[] family, byte[] column, + Permission.Action... actions) throws IOException { + Permission[] perms = new Permission[actions.length]; + for (int i = 0; i < actions.length; i++) { + perms[i] = new TablePermission(table, family, column, actions[i]); + } + + checkTablePerms(conf, table, perms); + } + + public void checkTablePerms(Configuration conf, byte[] table, Permission... perms) throws IOException { + CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder(); + for (Permission p : perms) { + request.addPermission(ProtobufUtil.toPermission(p)); + } + HTable acl = new HTable(conf, table); + try { + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0])); + try { + protocol.checkPermissions(null, request.build()); + } catch (ServiceException se) { + ProtobufUtil.toIOException(se); + } + } finally { + acl.close(); + } + } + + /*public void grant(AccessControllerProtocol protocol, User user, byte[] t, byte[] f, byte[] q, + Permission.Action... actions) throws IOException { + protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()), t, f, q, actions)); + }*/ } diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java index 82b708e..cb26e50 100644 --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java @@ -18,14 +18,12 @@ package org.apache.hadoop.hbase.security.access; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; -import java.lang.reflect.UndeclaredThrowableException; -import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.List; import java.util.Map; @@ -57,7 +55,6 @@ import org.apache.hadoop.hbase.client.Increment; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; -import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; import org.apache.hadoop.hbase.coprocessor.ObserverContext; @@ -77,15 +74,10 @@ import org.apache.hadoop.hbase.regionserver.HRegionServer; import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost; import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost; import org.apache.hadoop.hbase.regionserver.ScanType; -import org.apache.hadoop.hbase.exceptions.AccessDeniedException; import org.apache.hadoop.hbase.security.User; -import org.apache.hadoop.hbase.security.access.AccessControlLists; -import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.security.access.Permission.Action; -import org.apache.hadoop.hbase.security.access.UserPermission; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.JVMClusterUtil; - import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -103,7 +95,7 @@ import com.google.protobuf.ServiceException; */ @Category(LargeTests.class) @SuppressWarnings("rawtypes") -public class TestAccessController { +public class TestAccessController extends SecureTestUtil { private static final Log LOG = LogFactory.getLog(TestAccessController.class); private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static Configuration conf; @@ -225,77 +217,7 @@ public class TestAccessController { assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE).size()); } - public void verifyAllowed(User user, PrivilegedExceptionAction... actions) throws Exception { - for (PrivilegedExceptionAction action : actions) { - try { - user.runAs(action); - } catch (AccessDeniedException ade) { - fail("Expected action to pass for user '" + user.getShortName() + "' but was denied"); - } - } - } - - public void verifyAllowed(PrivilegedExceptionAction action, User... users) throws Exception { - for (User user : users) { - verifyAllowed(user, action); - } - } - - public void verifyDenied(User user, PrivilegedExceptionAction... actions) throws Exception { - for (PrivilegedExceptionAction action : actions) { - try { - user.runAs(action); - fail("Expected AccessDeniedException for user '" + user.getShortName() + "'"); - } catch (IOException e) { - boolean isAccessDeniedException = false; - if(e instanceof RetriesExhaustedWithDetailsException) { - // in case of batch operations, and put, the client assembles a - // RetriesExhaustedWithDetailsException instead of throwing an - // AccessDeniedException - for(Throwable ex : ((RetriesExhaustedWithDetailsException) e).getCauses()) { - if (ex instanceof AccessDeniedException) { - isAccessDeniedException = true; - break; - } - } - } - else { - // For doBulkLoad calls AccessDeniedException - // is buried in the stack trace - Throwable ex = e; - do { - if (ex instanceof AccessDeniedException) { - isAccessDeniedException = true; - break; - } - } while((ex = ex.getCause()) != null); - } - if (!isAccessDeniedException) { - fail("Not receiving AccessDeniedException for user '" + user.getShortName() + "'"); - } - } catch (UndeclaredThrowableException ute) { - // TODO why we get a PrivilegedActionException, which is unexpected? - Throwable ex = ute.getUndeclaredThrowable(); - if (ex instanceof PrivilegedActionException) { - ex = ((PrivilegedActionException) ex).getException(); - } - if (ex instanceof ServiceException) { - ServiceException se = (ServiceException)ex; - if (se.getCause() != null && se.getCause() instanceof AccessDeniedException) { - // expected result - return; - } - } - fail("Not receiving AccessDeniedException for user '" + user.getShortName() + "'"); - } - } - } - - public void verifyDenied(PrivilegedExceptionAction action, User... users) throws Exception { - for (User user : users) { - verifyDenied(user, action); - } - } + @Test public void testTableCreate() throws Exception { diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java new file mode 100644 index 0000000..2b1eafa --- /dev/null +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java @@ -0,0 +1,222 @@ +/* + * 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.hbase.security.access; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.security.PrivilegedExceptionAction; +import java.util.List; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Coprocessor; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MediumTests; +import org.apache.hadoop.hbase.NamespaceDescriptor; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; +import org.apache.hadoop.hbase.coprocessor.ObserverContext; +import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; +import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment; +import org.apache.hadoop.hbase.master.MasterCoprocessorHost; +import org.apache.hadoop.hbase.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.security.access.Permission.Action; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.google.common.collect.ListMultimap; +import com.google.protobuf.BlockingRpcChannel; + +@Category(MediumTests.class) +@SuppressWarnings("rawtypes") +public class TestNamespaceCommands extends SecureTestUtil { + private static HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static String TestNamespace = "ns1"; + private static Configuration conf; + private static MasterCoprocessorEnvironment CP_ENV; + private static AccessController ACCESS_CONTROLLER; + +//user with all permissions + private static User SUPERUSER; + // user with rw permissions + private static User USER_RW; + // user with create table permissions alone + private static User USER_CREATE; + // user with permission on namespace for testing all operations. + private static User USER_NSP_ADMIN; + + @BeforeClass + public static void beforeClass() throws Exception { + conf = UTIL.getConfiguration(); + SecureTestUtil.enableSecurity(conf); + conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); + UTIL.startMiniCluster(); + SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); + USER_RW = User.createUserForTesting(conf, "rw_user", new String[0]); + USER_CREATE = User.createUserForTesting(conf, "create_user", new String[0]); + USER_NSP_ADMIN = User.createUserForTesting(conf, "namespace_admin", new String[0]); + UTIL.getHBaseAdmin().createNamespace(NamespaceDescriptor.create(TestNamespace).build()); + + // Wait for the ACL table to become available + UTIL.waitTableAvailable(AccessControlLists.ACL_TABLE_NAME, 8000); + + HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); + MasterCoprocessorHost cpHost = UTIL.getMiniHBaseCluster().getMaster().getCoprocessorHost(); + cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); + ACCESS_CONTROLLER = (AccessController) cpHost.findCoprocessor(AccessController.class.getName()); + try { + BlockingRpcChannel service = acl.coprocessorService(AccessControlLists.ACL_TABLE_NAME); + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(service); + ProtobufUtil.grant(protocol, USER_NSP_ADMIN.getShortName(), AccessControlLists + .getNamespaceEntry(TestNamespace), new byte[0], new byte[0], Permission.Action.ADMIN); + } finally { + acl.close(); + } + } + + @AfterClass + public static void afterClass() throws Exception { + UTIL.getHBaseAdmin().deleteNamespace(TestNamespace); + UTIL.shutdownMiniCluster(); + } + + @Test + public void testAclTableEntries() throws Exception { + String userTestNamespace = "userTestNsp"; + AccessControlService.BlockingInterface protocol = null; + HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); + try { + BlockingRpcChannel service = acl.coprocessorService(AccessControlLists.ACL_TABLE_NAME); + protocol = AccessControlService.newBlockingStub(service); + ProtobufUtil.grant(protocol, userTestNamespace, + AccessControlLists.getNamespaceEntry(TestNamespace), ArrayUtils.EMPTY_BYTE_ARRAY, + ArrayUtils.EMPTY_BYTE_ARRAY, Permission.Action.WRITE); + Result result = acl.get(new Get(Bytes.toBytes(userTestNamespace))); + assertTrue(result != null); + ListMultimap perms = AccessControlLists.getTablePermissions(conf, + AccessControlLists.getNamespaceEntry(TestNamespace)); + assertEquals(2, perms.size()); + List namespacePerms = perms.get(userTestNamespace); + assertTrue(perms.containsKey(userTestNamespace)); + assertEquals(1, namespacePerms.size()); + assertEquals(Bytes.toString(AccessControlLists.getNamespaceEntry(TestNamespace)), + Bytes.toString(namespacePerms.get(0).getTable())); + assertEquals(null, namespacePerms.get(0).getFamily()); + assertEquals(null, namespacePerms.get(0).getQualifier()); + assertEquals(1, namespacePerms.get(0).getActions().length); + assertEquals(Permission.Action.WRITE, namespacePerms.get(0).getActions()[0]); + // Now revoke and check. + ProtobufUtil.revoke(protocol, userTestNamespace, + AccessControlLists.getNamespaceEntry(TestNamespace), ArrayUtils.EMPTY_BYTE_ARRAY, + ArrayUtils.EMPTY_BYTE_ARRAY, Permission.Action.WRITE); + perms = AccessControlLists.getTablePermissions(conf, + AccessControlLists.getNamespaceEntry(TestNamespace)); + assertEquals(1, perms.size()); + } finally { + acl.close(); + } + } + + @Test + public void testTableCreate() throws Exception { + PrivilegedExceptionAction createTable = new PrivilegedExceptionAction() { + public Object run() throws Exception { + HTableDescriptor htd = new HTableDescriptor(TestNamespace + ".testnewtable"); + htd.addFamily(new HColumnDescriptor("TestFamily")); + ACCESS_CONTROLLER.preCreateTable(ObserverContext.createAndPrepare(CP_ENV, null), htd, null); + return null; + } + }; + // verify that superuser and namespace admin can create tables in namespace. + verifyAllowed(createTable, SUPERUSER, USER_NSP_ADMIN); + // all others should be denied + verifyDenied(createTable, USER_CREATE, USER_RW); + } + + @Test + public void testModifyNamespace() throws Exception { + PrivilegedExceptionAction modifyNamespace = new PrivilegedExceptionAction() { + public Object run() throws Exception { + ACCESS_CONTROLLER.preModifyNamespace(ObserverContext.createAndPrepare(CP_ENV, null), + NamespaceDescriptor.create(TestNamespace).addConfiguration("abc", "156").build()); + return null; + } + }; + // verify that superuser or hbase admin can modify namespaces. + verifyAllowed(modifyNamespace, SUPERUSER); + // all others should be denied + verifyDenied(modifyNamespace, USER_NSP_ADMIN, USER_CREATE, USER_RW); + } + + @Test + public void testGrantRevoke() throws Exception{ + //Only HBase super user should be able to grant and revoke permissions to + // namespaces. + final String testUser = "testUser"; + PrivilegedExceptionAction grantAction = new PrivilegedExceptionAction() { + public Object run() throws Exception { + HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); + try { + BlockingRpcChannel service = acl.coprocessorService(AccessControlLists.ACL_TABLE_NAME); + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(service); + ProtobufUtil.grant(protocol, testUser, AccessControlLists.getNamespaceEntry(TestNamespace), + ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, Action.WRITE); + } finally { + acl.close(); + } + return null; + } + }; + + PrivilegedExceptionAction revokeAction = new PrivilegedExceptionAction() { + public Object run() throws Exception { + HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); + try { + BlockingRpcChannel service = acl.coprocessorService(AccessControlLists.ACL_TABLE_NAME); + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(service); + ProtobufUtil.revoke(protocol, testUser, AccessControlLists.getNamespaceEntry(TestNamespace), + ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, Action.WRITE); + } finally { + acl.close(); + } + return null; + } + }; + + verifyAllowed(grantAction, SUPERUSER); + verifyDenied(grantAction, USER_CREATE, USER_RW); + + verifyAllowed(revokeAction, SUPERUSER); + verifyDenied(revokeAction, USER_CREATE, USER_RW); + + } +}