in hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationCoprocessor.java [982:1273]
ColumnFamilyAccessResult evaluateAccess(ObserverContext<?> ctx, String operation, Action action, final RegionCoprocessorEnvironment env, final Map<byte[], ? extends Collection<?>> familyMap, String commandStr) throws AccessDeniedException {
if (LOG.isDebugEnabled()) {
LOG.debug("evaluateAccess: isColumnAuthOptimizationEnabled={}", hbasePlugin.getPropertyIsColumnAuthOptimizationEnabled());
}
String access = authUtils.getAccess(action);
User user = getActiveUser(ctx);
String userName = userUtils.getUserAsString(user);
final Map<String, Set<String>> colFamiliesForDebugLoggingOnly;
if (LOG.isDebugEnabled()) {
colFamiliesForDebugLoggingOnly = getColumnFamilies(familyMap);
LOG.debug("evaluateAccess: entered: user[{}], Operation[{}], access[{}], families[{}]", userName, operation, access, colFamiliesForDebugLoggingOnly);
} else {
colFamiliesForDebugLoggingOnly = Collections.emptyMap();
}
byte[] tableBytes = getTableName(env);
if (tableBytes == null || tableBytes.length == 0) {
LOG.debug("evaluateAccess: Unexpected: Couldn't get table from RegionCoprocessorEnvironment. Access denied, not audited");
throw new AccessDeniedException("Insufficient permissions for operation '" + operation + "',action: " + action);
}
String table = Bytes.toString(tableBytes);
ColumnFamilyAccessResult result;
if (canSkipAccessCheck(user, operation, access, table) || canSkipAccessCheck(user, operation, access, env)) {
LOG.debug("evaluateAccess: exiting: isKnownAccessPattern returned true: access allowed, not audited");
result = new ColumnFamilyAccessResult(true, true, null, null, null, null, null);
LOG.debug("evaluateAccess: exiting: user[{}], Operation[{}], access[{}], families[{}], verdict[{}]", userName, operation, access, colFamiliesForDebugLoggingOnly, result);
return result;
}
// let's create a session that would be reused. Set things on it that won't change.
HbaseAuditHandler auditHandler = factory.getAuditHandler();
AuthorizationSession session = new AuthorizationSession(hbasePlugin)
.operation(operation)
.otherInformation(commandStr)
.remoteAddress(getRemoteAddress())
.auditHandler(auditHandler)
.user(user)
.access(access)
.table(table);
LOG.debug("evaluateAccess: families to process: {}", colFamiliesForDebugLoggingOnly);
if (familyMap == null || familyMap.isEmpty()) {
LOG.debug("evaluateAccess: Null or empty families collection, ok. Table level access is desired");
session.buildRequest().authorize();
boolean authorized = session.isAuthorized();
String reason = "";
if (authorized) {
LOG.debug("evaluateAccess: table level access granted [{}]", table);
} else {
reason = String.format("Insufficient permissions for user ‘%s',action: %s, tableName:%s, no column families found.", user.getName(), operation, table);
}
AuthzAuditEvent event = auditHandler.getAndDiscardMostRecentEvent(); // this could be null, of course, depending on audit settings of table.
// if authorized then pass captured events as access allowed set else as access denied set.
result = new ColumnFamilyAccessResult(authorized, authorized, authorized ? Collections.singletonList(event) : null, null, authorized ? null : event, reason, null);
LOG.debug("evaluateAccess: exiting: user[{}], Operation[{}], access[{}], families[{}], verdict[{}]", userName, operation, access, colFamiliesForDebugLoggingOnly, result);
return result;
} else {
LOG.debug("evaluateAccess: Families collection not null. Skipping table-level check, will do finer level check");
}
boolean everythingIsAccessible = true;
boolean somethingIsAccessible = false;
/*
* we would have to accumulate audits of all successful accesses and any one denial (which in our case ends up being the last denial)
* We need to keep audit events for family level access check seperate because we don't want them logged in some cases.
*/
List<AuthzAuditEvent> authorizedEvents = new ArrayList<>();
List<AuthzAuditEvent> familyLevelAccessEvents = new ArrayList<>();
AuthzAuditEvent deniedEvent = null;
String denialReason = null;
// we need to cache the auths results so that we can create a filter, if needed
Map<String, Set<String>> columnsAccessAllowed = new HashMap<>();
Set<String> familesAccessAllowed = new HashSet<>();
Set<String> familesAccessDenied = new HashSet<>();
Set<String> familesAccessIndeterminate = new HashSet<>();
Set<String> familiesFullyAuthorized = new HashSet<>();
for (Map.Entry<byte[], ? extends Collection<?>> anEntry : familyMap.entrySet()) {
String family = Bytes.toString(anEntry.getKey());
session.columnFamily(family);
LOG.debug("evaluateAccess: Processing family: {}", family);
Collection<?> columns = anEntry.getValue();
if (columns == null || columns.isEmpty()) {
LOG.debug("evaluateAccess: columns collection null or empty, ok. Family level access is desired.");
session.column(null) // zap stale column from prior iteration of this loop, if any
.buildRequest()
.authorize();
AuthzAuditEvent auditEvent = auditHandler.getAndDiscardMostRecentEvent(); // capture it only for success
final boolean isColumnFamilyAuthorized = session.isAuthorized();
if (auditEvent != null) {
if (isColumnFamilyAuthorized) {
familyLevelAccessEvents.add(auditEvent);
} else {
if (deniedEvent == null) { // we need to capture just one denial event
LOG.debug("evaluateAccess: Setting denied access audit event with last auth failure audit event.");
deniedEvent = auditEvent;
}
}
}
LOG.debug("evaluateAccess: family level access for [{}] is evaluated to {}. Checking if [{}] descendants have access.", family, isColumnFamilyAuthorized, family);
// buildRequest again since resourceMatchingScope changed
// reset ResourceMatchingScope to SELF, ignoreDescendantDeny to true
session.resourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF_OR_DESCENDANTS)
.ignoreDescendantDeny(false)
.buildRequest()
.authorize();
auditEvent = auditHandler.getAndDiscardMostRecentEvent(); // capture it only for failure
if (session.isAuthorized()) {
LOG.debug("evaluateAccess: [{}] descendants have access", family);
somethingIsAccessible = true;
if (isColumnFamilyAuthorized) {
familesAccessAllowed.add(family);
if (auditEvent != null) {
LOG.debug("evaluateAccess: adding to family-level-access-granted-event-set");
familyLevelAccessEvents.add(auditEvent);
}
} else {
familesAccessIndeterminate.add(family);
LOG.debug("evaluateAccess: has partial access (of some type) in family [{}]", family);
everythingIsAccessible = false;
if (auditEvent != null && deniedEvent == null) { // we need to capture just one denial event
LOG.debug("evaluateAccess: Setting denied access audit event with last auth failure audit event.");
deniedEvent = auditEvent;
}
}
} else {
everythingIsAccessible = false;
if (isColumnFamilyAuthorized) {
somethingIsAccessible = true;
familesAccessIndeterminate.add(family);
LOG.debug("evaluateAccess: has partial access (of some type) in family [{}]", family);
if (auditEvent != null && deniedEvent == null) { // we need to capture just one denial event
LOG.debug("evaluateAccess: Setting denied access audit event with last auth failure audit event.");
deniedEvent = auditEvent;
}
} else {
LOG.debug("evaluateAccess: has no access of [{}] type in family [{}]", access, family);
familesAccessDenied.add(family);
denialReason = String.format("Insufficient permissions for user ‘%s',action: %s, tableName:%s, family:%s.", user.getName(), operation, table, family);
}
}
// Restore the headMatch setting
session.resourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF);
session.ignoreDescendantDeny(true);
} else {
boolean isColumnAuthOptimizationEnabled = hbasePlugin.getPropertyIsColumnAuthOptimizationEnabled();
LOG.debug("evaluateAccess: columns collection not empty. Skipping Family level check, will do finer level access check for columns.");
if (isColumnAuthOptimizationEnabled) {
session.column(null)
.buildRequest()
.authorize();
if (LOG.isDebugEnabled()) {
LOG.debug("evaluateAccess: isColumnAuthOptimizationEnabled={}, isColumnFamilyAuthorized={}", isColumnAuthOptimizationEnabled, session.isAuthorized());
}
if (session.isAuthorized()) {
//check if column family fully authorized i.e. no deny for columns
session.column(null)
.resourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF_OR_DESCENDANTS)
.ignoreDescendantDeny(false)
.buildRequest()
.authorize();
boolean isColumnFamilyAndDescendantsAuthorized = session.isAuthorized();
AuthzAuditEvent auditEvent = auditHandler.getAndDiscardMostRecentEvent();
// reset ResourceMatchingScope to SELF, ignoreDescendantDeny to true
session.resourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF).ignoreDescendantDeny(true);
LOG.debug("evaluateAccess: isColumnAuthOptimizationEnabled={}, isColumnFamilyAndDescendantsAuthorized={}", isColumnAuthOptimizationEnabled, isColumnFamilyAndDescendantsAuthorized);
if (isColumnFamilyAndDescendantsAuthorized) {
familiesFullyAuthorized.add(family);
if (auditEvent != null) {
LOG.debug("evaluateAccess: isColumnAuthOptimizationEnabled ={}, adding family {} to familiesFullyAuthorized", isColumnAuthOptimizationEnabled, family);
familyLevelAccessEvents.add(auditEvent);
}
continue;
}
}
}
Set<String> accessibleColumns = new HashSet<>(); // will be used in to populate our results cache for the filter
Iterator<String> columnIterator = new ColumnIterator(columns);
while (columnIterator.hasNext()) {
String column = columnIterator.next();
LOG.debug("evaluateAccess: Processing column: {}", column);
//buildRequest required again since now column is being set
session.column(column).buildRequest().authorize();
AuthzAuditEvent auditEvent = auditHandler.getAndDiscardMostRecentEvent();
if (session.isAuthorized()) {
LOG.debug("evaluateAccess: has column level access [{}, {}]", family, column);
// we need to do 3 things: housekeeping, capturing audit events, building the results cache for filter
somethingIsAccessible = true;
accessibleColumns.add(column);
if (auditEvent != null) {
LOG.debug("evaluateAccess: adding to access-granted-audit-event-set");
authorizedEvents.add(auditEvent);
}
} else {
LOG.debug("evaluateAccess: no column level access [{}, {}]", family, column);
somethingIsAccessible = false;
everythingIsAccessible = false;
denialReason = String.format("Insufficient permissions for user ‘%s',action: %s, tableName:%s, family:%s, column: %s", user.getName(), operation, table, family, column);
if (auditEvent != null && deniedEvent == null) { // we need to capture just one denial event
LOG.debug("evaluateAccess: Setting denied access audit event with last auth failure audit event.");
deniedEvent = auditEvent;
}
}
if (!accessibleColumns.isEmpty()) {
columnsAccessAllowed.put(family, accessibleColumns);
}
}
}
}
// Cache of auth results are encapsulated the in the filter. Not every caller of the function uses it - only preGet and preOpt will.
RangerAuthorizationFilter filter = new RangerAuthorizationFilter(session, familesAccessAllowed, familesAccessDenied, familesAccessIndeterminate, columnsAccessAllowed, familiesFullyAuthorized);
result = new ColumnFamilyAccessResult(everythingIsAccessible, somethingIsAccessible, authorizedEvents, familyLevelAccessEvents, deniedEvent, denialReason, filter);
LOG.debug("evaluateAccess: exiting: user[{}], Operation[{}], access[{}], families[{}], verdict[{}]", userName, operation, access, colFamiliesForDebugLoggingOnly, result);
return result;
}