/*
* 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.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.HRegionInfo;
import org.apache.hadoop.hbase.HServerAddress;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
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.CoprocessorException;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.security.AccessDeniedException;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Performs authorization checks for common operations, according to different
* levels of authorized users.
*/
public class TestAccessController {
private static Log LOG = LogFactory.getLog(TestAccessController.class);
private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private static Configuration conf;
// user with all permissions
private static User SUPERUSER;
// user granted with all global permission
private static User USER_ADMIN;
// table owner user
private static User USER_OWNER;
// user with rw permissions
private static User USER_RW;
// user with read-only permissions
private static User USER_RO;
// user with no permissions
private static User USER_NONE;
private static byte[] TEST_TABLE = Bytes.toBytes("testtable");
private static byte[] TEST_FAMILY = Bytes.toBytes("f1");
private static MasterCoprocessorEnvironment CP_ENV;
private static AccessController ACCESS_CONTROLLER;
@BeforeClass
public static void setupBeforeClass() throws Exception {
// setup configuration
conf = TEST_UTIL.getConfiguration();
SecureTestUtil.enableSecurity(conf);
TEST_UTIL.startMiniCluster();
MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster()
.getMaster().getCoprocessorHost();
cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf);
ACCESS_CONTROLLER = (AccessController)cpHost.findCoprocessor(
AccessController.class.getName());
CP_ENV = cpHost.createEnvironment(AccessController.class, ACCESS_CONTROLLER,
Coprocessor.PRIORITY_HIGHEST, 1, conf);
// create a set of test users
SUPERUSER = User.createUserForTesting(conf, "admin", new String[]{"supergroup"});
USER_ADMIN = User.createUserForTesting(conf, "admin2", new String[0]);
USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]);
USER_RW = User.createUserForTesting(conf, "rwuser", new String[0]);
USER_RO = User.createUserForTesting(conf, "rouser", new String[0]);
USER_NONE = User.createUserForTesting(conf, "nouser", new String[0]);
HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
HTableDescriptor htd = new HTableDescriptor(TEST_TABLE);
htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
htd.setOwnerString(USER_OWNER.getShortName());
admin.createTable(htd);
// initilize access control
HTable meta = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
AccessControllerProtocol protocol =
meta.coprocessorProxy(AccessControllerProtocol.class, TEST_TABLE);
protocol.grant(new UserPermission(Bytes.toBytes(USER_ADMIN.getShortName()),
Permission.Action.ADMIN, Permission.Action.CREATE,
Permission.Action.READ, Permission.Action.WRITE));
protocol.grant(new UserPermission(Bytes.toBytes(USER_RW.getShortName()),
TEST_TABLE, TEST_FAMILY, Permission.Action.READ, Permission.Action.WRITE));
protocol.grant(new UserPermission(Bytes.toBytes(USER_RO.getShortName()),
TEST_TABLE, TEST_FAMILY, Permission.Action.READ));
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
public void verifyAllowed(User user, PrivilegedExceptionAction action)
throws Exception {
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 action)
throws Exception {
try {
user.runAs(action);
fail("Expected AccessDeniedException for user '" + user.getShortName() + "'");
} catch (RetriesExhaustedWithDetailsException e) {
// in case of batch operations, and put, the client assembles a
// RetriesExhaustedWithDetailsException instead of throwing an
// AccessDeniedException
boolean isAccessDeniedException = false;
for ( Throwable ex : e.getCauses()) {
if (ex instanceof AccessDeniedException) {
isAccessDeniedException = true;
break;
}
}
if (!isAccessDeniedException ) {
fail("Not receiving AccessDeniedException for user '" +
user.getShortName() + "'");
}
} catch (AccessDeniedException ade) {
// expected result
}
}
public void verifyDenied(PrivilegedExceptionAction action, User... users)
throws Exception {
for (User user : users) {
verifyDenied(user, action);
}
}
@Test
public void testTableCreate() throws Exception {
PrivilegedExceptionAction createTable = new PrivilegedExceptionAction() {
public Object run() throws Exception {
HTableDescriptor htd = new HTableDescriptor("testnewtable");
htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
ACCESS_CONTROLLER.preCreateTable(
ObserverContext.createAndPrepare(CP_ENV, null), htd, null);
return null;
}
};
// verify that superuser can create tables
verifyAllowed(SUPERUSER, createTable);
verifyAllowed(USER_ADMIN, createTable);
// all others should be denied
verifyDenied(USER_OWNER, createTable);
verifyDenied(USER_RW, createTable);
verifyDenied(USER_RO, createTable);
verifyDenied(USER_NONE, createTable);
}
@Test
public void testTableModify() throws Exception {
PrivilegedExceptionAction modifyTable = new PrivilegedExceptionAction() {
public Object run() throws Exception {
HTableDescriptor htd = new HTableDescriptor(TEST_TABLE);
htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
htd.addFamily(new HColumnDescriptor("fam_"+User.getCurrent().getShortName()));
ACCESS_CONTROLLER.preModifyTable(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE, htd);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, modifyTable);
verifyDenied(USER_RW, modifyTable);
verifyDenied(USER_RO, modifyTable);
verifyDenied(USER_NONE, modifyTable);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, modifyTable);
verifyAllowed(USER_ADMIN, modifyTable);
}
@Test
public void testTableDelete() throws Exception {
PrivilegedExceptionAction deleteTable = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preDeleteTable(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, deleteTable);
verifyDenied(USER_RW, deleteTable);
verifyDenied(USER_RO, deleteTable);
verifyDenied(USER_NONE, deleteTable);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, deleteTable);
verifyAllowed(USER_ADMIN, deleteTable);
}
@Test
public void testAddColumn() throws Exception {
final HColumnDescriptor hcd = new HColumnDescriptor("fam_new");
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preAddColumn(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE, hcd);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testModifyColumn() throws Exception {
final HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY);
hcd.setMaxVersions(10);
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preModifyColumn(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE, hcd);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testDeleteColumn() throws Exception {
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preDeleteColumn(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE, TEST_FAMILY);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testTableDisable() throws Exception {
PrivilegedExceptionAction disableTable = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preDisableTable(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, disableTable);
verifyDenied(USER_RW, disableTable);
verifyDenied(USER_RO, disableTable);
verifyDenied(USER_NONE, disableTable);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, disableTable);
verifyAllowed(USER_ADMIN, disableTable);
}
@Test
public void testTableEnable() throws Exception {
PrivilegedExceptionAction enableTable = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preEnableTable(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, enableTable);
verifyDenied(USER_RW, enableTable);
verifyDenied(USER_RO, enableTable);
verifyDenied(USER_NONE, enableTable);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, enableTable);
verifyAllowed(USER_ADMIN, enableTable);
}
@Test
public void testMove() throws Exception {
HTable table = new HTable(TEST_UTIL.getConfiguration(), TEST_TABLE);
Map<HRegionInfo,HServerAddress> regions = table.getRegionsInfo();
final Map.Entry<HRegionInfo,HServerAddress> firstRegion =
regions.entrySet().iterator().next();
final ServerName server = TEST_UTIL.getHBaseCluster().getRegionServer(0).getServerName();
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preMove(ObserverContext.createAndPrepare(CP_ENV, null),
firstRegion.getKey(), server, server);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testAssign() throws Exception {
HTable table = new HTable(TEST_UTIL.getConfiguration(), TEST_TABLE);
Map<HRegionInfo,HServerAddress> regions = table.getRegionsInfo();
final Map.Entry<HRegionInfo,HServerAddress> firstRegion =
regions.entrySet().iterator().next();
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preAssign(ObserverContext.createAndPrepare(CP_ENV, null),
firstRegion.getKey());
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testUnassign() throws Exception {
HTable table = new HTable(TEST_UTIL.getConfiguration(), TEST_TABLE);
Map<HRegionInfo,HServerAddress> regions = table.getRegionsInfo();
final Map.Entry<HRegionInfo,HServerAddress> firstRegion =
regions.entrySet().iterator().next();
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preUnassign(ObserverContext.createAndPrepare(CP_ENV, null),
firstRegion.getKey(), false);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testBalance() throws Exception {
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preBalance(ObserverContext.createAndPrepare(CP_ENV, null));
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testBalanceSwitch() throws Exception {
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preBalanceSwitch(ObserverContext.createAndPrepare(CP_ENV, null), true);
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testShutdown() throws Exception {
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preShutdown(ObserverContext.createAndPrepare(CP_ENV, null));
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
@Test
public void testStopMaster() throws Exception {
PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
public Object run() throws Exception {
ACCESS_CONTROLLER.preStopMaster(ObserverContext.createAndPrepare(CP_ENV, null));
return null;
}
};
// all others should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_RO, action);
verifyDenied(USER_NONE, action);
// verify that superuser can create tables
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
}
private void verifyWrite(PrivilegedExceptionAction action) throws Exception {
// should be denied
verifyDenied(USER_NONE, action);
verifyDenied(USER_RO, action);
// should be allowed
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
verifyAllowed(USER_OWNER, action);
verifyAllowed(USER_RW, action);
}
private void verifyRead(PrivilegedExceptionAction action) throws Exception {
// should be denied
verifyDenied(USER_NONE, action);
// should be allowed
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
verifyAllowed(USER_OWNER, action);
verifyAllowed(USER_RW, action);
verifyAllowed(USER_RO, action);
}
private void verifyReadWrite(PrivilegedExceptionAction action) throws Exception {
// should be denied
verifyDenied(USER_NONE, action);
verifyDenied(USER_RO, action);
// should be allowed
verifyAllowed(SUPERUSER, action);
verifyAllowed(USER_ADMIN, action);
verifyAllowed(USER_OWNER, action);
verifyAllowed(USER_RW, action);
}
@Test
public void testRead() throws Exception {
// get action
PrivilegedExceptionAction getAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Get g = new Get(Bytes.toBytes("random_row"));
g.addFamily(TEST_FAMILY);
HTable t = new HTable(conf, TEST_TABLE);
t.get(g);
return null;
}
};
verifyRead(getAction);
// action for scanning
PrivilegedExceptionAction scanAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Scan s = new Scan();
s.addFamily(TEST_FAMILY);
HTable table = new HTable(conf, TEST_TABLE);
ResultScanner scanner = table.getScanner(s);
try {
for (Result r = scanner.next(); r != null; r = scanner.next()) {
// do nothing
}
} catch (IOException e) {
} finally {
scanner.close();
}
return null;
}
};
verifyRead(scanAction);
}
@Test
// test put, delete, increment
public void testWrite() throws Exception {
// put action
PrivilegedExceptionAction putAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Put p = new Put(Bytes.toBytes("random_row"));
p.add(TEST_FAMILY, Bytes.toBytes("Qualifier"), Bytes.toBytes(1));
HTable t = new HTable(conf, TEST_TABLE);
t.put(p);
return null;
}
};
verifyWrite(putAction);
// delete action
PrivilegedExceptionAction deleteAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Delete d = new Delete(Bytes.toBytes("random_row"));
d.deleteFamily(TEST_FAMILY);
HTable t = new HTable(conf, TEST_TABLE);
t.delete(d);
return null;
}
};
verifyWrite(deleteAction);
// increment action
PrivilegedExceptionAction incrementAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Increment inc = new Increment(Bytes.toBytes("random_row"));
inc.addColumn(TEST_FAMILY, Bytes.toBytes("Qualifier"), 1);
HTable t = new HTable(conf, TEST_TABLE);
t.increment(inc);
return null;
}
};
verifyWrite(incrementAction);
}
@Test
public void testReadWrite() throws Exception {
// action for checkAndDelete
PrivilegedExceptionAction checkAndDeleteAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Delete d = new Delete(Bytes.toBytes("random_row"));
d.deleteFamily(TEST_FAMILY);
HTable t = new HTable(conf, TEST_TABLE);
t.checkAndDelete(Bytes.toBytes("random_row"),
TEST_FAMILY, Bytes.toBytes("q"),
Bytes.toBytes("test_value"), d);
return null;
}
};
verifyReadWrite(checkAndDeleteAction);
// action for checkAndPut()
PrivilegedExceptionAction checkAndPut = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Put p = new Put(Bytes.toBytes("random_row"));
p.add(TEST_FAMILY, Bytes.toBytes("Qualifier"), Bytes.toBytes(1));
HTable t = new HTable(conf, TEST_TABLE);
t.checkAndPut(Bytes.toBytes("random_row"),
TEST_FAMILY, Bytes.toBytes("q"),
Bytes.toBytes("test_value"), p);
return null;
}
};
verifyReadWrite(checkAndPut);
}
@Test
public void testGrantRevoke() throws Exception {
final byte[] tableName = Bytes.toBytes("TempTable");
final byte[] family1 = Bytes.toBytes("f1");
final byte[] family2 = Bytes.toBytes("f2");
final byte[] qualifier = Bytes.toBytes("q");
// create table
HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
if (admin.tableExists(tableName)) {
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
HTableDescriptor htd = new HTableDescriptor(tableName);
htd.addFamily(new HColumnDescriptor(family1));
htd.addFamily(new HColumnDescriptor(family2));
htd.setOwnerString(USER_OWNER.getShortName());
admin.createTable(htd);
// create temp users
User user = User.createUserForTesting(TEST_UTIL.getConfiguration(),
"user", new String[0]);
// perms only stored against the first region
HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
AccessControllerProtocol protocol =
acl.coprocessorProxy(AccessControllerProtocol.class,
tableName);
// prepare actions:
PrivilegedExceptionAction putActionAll = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Put p = new Put(Bytes.toBytes("a"));
p.add(family1, qualifier, Bytes.toBytes("v1"));
p.add(family2, qualifier, Bytes.toBytes("v2"));
HTable t = new HTable(conf, tableName);
t.put(p);
return null;
}
};
PrivilegedExceptionAction putAction1 = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Put p = new Put(Bytes.toBytes("a"));
p.add(family1, qualifier, Bytes.toBytes("v1"));
HTable t = new HTable(conf, tableName);
t.put(p);
return null;
}
};
PrivilegedExceptionAction putAction2 = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Put p = new Put(Bytes.toBytes("a"));
p.add(family2, qualifier, Bytes.toBytes("v2"));
HTable t = new HTable(conf, tableName);
t.put(p);
return null;
}
};
PrivilegedExceptionAction getActionAll = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Get g = new Get(Bytes.toBytes("random_row"));
g.addFamily(family1);
g.addFamily(family2);
HTable t = new HTable(conf, tableName);
t.get(g);
return null;
}
};
PrivilegedExceptionAction getAction1 = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Get g = new Get(Bytes.toBytes("random_row"));
g.addFamily(family1);
HTable t = new HTable(conf, tableName);
t.get(g);
return null;
}
};
PrivilegedExceptionAction getAction2 = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Get g = new Get(Bytes.toBytes("random_row"));
g.addFamily(family2);
HTable t = new HTable(conf, tableName);
t.get(g);
return null;
}
};
PrivilegedExceptionAction deleteActionAll = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Delete d = new Delete(Bytes.toBytes("random_row"));
d.deleteFamily(family1);
d.deleteFamily(family2);
HTable t = new HTable(conf, tableName);
t.delete(d);
return null;
}
};
PrivilegedExceptionAction deleteAction1 = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Delete d = new Delete(Bytes.toBytes("random_row"));
d.deleteFamily(family1);
HTable t = new HTable(conf, tableName);
t.delete(d);
return null;
}
};
PrivilegedExceptionAction deleteAction2 = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Delete d = new Delete(Bytes.toBytes("random_row"));
d.deleteFamily(family2);
HTable t = new HTable(conf, tableName);
t.delete(d);
return null;
}
};
// initial check:
verifyDenied(user, getActionAll);
verifyDenied(user, getAction1);
verifyDenied(user, getAction2);
verifyDenied(user, putActionAll);
verifyDenied(user, putAction1);
verifyDenied(user, putAction2);
verifyDenied(user, deleteActionAll);
verifyDenied(user, deleteAction1);
verifyDenied(user, deleteAction2);
// grant table read permission
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, null, Permission.Action.READ));
Thread.sleep(100);
// check
verifyAllowed(user, getActionAll);
verifyAllowed(user, getAction1);
verifyAllowed(user, getAction2);
verifyDenied(user, putActionAll);
verifyDenied(user, putAction1);
verifyDenied(user, putAction2);
verifyDenied(user, deleteActionAll);
verifyDenied(user, deleteAction1);
verifyDenied(user, deleteAction2);
// grant table write permission
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, null, Permission.Action.WRITE));
Thread.sleep(100);
verifyDenied(user, getActionAll);
verifyDenied(user, getAction1);
verifyDenied(user, getAction2);
verifyAllowed(user, putActionAll);
verifyAllowed(user, putAction1);
verifyAllowed(user, putAction2);
verifyAllowed(user, deleteActionAll);
verifyAllowed(user, deleteAction1);
verifyAllowed(user, deleteAction2);
// revoke table permission
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, null, Permission.Action.READ, Permission.Action.WRITE));
protocol.revoke(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, null));
Thread.sleep(100);
verifyDenied(user, getActionAll);
verifyDenied(user, getAction1);
verifyDenied(user, getAction2);
verifyDenied(user, putActionAll);
verifyDenied(user, putAction1);
verifyDenied(user, putAction2);
verifyDenied(user, deleteActionAll);
verifyDenied(user, deleteAction1);
verifyDenied(user, deleteAction2);
// grant column family read permission
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family1, Permission.Action.READ));
Thread.sleep(100);
verifyAllowed(user, getActionAll);
verifyAllowed(user, getAction1);
verifyDenied(user, getAction2);
verifyDenied(user, putActionAll);
verifyDenied(user, putAction1);
verifyDenied(user, putAction2);
verifyDenied(user, deleteActionAll);
verifyDenied(user, deleteAction1);
verifyDenied(user, deleteAction2);
// grant column family write permission
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family2, Permission.Action.WRITE));
Thread.sleep(100);
verifyAllowed(user, getActionAll);
verifyAllowed(user, getAction1);
verifyDenied(user, getAction2);
verifyDenied(user, putActionAll);
verifyDenied(user, putAction1);
verifyAllowed(user, putAction2);
verifyDenied(user, deleteActionAll);
verifyDenied(user, deleteAction1);
verifyAllowed(user, deleteAction2);
// revoke column family permission
protocol.revoke(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family2));
Thread.sleep(100);
verifyAllowed(user, getActionAll);
verifyAllowed(user, getAction1);
verifyDenied(user, getAction2);
verifyDenied(user, putActionAll);
verifyDenied(user, putAction1);
verifyDenied(user, putAction2);
verifyDenied(user, deleteActionAll);
verifyDenied(user, deleteAction1);
verifyDenied(user, deleteAction2);
// delete table
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
private boolean hasFoundUserPermission(UserPermission userPermission,
List<UserPermission> perms) {
return perms.contains(userPermission);
}
@Test
public void testGrantRevokeAtQualifierLevel() throws Exception {
final byte[] tableName = Bytes.toBytes("testGrantRevokeAtQualifierLevel");
final byte[] family1 = Bytes.toBytes("f1");
final byte[] family2 = Bytes.toBytes("f2");
final byte[] qualifier = Bytes.toBytes("q");
// create table
HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
if (admin.tableExists(tableName)) {
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
HTableDescriptor htd = new HTableDescriptor(tableName);
htd.addFamily(new HColumnDescriptor(family1));
htd.addFamily(new HColumnDescriptor(family2));
htd.setOwnerString(USER_OWNER.getShortName());
admin.createTable(htd);
// create temp users
User user = User.createUserForTesting(TEST_UTIL.getConfiguration(),
"user", new String[0]);
HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
AccessControllerProtocol protocol =
acl.coprocessorProxy(AccessControllerProtocol.class, tableName);
PrivilegedExceptionAction getQualifierAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Get g = new Get(Bytes.toBytes("random_row"));
g.addColumn(family1, qualifier);
HTable t = new HTable(conf, tableName);
t.get(g);
return null;
}
};
PrivilegedExceptionAction putQualifierAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Put p = new Put(Bytes.toBytes("random_row"));
p.add(family1, qualifier, Bytes.toBytes("v1"));
HTable t = new HTable(conf, tableName);
t.put(p);
return null;
}
};
PrivilegedExceptionAction deleteQualifierAction = new PrivilegedExceptionAction() {
public Object run() throws Exception {
Delete d = new Delete(Bytes.toBytes("random_row"));
d.deleteColumn(family1, qualifier);
//d.deleteFamily(family1);
HTable t = new HTable(conf, tableName);
t.delete(d);
return null;
}
};
protocol.revoke(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family1));
verifyDenied(user, getQualifierAction);
verifyDenied(user, putQualifierAction);
verifyDenied(user, deleteQualifierAction);
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family1, qualifier, Permission.Action.READ));
Thread.sleep(100);
verifyAllowed(user, getQualifierAction);
verifyDenied(user, putQualifierAction);
verifyDenied(user, deleteQualifierAction);
// only grant write permission
// TODO: comment this portion after HBASE-3583
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family1, qualifier, Permission.Action.WRITE));
Thread.sleep(100);
verifyDenied(user, getQualifierAction);
verifyAllowed(user, putQualifierAction);
verifyAllowed(user, deleteQualifierAction);
// grant both read and write permission.
protocol.grant(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family1, qualifier,
Permission.Action.READ, Permission.Action.WRITE));
Thread.sleep(100);
verifyAllowed(user, getQualifierAction);
verifyAllowed(user, putQualifierAction);
verifyAllowed(user, deleteQualifierAction);
// revoke family level permission won't impact column level.
protocol.revoke(new UserPermission(Bytes.toBytes(user.getShortName()),
tableName, family1, qualifier));
Thread.sleep(100);
verifyDenied(user, getQualifierAction);
verifyDenied(user, putQualifierAction);
verifyDenied(user, deleteQualifierAction);
// delete table
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
@Test
public void testPermissionList() throws Exception {
final byte[] tableName = Bytes.toBytes("testPermissionList");
final byte[] family1 = Bytes.toBytes("f1");
final byte[] family2 = Bytes.toBytes("f2");
final byte[] qualifier = Bytes.toBytes("q");
final byte[] user = Bytes.toBytes("user");
// create table
HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
if (admin.tableExists(tableName)) {
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
HTableDescriptor htd = new HTableDescriptor(tableName);
htd.addFamily(new HColumnDescriptor(family1));
htd.addFamily(new HColumnDescriptor(family2));
htd.setOwnerString(USER_OWNER.getShortName());
admin.createTable(htd);
HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
AccessControllerProtocol protocol =
acl.coprocessorProxy(AccessControllerProtocol.class, tableName);
List<UserPermission> perms = protocol.getUserPermissions(tableName);
UserPermission up = new UserPermission(user,
tableName, family1, qualifier, Permission.Action.READ);
assertFalse("User should not be granted permission: " + up.toString(),
hasFoundUserPermission(up, perms));
// grant read permission
UserPermission upToSet = new UserPermission(user,
tableName, family1, qualifier, Permission.Action.READ);
protocol.grant(upToSet);
perms = protocol.getUserPermissions(tableName);
UserPermission upToVerify = new UserPermission(user,
tableName, family1, qualifier, Permission.Action.READ);
assertTrue("User should be granted permission: " + upToVerify.toString(),
hasFoundUserPermission(upToVerify, perms));
upToVerify = new UserPermission(user, tableName, family1, qualifier,
Permission.Action.WRITE);
assertFalse("User should not be granted permission: " + upToVerify.toString(),
hasFoundUserPermission(upToVerify, perms));
// grant read+write
upToSet = new UserPermission(user, tableName, family1, qualifier,
Permission.Action.WRITE, Permission.Action.READ);
protocol.grant(upToSet);
perms = protocol.getUserPermissions(tableName);
upToVerify = new UserPermission(user, tableName, family1, qualifier,
Permission.Action.WRITE, Permission.Action.READ);
assertTrue("User should be granted permission: " + upToVerify.toString(),
hasFoundUserPermission(upToVerify, perms));
protocol.revoke(upToSet);
perms = protocol.getUserPermissions(tableName);
assertFalse("User should not be granted permission: " + upToVerify.toString(),
hasFoundUserPermission(upToVerify, perms));
// delete table
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
/** global operations*/
private void verifyGlobal(PrivilegedExceptionAction<?> action) throws Exception {
// should be allowed
verifyAllowed(SUPERUSER, action);
// should be denied
verifyDenied(USER_OWNER, action);
verifyDenied(USER_RW, action);
verifyDenied(USER_NONE, action);
verifyDenied(USER_RO, action);
}
public void checkGlobalPerms(Permission.Action... actions) throws IOException {
HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
AccessControllerProtocol protocol =
acl.coprocessorProxy(AccessControllerProtocol.class, new byte[0]);
Permission[] perms = new Permission[actions.length];
for (int i=0; i < actions.length; i++) {
perms[i] = new Permission(actions[i]);
}
protocol.checkPermissions(perms);
}
public void checkTablePerms(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(table, perms);
}
public void checkTablePerms(byte[] table, Permission...perms) throws IOException {
HTable acl = new HTable(conf, table);
AccessControllerProtocol protocol =
acl.coprocessorProxy(AccessControllerProtocol.class, new byte[0]);
protocol.checkPermissions(perms);
}
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));
}
@Test
public void testCheckPermissions() throws Exception {
final HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
final AccessControllerProtocol protocol =
acl.coprocessorProxy(AccessControllerProtocol.class, TEST_TABLE);
//--------------------------------------
//test global permissions
PrivilegedExceptionAction<Void> globalAdmin = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkGlobalPerms(Permission.Action.ADMIN);
return null;
}
};
//verify that only superuser can admin
verifyGlobal(globalAdmin);
//--------------------------------------
//test multiple permissions
PrivilegedExceptionAction<Void> globalReadWrite = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkGlobalPerms(Permission.Action.READ, Permission.Action.WRITE);
return null;
}
};
verifyGlobal(globalReadWrite);
//--------------------------------------
//table/column/qualifier level permissions
final byte[] TEST_Q1 = Bytes.toBytes("q1");
final byte[] TEST_Q2 = Bytes.toBytes("q2");
User userTable = User.createUserForTesting(conf, "user_check_perms_table", new String[0]);
User userColumn = User.createUserForTesting(conf, "user_check_perms_family", new String[0]);
User userQualifier = User.createUserForTesting(conf, "user_check_perms_q", new String[0]);
grant(protocol, userTable, TEST_TABLE, null, null, Permission.Action.READ);
grant(protocol, userColumn, TEST_TABLE, TEST_FAMILY, null, Permission.Action.READ);
grant(protocol, userQualifier, TEST_TABLE, TEST_FAMILY, TEST_Q1, Permission.Action.READ);
PrivilegedExceptionAction<Void> tableRead = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkTablePerms(TEST_TABLE, null, null, Permission.Action.READ);
return null;
}
};
PrivilegedExceptionAction<Void> columnRead = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkTablePerms(TEST_TABLE, TEST_FAMILY, null, Permission.Action.READ);
return null;
}
};
PrivilegedExceptionAction<Void> qualifierRead = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkTablePerms(TEST_TABLE, TEST_FAMILY, TEST_Q1, Permission.Action.READ);
return null;
}
};
PrivilegedExceptionAction<Void> multiQualifierRead = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkTablePerms(TEST_TABLE, new Permission[] {
new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_Q1, Permission.Action.READ),
new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_Q2, Permission.Action.READ),
});
return null;
}
};
PrivilegedExceptionAction<Void> globalAndTableRead = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkTablePerms(TEST_TABLE, new Permission[] {
new Permission(Permission.Action.READ),
new TablePermission(TEST_TABLE, null, (byte[])null, Permission.Action.READ),
});
return null;
}
};
PrivilegedExceptionAction<Void> noCheck = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkTablePerms(TEST_TABLE, new Permission[0]);
return null;
}
};
verifyAllowed(tableRead, SUPERUSER, userTable);
verifyDenied(tableRead, userColumn, userQualifier);
verifyAllowed(columnRead, SUPERUSER, userTable, userColumn);
verifyDenied(columnRead, userQualifier);
verifyAllowed(qualifierRead, SUPERUSER, userTable, userColumn, userQualifier);
verifyAllowed(multiQualifierRead, SUPERUSER, userTable, userColumn);
verifyDenied(multiQualifierRead, userQualifier);
verifyAllowed(globalAndTableRead, SUPERUSER);
verifyDenied(globalAndTableRead, userTable, userColumn, userQualifier);
verifyAllowed(noCheck, SUPERUSER, userTable, userColumn, userQualifier);
//--------------------------------------
//test family level multiple permissions
PrivilegedExceptionAction<Void> familyReadWrite = new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
checkTablePerms(TEST_TABLE, TEST_FAMILY, null, Permission.Action.READ,
Permission.Action.WRITE);
return null;
}
};
// should be allowed
verifyAllowed(familyReadWrite, SUPERUSER, USER_OWNER, USER_RW);
// should be denied
verifyDenied(familyReadWrite, USER_NONE, USER_RO);
//--------------------------------------
//check for wrong table region
try {
//but ask for TablePermissions for TEST_TABLE
protocol.checkPermissions(new Permission[] {(Permission) new TablePermission(
TEST_TABLE, null, (byte[])null, Permission.Action.CREATE)});
fail("this should have thrown CoprocessorException");
} catch(CoprocessorException ex) {
//expected
}
}
}