Reduce the effective scope of CREATE and ADMIN permissions

This commit is contained in:
Andrew Purtell 2014-11-07 11:53:01 -08:00
parent 7718390703
commit 440767ff55
2 changed files with 235 additions and 67 deletions

View File

@ -291,21 +291,6 @@ public class AccessController extends BaseMasterAndRegionObserver
permRequest, tableName, families);
}
// Users with CREATE/ADMIN rights need to modify hbase:meta and _acl_ table
// e.g. When a new table is created a new entry in hbase:meta is added,
// so the user need to be allowed to write on it.
// e.g. When a table is removed an entry is removed from hbase:meta and _acl_
// and the user need to be allowed to write on both tables.
if (permRequest == Action.WRITE &&
(hri.isMetaRegion() ||
Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) &&
(authManager.authorize(user, Action.CREATE) ||
authManager.authorize(user, Action.ADMIN)))
{
return AuthResult.allow(request, "Table permission granted", user,
permRequest, tableName, families);
}
// 2. check for the table-level, if successful we can short-circuit
if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
return AuthResult.allow(request, "Table permission granted", user,
@ -931,31 +916,51 @@ public class AccessController extends BaseMasterAndRegionObserver
@Override
public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
TableName tableName) throws IOException {
AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName);
final TableName tableName) throws IOException {
final Configuration conf = c.getEnvironment().getConfiguration();
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
AccessControlLists.removeTablePermissions(conf, tableName);
return null;
}
});
}
@Override
public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
throws IOException {
public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c,
final TableName tableName) throws IOException {
requirePermission("truncateTable", tableName, null, null, Action.ADMIN);
List<UserPermission> acls = AccessControlLists.getUserTablePermissions(c.getEnvironment()
.getConfiguration(), tableName);
if (acls != null) {
tableAcls.put(tableName, acls);
}
final Configuration conf = c.getEnvironment().getConfiguration();
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
List<UserPermission> acls = AccessControlLists.getUserTablePermissions(conf, tableName);
if (acls != null) {
tableAcls.put(tableName, acls);
}
return null;
}
});
}
@Override
public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
TableName tableName) throws IOException {
List<UserPermission> perms = tableAcls.get(tableName);
if (perms != null) {
for (UserPermission perm : perms) {
AccessControlLists.addUserPermission(ctx.getEnvironment().getConfiguration(), perm);
final TableName tableName) throws IOException {
final Configuration conf = ctx.getEnvironment().getConfiguration();
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
List<UserPermission> perms = tableAcls.get(tableName);
if (perms != null) {
for (UserPermission perm : perms) {
AccessControlLists.addUserPermission(conf, perm);
}
}
tableAcls.remove(tableName);
return null;
}
}
tableAcls.remove(tableName);
});
}
@Override
@ -966,13 +971,20 @@ public class AccessController extends BaseMasterAndRegionObserver
@Override
public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
TableName tableName, HTableDescriptor htd) throws IOException {
String owner = htd.getOwnerString();
TableName tableName, final HTableDescriptor htd) throws IOException {
final Configuration conf = c.getEnvironment().getConfiguration();
// default the table owner to current user, if not specified.
if (owner == null) owner = getActiveUser().getShortName();
UserPermission userperm = new UserPermission(Bytes.toBytes(owner), htd.getTableName(), null,
Action.values());
AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(), userperm);
final String owner = (htd.getOwnerString() != null) ? htd.getOwnerString() :
getActiveUser().getShortName();
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
htd.getTableName(), null, Action.values());
AccessControlLists.addUserPermission(conf, userperm);
return null;
}
});
}
@Override
@ -995,8 +1007,15 @@ public class AccessController extends BaseMasterAndRegionObserver
@Override
public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
TableName tableName, byte[] col) throws IOException {
AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName, col);
final TableName tableName, final byte[] col) throws IOException {
final Configuration conf = c.getEnvironment().getConfiguration();
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
AccessControlLists.removeTablePermissions(conf, tableName, col);
return null;
}
});
}
@Override
@ -1127,9 +1146,15 @@ public class AccessController extends BaseMasterAndRegionObserver
@Override
public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
String namespace) throws IOException {
AccessControlLists.removeNamespacePermissions(ctx.getEnvironment().getConfiguration(),
namespace);
final String namespace) throws IOException {
final Configuration conf = ctx.getEnvironment().getConfiguration();
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
AccessControlLists.removeNamespacePermissions(conf, namespace);
return null;
}
});
LOG.info(namespace + "entry deleted in "+AccessControlLists.ACL_TABLE_NAME+" table.");
}
@ -1856,18 +1881,27 @@ public class AccessController extends BaseMasterAndRegionObserver
}
}
private AuthResult hasSomeAccess(RegionCoprocessorEnvironment e, String method, Action action) throws IOException {
private AuthResult hasSomeAccess(RegionCoprocessorEnvironment e, String method, Action action)
throws IOException {
User requestUser = getActiveUser();
TableName tableName = e.getRegion().getTableDesc().getTableName();
AuthResult authResult = permissionGranted(method, requestUser,
action, e, Collections.EMPTY_MAP);
final TableName tableName = e.getRegion().getTableDesc().getTableName();
AuthResult authResult = permissionGranted(method, requestUser, action, e,
Collections.EMPTY_MAP);
if (!authResult.isAllowed()) {
for(UserPermission userPerm:
AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), tableName)) {
for(Action userAction: userPerm.getActions()) {
if(userAction.equals(action)) {
final Configuration conf = e.getConfiguration();
// hasSomeAccess is called from bulkload pre hooks
List<UserPermission> perms =
User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
@Override
public List<UserPermission> run() throws Exception {
return AccessControlLists.getUserTablePermissions(conf, tableName);
}
});
for (UserPermission userPerm: perms) {
for (Action userAction: userPerm.getActions()) {
if (userAction.equals(action)) {
return AuthResult.allow(method, "Access allowed", requestUser,
action, tableName, null, null);
action, tableName, null, null);
}
}
}
@ -1942,7 +1976,7 @@ public class AccessController extends BaseMasterAndRegionObserver
public void grant(RpcController controller,
AccessControlProtos.GrantRequest request,
RpcCallback<AccessControlProtos.GrantResponse> done) {
UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
AccessControlProtos.GrantResponse response = null;
try {
// verify it's only running at .acl.
@ -1962,9 +1996,17 @@ public class AccessController extends BaseMasterAndRegionObserver
break;
case Namespace :
requireGlobalPermission("grant", Action.ADMIN, perm.getNamespace());
break;
}
AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
return null;
}
});
if (AUDITLOG.isTraceEnabled()) {
// audit log should store permission changes in addition to auth results
AUDITLOG.trace("Granted permission " + perm.toString());
@ -1985,7 +2027,7 @@ public class AccessController extends BaseMasterAndRegionObserver
public void revoke(RpcController controller,
AccessControlProtos.RevokeRequest request,
RpcCallback<AccessControlProtos.RevokeResponse> done) {
UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
AccessControlProtos.RevokeResponse response = null;
try {
// only allowed to be called on _acl_ region
@ -2005,9 +2047,17 @@ public class AccessController extends BaseMasterAndRegionObserver
break;
case Namespace :
requireGlobalPermission("revoke", Action.ADMIN, perm.getNamespace());
break;
}
AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
return null;
}
});
if (AUDITLOG.isTraceEnabled()) {
// audit log should record all permission changes
AUDITLOG.trace("Revoked permission " + perm.toString());
@ -2036,21 +2086,32 @@ public class AccessController extends BaseMasterAndRegionObserver
throw new CoprocessorException("AccessController not yet initialized");
}
List<UserPermission> perms = null;
if(request.getType() == AccessControlProtos.Permission.Type.Table) {
TableName table = null;
if (request.hasTableName()) {
table = ProtobufUtil.toTableName(request.getTableName());
}
if (request.getType() == AccessControlProtos.Permission.Type.Table) {
final TableName table = request.hasTableName() ?
ProtobufUtil.toTableName(request.getTableName()) : null;
requirePermission("userPermissions", table, null, null, Action.ADMIN);
perms = AccessControlLists.getUserTablePermissions(
regionEnv.getConfiguration(), table);
perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
@Override
public List<UserPermission> run() throws Exception {
return AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), table);
}
});
} else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
perms = AccessControlLists.getUserNamespacePermissions(
regionEnv.getConfiguration(), request.getNamespaceName().toStringUtf8());
final String namespace = request.getNamespaceName().toStringUtf8();
perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
@Override
public List<UserPermission> run() throws Exception {
return AccessControlLists.getUserNamespacePermissions(regionEnv.getConfiguration(),
namespace);
}
});
} else {
perms = AccessControlLists.getUserPermissions(
regionEnv.getConfiguration(), null);
perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
@Override
public List<UserPermission> run() throws Exception {
return AccessControlLists.getUserPermissions(regionEnv.getConfiguration(), null);
}
});
}
response = ResponseConverter.buildGetUserPermissionsResponse(perms);
} else {

View File

@ -27,6 +27,11 @@ import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
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.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.Permission.Action;
import org.apache.hadoop.hbase.testclassification.LargeTests;
@ -42,7 +47,11 @@ import org.junit.experimental.categories.Category;
@Category({SecurityTests.class, LargeTests.class})
public class TestAccessController2 extends SecureTestUtil {
private static final byte[] TEST_ROW = Bytes.toBytes("test");
private static final byte[] TEST_FAMILY = Bytes.toBytes("f");
private static final byte[] TEST_QUALIFIER = Bytes.toBytes("q");
private static final byte[] TEST_VALUE = Bytes.toBytes("value");
private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private static Configuration conf;
@ -101,4 +110,102 @@ public class TestAccessController2 extends SecureTestUtil {
assertTrue(perms.get(0).implies(Permission.Action.ADMIN));
}
@Test
public void testACLTableAccess() throws Exception {
final Configuration conf = TEST_UTIL.getConfiguration();
// Superuser
User superUser = User.createUserForTesting(conf, "admin", new String[] { "supergroup" });
// Global users
User globalRead = User.createUserForTesting(conf, "globalRead", new String[0]);
User globalWrite = User.createUserForTesting(conf, "globalWrite", new String[0]);
User globalCreate = User.createUserForTesting(conf, "globalCreate", new String[0]);
User globalAdmin = User.createUserForTesting(conf, "globalAdmin", new String[0]);
SecureTestUtil.grantGlobal(TEST_UTIL, globalRead.getShortName(), Action.READ);
SecureTestUtil.grantGlobal(TEST_UTIL, globalWrite.getShortName(), Action.WRITE);
SecureTestUtil.grantGlobal(TEST_UTIL, globalCreate.getShortName(), Action.CREATE);
SecureTestUtil.grantGlobal(TEST_UTIL, globalAdmin.getShortName(), Action.ADMIN);
// Namespace users
User nsRead = User.createUserForTesting(conf, "nsRead", new String[0]);
User nsWrite = User.createUserForTesting(conf, "nsWrite", new String[0]);
User nsCreate = User.createUserForTesting(conf, "nsCreate", new String[0]);
User nsAdmin = User.createUserForTesting(conf, "nsAdmin", new String[0]);
SecureTestUtil.grantOnNamespace(TEST_UTIL, nsRead.getShortName(),
TEST_TABLE.getTableName().getNamespaceAsString(), Action.READ);
SecureTestUtil.grantOnNamespace(TEST_UTIL, nsWrite.getShortName(),
TEST_TABLE.getTableName().getNamespaceAsString(), Action.WRITE);
SecureTestUtil.grantOnNamespace(TEST_UTIL, nsCreate.getShortName(),
TEST_TABLE.getTableName().getNamespaceAsString(), Action.CREATE);
SecureTestUtil.grantOnNamespace(TEST_UTIL, nsAdmin.getShortName(),
TEST_TABLE.getTableName().getNamespaceAsString(), Action.ADMIN);
// Table users
User tableRead = User.createUserForTesting(conf, "tableRead", new String[0]);
User tableWrite = User.createUserForTesting(conf, "tableWrite", new String[0]);
User tableCreate = User.createUserForTesting(conf, "tableCreate", new String[0]);
User tableAdmin = User.createUserForTesting(conf, "tableAdmin", new String[0]);
SecureTestUtil.grantOnTable(TEST_UTIL, tableRead.getShortName(),
TEST_TABLE.getTableName(), null, null, Action.READ);
SecureTestUtil.grantOnTable(TEST_UTIL, tableWrite.getShortName(),
TEST_TABLE.getTableName(), null, null, Action.WRITE);
SecureTestUtil.grantOnTable(TEST_UTIL, tableCreate.getShortName(),
TEST_TABLE.getTableName(), null, null, Action.CREATE);
SecureTestUtil.grantOnTable(TEST_UTIL, tableAdmin.getShortName(),
TEST_TABLE.getTableName(), null, null, Action.ADMIN);
// Write tests
AccessTestAction writeAction = new AccessTestAction() {
@Override
public Object run() throws Exception {
HTable t = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
try {
t.put(new Put(TEST_ROW).add(AccessControlLists.ACL_LIST_FAMILY, TEST_QUALIFIER,
TEST_VALUE));
return null;
} finally {
t.close();
}
}
};
// All writes to ACL table denied except for GLOBAL WRITE permission and superuser
verifyDenied(writeAction, globalAdmin, globalCreate, globalRead);
verifyDenied(writeAction, nsAdmin, nsCreate, nsRead, nsWrite);
verifyDenied(writeAction, tableAdmin, tableCreate, tableRead, tableWrite);
verifyAllowed(writeAction, superUser, globalWrite);
// Read tests
AccessTestAction scanAction = new AccessTestAction() {
@Override
public Object run() throws Exception {
HTable t = new HTable(conf, AccessControlLists.ACL_TABLE_NAME);
try {
ResultScanner s = t.getScanner(new Scan());
try {
for (Result r = s.next(); r != null; r = s.next()) {
// do nothing
}
} finally {
s.close();
}
return null;
} finally {
t.close();
}
}
};
// All reads from ACL table denied except for GLOBAL READ and superuser
verifyDenied(scanAction, globalAdmin, globalCreate, globalWrite);
verifyDenied(scanAction, nsCreate, nsAdmin, nsRead, nsWrite);
verifyDenied(scanAction, tableCreate, tableAdmin, tableRead, tableWrite);
verifyAllowed(scanAction, superUser, globalRead);
}
}