From ed703762aeebfce9855858834a3fa6a4f51a8977 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Thu, 9 Apr 2015 13:50:35 -0700 Subject: [PATCH] HBASE-13275 Setting hbase.security.authorization to false does not disable authorization --- .../apache/hadoop/hbase/security/User.java | 2 + .../security/access/AccessController.java | 308 +++-- .../visibility/VisibilityController.java | 100 +- .../hbase/security/HBaseKerberosUtils.java | 8 +- .../hbase/security/access/SecureTestUtil.java | 78 +- .../security/access/TestAccessController.java | 89 +- .../access/TestWithDisabledAuthorization.java | 1079 +++++++++++++++++ .../TestWithDisabledAuthorization.java | 237 ++++ 8 files changed, 1679 insertions(+), 222 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 a5ac51a78dc..58a3c665f36 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 @@ -52,6 +52,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"; protected UserGroupInformation ugi; 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 cd8f5fff499..7b306c05fa5 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 @@ -165,11 +165,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 */ @@ -178,25 +178,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 Region getRegion() { @@ -405,8 +410,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; @@ -422,7 +427,7 @@ public class AccessController extends BaseMasterAndRegionObserver } } logResult(result); - if (!result.isAllowed()) { + if (authorizationEnabled && !result.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); } } @@ -455,7 +460,7 @@ public class AccessController extends BaseMasterAndRegionObserver } } logResult(result); - if (!result.isAllowed()) { + if (authorizationEnabled && !result.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); } } @@ -485,7 +490,7 @@ public class AccessController extends BaseMasterAndRegionObserver } } logResult(result); - if (!result.isAllowed()) { + if (authorizationEnabled && !result.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); } } @@ -500,31 +505,6 @@ public class AccessController extends BaseMasterAndRegionObserver requireGlobalPermission(request, perm, null, null); } - /** - * 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 @@ -545,9 +525,11 @@ public class AccessController extends BaseMasterAndRegionObserver result = AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap); result.getParams().setTableName(tableName).setFamilies(familyMap); logResult(result); - 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() + ")"); + } } } @@ -570,9 +552,11 @@ public class AccessController extends BaseMasterAndRegionObserver authResult = AuthResult.deny(request, "Global check failed", user, perm, null); authResult.getParams().setNamespace(namespace); logResult(authResult); - 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() + ")"); + } } } @@ -598,7 +582,7 @@ public class AccessController extends BaseMasterAndRegionObserver } } logResult(result); - if (!result.isAllowed()) { + if (authorizationEnabled && !result.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); } @@ -629,7 +613,7 @@ public class AccessController extends BaseMasterAndRegionObserver } } logResult(result); - if (!result.isAllowed()) { + if (authorizationEnabled && !result.isAllowed()) { throw new AccessDeniedException("Insufficient permissions " + result.toContextString()); } @@ -764,6 +748,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()); @@ -899,8 +885,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) @@ -928,6 +920,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); @@ -1064,6 +1061,7 @@ public class AccessController extends BaseMasterAndRegionObserver public void preTruncateTable(ObserverContext c, final TableName tableName) throws IOException { requirePermission("truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE); + final Configuration conf = c.getEnvironment().getConfiguration(); User.runAsLoginUser(new PrivilegedExceptionAction() { @Override @@ -1163,8 +1161,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); } @@ -1243,6 +1245,7 @@ public class AccessController extends BaseMasterAndRegionObserver final SnapshotDescription snapshot) throws IOException { if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, getActiveUser())) { // list it, if user is the owner of snapshot + // TODO: We are not logging this for audit } else { requirePermission("listSnapshot", Action.ADMIN); } @@ -1272,6 +1275,7 @@ public class AccessController extends BaseMasterAndRegionObserver final SnapshotDescription snapshot) throws IOException { if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, getActiveUser())) { // Snapshot owner is allowed to delete the snapshot + // TODO: We are not logging this for audit } else { requirePermission("deleteSnapshot", Action.ADMIN); } @@ -1439,8 +1443,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()); } } @@ -1483,26 +1488,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.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 { @@ -1510,31 +1518,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.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)"); } @@ -1557,14 +1568,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); @@ -1572,10 +1584,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) { @@ -1616,8 +1629,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()); } } } @@ -1640,18 +1654,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()); } } } @@ -1673,9 +1687,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, @@ -1684,10 +1699,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) { @@ -1719,7 +1736,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()); } } @@ -1748,8 +1765,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; @@ -1776,7 +1794,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()); } } @@ -1801,7 +1819,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; @@ -1810,9 +1828,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); @@ -1820,10 +1839,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) { @@ -1832,6 +1853,7 @@ public class AccessController extends BaseMasterAndRegionObserver throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); } } + return null; } @@ -1852,8 +1874,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; @@ -1863,10 +1886,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, @@ -1875,10 +1899,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) { @@ -1887,6 +1913,7 @@ public class AccessController extends BaseMasterAndRegionObserver throw new DoNotRetryIOException("Cell ACLs cannot be persisted"); } } + return null; } @@ -1907,8 +1934,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; @@ -1990,7 +2018,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; @@ -2025,7 +2054,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!"); } } @@ -2119,11 +2148,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() { @@ -2170,7 +2199,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()); @@ -2264,9 +2293,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)) { @@ -2276,7 +2308,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); @@ -2287,12 +2320,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() + ")"); + } } } } @@ -2335,6 +2393,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 5fb0234e576..6e659ef47c0 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 @@ -125,7 +125,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; @@ -137,6 +137,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 { @@ -148,6 +152,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 @@ -200,6 +210,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); } @@ -208,6 +221,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); } @@ -216,6 +232,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); } @@ -224,6 +243,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); } @@ -232,6 +254,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); } @@ -244,7 +269,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()); @@ -298,9 +324,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 @@ -319,7 +348,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); @@ -366,6 +395,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; + } + CellVisibility cellVisibility = null; try { cellVisibility = delete.getCellVisibility(); @@ -513,6 +547,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; + } Region region = e.getEnvironment().getRegion(); Authorizations authorizations = null; try { @@ -547,6 +585,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; + } Region region = ctx.getEnvironment().getRegion(); TableName table = region.getRegionInfo().getTable(); if (table.isSystemTable()) { @@ -599,16 +641,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; } Region region = e.getEnvironment().getRegion(); Authorizations authorizations = null; @@ -657,6 +703,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"); @@ -668,6 +718,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"); @@ -691,7 +745,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 @@ -730,7 +784,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(); @@ -790,8 +846,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()); } @@ -862,7 +919,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") @@ -905,13 +962,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()); } @@ -955,7 +1014,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") @@ -979,7 +1038,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 1f9f4f55a83..237efe95790 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 @@ -62,8 +62,8 @@ public class HBaseKerberosUtils { public 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; } @@ -75,8 +75,8 @@ public class HBaseKerberosUtils { public static void setSecuredConfiguration(Configuration conf) { 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)); conf.set(MASTER_KRB_PRINCIPAL, System.getProperty(KRB_PRINCIPAL)); 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 b30c77058b4..fb06c05457a 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 @@ -55,11 +55,13 @@ import org.apache.hadoop.hbase.coprocessor.ObserverContext; import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 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.Region; 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; @@ -75,14 +77,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() + - "," + MasterSyncObserver.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 @@ -97,8 +92,19 @@ 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() + + "," + MasterSyncObserver.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) { @@ -716,4 +722,60 @@ public class SecureTestUtil { public static String convertToGroup(String group) { return AccessControlLists.GROUP_PREFIX + group; } + + public static 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())); + } + try(Connection conn = ConnectionFactory.createConnection(testUtil.getConfiguration()); + Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) { + 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); + } + } + } + + public static 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 static void checkTablePerms(HBaseTestingUtility testUtil, TableName table, + Permission... perms) throws IOException { + CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder(); + for (Permission p : perms) { + request.addPermission(ProtobufUtil.toPermission(p)); + } + + try(Connection conn = ConnectionFactory.createConnection(testUtil.getConfiguration()); + Table acl = conn.getTable(table)) { + AccessControlService.BlockingInterface protocol = + AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0])); + try { + protocol.checkPermissions(null, request.build()); + } catch (ServiceException se) { + ProtobufUtil.toIOException(se); + } + } + } + } 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 58b8587707b..ff0a720a405 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 @@ -138,7 +138,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; /** The systemUserConnection created here is tied to the system user. In case, you are planning @@ -1537,60 +1537,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())); - } - try(Connection conn = ConnectionFactory.createConnection(conf); - Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) { - 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); - } - } - } - - 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)); - } - - try(Connection conn = ConnectionFactory.createConnection(conf); - Table acl = conn.getTable(table)) { - AccessControlService.BlockingInterface protocol = - AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0])); - try { - protocol.checkPermissions(null, request.build()); - } catch (ServiceException se) { - ProtobufUtil.toIOException(se); - } - } - } - @Test public void testCheckPermissions() throws Exception { // -------------------------------------- @@ -1598,7 +1544,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; } }; @@ -1610,7 +1556,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; } }; @@ -1639,7 +1585,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; } }; @@ -1647,7 +1594,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; } }; @@ -1655,7 +1603,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; } }; @@ -1663,9 +1612,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; } }; @@ -1673,8 +1624,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; } }; @@ -1682,7 +1635,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; } }; @@ -1708,8 +1661,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 00000000000..7c2cb281f67 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestWithDisabledAuthorization.java @@ -0,0 +1,1079 @@ +/* + * 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.Admin; +import org.apache.hadoop.hbase.client.Append; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +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.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.client.Table; +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.protobuf.generated.QuotaProtos.Quotas; +import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress; +import org.apache.hadoop.hbase.regionserver.Region; +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.testclassification.SecurityTests; +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({SecurityTests.class, 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().getMasterCoprocessorHost(); + 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) + .getRegionServerCoprocessorHost(); + 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) + Admin 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()); + + Region 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 { + deleteTable(TEST_UTIL, 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 { + try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); + Table t = conn.getTable(TEST_TABLE.getTableName())) { + t.get(new Get(TEST_ROW).addFamily(TEST_FAMILY)); + } + 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); + + // preListSnapshot + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + SnapshotDescription snapshot = SnapshotDescription.newBuilder() + .setName("foo") + .build(); + ACCESS_CONTROLLER.preListSnapshot(ObserverContext.createAndPrepare(CP_ENV, null), + snapshot); + 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); + + // preGetTableNames + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + List descriptors = Lists.newArrayList(); + ACCESS_CONTROLLER.preGetTableNames(ObserverContext.createAndPrepare(CP_ENV, null), + 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); + + // preGetNamespaceDescriptor + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preGetNamespaceDescriptor(ObserverContext.createAndPrepare(CP_ENV, + null), + "test"); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preListNamespaceDescriptors + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + List descriptors = Lists.newArrayList(); + ACCESS_CONTROLLER.preListNamespaceDescriptors(ObserverContext.createAndPrepare(CP_ENV, + null), + descriptors); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preSetUserQuota + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + Quotas quotas = Quotas.newBuilder().build(); + ACCESS_CONTROLLER.preSetUserQuota(ObserverContext.createAndPrepare(CP_ENV, null), + "testuser", quotas); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preSetTableQuota + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + Quotas quotas = Quotas.newBuilder().build(); + ACCESS_CONTROLLER.preSetTableQuota(ObserverContext.createAndPrepare(CP_ENV, null), + TEST_TABLE.getTableName(), quotas); + return null; + } + }, SUPERUSER, USER_ADMIN, USER_RW, USER_RO, USER_OWNER, USER_CREATE, USER_QUAL, USER_NONE); + + // preSetNamespaceQuota + verifyAllowed(new AccessTestAction() { + @Override + public Object run() throws Exception { + Quotas quotas = Quotas.newBuilder().build(); + ACCESS_CONTROLLER.preSetNamespaceQuota(ObserverContext.createAndPrepare(CP_ENV, null), + "test", quotas); + 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()); + Region region_a = mock(Region.class); + when(region_a.getTableDesc()).thenReturn(htd); + Region region_b = mock(Region.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 { + try(Connection connection = ConnectionFactory.createConnection(conf); + Table t = connection.getTable(TEST_TABLE.getTableName())) { + 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; + } + }, 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); + Connection connection = ConnectionFactory.createConnection(conf); + Table t = connection.getTable(TEST_TABLE.getTableName()); + try { + ResultScanner scanner = t.getScanner(scan); + Result result = null; + do { + result = scanner.next(); + if (result != null) { + scanResults.addAll(result.listCells()); + } + } while (result != null); + } finally { + t.close(); + connection.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 00000000000..d5e83de5aa2 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestWithDisabledAuthorization.java @@ -0,0 +1,237 @@ +/** + * 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.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.client.Table; +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.testclassification.SecurityTests; +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({SecurityTests.class, 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 + try (Table t = createTableAndWriteDataWithLabels( + TableName.valueOf(TEST_NAME.getMethodName()), + SECRET, + PRIVATE, + SECRET + "|" + CONFIDENTIAL, + PRIVATE + "|" + CONFIDENTIAL)) { + Scan s = new Scan(); + s.setAuthorizations(new Authorizations()); + try (ResultScanner scanner = t.getScanner(s)) { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } + s = new Scan(); + s.setAuthorizations(new Authorizations(SECRET)); + try (ResultScanner scanner = t.getScanner(s)) { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } + s = new Scan(); + s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL)); + try (ResultScanner scanner = t.getScanner(s)) { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } + s = new Scan(); + s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL, PRIVATE)); + try (ResultScanner scanner = t.getScanner(s)) { + Result[] next = scanner.next(10); + assertEquals(next.length, 4); + } + } + } + + static Table 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.addColumn(TEST_FAMILY, TEST_QUALIFIER, HConstants.LATEST_TIMESTAMP, ZERO); + put.setCellVisibility(new CellVisibility(labelExps[i])); + puts.add(put); + } + Table table = TEST_UTIL.createTable(tableName, TEST_FAMILY); + table.put(puts); + return table; + } +}