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:
parent
caf2c0c67a
commit
ca6c71acd3
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue