HBASE-5371 Introduce AccessControllerProtocol.checkPermissions(Permission[] permissons) API (Enis Soztutar)

git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1244623 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Zhihong Yu 2012-02-15 18:13:28 +00:00
parent caf2c0c67a
commit ca6c71acd3
3 changed files with 290 additions and 14 deletions

View File

@ -14,17 +14,22 @@
package org.apache.hadoop.hbase.security.access;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.CoprocessorEnvironment;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
@ -52,8 +57,11 @@ import org.apache.hadoop.hbase.security.AccessDeniedException;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
import java.util.*;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Provides basic authorization checks for data access and administrative
@ -155,7 +163,7 @@ public class AccessController extends BaseRegionObserver
/**
* Version number for AccessControllerProtocol
*/
private static final long PROTOCOL_VERSION = 1L;
private static final long PROTOCOL_VERSION = 2L;
TableAuthManager authManager = null;
@ -958,6 +966,40 @@ public class AccessController extends BaseRegionObserver
}
}
@Override
public void checkPermissions(Permission[] permissions) throws IOException {
byte[] tableName = regionEnv.getRegion().getTableDesc().getName();
for (Permission permission : permissions) {
if (permission instanceof TablePermission) {
TablePermission tperm = (TablePermission) permission;
for (Permission.Action action : permission.getActions()) {
if (!Arrays.equals(tperm.getTable(), tableName)) {
throw new CoprocessorException(AccessController.class, String.format("This method "
+ "can only execute at the table specified in TablePermission. " +
"Table of the region:%s , requested table:%s", Bytes.toString(tableName),
Bytes.toString(tperm.getTable())));
}
HashMap<byte[], Set<byte[]>> familyMap = Maps.newHashMapWithExpectedSize(1);
if (tperm.getFamily() != null) {
if (tperm.getQualifier() != null) {
familyMap.put(tperm.getFamily(), Sets.newHashSet(tperm.getQualifier()));
} else {
familyMap.put(tperm.getFamily(), null);
}
}
requirePermission(action, regionEnv, familyMap);
}
} else {
for (Permission.Action action : permission.getActions()) {
requirePermission(action);
}
}
}
}
@Override
public long getProtocolVersion(String protocol, long clientVersion) throws IOException {
return PROTOCOL_VERSION;

View File

@ -18,15 +18,19 @@
package org.apache.hadoop.hbase.security.access;
import org.apache.hadoop.hbase.ipc.CoprocessorProtocol;
import java.io.IOException;
import java.util.List;
import org.apache.hadoop.hbase.ipc.CoprocessorProtocol;
/**
* A custom protocol defined for maintaining and querying access control lists.
*/
public interface AccessControllerProtocol extends CoprocessorProtocol {
/* V2: Added {@link #checkPermissions(Permission...)}) */
public static final long VERSION = 2L;
/**
* Grants the given user or group the privilege to perform the given actions
* over the specified scope contained in {@link TablePermission}
@ -65,4 +69,18 @@ public interface AccessControllerProtocol extends CoprocessorProtocol {
*/
public List<UserPermission> getUserPermissions(byte[] tableName)
throws IOException;
/**
* Checks whether the given Permissions will pass the access checks for the
* current user. Global permissions can be checked from the -acl- table
* or any other table, however TablePermissions can only be checked by
* the table's regions. If access control checks fail this method throws
* AccessDeniedException.
* @param permissions to check for. Permission subclasses can be used
* to do more specific checks at the table/family/column level.
* @throws IOException if there is an error checking the permissions
*/
public void checkPermissions(Permission[] permissions)
throws IOException;
}

View File

@ -18,9 +18,14 @@
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.*;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -32,7 +37,17 @@ 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.*;
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;
@ -43,8 +58,6 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Performs authorization checks for common operations, according to different
* levels of authorized users.
@ -126,6 +139,13 @@ public class TestAccessController {
}
}
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 {
@ -151,6 +171,13 @@ public class TestAccessController {
}
}
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() {
@ -977,4 +1004,193 @@ public class TestAccessController {
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(Bytes.toBytes(user.getShortName()), new TablePermission(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
}
}
}