From 9dd208ff91ed33bfdeb2dcc7e19bc0b8558615ce Mon Sep 17 00:00:00 2001 From: Andrew Kyle Purtell Date: Tue, 26 Nov 2013 23:33:31 +0000 Subject: [PATCH] HBASE-7662. [Per-KV security] Per cell ACLs stored in tags git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1545882 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/hadoop/hbase/client/Get.java | 20 +- .../apache/hadoop/hbase/client/Mutation.java | 55 ++ .../org/apache/hadoop/hbase/client/Query.java | 80 +++ .../org/apache/hadoop/hbase/client/Scan.java | 9 +- .../hadoop/hbase/protobuf/ProtobufUtil.java | 72 ++- .../access/AccessControlConstants.java | 39 ++ .../security/access/AccessControlFilter.java | 24 +- .../security/access/AccessControlLists.java | 39 +- .../security/access/AccessController.java | 487 ++++++++++------ .../security/access/TableAuthManager.java | 76 ++- .../hbase/security/access/SecureTestUtil.java | 2 + .../access/TestAccessControlFilter.java | 103 ++-- .../security/access/TestAccessController.java | 521 ++++++++++++++---- 13 files changed, 1144 insertions(+), 383 deletions(-) create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlConstants.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Get.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Get.java index 316a12f166d..68c4a93019a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Get.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Get.java @@ -69,7 +69,6 @@ public class Get extends Query private boolean cacheBlocks = true; private int storeLimit = -1; private int storeOffset = 0; - private Filter filter = null; private TimeRange tr = new TimeRange(); private boolean checkExistenceOnly = false; private boolean closestRowBefore = false; @@ -210,27 +209,14 @@ public class Get extends Query return this; } - /** - * Apply the specified server-side filter when performing the Get. - * Only {@link Filter#filterKeyValue(Cell)} is called AFTER all tests - * for ttl, column match, deletes and max versions have been run. - * @param filter filter to run on the server - * @return this for invocation chaining - */ + @Override public Get setFilter(Filter filter) { - this.filter = filter; + super.setFilter(filter); return this; } /* Accessors */ - /** - * @return Filter - */ - public Filter getFilter() { - return this.filter; - } - /** * Set whether blocks should be cached for this Get. *

@@ -429,4 +415,4 @@ public class Get extends Query // TODO: This is wrong. Can't have two gets the same just because on same row. return compareTo(other) == 0; } -} \ No newline at end of file +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Mutation.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Mutation.java index fed6abf5b1a..6d8be5f4db2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Mutation.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Mutation.java @@ -41,11 +41,15 @@ import org.apache.hadoop.hbase.Tag; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.io.HeapSize; import org.apache.hadoop.hbase.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.security.access.AccessControlConstants; +import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.security.visibility.CellVisibility; import org.apache.hadoop.hbase.security.visibility.VisibilityConstants; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.ClassSize; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataOutput; @@ -381,6 +385,57 @@ public abstract class Mutation extends OperationWithAttributes implements Row, C return ClassSize.align(heapsize); } + /** + * @return The serialized ACL for this operation, or null if none + */ + public byte[] getACL() { + return getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); + } + + /** + * @param user User short name + * @param perm Permissions for the user + */ + public void setACL(String user, Permission perms) { + setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL, + ProtobufUtil.toUsersAndPermissions(user, perms).toByteArray()); + } + + /** + * @param perms A map of permissions for a user or users + */ + public void setACL(Map perms) { + ListMultimap permMap = ArrayListMultimap.create(); + for (Map.Entry entry : perms.entrySet()) { + permMap.put(entry.getKey(), entry.getValue()); + } + setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL, + ProtobufUtil.toUsersAndPermissions(permMap).toByteArray()); + } + + /** + * @return true if ACLs should be evaluated on the cell level first + */ + public boolean getACLStrategy() { + byte[] bytes = getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY); + if (bytes != null) { + return Bytes.equals(bytes, AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY_CELL_FIRST); + } + return false; + } + + /** + * @param cellFirstStrategy true if ACLs should be evaluated on the cell + * level first, false if ACL should first be checked at the CF and table + * levels + */ + public void setACLStrategy(boolean cellFirstStrategy) { + if (cellFirstStrategy) { + setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY, + AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY_CELL_FIRST); + } + } + /** * Subclasses should override this method to add the heap size of their own fields. * @return the heap size to add (will be aligned). diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Query.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Query.java index 5f6aab216e1..660205b3906 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Query.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Query.java @@ -17,16 +17,45 @@ */ package org.apache.hadoop.hbase.client; +import java.util.Map; + import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.hbase.exceptions.DeserializationException; +import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.security.access.AccessControlConstants; +import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.security.visibility.Authorizations; import org.apache.hadoop.hbase.security.visibility.VisibilityConstants; +import org.apache.hadoop.hbase.util.Bytes; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; @InterfaceAudience.Public @InterfaceStability.Evolving public abstract class Query extends OperationWithAttributes { + protected Filter filter = null; + + /** + * @return Filter + */ + public Filter getFilter() { + return filter; + } + + /** + * Apply the specified server-side filter when performing the Query. + * Only {@link Filter#filterKeyValue(Cell)} is called AFTER all tests + * for ttl, column match, deletes and max versions have been run. + * @param filter filter to run on the server + * @return this for invocation chaining + */ + public Query setFilter(Filter filter) { + this.filter = filter; + return this; + } /** * Sets the authorizations to be used by this Query @@ -46,4 +75,55 @@ public abstract class Query extends OperationWithAttributes { if (authorizationsBytes == null) return null; return ProtobufUtil.toAuthorizations(authorizationsBytes); } + + /** + * @return The serialized ACL for this operation, or null if none + */ + public byte[] getACL() { + return getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); + } + + /** + * @param user User short name + * @param perm Permissions for the user + */ + public void setACL(String user, Permission perms) { + setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL, + ProtobufUtil.toUsersAndPermissions(user, perms).toByteArray()); + } + + /** + * @param perms A map of permissions for a user or users + */ + public void setACL(Map perms) { + ListMultimap permMap = ArrayListMultimap.create(); + for (Map.Entry entry : perms.entrySet()) { + permMap.put(entry.getKey(), entry.getValue()); + } + setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL, + ProtobufUtil.toUsersAndPermissions(permMap).toByteArray()); + } + + /** + * @return true if ACLs should be evaluated on the cell level first + */ + public boolean getACLStrategy() { + byte[] bytes = getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY); + if (bytes != null) { + return Bytes.equals(bytes, AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY_CELL_FIRST); + } + return false; + } + + /** + * @param cellFirstStrategy true if ACLs should be evaluated on the cell + * level first, false if ACL should first be checked at the CF and table + * levels + */ + public void setACLStrategy(boolean cellFirstStrategy) { + if (cellFirstStrategy) { + setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY, + AccessControlConstants.OP_ATTRIBUTE_ACL_STRATEGY_CELL_FIRST); + } + } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java index d28165ed9f2..5ea66193ead 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java @@ -110,7 +110,6 @@ public class Scan extends Query { private int caching = -1; private long maxResultSize = -1; private boolean cacheBlocks = true; - private Filter filter = null; private TimeRange tr = new TimeRange(); private Map> familyMap = new TreeMap>(Bytes.BYTES_COMPARATOR); @@ -404,13 +403,9 @@ public class Scan extends Query { this.maxResultSize = maxResultSize; } - /** - * Apply the specified server-side filter when performing the Scan. - * @param filter filter to run on the server - * @return this - */ + @Override public Scan setFilter(Filter filter) { - this.filter = filter; + super.setFilter(filter); return this; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java index 0c036518e88..41407011f29 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java @@ -1813,10 +1813,14 @@ public final class ProtobufUtil { AccessControlProtos.NamespacePermission.Builder builder = AccessControlProtos.NamespacePermission.newBuilder(); builder.setNamespaceName(ByteString.copyFromUtf8(tablePerm.getNamespace())); - for (Permission.Action a : perm.getActions()) { - builder.addAction(toPermissionAction(a)); - } + Permission.Action actions[] = perm.getActions(); + if (actions != null) { + for (Permission.Action a : actions) { + builder.addAction(toPermissionAction(a)); + } + } ret.setNamespacePermission(builder); + return ret.build(); } else if (tablePerm.hasTable()) { ret.setType(AccessControlProtos.Permission.Type.Table); @@ -1829,21 +1833,28 @@ public final class ProtobufUtil { if (tablePerm.hasQualifier()) { builder.setQualifier(ZeroCopyLiteralByteString.wrap(tablePerm.getQualifier())); } - for (Permission.Action a : perm.getActions()) { - builder.addAction(toPermissionAction(a)); + Permission.Action actions[] = perm.getActions(); + if (actions != null) { + for (Permission.Action a : actions) { + builder.addAction(toPermissionAction(a)); + } } ret.setTablePermission(builder); + return ret.build(); } - } else { - ret.setType(AccessControlProtos.Permission.Type.Global); + } - AccessControlProtos.GlobalPermission.Builder builder = - AccessControlProtos.GlobalPermission.newBuilder(); - for (Permission.Action a : perm.getActions()) { + ret.setType(AccessControlProtos.Permission.Type.Global); + + AccessControlProtos.GlobalPermission.Builder builder = + AccessControlProtos.GlobalPermission.newBuilder(); + Permission.Action actions[] = perm.getActions(); + if (actions != null) { + for (Permission.Action a: actions) { builder.addAction(toPermissionAction(a)); } - ret.setGlobalPermission(builder); } + ret.setGlobalPermission(builder); return ret.build(); } @@ -2552,4 +2563,43 @@ public final class ProtobufUtil { } return builder.build(); } + + public static AccessControlProtos.UsersAndPermissions toUsersAndPermissions(String user, + Permission perms) { + return AccessControlProtos.UsersAndPermissions.newBuilder() + .addUserPermissions(AccessControlProtos.UsersAndPermissions.UserPermissions.newBuilder() + .setUser(ByteString.copyFromUtf8(user)) + .addPermissions(toPermission(perms)) + .build()) + .build(); + } + + public static AccessControlProtos.UsersAndPermissions toUsersAndPermissions( + ListMultimap perms) { + AccessControlProtos.UsersAndPermissions.Builder builder = + AccessControlProtos.UsersAndPermissions.newBuilder(); + for (Map.Entry> entry : perms.asMap().entrySet()) { + AccessControlProtos.UsersAndPermissions.UserPermissions.Builder userPermBuilder = + AccessControlProtos.UsersAndPermissions.UserPermissions.newBuilder(); + userPermBuilder.setUser(ByteString.copyFromUtf8(entry.getKey())); + for (Permission perm: entry.getValue()) { + userPermBuilder.addPermissions(toPermission(perm)); + } + builder.addUserPermissions(userPermBuilder.build()); + } + return builder.build(); + } + + public static ListMultimap toUsersAndPermissions( + AccessControlProtos.UsersAndPermissions proto) { + ListMultimap result = ArrayListMultimap.create(); + for (AccessControlProtos.UsersAndPermissions.UserPermissions userPerms: + proto.getUserPermissionsList()) { + String user = userPerms.getUser().toStringUtf8(); + for (AccessControlProtos.Permission perm: userPerms.getPermissionsList()) { + result.put(user, toPermission(perm)); + } + } + return result; + } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlConstants.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlConstants.java new file mode 100644 index 00000000000..751c7443566 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlConstants.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +@InterfaceAudience.Public +@InterfaceStability.Evolving +public interface AccessControlConstants { + + // Operation attributes for cell level security + + /** Cell level ACL */ + public static final String OP_ATTRIBUTE_ACL = "acl"; + /** Cell level ACL evaluation strategy */ + public static final String OP_ATTRIBUTE_ACL_STRATEGY = "acl.strategy"; + /** Default cell ACL evaluation strategy: Table and CF first, then ACL */ + public static final byte[] OP_ATTRIBUTE_ACL_STRATEGY_DEFAULT = new byte[] { 0 }; + /** Alternate cell ACL evaluation strategy: Cell ACL first, then table and CF */ + public static final byte[] OP_ATTRIBUTE_ACL_STRATEGY_CELL_FIRST = new byte[] { 1 }; + +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java index 5e6cde22835..ab7da3f6f25 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java @@ -19,10 +19,8 @@ package org.apache.hadoop.hbase.security.access; import org.apache.hadoop.hbase.Cell; -import org.apache.hadoop.hbase.KeyValueUtil; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.exceptions.DeserializationException; -import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.filter.FilterBase; import org.apache.hadoop.hbase.security.User; @@ -46,6 +44,8 @@ class AccessControlFilter extends FilterBase { private TableAuthManager authManager; private TableName table; private User user; + private boolean isSystemTable; + private boolean cellFirstStrategy; /** * For Writable @@ -53,21 +53,27 @@ class AccessControlFilter extends FilterBase { AccessControlFilter() { } - AccessControlFilter(TableAuthManager mgr, User ugi, - TableName tableName) { + AccessControlFilter(TableAuthManager mgr, User ugi, TableName tableName, + boolean cellFirstStrategy) { authManager = mgr; table = tableName; user = ugi; + isSystemTable = tableName.isSystemTable(); + this.cellFirstStrategy = cellFirstStrategy; } @Override - public ReturnCode filterKeyValue(Cell c) { - // TODO go and redo auth manager to use Cell instead of KV. - KeyValue kv = KeyValueUtil.ensureKeyValue(c); - if (authManager.authorize(user, table, kv, TablePermission.Action.READ)) { + public ReturnCode filterKeyValue(Cell cell) { + if (isSystemTable) { return ReturnCode.INCLUDE; } - return ReturnCode.NEXT_COL; + if (authManager.authorize(user, table, cell, cellFirstStrategy, Permission.Action.READ)) { + return ReturnCode.INCLUDE; + } + // Before per cell ACLs we used to return the NEXT_COL hint, but we can + // no longer do that since, given the possibility of per cell ACLs + // anywhere, we now need to examine all KVs with this filter. + return ReturnCode.SKIP; } /** diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java index 0d605489afa..1a3d703eecd 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java @@ -24,6 +24,7 @@ import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -38,9 +39,9 @@ import org.apache.hadoop.hbase.CellUtil; 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.Tag; import org.apache.hadoop.hbase.catalog.MetaReader; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; @@ -60,12 +61,14 @@ import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos; import org.apache.hadoop.hbase.regionserver.BloomType; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.InternalScanner; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.io.Text; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; import com.google.protobuf.InvalidProtocolBufferException; /** @@ -99,6 +102,8 @@ public class AccessControlLists { /** Column family used to store ACL grants */ public static final String ACL_LIST_FAMILY_STR = "l"; public static final byte[] ACL_LIST_FAMILY = Bytes.toBytes(ACL_LIST_FAMILY_STR); + /** KV tag to store per cell access control lists */ + public static final byte ACL_TAG_TYPE = (byte) 1; public static final char NAMESPACE_PREFIX = '@'; @@ -675,4 +680,36 @@ public class AccessControlLists { } return Arrays.copyOfRange(namespace, 1, namespace.length); } + + public static List getCellPermissionsForUser(User user, Cell cell) + throws IOException { + List results = Lists.newArrayList(); + byte[] tags = CellUtil.getTagArray(cell); + Iterator tagsIterator = CellUtil.tagsIterator(tags, 0, tags.length); + while (tagsIterator.hasNext()) { + Tag tag = tagsIterator.next(); + if (tag.getType() == ACL_TAG_TYPE) { + // Deserialize the table permissions from the KV + ListMultimap kvPerms = ProtobufUtil.toUsersAndPermissions( + AccessControlProtos.UsersAndPermissions.newBuilder().mergeFrom( + tag.getBuffer(), tag.getTagOffset(), tag.getTagLength()).build()); + // Are there permissions for this user? + List userPerms = kvPerms.get(user.getShortName()); + if (userPerms != null) { + results.addAll(userPerms); + } + // Are there permissions for any of the groups this user belongs to? + String groupNames[] = user.getGroupNames(); + if (groupNames != null) { + for (String group : groupNames) { + List groupPerms = kvPerms.get(GROUP_PREFIX + group); + if (results != null) { + results.addAll(groupPerms); + } + } + } + } + } + return results; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java index 8703e45f096..e759d13267f 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.net.InetAddress; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -28,12 +29,14 @@ 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; import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.CoprocessorEnvironment; +import org.apache.hadoop.hbase.DoNotRetryIOException; +import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HRegionInfo; @@ -44,16 +47,20 @@ import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableNotDisabledException; import org.apache.hadoop.hbase.TableNotFoundException; +import org.apache.hadoop.hbase.Tag; import org.apache.hadoop.hbase.client.Append; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Increment; +import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Query; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Durability; import org.apache.hadoop.hbase.coprocessor.*; import org.apache.hadoop.hbase.filter.CompareFilter; +import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.filter.FilterList; import org.apache.hadoop.hbase.filter.ByteArrayComparable; import org.apache.hadoop.hbase.ipc.RequestContext; @@ -78,11 +85,11 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; import org.apache.hadoop.hbase.util.Pair; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableSet; 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; import static org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; @@ -357,35 +364,6 @@ public class AccessController extends BaseRegionObserver } } - /** - * Authorizes that the current user has any of the given permissions for the - * given table, column family and column qualifier. - * @param namespace - * @throws IOException if obtaining the current user fails - * @throws AccessDeniedException if user has no authorization - */ - private void requirePermission(String request, String namespace, - Action... permissions) throws IOException { - User user = getActiveUser(); - AuthResult result = null; - - for (Action permission : permissions) { - if (authManager.authorize(user, namespace, permission)) { - result = AuthResult.allow(request, "Table permission granted", user, - permission, namespace); - break; - } else { - // rest of the world - result = AuthResult.deny(request, "Insufficient permissions", user, - permission, namespace); - } - } - logResult(result); - if (!result.isAllowed()) { - throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); - } - } - /** * Authorizes that the current user has global privileges for the given action. * @param perm The action being requested @@ -462,48 +440,186 @@ public class AccessController extends BaseRegionObserver } } - /** - * Returns true if the current user is allowed the given action - * over at least one of the column qualifiers in the given column families. - */ - private boolean hasFamilyQualifierPermission(User user, - Permission.Action perm, - RegionCoprocessorEnvironment env, - Map> familyMap) - throws IOException { - HRegionInfo hri = env.getRegion().getRegionInfo(); - TableName tableName = hri.getTable(); + private void requireCoveringPermission(String request, RegionCoprocessorEnvironment e, + byte[] row, Map> familyMap, long timestamp, + boolean allVersions, Action...actions) throws IOException { + User user = getActiveUser(); - if (user == null) { - return false; + // First check table or CF level permissions, if they grant access we can + // early out before needing to enumerate over per KV perms. + + List cellCheckActions = Lists.newArrayList(); + // TODO: permissionGranted should support checking multiple actions or + // we should convert actions into a bitmap and pass that around. See + // HBASE-7123. + AuthResult results[] = new AuthResult[actions.length]; + for (int i = 0; i < actions.length; i++) { + results[i] = permissionGranted(request, user, actions[i], e, familyMap); + if (!results[i].isAllowed()) { + if (LOG.isTraceEnabled()) { + LOG.trace("Got " + results[i] + ", added to cellCheckActions"); + } + cellCheckActions.add(actions[i]); + } + } + // If all permissions checks passed, we can early out + if (cellCheckActions.isEmpty()) { + if (LOG.isTraceEnabled()) { + LOG.trace("All permissions checks passed, we can early out"); + } + for (int i = 0; i < results.length; i++) { + logResult(results[i]); + } + return; } - if (familyMap != null && familyMap.size() > 0) { - // at least one family must be allowed - for (Map.Entry> family : - familyMap.entrySet()) { - if (family.getValue() != null && !family.getValue().isEmpty()) { - for (byte[] qualifier : family.getValue()) { - if (authManager.matchPermission(user, tableName, - family.getKey(), qualifier, perm)) { - return true; - } - } + // Table or CF permissions do not allow, enumerate the covered KVs. We + // can stop at the first which does not grant access. + + Get get = new Get(row); + if (timestamp != HConstants.LATEST_TIMESTAMP) get.setTimeStamp(timestamp); + get.setMaxResultsPerColumnFamily(1); // Hold down memory use on wide rows + if (allVersions) { + get.setMaxVersions(); + } else { + get.setMaxVersions(1); + } + for (Map.Entry> entry: familyMap.entrySet()) { + byte[] col = entry.getKey(); + // TODO: HBASE-7114 could possibly unify the collection type in family + // maps so we would not need to do this + if (entry.getValue() instanceof Set) { + Set set = (Set)entry.getValue(); + if (set == null || set.isEmpty()) { + get.addFamily(col); } else { - if (authManager.matchPermission(user, tableName, family.getKey(), - perm)) { - return true; + for (byte[] qual: set) { + get.addColumn(col, qual); } } + } else if (entry.getValue() instanceof List) { + List list = (List)entry.getValue(); + if (list == null || list.isEmpty()) { + get.addFamily(col); + } else { + for (Cell cell: list) { + get.addColumn(col, CellUtil.cloneQualifier(cell)); + } + } + } else { + throw new RuntimeException("Unhandled collection type " + + entry.getValue().getClass().getName()); } - } else if (LOG.isDebugEnabled()) { - LOG.debug("Empty family map passed for permission check"); + } + if (LOG.isTraceEnabled()) { + LOG.trace("Scanning for cells with " + get); + } + RegionScanner scanner = getRegion(e).getScanner(new Scan(get)); + List cells = Lists.newArrayList(); + int numCells = 0; + try { + boolean more = false; + do { + cells.clear(); + more = scanner.next(cells); + for (Cell cell: cells) { + if (LOG.isTraceEnabled()) { + LOG.trace("Found cell " + cell); + } + for (Action action: cellCheckActions) { + // Are there permissions for this user for the cell? + if (!authManager.authorize(user, getTableName(e), cell, false, action)) { + AuthResult authResult = AuthResult.deny(request, "Insufficient permissions", + user, action, getTableName(e), CellUtil.cloneFamily(cell), + CellUtil.cloneQualifier(cell)); + logResult(authResult); + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); + } + } + numCells++; + } + } while (more); + } catch (AccessDeniedException ex) { + throw ex; + } catch (IOException ex) { + LOG.error("Exception while getting cells to calculate covering permission", ex); + } finally { + scanner.close(); } - return false; + // If there were no cells to check, throw the ADE + if (numCells < 1) { + if (LOG.isTraceEnabled()) { + LOG.trace("No cells found with scan"); + } + AuthResult authResult = AuthResult.deny(request, "Insufficient permissions", + user, cellCheckActions.get(0), getTableName(e), familyMap); + logResult(authResult); + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); + } + + // Log that authentication succeeded. We need to trade off logging maybe + // thousands of fine grained decisions with providing detail. + for (byte[] family: get.getFamilyMap().keySet()) { + for (Action action: actions) { + logResult(AuthResult.allow(request, "Permission granted", user, action, + getTableName(e), family, null)); + } + } + } + + private void addCellPermissions(final byte[] perms, Map> familyMap) { + // Iterate over the entries in the familyMap, replacing the cells therein + // with new cells including the ACL data + for (Map.Entry> e: familyMap.entrySet()) { + List newCells = Lists.newArrayList(); + for (Cell cell: e.getValue()) { + List tags = Lists.newArrayList(new Tag(AccessControlLists.ACL_TAG_TYPE, perms)); + byte[] tagBytes = CellUtil.getTagArray(cell); + Iterator tagIterator = CellUtil.tagsIterator(tagBytes, 0, tagBytes.length); + while (tagIterator.hasNext()) { + tags.add(tagIterator.next()); + } + // Ensure KeyValue so we can do a scatter gather copy. This is only a win if the + // incoming cell type is actually KeyValue. + KeyValue kv = KeyValueUtil.ensureKeyValue(cell); + byte[] bytes = kv.getBuffer(); + newCells.add( + new KeyValue(bytes, kv.getRowOffset(), kv.getRowLength(), + bytes, kv.getFamilyOffset(), kv.getFamilyLength(), + bytes, kv.getQualifierOffset(), kv.getQualifierLength(), + kv.getTimestamp(), KeyValue.Type.codeToType(kv.getTypeByte()), + bytes, kv.getValueOffset(), kv.getValueLength(), + tags)); + } + // This is supposed to be safe, won't CME + e.setValue(newCells); + } + } + + private void internalPreRead(final ObserverContext c, + final Query query) throws IOException { + TableName tableName = getTableName(c.getEnvironment()); + User activeUser = getActiveUser(); + Filter filter = query.getFilter(); + boolean cellFirstStrategy = query.getACLStrategy(); + // Don't wrap an AccessControlFilter + if (filter != null && filter instanceof AccessControlFilter) { + return; + } + Filter newFilter = (filter != null) + ? new FilterList(FilterList.Operator.MUST_PASS_ALL, + Lists.newArrayList( + new AccessControlFilter(authManager, activeUser, tableName, cellFirstStrategy), + filter)) + : new AccessControlFilter(authManager, activeUser, tableName, cellFirstStrategy); + query.setFilter(newFilter); } /* ---- MasterObserver implementation ---- */ + public void start(CoprocessorEnvironment env) throws IOException { ZooKeeperWatcher zk = null; @@ -581,11 +697,13 @@ public class AccessController extends BaseRegionObserver @Override public void preDeleteTableHandler(ObserverContext c, TableName tableName) throws IOException {} + @Override public void postDeleteTable(ObserverContext c, TableName tableName) throws IOException { AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName); } + @Override public void postDeleteTableHandler(ObserverContext c, TableName tableName) throws IOException {} @@ -625,9 +743,11 @@ public class AccessController extends BaseRegionObserver @Override public void preAddColumnHandler(ObserverContext c, TableName tableName, HColumnDescriptor column) throws IOException {} + @Override public void postAddColumn(ObserverContext c, TableName tableName, HColumnDescriptor column) throws IOException {} + @Override public void postAddColumnHandler(ObserverContext c, TableName tableName, HColumnDescriptor column) throws IOException {} @@ -641,14 +761,15 @@ public class AccessController extends BaseRegionObserver @Override public void preModifyColumnHandler(ObserverContext c, TableName tableName, HColumnDescriptor descriptor) throws IOException {} + @Override public void postModifyColumn(ObserverContext c, TableName tableName, HColumnDescriptor descriptor) throws IOException {} + @Override public void postModifyColumnHandler(ObserverContext c, TableName tableName, HColumnDescriptor descriptor) throws IOException {} - @Override public void preDeleteColumn(ObserverContext c, TableName tableName, byte[] col) throws IOException { @@ -658,12 +779,14 @@ public class AccessController extends BaseRegionObserver @Override public void preDeleteColumnHandler(ObserverContext c, TableName tableName, byte[] col) throws IOException {} + @Override public void postDeleteColumn(ObserverContext c, TableName tableName, byte[] col) throws IOException { AccessControlLists.removeTablePermissions(c.getEnvironment().getConfiguration(), tableName, col); } + @Override public void postDeleteColumnHandler(ObserverContext c, TableName tableName, byte[] col) throws IOException {} @@ -677,9 +800,11 @@ public class AccessController extends BaseRegionObserver @Override public void preEnableTableHandler(ObserverContext c, TableName tableName) throws IOException {} + @Override public void postEnableTable(ObserverContext c, TableName tableName) throws IOException {} + @Override public void postEnableTableHandler(ObserverContext c, TableName tableName) throws IOException {} @@ -697,9 +822,11 @@ public class AccessController extends BaseRegionObserver @Override public void preDisableTableHandler(ObserverContext c, TableName tableName) throws IOException {} + @Override public void postDisableTable(ObserverContext c, TableName tableName) throws IOException {} + @Override public void postDisableTableHandler(ObserverContext c, TableName tableName) throws IOException {} @@ -761,6 +888,7 @@ public class AccessController extends BaseRegionObserver requirePermission("balanceSwitch", Permission.Action.ADMIN); return newValue; } + @Override public void postBalanceSwitch(ObserverContext c, boolean oldValue, boolean newValue) throws IOException {} @@ -949,55 +1077,20 @@ public class AccessController extends BaseRegionObserver final byte [] row, final byte [] family, final Result result) throws IOException { assert family != null; - //noinspection PrimitiveArrayArgumentToVariableArgMethod - requirePermission("getClosestRowBefore", Permission.Action.READ, c.getEnvironment(), - makeFamilyMap(family, null)); + requireCoveringPermission("getClosestRowBefore", c.getEnvironment(), row, + makeFamilyMap(family, null), HConstants.LATEST_TIMESTAMP, false, Permission.Action.READ); } @Override public void preGetOp(final ObserverContext c, final Get get, final List result) throws IOException { - /* - if column family level checks fail, check for a qualifier level permission - in one of the families. If it is present, then continue with the AccessControlFilter. - */ - RegionCoprocessorEnvironment e = c.getEnvironment(); - User requestUser = getActiveUser(); - AuthResult authResult = permissionGranted("get", requestUser, - Permission.Action.READ, e, get.getFamilyMap()); - if (!authResult.isAllowed()) { - if (hasFamilyQualifierPermission(requestUser, - Permission.Action.READ, e, get.getFamilyMap())) { - TableName table = getTableName(e); - AccessControlFilter filter = new AccessControlFilter(authManager, - requestUser, table); - - // wrap any existing filter - if (get.getFilter() != null) { - FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL, - Lists.newArrayList(filter, get.getFilter())); - get.setFilter(wrapper); - } else { - get.setFilter(filter); - } - logResult(AuthResult.allow("get", "Access allowed with filter", requestUser, - Permission.Action.READ, authResult.getTableName(), get.getFamilyMap())); - } else { - logResult(authResult); - throw new AccessDeniedException("Insufficient permissions (table=" + - e.getRegion().getTableDesc().getTableName() + ", action=READ)"); - } - } else { - // log auth success - logResult(authResult); - } + internalPreRead(c, get); } @Override public boolean preExists(final ObserverContext c, final Get get, final boolean exists) throws IOException { - requirePermission("exists", Permission.Action.READ, c.getEnvironment(), - get.getFamilyMap()); + internalPreRead(c, get); return exists; } @@ -1005,8 +1098,18 @@ public class AccessController extends BaseRegionObserver public void prePut(final ObserverContext c, final Put put, final WALEdit edit, final Durability durability) throws IOException { - requirePermission("put", Permission.Action.WRITE, c.getEnvironment(), - put.getFamilyCellMap()); + // Require WRITE permission to the table, CF, or top visible value, if any. + // NOTE: We don't need to check the permissions for any earlier Puts + // because we treat the ACLs in each Put as timestamped like any other + // HBase value. A new ACL in a new Put applies to that Put. It doesn't + // change the ACL of any previous Put. This allows simple evolution of + // security policy over time without requiring expensive updates. + requireCoveringPermission("put", c.getEnvironment(), put.getRow(), + put.getFamilyCellMap(), put.getTimeStamp(), false, Permission.Action.WRITE); + byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); + if (bytes != null) { + addCellPermissions(bytes, put.getFamilyCellMap()); + } } @Override @@ -1021,8 +1124,17 @@ public class AccessController extends BaseRegionObserver public void preDelete(final ObserverContext c, final Delete delete, final WALEdit edit, final Durability durability) throws IOException { - requirePermission("delete", Permission.Action.WRITE, c.getEnvironment(), - delete.getFamilyCellMap()); + // An ACL on a delete is useless, we shouldn't allow it + if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) { + throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString()); + } + // Require WRITE permissions on all cells covered by the delete. Unlike + // for Puts we need to check all visible prior versions, because a major + // compaction could remove them. If the user doesn't have permission to + // overwrite any of the visible versions ('visible' defined as not covered + // by a tombstone already) then we have to disallow this operation. + requireCoveringPermission("delete", c.getEnvironment(), delete.getRow(), + delete.getFamilyCellMap(), delete.getTimeStamp(), true, Action.WRITE); } @Override @@ -1040,9 +1152,14 @@ public class AccessController extends BaseRegionObserver final CompareFilter.CompareOp compareOp, final ByteArrayComparable comparator, final Put put, final boolean result) throws IOException { - Map> familyMap = makeFamilyMap(family, qualifier); - requirePermission("checkAndPut", Permission.Action.READ, c.getEnvironment(), familyMap); - requirePermission("checkAndPut", Permission.Action.WRITE, c.getEnvironment(), familyMap); + // Require READ and WRITE permissions on the table, CF, and KV to update + requireCoveringPermission("checkAndPut", c.getEnvironment(), row, + makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false, + Action.READ, Action.WRITE); + byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); + if (bytes != null) { + addCellPermissions(bytes, put.getFamilyCellMap()); + } return result; } @@ -1052,9 +1169,16 @@ public class AccessController extends BaseRegionObserver final CompareFilter.CompareOp compareOp, final ByteArrayComparable comparator, final Delete delete, final boolean result) throws IOException { - Map> familyMap = makeFamilyMap(family, qualifier); - requirePermission("checkAndDelete", Permission.Action.READ, c.getEnvironment(), familyMap); - requirePermission("checkAndDelete", Permission.Action.WRITE, c.getEnvironment(), familyMap); + // An ACL on a delete is useless, we shouldn't allow it + if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) { + throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " + + delete.toString()); + } + // Require READ and WRITE permissions on the table, CF, and the KV covered + // by the delete + requireCoveringPermission("checkAndDelete", c.getEnvironment(), row, + makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false, + Action.READ, Action.WRITE); return result; } @@ -1063,15 +1187,25 @@ public class AccessController extends BaseRegionObserver final byte [] row, final byte [] family, final byte [] qualifier, final long amount, final boolean writeToWAL) throws IOException { - Map> familyMap = makeFamilyMap(family, qualifier); - requirePermission("incrementColumnValue", Permission.Action.WRITE, c.getEnvironment(), familyMap); + // Require WRITE permission to the table, CF, and the KV to be replaced by the + // incremented value + requireCoveringPermission("incrementColumnValue", c.getEnvironment(), row, + makeFamilyMap(family, qualifier), HConstants.LATEST_TIMESTAMP, false, + Action.WRITE); return -1; } @Override public Result preAppend(ObserverContext c, Append append) throws IOException { - requirePermission("append", Permission.Action.WRITE, c.getEnvironment(), append.getFamilyCellMap()); + // Require WRITE permission to the table, CF, and the KV to be appended + requireCoveringPermission("append", c.getEnvironment(), append.getRow(), + append.getFamilyCellMap(), append.getTimeStamp(), false, + Action.WRITE); + byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); + if (bytes != null) { + addCellPermissions(bytes, append.getFamilyCellMap()); + } return null; } @@ -1079,59 +1213,87 @@ public class AccessController extends BaseRegionObserver public Result preIncrement(final ObserverContext c, final Increment increment) throws IOException { - // Create a map of family to qualifiers. - Map> familyMap = new TreeMap>(Bytes.BYTES_COMPARATOR); - for (Map.Entry> entry: increment.getFamilyCellMap().entrySet()) { - Set qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR); - for (Cell cell: entry.getValue()) { - KeyValue kv = KeyValueUtil.ensureKeyValue(cell); - qualifiers.add(kv.getQualifier()); - } - familyMap.put(entry.getKey(), qualifiers); + // Require WRITE permission to the table, CF, and the KV to be replaced by + // the incremented value + requireCoveringPermission("increment", c.getEnvironment(), increment.getRow(), + increment.getFamilyCellMap(), increment.getTimeRange().getMax(), false, + Action.WRITE); + byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); + if (bytes != null) { + addCellPermissions(bytes, increment.getFamilyCellMap()); } - requirePermission("increment", Permission.Action.WRITE, c.getEnvironment(), familyMap); return null; } + @Override + public Cell postMutationBeforeWAL(ObserverContext ctx, + MutationType opType, Mutation mutation, Cell oldCell, Cell newCell) throws IOException { + + List tags = Lists.newArrayList(); + ListMultimap perms = ArrayListMultimap.create(); + if (oldCell != null) { + byte[] tagBytes = CellUtil.getTagArray(oldCell); + Iterator tagIterator = CellUtil.tagsIterator(tagBytes, 0, tagBytes.length); + while (tagIterator.hasNext()) { + Tag tag = tagIterator.next(); + if (tag.getType() != AccessControlLists.ACL_TAG_TYPE) { + if (LOG.isTraceEnabled()) { + LOG.trace("Carrying forward tag from " + oldCell + ": type " + tag.getType() + + " length " + tag.getValue().length); + } + tags.add(tag); + } else { + ListMultimap kvPerms = ProtobufUtil.toUsersAndPermissions( + AccessControlProtos.UsersAndPermissions.newBuilder().mergeFrom( + tag.getBuffer(), tag.getTagOffset(), tag.getTagLength()).build()); + perms.putAll(kvPerms); + } + } + } + + // Do we have an ACL on the operation? + byte[] aclBytes = mutation.getACL(); + if (aclBytes != null) { + // Yes, use it + tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE, aclBytes)); + } else { + // No, use what we carried forward + if (perms != null) { + // TODO: If we collected ACLs from more than one tag we may have a + // List of size > 1, this can be collapsed into a single + // Permission + if (LOG.isTraceEnabled()) { + LOG.trace("Carrying forward ACLs from " + oldCell + ": " + perms); + } + tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE, + ProtobufUtil.toUsersAndPermissions(perms).toByteArray())); + } + } + + // If we have no tags to add, just return + if (tags.isEmpty()) { + return newCell; + } + + // We need to create another KV, unfortunately, because the current new KV + // has no space for tags + KeyValue newKv = KeyValueUtil.ensureKeyValue(newCell); + byte[] bytes = newKv.getBuffer(); + KeyValue rewriteKv = new KeyValue(bytes, newKv.getRowOffset(), newKv.getRowLength(), + bytes, newKv.getFamilyOffset(), newKv.getFamilyLength(), + bytes, newKv.getQualifierOffset(), newKv.getQualifierLength(), + newKv.getTimestamp(), KeyValue.Type.codeToType(newKv.getTypeByte()), + bytes, newKv.getValueOffset(), newKv.getValueLength(), + tags); + // Preserve mvcc data + rewriteKv.setMvccVersion(newKv.getMvccVersion()); + return rewriteKv; + } + @Override public RegionScanner preScannerOpen(final ObserverContext c, final Scan scan, final RegionScanner s) throws IOException { - /* - if column family level checks fail, check for a qualifier level permission - in one of the families. If it is present, then continue with the AccessControlFilter. - */ - RegionCoprocessorEnvironment e = c.getEnvironment(); - User user = getActiveUser(); - AuthResult authResult = permissionGranted("scannerOpen", user, Permission.Action.READ, e, - scan.getFamilyMap()); - if (!authResult.isAllowed()) { - if (hasFamilyQualifierPermission(user, Permission.Action.READ, e, - scan.getFamilyMap())) { - TableName table = getTableName(e); - AccessControlFilter filter = new AccessControlFilter(authManager, - user, table); - - // wrap any existing filter - if (scan.hasFilter()) { - FilterList wrapper = new FilterList(FilterList.Operator.MUST_PASS_ALL, - Lists.newArrayList(filter, scan.getFilter())); - scan.setFilter(wrapper); - } else { - scan.setFilter(filter); - } - logResult(AuthResult.allow("scannerOpen", "Access allowed with filter", user, - Permission.Action.READ, authResult.getTableName(), scan.getFamilyMap())); - } else { - // no table/family level perms and no qualifier level perms, reject - logResult(authResult); - throw new AccessDeniedException("Insufficient permissions for user '"+ - (user != null ? user.getShortName() : "null")+"' "+ - "for scanner open on table " + getTableName(e)); - } - } else { - // log success - logResult(authResult); - } + internalPreRead(c, scan); return s; } @@ -1423,6 +1585,10 @@ public class AccessController extends BaseRegionObserver return AccessControlProtos.AccessControlService.newReflectiveService(this); } + private HRegion getRegion(RegionCoprocessorEnvironment e) { + return e.getRegion(); + } + private TableName getTableName(RegionCoprocessorEnvironment e) { HRegion region = e.getRegion(); TableName tableName = null; @@ -1519,4 +1685,5 @@ public class AccessController extends BaseRegionObserver public void postGetTableDescriptors(ObserverContext ctx, List descriptors) throws IOException { } + } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java index 9b24ed9de61..d81f28ae862 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java @@ -24,9 +24,10 @@ import com.google.common.collect.Lists; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.exceptions.DeserializationException; -import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.UserProvider; import org.apache.hadoop.hbase.util.Bytes; @@ -342,42 +343,65 @@ public class TableAuthManager { return false; } - public boolean authorize(User user, TableName table, KeyValue kv, - Permission.Action action) { - PermissionCache tablePerms = tableCache.get(table); - if (tablePerms != null) { - List userPerms = tablePerms.getUser(user.getShortName()); - if (authorize(userPerms, table, kv, action)) { - return true; + private boolean checkCellPermissions(User user, Cell cell, Permission.Action action) { + try { + List perms = AccessControlLists.getCellPermissionsForUser(user, cell); + if (LOG.isTraceEnabled()) { + LOG.trace("Found perms for user " + user.getShortName() + " in cell " + + cell + ": " + perms); } + for (Permission p: perms) { + if (p.implies(action)) { + return true; + } + } + } catch (IOException e) { + // We failed to parse the KV tag + LOG.error("Failed parse of ACL tag in cell " + cell); + // Fall through to check with the table and CF perms we were able + // to collect regardless + } + return false; + } - String[] groupNames = user.getGroupNames(); - if (groupNames != null) { - for (String group : groupNames) { - List groupPerms = tablePerms.getGroup(group); - if (authorize(groupPerms, table, kv, action)) { - return true; - } + private boolean checkTableColumnPermissions(User user, TableName table, Cell cell, + Permission.Action action) { + // TODO: Do not clone here + byte[] family = CellUtil.cloneFamily(cell); + byte[] qualifier = CellUtil.cloneQualifier(cell); + // User is authorized at table or CF level + if (authorizeUser(user.getShortName(), table, family, qualifier, action)) { + return true; + } + String groupNames[] = user.getGroupNames(); + if (groupNames != null) { + for (String group: groupNames) { + // TODO: authorizeGroup should check qualifier too? + // Group is authorized at table or CF level + if (authorizeGroup(group, table, family, action)) { + return true; } } } return false; } - private boolean authorize(List perms, TableName table, KeyValue kv, + /** + * Authorize a user for a given KV. This is called from AccessControlFilter. + */ + public boolean authorize(User user, TableName table, Cell cell, boolean cellFirstStrategy, Permission.Action action) { - if (perms != null) { - for (TablePermission p : perms) { - if (p.implies(table, kv, action)) { - return true; - } + if (cellFirstStrategy) { + if (checkCellPermissions(user, cell, action)) { + return true; } - } else if (LOG.isDebugEnabled()) { - LOG.debug("No permissions for authorize() check, table=" + - table); + return checkTableColumnPermissions(user, table, cell, action); + } else { + if (checkTableColumnPermissions(user, table, cell, action)) { + return true; + } + return checkCellPermissions(user, cell, action); } - - return false; } public boolean authorize(User user, String namespace, Permission.Action action) { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java index e7db7c5c16f..9cd630d240f 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java @@ -50,6 +50,8 @@ public class SecureTestUtil { // add the process running user to superusers String currentUser = User.getCurrent().getName(); conf.set("hbase.superuser", "admin,"+currentUser); + // Need HFile V3 for tags for security features + conf.setInt("hfile.format.version", 3); } public void verifyAllowed(User user, PrivilegedExceptionAction... actions) throws Exception { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessControlFilter.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessControlFilter.java index 0f4360b68eb..1f21fb62bc6 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessControlFilter.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessControlFilter.java @@ -21,7 +21,6 @@ package org.apache.hadoop.hbase.security.access; 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.security.PrivilegedExceptionAction; @@ -29,8 +28,6 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.LargeTests; @@ -42,7 +39,6 @@ import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; -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; @@ -58,7 +54,6 @@ import com.google.protobuf.BlockingRpcChannel; @Category(LargeTests.class) public class TestAccessControlFilter { @Rule public TestName name = new TestName(); - private static Log LOG = LogFactory.getLog(TestAccessControlFilter.class); private static HBaseTestingUtility TEST_UTIL; private static User ADMIN; @@ -116,15 +111,19 @@ public class TestAccessControlFilter { public Object run() throws Exception { HTable aclmeta = new HTable(TEST_UTIL.getConfiguration(), AccessControlLists.ACL_TABLE_NAME); - byte[] table = Bytes.toBytes(name.getMethodName()); - BlockingRpcChannel service = aclmeta.coprocessorService(table); - AccessControlService.BlockingInterface protocol = - AccessControlService.newBlockingStub(service); - ProtobufUtil.grant(protocol, READER.getShortName(), - TABLE, null, null, Permission.Action.READ); - ProtobufUtil.grant(protocol, LIMITED.getShortName(), - TABLE, FAMILY, PUBLIC_COL, Permission.Action.READ); - return null; + try { + byte[] table = Bytes.toBytes(name.getMethodName()); + BlockingRpcChannel service = aclmeta.coprocessorService(table); + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(service); + ProtobufUtil.grant(protocol, READER.getShortName(), + TABLE, null, null, Permission.Action.READ); + ProtobufUtil.grant(protocol, LIMITED.getShortName(), + TABLE, FAMILY, PUBLIC_COL, Permission.Action.READ); + return null; + } finally { + aclmeta.close(); + } } }); @@ -145,18 +144,22 @@ public class TestAccessControlFilter { // force a new RS connection conf.set("testkey", UUID.randomUUID().toString()); HTable t = new HTable(conf, TABLE); - ResultScanner rs = t.getScanner(new Scan()); - int rowcnt = 0; - for (Result r : rs) { - rowcnt++; - int rownum = Bytes.toInt(r.getRow()); - assertTrue(r.containsColumn(FAMILY, PRIVATE_COL)); - assertEquals("secret "+rownum, Bytes.toString(r.getValue(FAMILY, PRIVATE_COL))); - assertTrue(r.containsColumn(FAMILY, PUBLIC_COL)); - assertEquals("info "+rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL))); + try { + ResultScanner rs = t.getScanner(new Scan()); + int rowcnt = 0; + for (Result r : rs) { + rowcnt++; + int rownum = Bytes.toInt(r.getRow()); + assertTrue(r.containsColumn(FAMILY, PRIVATE_COL)); + assertEquals("secret "+rownum, Bytes.toString(r.getValue(FAMILY, PRIVATE_COL))); + assertTrue(r.containsColumn(FAMILY, PUBLIC_COL)); + assertEquals("info "+rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL))); + } + assertEquals("Expected 100 rows returned", 100, rowcnt); + return null; + } finally { + t.close(); } - assertEquals("Expected 100 rows returned", 100, rowcnt); - return null; } }); @@ -167,34 +170,46 @@ public class TestAccessControlFilter { // force a new RS connection conf.set("testkey", UUID.randomUUID().toString()); HTable t = new HTable(conf, TABLE); - ResultScanner rs = t.getScanner(new Scan()); - int rowcnt = 0; - for (Result r : rs) { - rowcnt++; - int rownum = Bytes.toInt(r.getRow()); - assertFalse(r.containsColumn(FAMILY, PRIVATE_COL)); - assertTrue(r.containsColumn(FAMILY, PUBLIC_COL)); - assertEquals("info " + rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL))); + try { + ResultScanner rs = t.getScanner(new Scan()); + int rowcnt = 0; + for (Result r : rs) { + rowcnt++; + int rownum = Bytes.toInt(r.getRow()); + assertFalse(r.containsColumn(FAMILY, PRIVATE_COL)); + assertTrue(r.containsColumn(FAMILY, PUBLIC_COL)); + assertEquals("info " + rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL))); + } + assertEquals("Expected 100 rows returned", 100, rowcnt); + return null; + } finally { + t.close(); } - assertEquals("Expected 100 rows returned", 100, rowcnt); - return null; } }); // test as user with no permission - DENIED.runAs(new PrivilegedExceptionAction(){ + DENIED.runAs(new PrivilegedExceptionAction(){ public Object run() throws Exception { + Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); + // force a new RS connection + conf.set("testkey", UUID.randomUUID().toString()); + HTable t = new HTable(conf, TABLE); try { - Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); - // force a new RS connection - conf.set("testkey", UUID.randomUUID().toString()); - HTable t = new HTable(conf, TABLE); ResultScanner rs = t.getScanner(new Scan()); - fail("Attempt to open scanner should have been denied"); - } catch (AccessDeniedException ade) { - // expected + int rowcnt = 0; + for (Result r : rs) { + rowcnt++; + int rownum = Bytes.toInt(r.getRow()); + assertFalse(r.containsColumn(FAMILY, PRIVATE_COL)); + assertTrue(r.containsColumn(FAMILY, PUBLIC_COL)); + assertEquals("info " + rownum, Bytes.toString(r.getValue(FAMILY, PUBLIC_COL))); + } + assertEquals("Expected 0 rows returned", 0, rowcnt); + return null; + } finally { + t.close(); } - return null; } }); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java index 2eac4fff1ba..04d0865b92d 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java @@ -38,6 +38,7 @@ import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.Coprocessor; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HColumnDescriptor; @@ -87,6 +88,8 @@ import org.apache.hadoop.hbase.security.access.Permission.Action; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.JVMClusterUtil; import org.apache.hadoop.hbase.util.TestTableName; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -95,6 +98,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import com.google.common.collect.Lists; import com.google.protobuf.BlockingRpcChannel; import com.google.protobuf.ServiceException; @@ -103,9 +107,15 @@ import com.google.protobuf.ServiceException; * levels of authorized users. */ @Category(LargeTests.class) -@SuppressWarnings("rawtypes") public class TestAccessController extends SecureTestUtil { private static final Log LOG = LogFactory.getLog(TestAccessController.class); + + static { + Logger.getLogger(AccessController.class).setLevel(Level.TRACE); + Logger.getLogger(AccessControlFilter.class).setLevel(Level.TRACE); + Logger.getLogger(TableAuthManager.class).setLevel(Level.TRACE); + } + @Rule public TestTableName TEST_TABLE = new TestTableName(); private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static Configuration conf; @@ -125,9 +135,10 @@ public class TestAccessController extends SecureTestUtil { // user with no permissions private static User USER_NONE; - private static TableName TEST_TABLE2 = - TableName.valueOf("testtable2"); + private static TableName TEST_TABLE2 = TableName.valueOf("testtable2"); private static byte[] TEST_FAMILY = Bytes.toBytes("f1"); + private static byte[] TEST_QUALIFIER = Bytes.toBytes("q1"); + private static byte[] TEST_ROW = Bytes.toBytes("r1"); private static MasterCoprocessorEnvironment CP_ENV; private static AccessController ACCESS_CONTROLLER; @@ -235,19 +246,50 @@ public class TestAccessController extends SecureTestUtil { assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size()); } - @Override - public void verifyAllowed(PrivilegedExceptionAction action, User... users) throws Exception { + /** + * An AccessTestAction performs an action that will be examined to confirm + * the results conform to expected access rights. + *

+ * To indicate an action was allowed, return null or a non empty list of + * KeyValues. + *

+ * To indicate the action was not allowed, either throw an AccessDeniedException + * or return an empty list of KeyValues. + */ + static interface AccessTestAction extends PrivilegedExceptionAction { } + + public void verifyAllowed(User user, AccessTestAction... actions) throws Exception { + for (AccessTestAction action : actions) { + try { + Object obj = user.runAs(action); + if (obj != null && obj instanceof List) { + List results = (List) obj; + if (results != null && results.isEmpty()) { + fail("Empty non null results from action for user '" + user.getShortName() + "'"); + } + } + } catch (AccessDeniedException ade) { + fail("Expected action to pass for user '" + user.getShortName() + "' but was denied"); + } + } + } + + public void verifyAllowed(AccessTestAction action, User... users) throws Exception { for (User user : users) { verifyAllowed(user, action); } } - @Override - public void verifyDenied(User user, PrivilegedExceptionAction... actions) throws Exception { - for (PrivilegedExceptionAction action : actions) { + public void verifyDenied(User user, AccessTestAction... actions) throws Exception { + for (AccessTestAction action : actions) { try { - user.runAs(action); - fail("Expected AccessDeniedException for user '" + user.getShortName() + "'"); + Object obj = user.runAs(action); + if (obj != null && obj instanceof List) { + List results = (List) obj; + if (results != null && !results.isEmpty()) { + fail("Expected no results for user '" + user.getShortName() + "'"); + } + } } catch (IOException e) { boolean isAccessDeniedException = false; if(e instanceof RetriesExhaustedWithDetailsException) { @@ -293,8 +335,7 @@ public class TestAccessController extends SecureTestUtil { } } - @Override - public void verifyDenied(PrivilegedExceptionAction action, User... users) throws Exception { + public void verifyDenied(AccessTestAction action, User... users) throws Exception { for (User user : users) { verifyDenied(user, action); } @@ -302,7 +343,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testTableCreate() throws Exception { - PrivilegedExceptionAction createTable = new PrivilegedExceptionAction() { + AccessTestAction createTable = new AccessTestAction() { @Override public Object run() throws Exception { HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("testnewtable")); @@ -321,7 +362,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testTableModify() throws Exception { - PrivilegedExceptionAction modifyTable = new PrivilegedExceptionAction() { + AccessTestAction modifyTable = new AccessTestAction() { @Override public Object run() throws Exception { HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); @@ -339,7 +380,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testTableDelete() throws Exception { - PrivilegedExceptionAction deleteTable = new PrivilegedExceptionAction() { + AccessTestAction deleteTable = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER @@ -355,7 +396,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testAddColumn() throws Exception { final HColumnDescriptor hcd = new HColumnDescriptor("fam_new"); - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preAddColumn(ObserverContext.createAndPrepare(CP_ENV, null), TEST_TABLE.getTableName(), @@ -372,7 +413,7 @@ public class TestAccessController extends SecureTestUtil { public void testModifyColumn() throws Exception { final HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); hcd.setMaxVersions(10); - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preModifyColumn(ObserverContext.createAndPrepare(CP_ENV, null), @@ -387,7 +428,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testDeleteColumn() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preDeleteColumn(ObserverContext.createAndPrepare(CP_ENV, null), @@ -402,7 +443,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testTableDisable() throws Exception { - PrivilegedExceptionAction disableTable = new PrivilegedExceptionAction() { + AccessTestAction disableTable = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preDisableTable(ObserverContext.createAndPrepare(CP_ENV, null), @@ -411,7 +452,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction disableAclTable = new PrivilegedExceptionAction() { + AccessTestAction disableAclTable = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preDisableTable(ObserverContext.createAndPrepare(CP_ENV, null), @@ -429,7 +470,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testTableEnable() throws Exception { - PrivilegedExceptionAction enableTable = new PrivilegedExceptionAction() { + AccessTestAction enableTable = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER @@ -453,7 +494,7 @@ public class TestAccessController extends SecureTestUtil { } final Map.Entry firstRegion = regions.entrySet().iterator().next(); final ServerName server = TEST_UTIL.getHBaseCluster().getRegionServer(0).getServerName(); - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preMove(ObserverContext.createAndPrepare(CP_ENV, null), @@ -477,7 +518,7 @@ public class TestAccessController extends SecureTestUtil { } final Map.Entry firstRegion = regions.entrySet().iterator().next(); - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preAssign(ObserverContext.createAndPrepare(CP_ENV, null), @@ -501,7 +542,7 @@ public class TestAccessController extends SecureTestUtil { } final Map.Entry firstRegion = regions.entrySet().iterator().next(); - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preUnassign(ObserverContext.createAndPrepare(CP_ENV, null), @@ -525,7 +566,7 @@ public class TestAccessController extends SecureTestUtil { } final Map.Entry firstRegion = regions.entrySet().iterator().next(); - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preRegionOffline(ObserverContext.createAndPrepare(CP_ENV, null), @@ -540,7 +581,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testBalance() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preBalance(ObserverContext.createAndPrepare(CP_ENV, null)); @@ -554,7 +595,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testBalanceSwitch() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preBalanceSwitch(ObserverContext.createAndPrepare(CP_ENV, null), true); @@ -568,7 +609,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testShutdown() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preShutdown(ObserverContext.createAndPrepare(CP_ENV, null)); @@ -582,7 +623,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testStopMaster() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preStopMaster(ObserverContext.createAndPrepare(CP_ENV, null)); @@ -594,14 +635,14 @@ public class TestAccessController extends SecureTestUtil { verifyDenied(action, USER_CREATE, USER_OWNER, USER_RW, USER_RO, USER_NONE); } - private void verifyWrite(PrivilegedExceptionAction action) throws Exception { + private void verifyWrite(AccessTestAction action) throws Exception { verifyAllowed(action, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, USER_RW); verifyDenied(action, USER_NONE, USER_RO); } @Test public void testSplit() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preSplit(ObserverContext.createAndPrepare(RCP_ENV, null)); @@ -615,12 +656,12 @@ public class TestAccessController extends SecureTestUtil { @Test public void testSplitWithSplitRow() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preSplit( ObserverContext.createAndPrepare(RCP_ENV, null), - Bytes.toBytes("row2")); + TEST_ROW); return null; } }; @@ -632,7 +673,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testFlush() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preFlush(ObserverContext.createAndPrepare(RCP_ENV, null)); @@ -646,7 +687,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testCompact() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preCompact(ObserverContext.createAndPrepare(RCP_ENV, null), null, null, @@ -661,7 +702,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testPreCompactSelection() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preCompactSelection(ObserverContext.createAndPrepare(RCP_ENV, null), null, null); @@ -673,12 +714,12 @@ public class TestAccessController extends SecureTestUtil { verifyDenied(action, USER_CREATE, USER_RW, USER_RO, USER_NONE); } - private void verifyRead(PrivilegedExceptionAction action) throws Exception { + private void verifyRead(AccessTestAction action) throws Exception { verifyAllowed(action, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, USER_RW, USER_RO); verifyDenied(action, USER_NONE); } - private void verifyReadWrite(PrivilegedExceptionAction action) throws Exception { + private void verifyReadWrite(AccessTestAction action) throws Exception { verifyAllowed(action, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, USER_RW); verifyDenied(action, USER_NONE, USER_RO); } @@ -686,10 +727,10 @@ public class TestAccessController extends SecureTestUtil { @Test public void testRead() throws Exception { // get action - PrivilegedExceptionAction getAction = new PrivilegedExceptionAction() { + AccessTestAction getAction = new AccessTestAction() { @Override public Object run() throws Exception { - Get g = new Get(Bytes.toBytes("random_row")); + Get g = new Get(TEST_ROW); g.addFamily(TEST_FAMILY); HTable t = new HTable(conf, TEST_TABLE.getTableName()); try { @@ -703,7 +744,7 @@ public class TestAccessController extends SecureTestUtil { verifyRead(getAction); // action for scanning - PrivilegedExceptionAction scanAction = new PrivilegedExceptionAction() { + AccessTestAction scanAction = new AccessTestAction() { @Override public Object run() throws Exception { Scan s = new Scan(); @@ -733,11 +774,11 @@ public class TestAccessController extends SecureTestUtil { // test put, delete, increment public void testWrite() throws Exception { // put action - PrivilegedExceptionAction putAction = new PrivilegedExceptionAction() { + AccessTestAction putAction = new AccessTestAction() { @Override public Object run() throws Exception { - Put p = new Put(Bytes.toBytes("random_row")); - p.add(TEST_FAMILY, Bytes.toBytes("Qualifier"), Bytes.toBytes(1)); + Put p = new Put(TEST_ROW); + p.add(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes(1)); HTable t = new HTable(conf, TEST_TABLE.getTableName()); try { t.put(p); @@ -750,10 +791,10 @@ public class TestAccessController extends SecureTestUtil { verifyWrite(putAction); // delete action - PrivilegedExceptionAction deleteAction = new PrivilegedExceptionAction() { + AccessTestAction deleteAction = new AccessTestAction() { @Override public Object run() throws Exception { - Delete d = new Delete(Bytes.toBytes("random_row")); + Delete d = new Delete(TEST_ROW); d.deleteFamily(TEST_FAMILY); HTable t = new HTable(conf, TEST_TABLE.getTableName()); try { @@ -767,11 +808,11 @@ public class TestAccessController extends SecureTestUtil { verifyWrite(deleteAction); // increment action - PrivilegedExceptionAction incrementAction = new PrivilegedExceptionAction() { + AccessTestAction incrementAction = new AccessTestAction() { @Override public Object run() throws Exception { - Increment inc = new Increment(Bytes.toBytes("random_row")); - inc.addColumn(TEST_FAMILY, Bytes.toBytes("Qualifier"), 1); + Increment inc = new Increment(TEST_ROW); + inc.addColumn(TEST_FAMILY, TEST_QUALIFIER, 1); HTable t = new HTable(conf, TEST_TABLE.getTableName()); try { t.increment(inc); @@ -787,14 +828,14 @@ public class TestAccessController extends SecureTestUtil { @Test public void testReadWrite() throws Exception { // action for checkAndDelete - PrivilegedExceptionAction checkAndDeleteAction = new PrivilegedExceptionAction() { + AccessTestAction checkAndDeleteAction = new AccessTestAction() { @Override public Object run() throws Exception { - Delete d = new Delete(Bytes.toBytes("random_row")); + Delete d = new Delete(TEST_ROW); d.deleteFamily(TEST_FAMILY); HTable t = new HTable(conf, TEST_TABLE.getTableName()); try { - t.checkAndDelete(Bytes.toBytes("random_row"), TEST_FAMILY, Bytes.toBytes("q"), + t.checkAndDelete(TEST_ROW, TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("test_value"), d); } finally { t.close(); @@ -805,14 +846,14 @@ public class TestAccessController extends SecureTestUtil { verifyReadWrite(checkAndDeleteAction); // action for checkAndPut() - PrivilegedExceptionAction checkAndPut = new PrivilegedExceptionAction() { + AccessTestAction checkAndPut = new AccessTestAction() { @Override public Object run() throws Exception { - Put p = new Put(Bytes.toBytes("random_row")); - p.add(TEST_FAMILY, Bytes.toBytes("Qualifier"), Bytes.toBytes(1)); + Put p = new Put(TEST_ROW); + p.add(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes(1)); HTable t = new HTable(conf, TEST_TABLE.getTableName()); try { - t.checkAndPut(Bytes.toBytes("random_row"), TEST_FAMILY, Bytes.toBytes("q"), + t.checkAndPut(TEST_ROW, TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("test_value"), p); } finally { t.close(); @@ -832,7 +873,7 @@ public class TestAccessController extends SecureTestUtil { //so users creating HFiles have write permissions fs.setPermission(dir, FsPermission.valueOf("-rwxrwxrwx")); - PrivilegedExceptionAction bulkLoadAction = new PrivilegedExceptionAction() { + AccessTestAction bulkLoadAction = new AccessTestAction() { @Override public Object run() throws Exception { int numRows = 3; @@ -842,7 +883,7 @@ public class TestAccessController extends SecureTestUtil { Path bulkLoadBasePath = new Path(dir, new Path(User.getCurrent().getName())); new BulkLoadHelper(bulkLoadBasePath) - .bulkLoadHFile(TEST_TABLE.getTableName(), TEST_FAMILY, Bytes.toBytes("q"), hfileRanges, numRows); + .bulkLoadHFile(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_QUALIFIER, hfileRanges, numRows); return null; } @@ -939,11 +980,11 @@ public class TestAccessController extends SecureTestUtil { @Test public void testAppend() throws Exception { - PrivilegedExceptionAction appendAction = new PrivilegedExceptionAction() { + AccessTestAction appendAction = new AccessTestAction() { @Override public Object run() throws Exception { - byte[] row = Bytes.toBytes("random_row"); - byte[] qualifier = Bytes.toBytes("q"); + byte[] row = TEST_ROW; + byte[] qualifier = TEST_QUALIFIER; Put put = new Put(row); put.add(TEST_FAMILY, qualifier, Bytes.toBytes(1)); Append append = new Append(row); @@ -964,9 +1005,263 @@ public class TestAccessController extends SecureTestUtil { } @Test - public void testGrantRevoke() throws Exception { + public void testCellPermissions() throws Exception { + // table/column/qualifier level permissions + final byte[] TEST_ROW = Bytes.toBytes("cellpermtest"); + final byte[] TEST_Q1 = Bytes.toBytes("q1"); + final byte[] TEST_Q2 = Bytes.toBytes("q2"); + final byte[] TEST_Q3 = Bytes.toBytes("q3"); + final byte[] TEST_Q4 = Bytes.toBytes("q4"); + // test value + final byte[] ZERO = Bytes.toBytes(0L); - PrivilegedExceptionAction grantAction = new PrivilegedExceptionAction() { + /* ---- Setup ---- */ + + // additional test user + final User userOther = User.createUserForTesting(conf, "user_check_cell_perms_other", + new String[0]); + + // store two sets of values, one store with a cell level ACL, and one without + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + Put p; + // with ro ACL + p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO); + p.setACL(userOther.getShortName(), new Permission(Permission.Action.READ)); + t.put(p); + // with rw ACL + p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q2, ZERO); + p.setACL(userOther.getShortName(), new Permission(Permission.Action.READ, + Permission.Action.WRITE)); + t.put(p); + // no ACL + p = new Put(TEST_ROW) + .add(TEST_FAMILY, TEST_Q3, ZERO) + .add(TEST_FAMILY, TEST_Q4, ZERO); + t.put(p); + } finally { + t.close(); + } + return null; + } + }, USER_OWNER); + + /* ---- Gets ---- */ + + AccessTestAction getQ1 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + return t.get(get).listCells(); + } finally { + t.close(); + } + } + }; + + AccessTestAction getQ2 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + return t.get(get).listCells(); + } finally { + t.close(); + } + } + }; + + AccessTestAction getQ3 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q3); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + return t.get(get).listCells(); + } finally { + t.close(); + } + } + }; + + AccessTestAction getQ4 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q4); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + return t.get(get).listCells(); + } finally { + t.close(); + } + } + }; + + // Confirm special read access set at cell level + + verifyAllowed(getQ1, userOther); + verifyAllowed(getQ2, userOther); + + // Confirm this access does not extend to other cells + + verifyDenied(getQ3, userOther); + verifyDenied(getQ4, userOther); + + /* ---- Scans ---- */ + + // check that a scan over the test data returns the expected number of KVs + + final List scanResults = Lists.newArrayList(); + + AccessTestAction scanAction = new AccessTestAction() { + @Override + public List run() throws Exception { + Scan scan = new Scan(); + scan.setStartRow(TEST_ROW); + scan.setStopRow(Bytes.add(TEST_ROW, new byte[]{ 0 } )); + scan.addFamily(TEST_FAMILY); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + ResultScanner scanner = t.getScanner(scan); + Result result = null; + do { + result = scanner.next(); + if (result != null) { + scanResults.addAll(result.listCells()); + } + } while (result != null); + } finally { + t.close(); + } + return scanResults; + } + }; + + // owner will see all values + scanResults.clear(); + verifyAllowed(scanAction, USER_OWNER); + assertEquals(4, scanResults.size()); + + // other user will see 2 values + scanResults.clear(); + verifyAllowed(scanAction, userOther); + assertEquals(2, scanResults.size()); + + /* ---- Increments ---- */ + + AccessTestAction incrementQ1 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1, 1L); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + t.increment(i); + } finally { + t.close(); + } + return null; + } + }; + + AccessTestAction incrementQ2 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2, 1L); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + t.increment(i); + } finally { + t.close(); + } + return null; + } + }; + + AccessTestAction incrementQ2newDenyACL = new AccessTestAction() { + @Override + public Object run() throws Exception { + Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2, 1L); + // Tag this increment with an ACL that denies write permissions to userOther + i.setACL(userOther.getShortName(), new Permission(Permission.Action.READ)); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + t.increment(i); + } finally { + t.close(); + } + return null; + } + }; + + AccessTestAction incrementQ3 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q3, 1L); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + t.increment(i); + } finally { + t.close(); + } + return null; + } + }; + + verifyDenied(incrementQ1, userOther); + verifyDenied(incrementQ3, userOther); + + // We should be able to increment Q2 twice, the previous ACL will be + // carried forward + verifyAllowed(incrementQ2, userOther); + verifyAllowed(incrementQ2newDenyACL, userOther); + // But not again after we denied ourselves write permission with an ACL + // update + verifyDenied(incrementQ2, userOther); + + /* ---- Deletes ---- */ + + AccessTestAction deleteFamily = new AccessTestAction() { + @Override + public Object run() throws Exception { + Delete delete = new Delete(TEST_ROW).deleteFamily(TEST_FAMILY); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + t.delete(delete); + } finally { + t.close(); + } + return null; + } + }; + + AccessTestAction deleteQ1 = new AccessTestAction() { + @Override + public Object run() throws Exception { + Delete delete = new Delete(TEST_ROW).deleteColumn(TEST_FAMILY, TEST_Q1); + HTable t = new HTable(conf, TEST_TABLE.getTableName()); + try { + t.delete(delete); + } finally { + t.close(); + } + return null; + } + }; + + verifyDenied(deleteFamily, userOther); + verifyDenied(deleteQ1, userOther); + verifyAllowed(deleteQ1, USER_OWNER); + } + + @Test + public void testGrantRevoke() throws Exception { + AccessTestAction grantAction = new AccessTestAction() { @Override public Object run() throws Exception { HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); @@ -983,7 +1278,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction revokeAction = new PrivilegedExceptionAction() { + AccessTestAction revokeAction = new AccessTestAction() { @Override public Object run() throws Exception { HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); @@ -1000,7 +1295,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction getPermissionsAction = new PrivilegedExceptionAction() { + AccessTestAction getPermissionsAction = new AccessTestAction() { @Override public Object run() throws Exception { HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); @@ -1052,7 +1347,7 @@ public class TestAccessController extends SecureTestUtil { .createUserForTesting(TEST_UTIL.getConfiguration(), "gbluser", new String[0]); // prepare actions: - PrivilegedExceptionAction putActionAll = new PrivilegedExceptionAction() { + AccessTestAction putActionAll = new AccessTestAction() { @Override public Object run() throws Exception { Put p = new Put(Bytes.toBytes("a")); @@ -1067,7 +1362,8 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction putAction1 = new PrivilegedExceptionAction() { + + AccessTestAction putAction1 = new AccessTestAction() { @Override public Object run() throws Exception { Put p = new Put(Bytes.toBytes("a")); @@ -1081,7 +1377,8 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction putAction2 = new PrivilegedExceptionAction() { + + AccessTestAction putAction2 = new AccessTestAction() { @Override public Object run() throws Exception { Put p = new Put(Bytes.toBytes("a")); @@ -1095,10 +1392,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction getActionAll = new PrivilegedExceptionAction() { + + AccessTestAction getActionAll = new AccessTestAction() { @Override public Object run() throws Exception { - Get g = new Get(Bytes.toBytes("random_row")); + Get g = new Get(TEST_ROW); g.addFamily(family1); g.addFamily(family2); HTable t = new HTable(conf, tableName); @@ -1110,10 +1408,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction getAction1 = new PrivilegedExceptionAction() { + + AccessTestAction getAction1 = new AccessTestAction() { @Override public Object run() throws Exception { - Get g = new Get(Bytes.toBytes("random_row")); + Get g = new Get(TEST_ROW); g.addFamily(family1); HTable t = new HTable(conf, tableName); try { @@ -1124,10 +1423,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction getAction2 = new PrivilegedExceptionAction() { + + AccessTestAction getAction2 = new AccessTestAction() { @Override public Object run() throws Exception { - Get g = new Get(Bytes.toBytes("random_row")); + Get g = new Get(TEST_ROW); g.addFamily(family2); HTable t = new HTable(conf, tableName); try { @@ -1138,10 +1438,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction deleteActionAll = new PrivilegedExceptionAction() { + + AccessTestAction deleteActionAll = new AccessTestAction() { @Override public Object run() throws Exception { - Delete d = new Delete(Bytes.toBytes("random_row")); + Delete d = new Delete(TEST_ROW); d.deleteFamily(family1); d.deleteFamily(family2); HTable t = new HTable(conf, tableName); @@ -1153,10 +1454,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction deleteAction1 = new PrivilegedExceptionAction() { + + AccessTestAction deleteAction1 = new AccessTestAction() { @Override public Object run() throws Exception { - Delete d = new Delete(Bytes.toBytes("random_row")); + Delete d = new Delete(TEST_ROW); d.deleteFamily(family1); HTable t = new HTable(conf, tableName); try { @@ -1167,10 +1469,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction deleteAction2 = new PrivilegedExceptionAction() { + + AccessTestAction deleteAction2 = new AccessTestAction() { @Override public Object run() throws Exception { - Delete d = new Delete(Bytes.toBytes("random_row")); + Delete d = new Delete(TEST_ROW); d.deleteFamily(family2); HTable t = new HTable(conf, tableName); try { @@ -1372,10 +1675,10 @@ public class TestAccessController extends SecureTestUtil { // create temp users User user = User.createUserForTesting(TEST_UTIL.getConfiguration(), "user", new String[0]); - PrivilegedExceptionAction getQualifierAction = new PrivilegedExceptionAction() { + AccessTestAction getQualifierAction = new AccessTestAction() { @Override public Object run() throws Exception { - Get g = new Get(Bytes.toBytes("random_row")); + Get g = new Get(TEST_ROW); g.addColumn(family1, qualifier); HTable t = new HTable(conf, tableName); try { @@ -1386,10 +1689,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction putQualifierAction = new PrivilegedExceptionAction() { + + AccessTestAction putQualifierAction = new AccessTestAction() { @Override public Object run() throws Exception { - Put p = new Put(Bytes.toBytes("random_row")); + Put p = new Put(TEST_ROW); p.add(family1, qualifier, Bytes.toBytes("v1")); HTable t = new HTable(conf, tableName); try { @@ -1400,10 +1704,11 @@ public class TestAccessController extends SecureTestUtil { return null; } }; - PrivilegedExceptionAction deleteQualifierAction = new PrivilegedExceptionAction() { + + AccessTestAction deleteQualifierAction = new AccessTestAction() { @Override public Object run() throws Exception { - Delete d = new Delete(Bytes.toBytes("random_row")); + Delete d = new Delete(TEST_ROW); d.deleteColumn(family1, qualifier); // d.deleteFamily(family1); HTable t = new HTable(conf, tableName); @@ -1657,7 +1962,7 @@ public class TestAccessController extends SecureTestUtil { } /** global operations */ - private void verifyGlobal(PrivilegedExceptionAction action) throws Exception { + private void verifyGlobal(AccessTestAction action) throws Exception { verifyAllowed(action, SUPERUSER); verifyDenied(action, USER_CREATE, USER_RW, USER_NONE, USER_RO); @@ -1724,7 +2029,7 @@ public class TestAccessController extends SecureTestUtil { public void testCheckPermissions() throws Exception { // -------------------------------------- // test global permissions - PrivilegedExceptionAction globalAdmin = new PrivilegedExceptionAction() { + AccessTestAction globalAdmin = new AccessTestAction() { @Override public Void run() throws Exception { checkGlobalPerms(Permission.Action.ADMIN); @@ -1736,7 +2041,7 @@ public class TestAccessController extends SecureTestUtil { // -------------------------------------- // test multiple permissions - PrivilegedExceptionAction globalReadWrite = new PrivilegedExceptionAction() { + AccessTestAction globalReadWrite = new AccessTestAction() { @Override public Void run() throws Exception { checkGlobalPerms(Permission.Action.READ, Permission.Action.WRITE); @@ -1770,7 +2075,7 @@ public class TestAccessController extends SecureTestUtil { acl.close(); } - PrivilegedExceptionAction tableRead = new PrivilegedExceptionAction() { + AccessTestAction tableRead = new AccessTestAction() { @Override public Void run() throws Exception { checkTablePerms(TEST_TABLE.getTableName(), null, null, Permission.Action.READ); @@ -1778,7 +2083,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction columnRead = new PrivilegedExceptionAction() { + AccessTestAction columnRead = new AccessTestAction() { @Override public Void run() throws Exception { checkTablePerms(TEST_TABLE.getTableName(), TEST_FAMILY, null, Permission.Action.READ); @@ -1786,7 +2091,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction qualifierRead = new PrivilegedExceptionAction() { + AccessTestAction qualifierRead = new AccessTestAction() { @Override public Void run() throws Exception { checkTablePerms(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, Permission.Action.READ); @@ -1794,7 +2099,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction multiQualifierRead = new PrivilegedExceptionAction() { + AccessTestAction multiQualifierRead = new AccessTestAction() { @Override public Void run() throws Exception { checkTablePerms(TEST_TABLE.getTableName(), new Permission[] { @@ -1804,7 +2109,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction globalAndTableRead = new PrivilegedExceptionAction() { + AccessTestAction globalAndTableRead = new AccessTestAction() { @Override public Void run() throws Exception { checkTablePerms(TEST_TABLE.getTableName(), new Permission[] { new Permission(Permission.Action.READ), @@ -1813,7 +2118,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction noCheck = new PrivilegedExceptionAction() { + AccessTestAction noCheck = new AccessTestAction() { @Override public Void run() throws Exception { checkTablePerms(TEST_TABLE.getTableName(), new Permission[0]); @@ -1839,7 +2144,7 @@ public class TestAccessController extends SecureTestUtil { // -------------------------------------- // test family level multiple permissions - PrivilegedExceptionAction familyReadWrite = new PrivilegedExceptionAction() { + AccessTestAction familyReadWrite = new AccessTestAction() { @Override public Void run() throws Exception { checkTablePerms(TEST_TABLE.getTableName(), TEST_FAMILY, null, Permission.Action.READ, @@ -1880,7 +2185,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testStopRegionServer() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preStopRegionServer(ObserverContext.createAndPrepare(RSCP_ENV, null)); @@ -1894,7 +2199,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testOpenRegion() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preOpen(ObserverContext.createAndPrepare(RCP_ENV, null)); @@ -1908,7 +2213,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testCloseRegion() throws Exception { - PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + AccessTestAction action = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preClose(ObserverContext.createAndPrepare(RCP_ENV, null), false); @@ -1922,7 +2227,7 @@ public class TestAccessController extends SecureTestUtil { @Test public void testSnapshot() throws Exception { - PrivilegedExceptionAction snapshotAction = new PrivilegedExceptionAction() { + AccessTestAction snapshotAction = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), @@ -1931,7 +2236,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction deleteAction = new PrivilegedExceptionAction() { + AccessTestAction deleteAction = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preDeleteSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), @@ -1940,7 +2245,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction restoreAction = new PrivilegedExceptionAction() { + AccessTestAction restoreAction = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preRestoreSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), @@ -1949,7 +2254,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction cloneAction = new PrivilegedExceptionAction() { + AccessTestAction cloneAction = new AccessTestAction() { @Override public Object run() throws Exception { ACCESS_CONTROLLER.preCloneSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), @@ -2011,7 +2316,7 @@ public class TestAccessController extends SecureTestUtil { final Map.Entry firstRegion = regions.entrySet() .iterator().next(); - PrivilegedExceptionAction moveAction = new PrivilegedExceptionAction() { + AccessTestAction moveAction = new AccessTestAction() { @Override public Object run() throws Exception { admin.move(firstRegion.getKey().getEncodedNameAsBytes(), @@ -2037,7 +2342,7 @@ public class TestAccessController extends SecureTestUtil { } // Verify write permission for user "admin2" who has the global // permissions. - PrivilegedExceptionAction putAction = new PrivilegedExceptionAction() { + AccessTestAction putAction = new AccessTestAction() { @Override public Object run() throws Exception { Put put = new Put(Bytes.toBytes("test")); @@ -2068,7 +2373,7 @@ public class TestAccessController extends SecureTestUtil { acl.close(); } - PrivilegedExceptionAction listTablesAction = new PrivilegedExceptionAction() { + AccessTestAction listTablesAction = new AccessTestAction() { @Override public Object run() throws Exception { HBaseAdmin admin = new HBaseAdmin(TEST_UTIL.getConfiguration()); @@ -2081,7 +2386,7 @@ public class TestAccessController extends SecureTestUtil { } }; - PrivilegedExceptionAction getTableDescAction = new PrivilegedExceptionAction() { + AccessTestAction getTableDescAction = new AccessTestAction() { @Override public Object run() throws Exception { HBaseAdmin admin = new HBaseAdmin(TEST_UTIL.getConfiguration()); @@ -2117,7 +2422,7 @@ public class TestAccessController extends SecureTestUtil { acl.close(); } - PrivilegedExceptionAction deleteTableAction = new PrivilegedExceptionAction() { + AccessTestAction deleteTableAction = new AccessTestAction() { @Override public Object run() throws Exception { HBaseAdmin admin = new HBaseAdmin(TEST_UTIL.getConfiguration());