From 3f35ea98b457286e530791654fe40295268044b6 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Sun, 5 Apr 2015 16:59:37 -0700 Subject: [PATCH] HBASE-13275 Setting hbase.security.authorization to false does not disable authorization Conflicts: hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java --- .../org/apache/hadoop/hbase/security/User.java | 2 + .../hbase/security/access/AccessController.java | 392 +++++--- .../security/visibility/VisibilityController.java | 100 +- .../hadoop/hbase/security/HBaseKerberosUtils.java | 8 +- .../hbase/security/access/SecureTestUtil.java | 79 +- .../security/access/TestAccessController.java | 92 +- .../access/TestWithDisabledAuthorization.java | 1001 ++++++++++++++++++++ .../visibility/TestWithDisabledAuthorization.java | 251 +++++ 8 files changed, 1701 insertions(+), 224 deletions(-) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestWithDisabledAuthorization.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestWithDisabledAuthorization.java diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java index 6c99d81..27195d0 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java @@ -54,6 +54,8 @@ import org.apache.hadoop.security.token.TokenIdentifier; public abstract class User { public static final String HBASE_SECURITY_CONF_KEY = "hbase.security.authentication"; + public static final String HBASE_SECURITY_AUTHORIZATION_CONF_KEY = + "hbase.security.authorization"; private static Log LOG = LogFactory.getLog(User.class); 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 0750776..6fd16cd 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 @@ -66,7 +66,6 @@ import org.apache.hadoop.hbase.coprocessor.CoprocessorException; import org.apache.hadoop.hbase.coprocessor.CoprocessorService; import org.apache.hadoop.hbase.coprocessor.EndpointObserver; import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; -import org.apache.hadoop.hbase.coprocessor.MasterObserver; import org.apache.hadoop.hbase.coprocessor.ObserverContext; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment; @@ -163,11 +162,11 @@ public class AccessController extends BaseMasterAndRegionObserver TableAuthManager authManager = null; - // flags if we are running on a region of the _acl_ table + /** flags if we are running on a region of the _acl_ table */ boolean aclRegion = false; - // defined only for Endpoint implementation, so it can have way to - // access region services. + /** defined only for Endpoint implementation, so it can have way to + access region services */ private RegionCoprocessorEnvironment regionEnv; /** Mapping of scanner instances to the user who created them */ @@ -176,25 +175,30 @@ public class AccessController extends BaseMasterAndRegionObserver private Map> tableAcls; - // Provider for mapping principal names to Users + /** Provider for mapping principal names to Users */ private UserProvider userProvider; - // The list of users with superuser authority + /** The list of users with superuser authority */ private List superusers; - // if we are able to support cell ACLs + /** if we are active, usually true, only not true if "hbase.security.authorization" + has been set to false in site configuration */ + boolean authorizationEnabled; + + /** if we are able to support cell ACLs */ boolean cellFeaturesEnabled; - // if we should check EXEC permissions + /** if we should check EXEC permissions */ boolean shouldCheckExecPermission; - // if we should terminate access checks early as soon as table or CF grants - // allow access; pre-0.98 compatible behavior + /** if we should terminate access checks early as soon as table or CF grants + allow access; pre-0.98 compatible behavior */ boolean compatibleEarlyTermination; + /** if we have been successfully initialized */ private volatile boolean initialized = false; - // This boolean having relevance only in the Master. + /** if the ACL table is available, only relevant in the master */ private volatile boolean aclTabAvailable = false; public HRegion getRegion() { @@ -404,8 +408,8 @@ public class AccessController extends BaseMasterAndRegionObserver * @throws IOException if obtaining the current user fails * @throws AccessDeniedException if user has no authorization */ - private void requirePermission(String request, TableName tableName, byte[] family, byte[] qualifier, - Action... permissions) throws IOException { + private void requirePermission(String request, TableName tableName, byte[] family, + byte[] qualifier, Action... permissions) throws IOException { User user = getActiveUser(); AuthResult result = null; @@ -421,7 +425,38 @@ public class AccessController extends BaseMasterAndRegionObserver } } logResult(result); - if (!result.isAllowed()) { + if (authorizationEnabled && !result.isAllowed()) { + throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); + } + } + + /** + * Authorizes that the current user has any of the given permissions for the + * given table, column family and column qualifier. + * @param tableName Table requested + * @param family Column family param + * @param qualifier Column qualifier param + * @throws IOException if obtaining the current user fails + * @throws AccessDeniedException if user has no authorization + */ + private void requireTablePermission(String request, TableName tableName, byte[] family, + byte[] qualifier, Action... permissions) throws IOException { + User user = getActiveUser(); + AuthResult result = null; + + for (Action permission : permissions) { + if (authManager.authorize(user, tableName, null, null, permission)) { + result = AuthResult.allow(request, "Table permission granted", user, + permission, tableName, null, null); + break; + } else { + // rest of the world + result = AuthResult.deny(request, "Insufficient permissions", user, + permission, tableName, family, qualifier); + } + } + logResult(result); + if (authorizationEnabled && !result.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); } } @@ -451,7 +486,7 @@ public class AccessController extends BaseMasterAndRegionObserver } } logResult(result); - if (!result.isAllowed()) { + if (authorizationEnabled && !result.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); } } @@ -467,31 +502,6 @@ public class AccessController extends BaseMasterAndRegionObserver } /** - * Authorizes that the current user has permission to perform the given - * action on the set of table column families. - * @param perm Action that is required - * @param env The current coprocessor environment - * @param families The map of column families-qualifiers. - * @throws AccessDeniedException if the authorization check failed - */ - private void requirePermission(String request, Action perm, - RegionCoprocessorEnvironment env, - Map> families) - throws IOException { - User user = getActiveUser(); - AuthResult result = permissionGranted(request, user, perm, env, families); - logResult(result); - - if (!result.isAllowed()) { - throw new AccessDeniedException("Insufficient permissions (table=" + - env.getRegion().getTableDesc().getTableName()+ - ((families != null && families.size() > 0) ? ", family: " + - result.toFamilyString() : "") + ", action=" + - perm.toString() + ")"); - } - } - - /** * Checks that the user has the given global permission. The generated * audit log message will contain context information for the operation * being authorized, based on the given parameters. @@ -507,9 +517,11 @@ public class AccessController extends BaseMasterAndRegionObserver logResult(AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap)); } else { logResult(AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap)); - throw new AccessDeniedException("Insufficient permissions for user '" + + if (authorizationEnabled) { + throw new AccessDeniedException("Insufficient permissions for user '" + (user != null ? user.getShortName() : "null") +"' (global, action=" + perm.toString() + ")"); + } } } @@ -528,9 +540,68 @@ public class AccessController extends BaseMasterAndRegionObserver logResult(AuthResult.allow(request, "Global check allowed", user, perm, namespace)); } else { logResult(AuthResult.deny(request, "Global check failed", user, perm, namespace)); - throw new AccessDeniedException("Insufficient permissions for user '" + + if (authorizationEnabled) { + throw new AccessDeniedException("Insufficient permissions for user '" + (user != null ? user.getShortName() : "null") +"' (global, action=" + perm.toString() + ")"); + } + } + } + + /** + * Checks that the user has the given global or namespace permission. + * @param namespace + * @param permissions Actions being requested + */ + public void requireNamespacePermission(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, "Namespace permission granted", + user, permission, namespace); + break; + } else { + // rest of the world + result = AuthResult.deny(request, "Insufficient permissions", user, + permission, namespace); + } + } + logResult(result); + if (authorizationEnabled && !result.isAllowed()) { + throw new AccessDeniedException("Insufficient permissions " + + result.toContextString()); + } + } + + /** + * Checks that the user has the given global or namespace permission. + * @param namespace + * @param permissions Actions being requested + */ + public void requireNamespacePermission(String request, String namespace, TableName tableName, + Map> familyMap, Action... permissions) + throws IOException { + User user = getActiveUser(); + AuthResult result = null; + + for (Action permission : permissions) { + if (authManager.authorize(user, namespace, permission)) { + result = AuthResult.allow(request, "Namespace permission granted", + user, permission, namespace); + break; + } else { + // rest of the world + result = AuthResult.deny(request, "Insufficient permissions", user, + permission, namespace); + } + } + logResult(result); + if (authorizationEnabled && !result.isAllowed()) { + throw new AccessDeniedException("Insufficient permissions " + + result.toContextString()); } } @@ -663,6 +734,8 @@ public class AccessController extends BaseMasterAndRegionObserver } } } + } else if (entry.getValue() == null) { + get.addFamily(col); } else { throw new RuntimeException("Unhandled collection type " + entry.getValue().getClass().getName()); @@ -806,8 +879,14 @@ public class AccessController extends BaseMasterAndRegionObserver // Checks whether incoming cells contain any tag with type as ACL_TAG_TYPE. This tag // type is reserved and should not be explicitly set by user. private void checkForReservedTagPresence(User user, Mutation m) throws IOException { + // No need to check if we're not going to throw + if (!authorizationEnabled) { + m.setAttribute(TAG_CHECK_PASSED, TRUE); + return; + } // Superusers are allowed to store cells unconditionally. if (superusers.contains(user.getShortName())) { + m.setAttribute(TAG_CHECK_PASSED, TRUE); return; } // We already checked (prePut vs preBatchMutation) @@ -835,6 +914,11 @@ public class AccessController extends BaseMasterAndRegionObserver CompoundConfiguration conf = new CompoundConfiguration(); conf.add(env.getConfiguration()); + authorizationEnabled = conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true); + if (!authorizationEnabled) { + LOG.warn("The AccessController has been loaded with authorization checks disabled."); + } + shouldCheckExecPermission = conf.getBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY, AccessControlConstants.DEFAULT_EXEC_PERMISSION_CHECKS); @@ -1081,8 +1165,12 @@ public class AccessController extends BaseMasterAndRegionObserver public void preDisableTable(ObserverContext c, TableName tableName) throws IOException { if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) { + // We have to unconditionally disallow disable of the ACL table when we are installed, + // even if not enforcing authorizations. We are still allowing grants and revocations, + // checking permissions and logging audit messages, etc. If the ACL table is not + // available we will fail random actions all over the place. throw new AccessDeniedException("Not allowed to disable " - + AccessControlLists.ACL_TABLE_NAME + " table."); + + AccessControlLists.ACL_TABLE_NAME + " table with AccessController installed"); } requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE); } @@ -1307,8 +1395,9 @@ public class AccessController extends BaseMasterAndRegionObserver authResult.setReason("Covering cell set"); } logResult(authResult); - if (!authResult.isAllowed()) { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + if (authorizationEnabled && !authResult.isAllowed()) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } @@ -1351,26 +1440,29 @@ public class AccessController extends BaseMasterAndRegionObserver // grants three times (permissionGranted above, here, and in the // filter) but that's the price of backwards compatibility. if (hasFamilyQualifierPermission(user, Action.READ, env, families)) { - Filter ourFilter = new AccessControlFilter(authManager, user, table, - AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY, - cfVsMaxVersions); - // wrap any existing filter - if (filter != null) { - ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, - Lists.newArrayList(ourFilter, filter)); - } - authResult.setAllowed(true);; + authResult.setAllowed(true); authResult.setReason("Access allowed with filter"); - switch (opType) { - case GET: - case EXISTS: - ((Get)query).setFilter(ourFilter); - break; - case SCAN: - ((Scan)query).setFilter(ourFilter); - break; - default: - throw new RuntimeException("Unhandled operation " + opType); + // Only wrap the filter if we are enforcing authorizations + if (authorizationEnabled) { + Filter ourFilter = new AccessControlFilter(authManager, user, table, + AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY, + cfVsMaxVersions); + // wrap any existing filter + if (filter != null) { + ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, + Lists.newArrayList(ourFilter, filter)); + } + switch (opType) { + case GET: + case EXISTS: + ((Get)query).setFilter(ourFilter); + break; + case SCAN: + ((Scan)query).setFilter(ourFilter); + break; + default: + throw new RuntimeException("Unhandled operation " + opType); + } } } } else { @@ -1378,31 +1470,34 @@ public class AccessController extends BaseMasterAndRegionObserver // than whole table or CF. Simply inject a filter and return what is // allowed. We will not throw an AccessDeniedException. This is a // behavioral change since 0.96. - Filter ourFilter = new AccessControlFilter(authManager, user, table, - AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions); - // wrap any existing filter - if (filter != null) { - ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, - Lists.newArrayList(ourFilter, filter)); - } - authResult.setAllowed(true);; + authResult.setAllowed(true); authResult.setReason("Access allowed with filter"); - switch (opType) { - case GET: - case EXISTS: - ((Get)query).setFilter(ourFilter); - break; - case SCAN: - ((Scan)query).setFilter(ourFilter); - break; - default: - throw new RuntimeException("Unhandled operation " + opType); + // Only wrap the filter if we are enforcing authorizations + if (authorizationEnabled) { + Filter ourFilter = new AccessControlFilter(authManager, user, table, + AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions); + // wrap any existing filter + if (filter != null) { + ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, + Lists.newArrayList(ourFilter, filter)); + } + switch (opType) { + case GET: + case EXISTS: + ((Get)query).setFilter(ourFilter); + break; + case SCAN: + ((Scan)query).setFilter(ourFilter); + break; + default: + throw new RuntimeException("Unhandled operation " + opType); + } } } } logResult(authResult); - if (!authResult.isAllowed()) { + if (authorizationEnabled && !authResult.isAllowed()) { throw new AccessDeniedException("Insufficient permissions (table=" + table + ", action=READ)"); } @@ -1425,14 +1520,15 @@ public class AccessController extends BaseMasterAndRegionObserver public void prePut(final ObserverContext c, final Put put, final WALEdit edit, final Durability durability) throws IOException { + User user = getActiveUser(); + checkForReservedTagPresence(user, put); + // 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. - User user = getActiveUser(); - checkForReservedTagPresence(user, put); RegionCoprocessorEnvironment env = c.getEnvironment(); Map> families = put.getFamilyCellMap(); AuthResult authResult = permissionGranted(OpType.PUT, user, env, families, Action.WRITE); @@ -1440,10 +1536,11 @@ public class AccessController extends BaseMasterAndRegionObserver if (!authResult.isAllowed()) { if (cellFeaturesEnabled && !compatibleEarlyTermination) { put.setAttribute(CHECK_COVERING_PERM, TRUE); - } else { + } else if (authorizationEnabled) { throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); } } + // Add cell ACLs from the operation to the cells themselves byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); if (bytes != null) { @@ -1484,8 +1581,9 @@ public class AccessController extends BaseMasterAndRegionObserver if (!authResult.isAllowed()) { if (cellFeaturesEnabled && !compatibleEarlyTermination) { delete.setAttribute(CHECK_COVERING_PERM, TRUE); - } else { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + } else if (authorizationEnabled) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } } @@ -1508,18 +1606,18 @@ public class AccessController extends BaseMasterAndRegionObserver opType = OpType.DELETE; } AuthResult authResult = null; - if (checkCoveringPermission(opType, c.getEnvironment(), m.getRow(), m.getFamilyCellMap(), - m.getTimeStamp(), Action.WRITE)) { - authResult = AuthResult.allow(opType.toString(), "Covering cell set", getActiveUser(), - Action.WRITE, table, m.getFamilyCellMap()); + if (checkCoveringPermission(opType, c.getEnvironment(), m.getRow(), + m.getFamilyCellMap(), m.getTimeStamp(), Action.WRITE)) { + authResult = AuthResult.allow(opType.toString(), "Covering cell set", + getActiveUser(), Action.WRITE, table, m.getFamilyCellMap()); } else { - authResult = AuthResult.deny(opType.toString(), "Covering cell set", getActiveUser(), - Action.WRITE, table, m.getFamilyCellMap()); + authResult = AuthResult.deny(opType.toString(), "Covering cell set", + getActiveUser(), Action.WRITE, table, m.getFamilyCellMap()); } logResult(authResult); - if (!authResult.isAllowed()) { + if (authorizationEnabled && !authResult.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " - + authResult.toContextString()); + + authResult.toContextString()); } } } @@ -1541,9 +1639,10 @@ public class AccessController extends BaseMasterAndRegionObserver final CompareFilter.CompareOp compareOp, final ByteArrayComparable comparator, final Put put, final boolean result) throws IOException { - // Require READ and WRITE permissions on the table, CF, and KV to update User user = getActiveUser(); checkForReservedTagPresence(user, put); + + // Require READ and WRITE permissions on the table, CF, and KV to update RegionCoprocessorEnvironment env = c.getEnvironment(); Map> families = makeFamilyMap(family, qualifier); AuthResult authResult = permissionGranted(OpType.CHECK_AND_PUT, user, env, families, @@ -1552,10 +1651,12 @@ public class AccessController extends BaseMasterAndRegionObserver if (!authResult.isAllowed()) { if (cellFeaturesEnabled && !compatibleEarlyTermination) { put.setAttribute(CHECK_COVERING_PERM, TRUE); - } else { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + } else if (authorizationEnabled) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } + byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); if (bytes != null) { if (cellFeaturesEnabled) { @@ -1587,7 +1688,7 @@ public class AccessController extends BaseMasterAndRegionObserver getActiveUser(), Action.READ, table, families); } logResult(authResult); - if (!authResult.isAllowed()) { + if (authorizationEnabled && !authResult.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); } } @@ -1616,8 +1717,9 @@ public class AccessController extends BaseMasterAndRegionObserver if (!authResult.isAllowed()) { if (cellFeaturesEnabled && !compatibleEarlyTermination) { delete.setAttribute(CHECK_COVERING_PERM, TRUE); - } else { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + } else if (authorizationEnabled) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } return result; @@ -1644,7 +1746,7 @@ public class AccessController extends BaseMasterAndRegionObserver getActiveUser(), Action.READ, table, families); } logResult(authResult); - if (!authResult.isAllowed()) { + if (authorizationEnabled && !authResult.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); } } @@ -1669,7 +1771,7 @@ public class AccessController extends BaseMasterAndRegionObserver authResult.setReason("Covering cell set"); } logResult(authResult); - if (!authResult.isAllowed()) { + if (authorizationEnabled && !authResult.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); } return -1; @@ -1678,9 +1780,10 @@ public class AccessController extends BaseMasterAndRegionObserver @Override public Result preAppend(ObserverContext c, Append append) throws IOException { - // Require WRITE permission to the table, CF, and the KV to be appended User user = getActiveUser(); checkForReservedTagPresence(user, append); + + // Require WRITE permission to the table, CF, and the KV to be appended RegionCoprocessorEnvironment env = c.getEnvironment(); Map> families = append.getFamilyCellMap(); AuthResult authResult = permissionGranted(OpType.APPEND, user, env, families, Action.WRITE); @@ -1688,10 +1791,12 @@ public class AccessController extends BaseMasterAndRegionObserver if (!authResult.isAllowed()) { if (cellFeaturesEnabled && !compatibleEarlyTermination) { append.setAttribute(CHECK_COVERING_PERM, TRUE); - } else { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + } else if (authorizationEnabled) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } + byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); if (bytes != null) { if (cellFeaturesEnabled) { @@ -1700,6 +1805,7 @@ public class AccessController extends BaseMasterAndRegionObserver throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); } } + return null; } @@ -1720,8 +1826,9 @@ public class AccessController extends BaseMasterAndRegionObserver getActiveUser(), Action.WRITE, table, append.getFamilyCellMap()); } logResult(authResult); - if (!authResult.isAllowed()) { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + if (authorizationEnabled && !authResult.isAllowed()) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } return null; @@ -1731,10 +1838,11 @@ public class AccessController extends BaseMasterAndRegionObserver public Result preIncrement(final ObserverContext c, final Increment increment) throws IOException { - // Require WRITE permission to the table, CF, and the KV to be replaced by - // the incremented value User user = getActiveUser(); checkForReservedTagPresence(user, increment); + + // Require WRITE permission to the table, CF, and the KV to be replaced by + // the incremented value RegionCoprocessorEnvironment env = c.getEnvironment(); Map> families = increment.getFamilyCellMap(); AuthResult authResult = permissionGranted(OpType.INCREMENT, user, env, families, @@ -1743,10 +1851,12 @@ public class AccessController extends BaseMasterAndRegionObserver if (!authResult.isAllowed()) { if (cellFeaturesEnabled && !compatibleEarlyTermination) { increment.setAttribute(CHECK_COVERING_PERM, TRUE); - } else { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + } else if (authorizationEnabled) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } + byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL); if (bytes != null) { if (cellFeaturesEnabled) { @@ -1755,6 +1865,7 @@ public class AccessController extends BaseMasterAndRegionObserver throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); } } + return null; } @@ -1775,8 +1886,9 @@ public class AccessController extends BaseMasterAndRegionObserver getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap()); } logResult(authResult); - if (!authResult.isAllowed()) { - throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); + if (authorizationEnabled && !authResult.isAllowed()) { + throw new AccessDeniedException("Insufficient permissions " + + authResult.toContextString()); } } return null; @@ -1869,7 +1981,8 @@ public class AccessController extends BaseMasterAndRegionObserver public RegionScanner postScannerOpen(final ObserverContext c, final Scan scan, final RegionScanner s) throws IOException { User user = getActiveUser(); - if (user != null && user.getShortName() != null) { // store reference to scanner owner for later checks + if (user != null && user.getShortName() != null) { + // store reference to scanner owner for later checks scannerOwners.put(s, user.getShortName()); } return s; @@ -1904,7 +2017,7 @@ public class AccessController extends BaseMasterAndRegionObserver private void requireScannerOwner(InternalScanner s) throws AccessDeniedException { String requestUserName = RpcServer.getRequestUserName(); String owner = scannerOwners.get(s); - if (owner != null && !owner.equals(requestUserName)) { + if (authorizationEnabled && owner != null && !owner.equals(requestUserName)) { throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!"); } } @@ -1998,11 +2111,11 @@ public class AccessController extends BaseMasterAndRegionObserver case Global : case Table : requirePermission("grant", perm.getTableName(), perm.getFamily(), - perm.getQualifier(), Action.ADMIN); + perm.getQualifier(), Action.ADMIN); break; case Namespace : requireGlobalPermission("grant", Action.ADMIN, perm.getNamespace()); - break; + break; } User.runAsLoginUser(new PrivilegedExceptionAction() { @@ -2049,7 +2162,7 @@ public class AccessController extends BaseMasterAndRegionObserver case Global : case Table : requirePermission("revoke", perm.getTableName(), perm.getFamily(), - perm.getQualifier(), Action.ADMIN); + perm.getQualifier(), Action.ADMIN); break; case Namespace : requireGlobalPermission("revoke", Action.ADMIN, perm.getNamespace()); @@ -2143,9 +2256,12 @@ public class AccessController extends BaseMasterAndRegionObserver } AccessControlProtos.CheckPermissionsResponse response = null; try { + User user = getActiveUser(); TableName tableName = regionEnv.getRegion().getTableDesc().getTableName(); for (Permission permission : permissions) { if (permission instanceof TablePermission) { + // Check table permissions + TablePermission tperm = (TablePermission) permission; for (Action action : permission.getActions()) { if (!tperm.getTableName().equals(tableName)) { @@ -2155,7 +2271,8 @@ public class AccessController extends BaseMasterAndRegionObserver tperm.getTableName())); } - Map> familyMap = new TreeMap>(Bytes.BYTES_COMPARATOR); + Map> familyMap = + new TreeMap>(Bytes.BYTES_COMPARATOR); if (tperm.getFamily() != null) { if (tperm.getQualifier() != null) { Set qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR); @@ -2166,12 +2283,37 @@ public class AccessController extends BaseMasterAndRegionObserver } } - requirePermission("checkPermissions", action, regionEnv, familyMap); + AuthResult result = permissionGranted("checkPermissions", user, action, regionEnv, + familyMap); + logResult(result); + if (!result.isAllowed()) { + // Even if passive we need to throw an exception here, we support checking + // effective permissions, so throw unconditionally + throw new AccessDeniedException("Insufficient permissions (table=" + tableName + + (familyMap.size() > 0 ? ", family: " + result.toFamilyString() : "") + + ", action=" + action.toString() + ")"); + } } } else { + // Check global permissions + for (Action action : permission.getActions()) { - requirePermission("checkPermissions", action); + AuthResult result; + if (authManager.authorize(user, action)) { + result = AuthResult.allow("checkPermissions", "Global action allowed", user, + action, null, null); + } else { + result = AuthResult.deny("checkPermissions", "Global action denied", user, action, + null, null); + } + logResult(result); + if (!result.isAllowed()) { + // Even if passive we need to throw an exception here, we support checking + // effective permissions, so throw unconditionally + throw new AccessDeniedException("Insufficient permissions (action=" + + action.toString() + ")"); + } } } } @@ -2214,6 +2356,10 @@ public class AccessController extends BaseMasterAndRegionObserver } private void isSystemOrSuperUser(Configuration conf) throws IOException { + // No need to check if we're not going to throw + if (!authorizationEnabled) { + return; + } User user = userProvider.getCurrent(); if (user == null) { throw new IOException("Unable to obtain the current user, " + diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java index 4373bc7..9d1075d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java @@ -127,7 +127,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements // flags if we are running on a region of the 'labels' table private boolean labelsRegion = false; // Flag denoting whether AcessController is available or not. - private boolean acOn = false; + private boolean accessControllerAvailable = false; private Configuration conf; private volatile boolean initialized = false; private boolean checkAuths = false; @@ -139,6 +139,10 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements private List superGroups; private VisibilityLabelService visibilityLabelService; + /** if we are active, usually true, only not true if "hbase.security.authorization" + has been set to false in site configuration */ + boolean authorizationEnabled; + // Add to this list if there are any reserved tag types private static ArrayList RESERVED_VIS_TAG_TYPES = new ArrayList(); static { @@ -150,6 +154,12 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public void start(CoprocessorEnvironment env) throws IOException { this.conf = env.getConfiguration(); + + authorizationEnabled = conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true); + if (!authorizationEnabled) { + LOG.warn("The VisibilityController has been loaded with authorization checks disabled."); + } + if (HFile.getFormatVersion(conf) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) { throw new RuntimeException("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS + " is required to persist visibility labels. Consider setting " + HFile.FORMAT_VERSION_KEY @@ -202,6 +212,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public void preModifyTable(ObserverContext ctx, TableName tableName, HTableDescriptor htd) throws IOException { + if (!authorizationEnabled) { + return; + } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } @@ -210,6 +223,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public void preAddColumn(ObserverContext ctx, TableName tableName, HColumnDescriptor column) throws IOException { + if (!authorizationEnabled) { + return; + } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } @@ -218,6 +234,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public void preModifyColumn(ObserverContext ctx, TableName tableName, HColumnDescriptor descriptor) throws IOException { + if (!authorizationEnabled) { + return; + } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } @@ -226,6 +245,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public void preDeleteColumn(ObserverContext ctx, TableName tableName, byte[] c) throws IOException { + if (!authorizationEnabled) { + return; + } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } @@ -234,6 +256,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public void preDisableTable(ObserverContext ctx, TableName tableName) throws IOException { + if (!authorizationEnabled) { + return; + } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot disable " + LABELS_TABLE_NAME); } @@ -246,7 +271,8 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements // Read the entire labels table and populate the zk if (e.getEnvironment().getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) { this.labelsRegion = true; - this.acOn = CoprocessorHost.getLoadedCoprocessors().contains(AccessController.class.getName()); + this.accessControllerAvailable = CoprocessorHost.getLoadedCoprocessors() + .contains(AccessController.class.getName()); // Defer the init of VisibilityLabelService on labels region until it is in recovering state. if (!e.getEnvironment().getRegion().isRecovering()) { initVisibilityLabelService(e.getEnvironment()); @@ -300,9 +326,12 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) { pair = checkForReservedVisibilityTagPresence(cellScanner.current(), pair); if (!pair.getFirst()) { - miniBatchOp.setOperationStatus(i, new OperationStatus(SANITY_CHECK_FAILURE, + // Don't disallow reserved tags if authorization is disabled + if (authorizationEnabled) { + miniBatchOp.setOperationStatus(i, new OperationStatus(SANITY_CHECK_FAILURE, "Mutation contains cell with reserved type tag")); - sanityFailure = true; + sanityFailure = true; + } break; } else { // Indicates that the cell has a the tag which was modified in the src replication cluster @@ -321,7 +350,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements List visibilityTags = labelCache.get(labelsExp); if (visibilityTags == null) { // Don't check user auths for labels with Mutations when the user is super user - boolean authCheck = this.checkAuths && !(isSystemOrSuperUser()); + boolean authCheck = authorizationEnabled && checkAuths && !(isSystemOrSuperUser()); try { visibilityTags = this.visibilityLabelService.createVisibilityExpTags(labelsExp, true, authCheck); @@ -374,6 +403,11 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements public void prePrepareTimeStampForDeleteVersion( ObserverContext ctx, Mutation delete, Cell cell, byte[] byteNow, Get get) throws IOException { + // Nothing to do if we are not filtering by visibility + if (!authorizationEnabled) { + return; + } + KeyValue kv = KeyValueUtil.ensureKeyValue(cell); CellVisibility cellVisibility = null; try { @@ -523,6 +557,10 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements if (!initialized) { throw new VisibilityControllerNotReadyException("VisibilityController not yet initialized!"); } + // Nothing to do if authorization is not enabled + if (!authorizationEnabled) { + return s; + } HRegion region = e.getEnvironment().getRegion(); Authorizations authorizations = null; try { @@ -557,6 +595,10 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements public DeleteTracker postInstantiateDeleteTracker( ObserverContext ctx, DeleteTracker delTracker) throws IOException { + // Nothing to do if we are not filtering by visibility + if (!authorizationEnabled) { + return delTracker; + } HRegion region = ctx.getEnvironment().getRegion(); TableName table = region.getRegionInfo().getTable(); if (table.isSystemTable()) { @@ -609,16 +651,20 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements // This is duplicated code! String requestUName = RpcServer.getRequestUserName(); String owner = scannerOwners.get(s); - if (owner != null && !owner.equals(requestUName)) { + if (authorizationEnabled && owner != null && !owner.equals(requestUName)) { throw new AccessDeniedException("User '" + requestUName + "' is not the scanner owner!"); } } @Override - public void preGetOp(ObserverContext e, Get get, List results) - throws IOException { + public void preGetOp(ObserverContext e, Get get, + List results) throws IOException { if (!initialized) { - throw new VisibilityControllerNotReadyException("VisibilityController not yet initialized!"); + throw new VisibilityControllerNotReadyException("VisibilityController not yet initialized"); + } + // Nothing useful to do if authorization is not enabled + if (!authorizationEnabled) { + return; } HRegion region = e.getEnvironment().getRegion(); Authorizations authorizations = null; @@ -667,6 +713,10 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public Result preAppend(ObserverContext e, Append append) throws IOException { + // If authorization is not enabled, we don't care about reserved tags + if (!authorizationEnabled) { + return null; + } for (CellScanner cellScanner = append.cellScanner(); cellScanner.advance();) { if (!checkForReservedVisibilityTagPresence(cellScanner.current())) { throw new FailedSanityCheckException("Append contains cell with reserved type tag"); @@ -678,6 +728,10 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements @Override public Result preIncrement(ObserverContext e, Increment increment) throws IOException { + // If authorization is not enabled, we don't care about reserved tags + if (!authorizationEnabled) { + return null; + } for (CellScanner cellScanner = increment.cellScanner(); cellScanner.advance();) { if (!checkForReservedVisibilityTagPresence(cellScanner.current())) { throw new FailedSanityCheckException("Increment contains cell with reserved type tag"); @@ -701,7 +755,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements } // Prepend new visibility tags to a new list of tags for the cell // Don't check user auths for labels with Mutations when the user is super user - boolean authCheck = this.checkAuths && !(isSystemOrSuperUser()); + boolean authCheck = authorizationEnabled && checkAuths && !(isSystemOrSuperUser()); tags.addAll(this.visibilityLabelService.createVisibilityExpTags(cellVisibility.getExpression(), true, authCheck)); // Save an object allocation where we can @@ -749,7 +803,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements } else { List labels = new ArrayList(visLabels.size()); try { - checkCallingUserAuth(); + if (authorizationEnabled) { + checkCallingUserAuth(); + } RegionActionResult successResult = RegionActionResult.newBuilder().build(); for (VisibilityLabel visLabel : visLabels) { byte[] label = visLabel.getLabel().toByteArray(); @@ -809,8 +865,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements byte[] user = request.getUser().toByteArray(); List labelAuths = new ArrayList(auths.size()); try { - checkCallingUserAuth(); - + if (authorizationEnabled) { + checkCallingUserAuth(); + } for (ByteString authBS : auths) { labelAuths.add(authBS.toByteArray()); } @@ -880,7 +937,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements try { // We do ACL check here as we create scanner directly on region. It will not make calls to // AccessController CP methods. - if (this.acOn && !isSystemOrSuperUser()) { + if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) { User requestingUser = VisibilityUtils.getActiveUser(); throw new AccessDeniedException("User '" + (requestingUser != null ? requestingUser.getShortName() : "null") @@ -934,13 +991,15 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements List labelAuths = new ArrayList(auths.size()); try { // When AC is ON, do AC based user auth check - if (this.acOn && !isSystemOrSuperUser()) { + if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) { User user = VisibilityUtils.getActiveUser(); throw new AccessDeniedException("User '" + (user != null ? user.getShortName() : "null") + " is not authorized to perform this action."); } - checkCallingUserAuth(); // When AC is not in place the calling user should have SYSTEM_LABEL - // auth to do this action. + if (authorizationEnabled) { + checkCallingUserAuth(); // When AC is not in place the calling user should have + // SYSTEM_LABEL auth to do this action. + } for (ByteString authBS : auths) { labelAuths.add(authBS.toByteArray()); } @@ -984,7 +1043,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements try { // We do ACL check here as we create scanner directly on region. It will not make calls to // AccessController CP methods. - if (this.acOn && !isSystemOrSuperUser()) { + if (authorizationEnabled && accessControllerAvailable && !isSystemOrSuperUser()) { User requestingUser = VisibilityUtils.getActiveUser(); throw new AccessDeniedException("User '" + (requestingUser != null ? requestingUser.getShortName() : "null") @@ -1008,7 +1067,10 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements } private void checkCallingUserAuth() throws IOException { - if (!this.acOn) { + if (!authorizationEnabled) { // Redundant, but just in case + return; + } + if (!accessControllerAvailable) { User user = VisibilityUtils.getActiveUser(); if (user == null) { throw new IOException("Unable to retrieve calling user"); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java index 5854f0c..eddd1b0 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java @@ -61,16 +61,16 @@ class HBaseKerberosUtils { static Configuration getConfigurationWoPrincipal() { Configuration conf = HBaseConfiguration.create(); conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); - conf.set("hbase.security.authentication", "kerberos"); - conf.setBoolean("hbase.security.authorization", true); + conf.set(User.HBASE_SECURITY_CONF_KEY, "kerberos"); + conf.setBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true); return conf; } static Configuration getSecuredConfiguration() { Configuration conf = HBaseConfiguration.create(); conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); - conf.set("hbase.security.authentication", "kerberos"); - conf.setBoolean("hbase.security.authorization", true); + conf.set(User.HBASE_SECURITY_CONF_KEY, "kerberos"); + conf.setBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true); conf.set(KRB_KEYTAB_FILE, System.getProperty(KRB_KEYTAB_FILE)); conf.set(KRB_PRINCIPAL, System.getProperty(KRB_PRINCIPAL)); return conf; 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 8227b7f..fce5026 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 @@ -44,11 +44,13 @@ import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException; import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; import org.apache.hadoop.hbase.io.hfile.HFile; import org.apache.hadoop.hbase.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos; import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService; import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.CheckPermissionsRequest; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.security.access.Permission.Action; import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread; import com.google.common.collect.Lists; @@ -64,13 +66,7 @@ public class SecureTestUtil { private static final Log LOG = LogFactory.getLog(SecureTestUtil.class); private static final int WAIT_TIME = 10000; - public static void enableSecurity(Configuration conf) throws IOException { - conf.set("hadoop.security.authorization", "false"); - conf.set("hadoop.security.authentication", "simple"); - conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); - conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName() + - "," + SecureBulkLoadEndpoint.class.getName()); - conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); + public static void configureSuperuser(Configuration conf) throws IOException { // The secure minicluster creates separate service principals based on the // current user's name, one for each slave. We need to add all of these to // the superuser list or security won't function properly. We expect the @@ -85,8 +81,18 @@ public class SecureTestUtil { sb.append(currentUser); sb.append(".hfs."); sb.append(i); } conf.set("hbase.superuser", sb.toString()); + } + + public static void enableSecurity(Configuration conf) throws IOException { + conf.set("hadoop.security.authorization", "false"); + conf.set("hadoop.security.authentication", "simple"); + conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); + conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName() + + "," + SecureBulkLoadEndpoint.class.getName()); + conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); // Need HFile V3 for tags for security features conf.setInt(HFile.FORMAT_VERSION_KEY, 3); + configureSuperuser(conf); } public static void verifyConfiguration(Configuration conf) { @@ -620,4 +626,63 @@ public class SecureTestUtil { public static String convertToGroup(String group) { return AccessControlLists.GROUP_PREFIX + group; } + + public void checkGlobalPerms(HBaseTestingUtility testUtil, Permission.Action... actions) + throws IOException { + Permission[] perms = new Permission[actions.length]; + for (int i = 0; i < actions.length; i++) { + perms[i] = new Permission(actions[i]); + } + CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder(); + for (Action a : actions) { + request.addPermission(AccessControlProtos.Permission.newBuilder() + .setType(AccessControlProtos.Permission.Type.Global) + .setGlobalPermission( + AccessControlProtos.GlobalPermission.newBuilder() + .addAction(ProtobufUtil.toPermissionAction(a)).build())); + } + HTable acl = new HTable(testUtil.getConfiguration(), AccessControlLists.ACL_TABLE_NAME); + try { + BlockingRpcChannel channel = acl.coprocessorService(new byte[0]); + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(channel); + try { + protocol.checkPermissions(null, request.build()); + } catch (ServiceException se) { + ProtobufUtil.toIOException(se); + } + } finally { + acl.close(); + } + } + + public void checkTablePerms(HBaseTestingUtility testUtil, TableName table, byte[] family, + byte[] column, Permission.Action... actions) throws IOException { + Permission[] perms = new Permission[actions.length]; + for (int i = 0; i < actions.length; i++) { + perms[i] = new TablePermission(table, family, column, actions[i]); + } + checkTablePerms(testUtil, table, perms); + } + + public void checkTablePerms(HBaseTestingUtility testUtil, TableName table, Permission... perms) + throws IOException { + CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder(); + for (Permission p : perms) { + request.addPermission(ProtobufUtil.toPermission(p)); + } + HTable acl = new HTable(testUtil.getConfiguration(), table); + try { + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0])); + try { + protocol.checkPermissions(null, request.build()); + } catch (ServiceException se) { + ProtobufUtil.toIOException(se); + } + } finally { + acl.close(); + } + } + } 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 a43e208..825351e 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 @@ -130,7 +130,7 @@ public class TestAccessController extends SecureTestUtil { } @Rule public TestTableName TEST_TABLE = new TestTableName(); - private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static Configuration conf; // user with all permissions @@ -1593,63 +1593,6 @@ public class TestAccessController extends SecureTestUtil { verifyDenied(action, USER_CREATE, USER_RW, USER_NONE, USER_RO); } - public void checkGlobalPerms(Permission.Action... actions) throws IOException { - Permission[] perms = new Permission[actions.length]; - for (int i = 0; i < actions.length; i++) { - perms[i] = new Permission(actions[i]); - } - CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder(); - for (Action a : actions) { - request.addPermission(AccessControlProtos.Permission.newBuilder() - .setType(AccessControlProtos.Permission.Type.Global) - .setGlobalPermission( - AccessControlProtos.GlobalPermission.newBuilder() - .addAction(ProtobufUtil.toPermissionAction(a)).build())); - } - HTable acl = new HTable(conf, AccessControlLists.ACL_TABLE_NAME); - try { - BlockingRpcChannel channel = acl.coprocessorService(new byte[0]); - AccessControlService.BlockingInterface protocol = - AccessControlService.newBlockingStub(channel); - try { - protocol.checkPermissions(null, request.build()); - } catch (ServiceException se) { - ProtobufUtil.toIOException(se); - } - } finally { - acl.close(); - } - } - - public void checkTablePerms(TableName table, byte[] family, byte[] column, - Permission.Action... actions) throws IOException { - Permission[] perms = new Permission[actions.length]; - for (int i = 0; i < actions.length; i++) { - perms[i] = new TablePermission(table, family, column, actions[i]); - } - - checkTablePerms(table, perms); - } - - public void checkTablePerms(TableName table, Permission... perms) throws IOException { - CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder(); - for (Permission p : perms) { - request.addPermission(ProtobufUtil.toPermission(p)); - } - HTable acl = new HTable(conf, table); - try { - AccessControlService.BlockingInterface protocol = - AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0])); - try { - protocol.checkPermissions(null, request.build()); - } catch (ServiceException se) { - ProtobufUtil.toIOException(se); - } - } finally { - acl.close(); - } - } - @Test public void testCheckPermissions() throws Exception { // -------------------------------------- @@ -1657,7 +1600,7 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction globalAdmin = new AccessTestAction() { @Override public Void run() throws Exception { - checkGlobalPerms(Permission.Action.ADMIN); + checkGlobalPerms(TEST_UTIL, Permission.Action.ADMIN); return null; } }; @@ -1669,7 +1612,7 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction globalReadWrite = new AccessTestAction() { @Override public Void run() throws Exception { - checkGlobalPerms(Permission.Action.READ, Permission.Action.WRITE); + checkGlobalPerms(TEST_UTIL, Permission.Action.READ, Permission.Action.WRITE); return null; } }; @@ -1698,7 +1641,8 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction tableRead = new AccessTestAction() { @Override public Void run() throws Exception { - checkTablePerms(TEST_TABLE.getTableName(), null, null, Permission.Action.READ); + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), null, null, + Permission.Action.READ); return null; } }; @@ -1706,7 +1650,8 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction columnRead = new AccessTestAction() { @Override public Void run() throws Exception { - checkTablePerms(TEST_TABLE.getTableName(), TEST_FAMILY, null, Permission.Action.READ); + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, null, + Permission.Action.READ); return null; } }; @@ -1714,7 +1659,8 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction qualifierRead = new AccessTestAction() { @Override public Void run() throws Exception { - checkTablePerms(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, Permission.Action.READ); + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, + Permission.Action.READ); return null; } }; @@ -1722,9 +1668,11 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction multiQualifierRead = new AccessTestAction() { @Override public Void run() throws Exception { - checkTablePerms(TEST_TABLE.getTableName(), new Permission[] { - new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, Permission.Action.READ), - new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q2, Permission.Action.READ), }); + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), new Permission[] { + new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, + Permission.Action.READ), + new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q2, + Permission.Action.READ), }); return null; } }; @@ -1732,8 +1680,10 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction globalAndTableRead = new AccessTestAction() { @Override public Void run() throws Exception { - checkTablePerms(TEST_TABLE.getTableName(), new Permission[] { new Permission(Permission.Action.READ), - new TablePermission(TEST_TABLE.getTableName(), null, (byte[]) null, Permission.Action.READ), }); + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), + new Permission[] { new Permission(Permission.Action.READ), + new TablePermission(TEST_TABLE.getTableName(), null, (byte[]) null, + Permission.Action.READ), }); return null; } }; @@ -1741,7 +1691,7 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction noCheck = new AccessTestAction() { @Override public Void run() throws Exception { - checkTablePerms(TEST_TABLE.getTableName(), new Permission[0]); + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), new Permission[0]); return null; } }; @@ -1767,8 +1717,8 @@ public class TestAccessController extends SecureTestUtil { AccessTestAction familyReadWrite = new AccessTestAction() { @Override public Void run() throws Exception { - checkTablePerms(TEST_TABLE.getTableName(), TEST_FAMILY, null, Permission.Action.READ, - Permission.Action.WRITE); + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, null, + Permission.Action.READ, Permission.Action.WRITE); return null; } }; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestWithDisabledAuthorization.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestWithDisabledAuthorization.java new file mode 100644 index 0000000..b8fb9db --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestWithDisabledAuthorization.java @@ -0,0 +1,1001 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.access; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +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.Coprocessor; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.NamespaceDescriptor; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.TableNotFoundException; +import org.apache.hadoop.hbase.client.Append; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.Durability; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Increment; +import org.apache.hadoop.hbase.client.Mutation; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; +import org.apache.hadoop.hbase.coprocessor.ObserverContext; +import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; +import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment; +import org.apache.hadoop.hbase.filter.BinaryComparator; +import org.apache.hadoop.hbase.filter.CompareFilter; +import org.apache.hadoop.hbase.master.MasterCoprocessorHost; +import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription; +import org.apache.hadoop.hbase.regionserver.HRegion; +import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress; +import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost; +import org.apache.hadoop.hbase.regionserver.RegionScanner; +import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost; +import org.apache.hadoop.hbase.regionserver.wal.WALEdit; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.security.access.Permission.Action; +import org.apache.hadoop.hbase.testclassification.LargeTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.Pair; +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; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.google.common.collect.Lists; + +@Category(LargeTests.class) +public class TestWithDisabledAuthorization extends SecureTestUtil { + private static final Log LOG = LogFactory.getLog(TestWithDisabledAuthorization.class); + + static { + Logger.getLogger(AccessController.class).setLevel(Level.TRACE); + Logger.getLogger(AccessControlFilter.class).setLevel(Level.TRACE); + Logger.getLogger(TableAuthManager.class).setLevel(Level.TRACE); + } + + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + + private static final byte[] TEST_FAMILY = Bytes.toBytes("f1"); + private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2"); + private static final byte[] TEST_ROW = Bytes.toBytes("testrow"); + private static final byte[] TEST_Q1 = Bytes.toBytes("q1"); + private static final byte[] TEST_Q2 = Bytes.toBytes("q2"); + private static final byte[] TEST_Q3 = Bytes.toBytes("q3"); + private static final byte[] TEST_Q4 = Bytes.toBytes("q4"); + private static final byte[] ZERO = Bytes.toBytes(0L); + + private static MasterCoprocessorEnvironment CP_ENV; + private static AccessController ACCESS_CONTROLLER; + private static RegionServerCoprocessorEnvironment RSCP_ENV; + private RegionCoprocessorEnvironment RCP_ENV; + + @Rule public TestTableName TEST_TABLE = new TestTableName(); + + // default users + + // superuser + private static User SUPERUSER; + // user granted with all global permission + private static User USER_ADMIN; + // user with rw permissions on column family. + private static User USER_RW; + // user with read-only permissions + private static User USER_RO; + // user is table owner. will have all permissions on table + private static User USER_OWNER; + // user with create table permissions alone + private static User USER_CREATE; + // user with no permissions + private static User USER_NONE; + // user with only partial read-write perms (on family:q1 only) + private static User USER_QUAL; + + @BeforeClass + public static void setupBeforeClass() throws Exception { + Configuration conf = TEST_UTIL.getConfiguration(); + // Enable security + enableSecurity(conf); + // We expect 0.98 cell ACL semantics + conf.setBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, false); + // Enable EXEC permission checking + conf.setBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY, true); + // Verify enableSecurity sets up what we require + verifyConfiguration(conf); + + // Now, DISABLE only active authorization + conf.setBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, false); + + // Start the minicluster + TEST_UTIL.startMiniCluster(); + MasterCoprocessorHost cpHost = + TEST_UTIL.getMiniHBaseCluster().getMaster().getCoprocessorHost(); + cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); + ACCESS_CONTROLLER = (AccessController) cpHost.findCoprocessor(AccessController.class.getName()); + CP_ENV = cpHost.createEnvironment(AccessController.class, ACCESS_CONTROLLER, + Coprocessor.PRIORITY_HIGHEST, 1, conf); + RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0) + .getCoprocessorHost(); + RSCP_ENV = rsHost.createEnvironment(AccessController.class, ACCESS_CONTROLLER, + Coprocessor.PRIORITY_HIGHEST, 1, conf); + + // Wait for the ACL table to become available + TEST_UTIL.waitUntilAllRegionsAssigned(AccessControlLists.ACL_TABLE_NAME); + + // create a set of test users + SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); + USER_ADMIN = User.createUserForTesting(conf, "admin2", new String[0]); + USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]); + USER_CREATE = User.createUserForTesting(conf, "tbl_create", new String[0]); + USER_RW = User.createUserForTesting(conf, "rwuser", new String[0]); + USER_RO = User.createUserForTesting(conf, "rouser", new String[0]); + USER_QUAL = User.createUserForTesting(conf, "rwpartial", new String[0]); + USER_NONE = User.createUserForTesting(conf, "nouser", new String[0]); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + TEST_UTIL.shutdownMiniCluster(); + } + + @Before + public void setUp() throws Exception { + // Create the test table (owner added to the _acl_ table) + HBaseAdmin admin = TEST_UTIL.getHBaseAdmin(); + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); + hcd.setMaxVersions(100); + htd.addFamily(hcd); + htd.setOwner(USER_OWNER); + admin.createTable(htd, new byte[][] { Bytes.toBytes("s") }); + TEST_UTIL.waitUntilAllRegionsAssigned(TEST_TABLE.getTableName()); + + HRegion region = TEST_UTIL.getHBaseCluster().getRegions(TEST_TABLE.getTableName()).get(0); + RegionCoprocessorHost rcpHost = region.getCoprocessorHost(); + RCP_ENV = rcpHost.createEnvironment(AccessController.class, ACCESS_CONTROLLER, + Coprocessor.PRIORITY_HIGHEST, 1, TEST_UTIL.getConfiguration()); + + // Set up initial grants + + grantGlobal(TEST_UTIL, USER_ADMIN.getShortName(), + Permission.Action.ADMIN, + Permission.Action.CREATE, + Permission.Action.READ, + Permission.Action.WRITE); + + grantOnTable(TEST_UTIL, USER_RW.getShortName(), + TEST_TABLE.getTableName(), TEST_FAMILY, null, + Permission.Action.READ, + Permission.Action.WRITE); + + // USER_CREATE is USER_RW plus CREATE permissions + grantOnTable(TEST_UTIL, USER_CREATE.getShortName(), + TEST_TABLE.getTableName(), null, null, + Permission.Action.CREATE, + Permission.Action.READ, + Permission.Action.WRITE); + + grantOnTable(TEST_UTIL, USER_RO.getShortName(), + TEST_TABLE.getTableName(), TEST_FAMILY, null, + Permission.Action.READ); + + grantOnTable(TEST_UTIL, USER_QUAL.getShortName(), + TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, + Permission.Action.READ, + Permission.Action.WRITE); + + assertEquals(5, AccessControlLists.getTablePermissions(TEST_UTIL.getConfiguration(), + TEST_TABLE.getTableName()).size()); + } + + @After + public void tearDown() throws Exception { + // Clean the _acl_ table + try { + TEST_UTIL.deleteTable(TEST_TABLE.getTableName()); + } catch (TableNotFoundException ex) { + // Test deleted the table, no problem + LOG.info("Test deleted table " + TEST_TABLE.getTableName()); + } + // Verify all table/namespace permissions are erased + assertEquals(0, AccessControlLists.getTablePermissions(TEST_UTIL.getConfiguration(), + TEST_TABLE.getTableName()).size()); + assertEquals(0, AccessControlLists.getNamespacePermissions(TEST_UTIL.getConfiguration(), + TEST_TABLE.getTableName().getNamespaceAsString()).size()); + } + + @Test + public void testCheckPermissions() throws Exception { + + AccessTestAction checkGlobalAdmin = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkGlobalPerms(TEST_UTIL, Permission.Action.ADMIN); + return null; + } + }; + + verifyAllowed(checkGlobalAdmin, SUPERUSER, USER_ADMIN); + verifyDenied(checkGlobalAdmin, USER_OWNER, USER_CREATE, USER_RW, USER_RO, USER_QUAL, + USER_NONE); + + AccessTestAction checkGlobalRead = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkGlobalPerms(TEST_UTIL, Permission.Action.READ); + return null; + } + }; + + verifyAllowed(checkGlobalRead, SUPERUSER, USER_ADMIN); + verifyDenied(checkGlobalRead, USER_OWNER, USER_CREATE, USER_RW, USER_RO, USER_QUAL, + USER_NONE); + + AccessTestAction checkGlobalReadWrite = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkGlobalPerms(TEST_UTIL, Permission.Action.READ, Permission.Action.WRITE); + return null; + } + }; + + verifyAllowed(checkGlobalReadWrite, SUPERUSER, USER_ADMIN); + verifyDenied(checkGlobalReadWrite, USER_OWNER, USER_CREATE, USER_RW, USER_RO, USER_QUAL, + USER_NONE); + + AccessTestAction checkTableAdmin = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), null, null, + Permission.Action.ADMIN); + return null; + } + }; + + verifyAllowed(checkTableAdmin, SUPERUSER, USER_ADMIN, USER_OWNER); + verifyDenied(checkTableAdmin, USER_CREATE, USER_RW, USER_RO, USER_QUAL, USER_NONE); + + AccessTestAction checkTableCreate = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), null, null, + Permission.Action.CREATE); + return null; + } + }; + + verifyAllowed(checkTableCreate, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE); + verifyDenied(checkTableCreate, USER_RW, USER_RO, USER_QUAL, USER_NONE); + + AccessTestAction checkTableRead = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), null, null, + Permission.Action.READ); + return null; + } + }; + + verifyAllowed(checkTableRead, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE); + verifyDenied(checkTableRead, USER_RW, USER_RO, USER_QUAL, USER_NONE); + + AccessTestAction checkTableReadWrite = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), null, null, + Permission.Action.READ, Permission.Action.WRITE); + return null; + } + }; + + verifyAllowed(checkTableReadWrite, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE); + verifyDenied(checkTableReadWrite, USER_RW, USER_RO, USER_QUAL, USER_NONE); + + AccessTestAction checkColumnRead = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, null, + Permission.Action.READ); + return null; + } + }; + + verifyAllowed(checkColumnRead, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, USER_RW, + USER_RO); + verifyDenied(checkColumnRead, USER_QUAL, USER_NONE); + + AccessTestAction checkColumnReadWrite = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, null, + Permission.Action.READ, Permission.Action.WRITE); + return null; + } + }; + + verifyAllowed(checkColumnReadWrite, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, + USER_RW); + verifyDenied(checkColumnReadWrite, USER_RO, USER_QUAL, USER_NONE); + + AccessTestAction checkQualifierRead = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, + Permission.Action.READ); + return null; + } + }; + + verifyAllowed(checkQualifierRead, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, USER_RW, + USER_RO, USER_QUAL); + verifyDenied(checkQualifierRead, USER_NONE); + + AccessTestAction checkQualifierReadWrite = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, + Permission.Action.READ, Permission.Action.WRITE); + return null; + } + }; + + verifyAllowed(checkQualifierReadWrite, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, + USER_RW, USER_QUAL); + verifyDenied(checkQualifierReadWrite, USER_RO, USER_NONE); + + AccessTestAction checkMultiQualifierRead = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), new Permission[] { + new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, + Permission.Action.READ), + new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q2, + Permission.Action.READ), }); + return null; + } + }; + + verifyAllowed(checkMultiQualifierRead, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, + USER_RW, USER_RO); + verifyDenied(checkMultiQualifierRead, USER_QUAL, USER_NONE); + + AccessTestAction checkMultiQualifierReadWrite = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), new Permission[] { + new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q1, + Permission.Action.READ, Permission.Action.WRITE), + new TablePermission(TEST_TABLE.getTableName(), TEST_FAMILY, TEST_Q2, + Permission.Action.READ, Permission.Action.WRITE), }); + return null; + } + }; + + verifyAllowed(checkMultiQualifierReadWrite, SUPERUSER, USER_ADMIN, USER_OWNER, USER_CREATE, + USER_RW); + verifyDenied(checkMultiQualifierReadWrite, USER_RO, USER_QUAL, USER_NONE); + } + + /** Test grants and revocations with authorization disabled */ + @Test + public void testPassiveGrantRevoke() throws Exception { + + // Add a test user + + User tblUser = User.createUserForTesting(TEST_UTIL.getConfiguration(), "tbluser", + new String[0]); + + // If we check now, the test user won't have permissions + + AccessTestAction checkTableRead = new AccessTestAction() { + @Override + public Void run() throws Exception { + checkTablePerms(TEST_UTIL, TEST_TABLE.getTableName(), TEST_FAMILY, null, + Permission.Action.READ); + return null; + } + }; + + verifyDenied(tblUser, checkTableRead); + + // An actual read won't be denied + + AccessTestAction tableRead = new AccessTestAction() { + @Override + public Void run() throws Exception { + HTable t = new HTable(TEST_UTIL.getConfiguration(), TEST_TABLE.getTableName()); + try { + t.get(new Get(TEST_ROW).addFamily(TEST_FAMILY)); + } finally { + t.close(); + } + return null; + } + }; + + verifyAllowed(tblUser, tableRead); + + // Grant read perms to the test user + + grantOnTable(TEST_UTIL, tblUser.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY, + null, Permission.Action.READ); + + // Now both the permission check and actual op will succeed + + verifyAllowed(tblUser, checkTableRead); + verifyAllowed(tblUser, tableRead); + + // Revoke read perms from the test user + + revokeFromTable(TEST_UTIL, tblUser.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY, + null, Permission.Action.READ); + + // Now the permission check will indicate revocation but the actual op will still succeed + + verifyDenied(tblUser, checkTableRead); + verifyAllowed(tblUser, tableRead); + } + + /** Test master observer */ + @Test + public void testPassiveMasterOperations() throws Exception { + + // preCreateTable + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + htd.addFamily(new HColumnDescriptor(TEST_FAMILY)); + ACCESS_CONTROLLER.preCreateTable(ObserverContext.createAndPrepare(CP_ENV, null), htd, + null); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preModifyTable + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + htd.addFamily(new HColumnDescriptor(TEST_FAMILY)); + htd.addFamily(new HColumnDescriptor(TEST_FAMILY2)); + ACCESS_CONTROLLER.preModifyTable(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName(), htd); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preDeleteTable + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preDeleteTable(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName()); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preTruncateTable + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preTruncateTable(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName()); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preAddColumn + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY2); + ACCESS_CONTROLLER.preAddColumn(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName(), hcd); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preModifyColumn + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY2); + ACCESS_CONTROLLER.preModifyColumn(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName(), hcd); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preDeleteColumn + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preDeleteColumn(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName(), TEST_FAMILY2); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preEnableTable + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preEnableTable(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName()); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preDisableTable + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preDisableTable(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName()); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preMove + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HRegionInfo region = new HRegionInfo(TEST_TABLE.getTableName()); + ServerName srcServer = ServerName.valueOf("1.1.1.1", 1, 0); + ServerName destServer = ServerName.valueOf("2.2.2.2", 2, 0); + ACCESS_CONTROLLER.preMove(ObserverContext.createAndPrepare(CP_ENV, null), region, + srcServer, destServer); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preAssign + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HRegionInfo region = new HRegionInfo(TEST_TABLE.getTableName()); + ACCESS_CONTROLLER.preAssign(ObserverContext.createAndPrepare(CP_ENV, null), region); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preUnassign + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HRegionInfo region = new HRegionInfo(TEST_TABLE.getTableName()); + ACCESS_CONTROLLER.preUnassign(ObserverContext.createAndPrepare(CP_ENV, null), region, + true); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preBalance + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preBalance(ObserverContext.createAndPrepare(CP_ENV, null)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preBalanceSwitch + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preBalanceSwitch(ObserverContext.createAndPrepare(CP_ENV, null), + true); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preSnapshot + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + SnapshotDescription snapshot = SnapshotDescription.newBuilder() + .setName("foo") + .build(); + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + ACCESS_CONTROLLER.preSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), + snapshot, htd); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preCloneSnapshot + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + SnapshotDescription snapshot = SnapshotDescription.newBuilder() + .setName("foo") + .build(); + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + ACCESS_CONTROLLER.preCloneSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), + snapshot, htd); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preRestoreSnapshot + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + SnapshotDescription snapshot = SnapshotDescription.newBuilder() + .setName("foo") + .build(); + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + ACCESS_CONTROLLER.preRestoreSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), + snapshot, htd); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preDeleteSnapshot + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + SnapshotDescription snapshot = SnapshotDescription.newBuilder() + .setName("foo") + .build(); + ACCESS_CONTROLLER.preDeleteSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), + snapshot); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preGetTableDescriptors + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + List tableNamesList = Lists.newArrayList(); + tableNamesList.add(TEST_TABLE.getTableName()); + List descriptors = Lists.newArrayList(); + ACCESS_CONTROLLER.preGetTableDescriptors(ObserverContext.createAndPrepare(CP_ENV, null), + tableNamesList, descriptors); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preCreateNamespace + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + NamespaceDescriptor ns = NamespaceDescriptor.create("test").build(); + ACCESS_CONTROLLER.preCreateNamespace(ObserverContext.createAndPrepare(CP_ENV, null), + ns); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preDeleteNamespace + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preDeleteNamespace(ObserverContext.createAndPrepare(CP_ENV, null), + "test"); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preModifyNamespace + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + NamespaceDescriptor ns = NamespaceDescriptor.create("test").build(); + ACCESS_CONTROLLER.preModifyNamespace(ObserverContext.createAndPrepare(CP_ENV, null), + ns); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + } + + /** Test region server observer */ + @Test + public void testPassiveRegionServerOperations() throws Exception { + // preStopRegionServer + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preStopRegionServer(ObserverContext.createAndPrepare(RSCP_ENV, null)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preMerge + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); + HRegion region_a = mock(HRegion.class); + when(region_a.getTableDesc()).thenReturn(htd); + HRegion region_b = mock(HRegion.class); + when(region_b.getTableDesc()).thenReturn(htd); + ACCESS_CONTROLLER.preMerge(ObserverContext.createAndPrepare(RSCP_ENV, null), region_a, + region_b); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preRollWALWriterRequest + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preRollWALWriterRequest(ObserverContext.createAndPrepare(RSCP_ENV, + null)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + } + + /** Test region observer */ + @Test + public void testPassiveRegionOperations() throws Exception { + + // preOpen + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preOpen(ObserverContext.createAndPrepare(RCP_ENV, null)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preFlush + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preFlush(ObserverContext.createAndPrepare(RCP_ENV, null)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preSplit + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preSplit(ObserverContext.createAndPrepare(RCP_ENV, null)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preGetClosestRowBefore + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preGetClosestRowBefore(ObserverContext.createAndPrepare(RCP_ENV, null), + TEST_ROW, TEST_FAMILY, new Result()); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preGetOp + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + List cells = Lists.newArrayList(); + ACCESS_CONTROLLER.preGetOp(ObserverContext.createAndPrepare(RCP_ENV, null), + new Get(TEST_ROW), cells); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preExists + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preExists(ObserverContext.createAndPrepare(RCP_ENV, null), + new Get(TEST_ROW), true); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // prePut + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.prePut(ObserverContext.createAndPrepare(RCP_ENV, null), + new Put(TEST_ROW), new WALEdit(), Durability.USE_DEFAULT); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preDelete + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preDelete(ObserverContext.createAndPrepare(RCP_ENV, null), + new Delete(TEST_ROW), new WALEdit(), Durability.USE_DEFAULT); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preBatchMutate + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preBatchMutate(ObserverContext.createAndPrepare(RCP_ENV, null), + new MiniBatchOperationInProgress(null, null, null, 0, 0)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preCheckAndPut + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preCheckAndPut(ObserverContext.createAndPrepare(RCP_ENV, null), + TEST_ROW, TEST_FAMILY, TEST_Q1, CompareFilter.CompareOp.EQUAL, + new BinaryComparator("foo".getBytes()), new Put(TEST_ROW), true); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preCheckAndDelete + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preCheckAndDelete(ObserverContext.createAndPrepare(RCP_ENV, null), + TEST_ROW, TEST_FAMILY, TEST_Q1, CompareFilter.CompareOp.EQUAL, + new BinaryComparator("foo".getBytes()), new Delete(TEST_ROW), true); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preAppend + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preAppend(ObserverContext.createAndPrepare(RCP_ENV, null), + new Append(TEST_ROW)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preIncrement + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preIncrement(ObserverContext.createAndPrepare(RCP_ENV, null), + new Increment(TEST_ROW)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preScannerOpen + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preScannerOpen(ObserverContext.createAndPrepare(RCP_ENV, null), + new Scan(), mock(RegionScanner.class)); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preBulkLoadHFile + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + List> paths = Lists.newArrayList(); + ACCESS_CONTROLLER.preBulkLoadHFile(ObserverContext.createAndPrepare(RCP_ENV, null), + paths); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + } + + @Test + public void testPassiveCellPermissions() throws Exception { + final Configuration conf = TEST_UTIL.getConfiguration(); + + // 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(USER_NONE.getShortName(), new Permission(Action.READ)); + t.put(p); + // with rw ACL + p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q2, ZERO); + p.setACL(USER_NONE.getShortName(), new Permission(Action.READ, 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); + return null; + } finally { + t.close(); + } + } + }, USER_OWNER); + + // 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); + try { + Result result = null; + do { + result = scanner.next(); + if (result != null) { + scanResults.addAll(result.listCells()); + } + } while (result != null); + } finally { + scanner.close(); + } + } finally { + t.close(); + } + return scanResults; + } + }; + + // owner will see all values + scanResults.clear(); + verifyAllowed(scanAction, USER_OWNER); + assertEquals(4, scanResults.size()); + + // other user will also see 4 values + // if cell filtering was active, we would only see 2 values + scanResults.clear(); + verifyAllowed(scanAction, USER_NONE); + assertEquals(4, scanResults.size()); + } + +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestWithDisabledAuthorization.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestWithDisabledAuthorization.java new file mode 100644 index 0000000..bd7bc36 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestWithDisabledAuthorization.java @@ -0,0 +1,251 @@ +/** + * 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.visibility; + +import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME; +import static org.junit.Assert.*; + +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.security.access.SecureTestUtil; +import org.apache.hadoop.hbase.testclassification.LargeTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; + +import com.google.protobuf.ByteString; + +@Category(LargeTests.class) +public class TestWithDisabledAuthorization { + + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + + private static final String CONFIDENTIAL = "confidential"; + private static final String SECRET = "secret"; + private static final String PRIVATE = "private"; + private static final byte[] TEST_FAMILY = Bytes.toBytes("test"); + private static final byte[] TEST_QUALIFIER = Bytes.toBytes("q"); + private static final byte[] ZERO = Bytes.toBytes(0L); + + + @Rule + public final TestName TEST_NAME = new TestName(); + + private static User SUPERUSER; + private static User USER_RW; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + Configuration conf = TEST_UTIL.getConfiguration(); + + // Set up superuser + SecureTestUtil.configureSuperuser(conf); + + // Install the VisibilityController as a system processor + VisibilityTestUtil.enableVisiblityLabels(conf); + + // Now, DISABLE active authorization + conf.setBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, false); + + TEST_UTIL.startMiniCluster(); + + // Wait for the labels table to become available + TEST_UTIL.waitUntilAllRegionsAssigned(LABELS_TABLE_NAME); + + // create a set of test users + SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); + USER_RW = User.createUserForTesting(conf, "rwuser", new String[0]); + + // Define test labels + SUPERUSER.runAs(new PrivilegedExceptionAction() { + public Void run() throws Exception { + try { + VisibilityClient.addLabels(TEST_UTIL.getConfiguration(), + new String[] { SECRET, CONFIDENTIAL, PRIVATE }); + VisibilityClient.setAuths(TEST_UTIL.getConfiguration(), + new String[] { SECRET, CONFIDENTIAL }, + USER_RW.getShortName()); + } catch (Throwable t) { + fail("Should not have failed"); + } + return null; + } + }); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + TEST_UTIL.shutdownMiniCluster(); + } + + @Test + public void testManageUserAuths() throws Throwable { + // Even though authorization is disabled, we should be able to manage user auths + + SUPERUSER.runAs(new PrivilegedExceptionAction() { + public Void run() throws Exception { + try { + VisibilityClient.setAuths(TEST_UTIL.getConfiguration(), + new String[] { SECRET, CONFIDENTIAL }, + USER_RW.getShortName()); + } catch (Throwable t) { + fail("Should not have failed"); + } + return null; + } + }); + + PrivilegedExceptionAction> getAuths = + new PrivilegedExceptionAction>() { + public List run() throws Exception { + GetAuthsResponse authsResponse = null; + try { + authsResponse = VisibilityClient.getAuths(TEST_UTIL.getConfiguration(), + USER_RW.getShortName()); + } catch (Throwable t) { + fail("Should not have failed"); + } + List authsList = new ArrayList(); + for (ByteString authBS : authsResponse.getAuthList()) { + authsList.add(Bytes.toString(authBS.toByteArray())); + } + return authsList; + } + }; + + List authsList = SUPERUSER.runAs(getAuths); + assertEquals(2, authsList.size()); + assertTrue(authsList.contains(SECRET)); + assertTrue(authsList.contains(CONFIDENTIAL)); + + SUPERUSER.runAs(new PrivilegedExceptionAction() { + public Void run() throws Exception { + try { + VisibilityClient.clearAuths(TEST_UTIL.getConfiguration(), + new String[] { SECRET }, + USER_RW.getShortName()); + } catch (Throwable t) { + fail("Should not have failed"); + } + return null; + } + }); + + authsList = SUPERUSER.runAs(getAuths); + assertEquals(1, authsList.size()); + assertTrue(authsList.contains(CONFIDENTIAL)); + + SUPERUSER.runAs(new PrivilegedExceptionAction() { + public Void run() throws Exception { + try { + VisibilityClient.clearAuths(TEST_UTIL.getConfiguration(), + new String[] { CONFIDENTIAL }, + USER_RW.getShortName()); + } catch (Throwable t) { + fail("Should not have failed"); + } + return null; + } + }); + + authsList = SUPERUSER.runAs(getAuths); + assertEquals(0, authsList.size()); + } + + @Test + public void testPassiveVisibility() throws Exception { + // No values should be filtered regardless of authorization if we are passive + HTable t = createTableAndWriteDataWithLabels( + TableName.valueOf(TEST_NAME.getMethodName()), + SECRET, + PRIVATE, + SECRET + "|" + CONFIDENTIAL, + PRIVATE + "|" + CONFIDENTIAL); + try { + Scan s = new Scan(); + s.setAuthorizations(new Authorizations()); + ResultScanner scanner = t.getScanner(s); + try { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } finally { + scanner.close(); + } + s = new Scan(); + s.setAuthorizations(new Authorizations(SECRET)); + scanner = t.getScanner(s); + try { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } finally { + scanner.close(); + } + s = new Scan(); + s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); + scanner = t.getScanner(s); + try { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } finally { + scanner.close(); + } + s = new Scan(); + s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL, PRIVATE)); + scanner = t.getScanner(s); + try { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } finally { + scanner.close(); + } + } finally { + t.close(); + } + } + + static HTable createTableAndWriteDataWithLabels(TableName tableName, String... labelExps) + throws Exception { + List puts = new ArrayList(); + for (int i = 0; i < labelExps.length; i++) { + Put put = new Put(Bytes.toBytes("row" + (i+1))); + put.add(TEST_FAMILY, TEST_QUALIFIER, HConstants.LATEST_TIMESTAMP, ZERO); + put.setCellVisibility(new CellVisibility(labelExps[i])); + puts.add(put); + } + HTable table = TEST_UTIL.createTable(tableName, TEST_FAMILY); + table.put(puts); + return table; + } +} -- 2.2.2