in hdfs-agent/src/main/java/org/apache/ranger/authorization/hadoop/RangerAccessControlEnforcer.java [110:468]
private void checkRangerPermission(String fsOwner, String superGroup, UserGroupInformation ugi, INodeAttributes[] inodeAttrs, INode[] inodes,
byte[][] pathByNameArr, int snapshotId, String path, int ancestorIndex, boolean doCheckOwner, FsAction ancestorAccess,
FsAction parentAccess, FsAction access, FsAction subAccess, boolean ignoreEmptyDir, String operationName, CallerContext callerContext) throws AccessControlException {
AuthzStatus authzStatus = AuthzStatus.NOT_DETERMINED;
String resourcePath = path;
AuthzContext context = new AuthzContext(ugi, operationName, access == null && parentAccess == null && ancestorAccess == null && subAccess == null);
if (LOG.isDebugEnabled()) {
LOG.debug("==> RangerAccessControlEnforcer.checkRangerPermission(fsOwner={}; superGroup={}, inodesCount={}, snapshotId={}, user={}, provided-path={}, ancestorIndex={}, doCheckOwner={}, ancestorAccess={}, parentAccess={}, access={}, subAccess={}, ignoreEmptyDir={}, operationName={}, callerContext={})",
fsOwner, superGroup, inodes != null ? inodes.length : 0, snapshotId, context.user, path, ancestorIndex, doCheckOwner,
ancestorAccess, parentAccess, access, subAccess, ignoreEmptyDir, operationName, callerContext);
LOG.info("operationName={}, path={}, user={}, ancestorIndex={}, ancestorAccess={}, parentAccess={}, access={}, subAccess={}", context.operationName, path, context.user, ancestorIndex, ancestorAccess, parentAccess, access, subAccess);
}
OptimizedAuthzContext optAuthzContext = null;
RangerPerfTracer perf = null;
if (RangerPerfTracer.isPerfTraceEnabled(PERF_HDFSAUTH_REQUEST_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_HDFSAUTH_REQUEST_LOG, "RangerHdfsAuthorizer.checkRangerPermission(provided-path=" + path + ")");
}
try {
INode ancestor = null;
INode parent = null;
INode inode = null;
String providedPath = path;
boolean useDefaultAuthorizerOnly = false;
boolean doNotGenerateAuditRecord = false;
if (plugin != null && !ArrayUtils.isEmpty(inodes)) {
int sz = inodeAttrs.length;
LOG.trace("Size of INodeAttrs array:[{}]", sz);
LOG.trace("Size of INodes array:[{}]", inodes.length);
byte[][] components = new byte[sz][];
int i = 0;
for (; i < sz; i++) {
if (inodeAttrs[i] != null) {
components[i] = inodeAttrs[i].getLocalNameBytes();
} else {
break;
}
}
if (i != sz) {
LOG.trace("Input INodeAttributes array contains null at position {}", i);
LOG.trace("Will use only first [{}] components", i);
}
if (sz == 1 && inodes.length == 1 && inodes[0].getParent() != null) {
LOG.trace("Using the only inode in the array to figure out path to resource. No audit record will be generated for this authorization request");
doNotGenerateAuditRecord = true;
resourcePath = inodes[0].getFullPathName();
if (snapshotId != Snapshot.CURRENT_STATE_ID) {
useDefaultAuthorizerOnly = true;
LOG.trace("path:[{}] is for a snapshot, id=[{}], default Authorizer will be used to authorize this request", resourcePath, snapshotId);
} else {
LOG.trace("path:[{}] is not for a snapshot, id=[{}]. It will be used to authorize this request", resourcePath, snapshotId);
}
} else {
if (snapshotId != Snapshot.CURRENT_STATE_ID) {
resourcePath = DFSUtil.byteArray2PathString(pathByNameArr);
LOG.trace("pathByNameArr array is used to figure out path to resource, resourcePath:[{}]", resourcePath);
} else {
resourcePath = DFSUtil.byteArray2PathString(components, 0, i);
LOG.trace("INodeAttributes array is used to figure out path to resource, resourcePath:[{}]", resourcePath);
}
}
if (ancestorIndex >= inodes.length) {
ancestorIndex = inodes.length - 1;
}
for (; ancestorIndex >= 0 && inodes[ancestorIndex] == null; ancestorIndex--) {
// empty
}
ancestor = inodes.length > ancestorIndex && ancestorIndex >= 0 ? inodes[ancestorIndex] : null;
parent = inodes.length > 1 ? inodes[inodes.length - 2] : null;
inode = inodes[inodes.length - 1]; // could be null while creating a new file
/*
Check if optimization is done
*/
if (plugin.isAuthzOptimizationEnabled() && OperationOptimizer.isOptimizableOperation(operationName)) {
optAuthzContext = (new OperationOptimizer(this, operationName, resourcePath, ancestorAccess, parentAccess, access, subAccess, components, inodeAttrs, ancestorIndex, ancestor, parent, inode)).optimize();
}
if (optAuthzContext == OPT_BYPASS_AUTHZ) {
authzStatus = AuthzStatus.ALLOW;
return;
} else if (optAuthzContext != null && optAuthzContext.authzStatus != null) {
authzStatus = optAuthzContext.authzStatus;
LOG.debug("OperationOptimizer.optimize() returned {}, operationName={} has been pre-computed. Returning without any access evaluation!", authzStatus, operationName);
if (authzStatus == AuthzStatus.ALLOW) {
return;
}
final FsAction action;
if (access != null) {
action = access;
} else if (parentAccess != null) {
action = parentAccess;
} else if (ancestorAccess != null) {
action = ancestorAccess;
} else {
action = FsAction.EXECUTE;
}
throw new RangerAccessControlException("Permission denied: user=" + context.user + ", access=" + action + ", inode=\"" + resourcePath + "\"");
} else {
authzStatus = useDefaultAuthorizerOnly ? AuthzStatus.NOT_DETERMINED : AuthzStatus.ALLOW;
}
if (optAuthzContext != null) {
access = optAuthzContext.access;
parentAccess = optAuthzContext.parentAccess;
ancestorAccess = optAuthzContext.ancestorAccess;
} else {
LOG.debug("OperationOptimizer.optimize() returned null, operationName={} needs to be evaluated!", operationName);
}
context.isTraverseOnlyCheck = parentAccess == null && ancestorAccess == null && access == null && subAccess == null;
context.auditHandler = doNotGenerateAuditRecord ? null : new RangerHdfsAuditHandler(providedPath, context.isTraverseOnlyCheck, plugin.getHadoopModuleName(), plugin.getExcludedUsers(), callerContext != null ? callerContext.toString() : null);
/* Hadoop versions prior to 2.8.0 didn't ask for authorization of parent/ancestor traversal for
* reading or writing a file. However, Hadoop version 2.8.0 and later ask traversal authorization for
* such accesses. This means 2 authorization calls are made to the authorizer for a single access:
* 1. traversal authorization (where access, parentAccess, ancestorAccess and subAccess are null)
* 2. authorization for the requested permission (such as READ for reading a file)
*
* For the first call, Ranger authorizer would:
* - Deny traversal if Ranger policies explicitly deny EXECUTE access on the parent or closest ancestor
* - Else, allow traversal
*
* There are no changes to authorization of the second call listed above.
*
* This approach would ensure that Ranger authorization will continue to work with existing policies,
* without requiring policy migration/update, for the changes in behaviour in Hadoop 2.8.0.
*/
if (authzStatus == AuthzStatus.ALLOW && context.isTraverseOnlyCheck) {
authzStatus = traverseOnlyCheck(inode, inodeAttrs, resourcePath, components, parent, ancestor, ancestorIndex, context);
}
// checkStickyBit
if (authzStatus == AuthzStatus.ALLOW && parentAccess != null && parentAccess.implies(FsAction.WRITE) && parent != null && inode != null) {
if (parent.getFsPermission() != null && parent.getFsPermission().getStickyBit()) {
// user should be owner of the parent or the inode
authzStatus = (StringUtils.equals(parent.getUserName(), context.user) || StringUtils.equals(inode.getUserName(), context.user)) ? AuthzStatus.ALLOW : AuthzStatus.NOT_DETERMINED;
}
}
// checkAncestorAccess
if (authzStatus == AuthzStatus.ALLOW && ancestorAccess != null && ancestor != null) {
INodeAttributes ancestorAttribs = inodeAttrs.length > ancestorIndex ? inodeAttrs[ancestorIndex] : null;
String ancestorPath = ancestorAttribs != null ? DFSUtil.byteArray2PathString(components, 0, ancestorIndex + 1) : null;
authzStatus = isAccessAllowed(ancestor, ancestorAttribs, ancestorPath, ancestorAccess, context);
if (authzStatus == AuthzStatus.NOT_DETERMINED) {
authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
ancestorAccess, null, null, null, ignoreEmptyDir, ancestor, parent, inode, context);
}
}
// checkParentAccess
if (authzStatus == AuthzStatus.ALLOW && parentAccess != null && parent != null) {
INodeAttributes parentAttribs = inodeAttrs.length > 1 ? inodeAttrs[inodeAttrs.length - 2] : null;
String parentPath = parentAttribs != null ? DFSUtil.byteArray2PathString(components, 0, inodeAttrs.length - 1) : null;
authzStatus = isAccessAllowed(parent, parentAttribs, parentPath, parentAccess, context);
if (authzStatus == AuthzStatus.NOT_DETERMINED) {
authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
null, parentAccess, null, null, ignoreEmptyDir, ancestor, parent, inode, context);
}
}
// checkINodeAccess
if (authzStatus == AuthzStatus.ALLOW && access != null && inode != null) {
INodeAttributes inodeAttribs = inodeAttrs.length > 0 ? inodeAttrs[inodeAttrs.length - 1] : null;
authzStatus = isAccessAllowed(inode, inodeAttribs, resourcePath, access, context);
if (authzStatus == AuthzStatus.NOT_DETERMINED) {
authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
null, null, access, null, ignoreEmptyDir, ancestor, parent, inode, context);
}
}
// checkSubAccess
if (authzStatus == AuthzStatus.ALLOW && subAccess != null && inode != null && inode.isDirectory()) {
Stack<SubAccessData> directories = new Stack<>();
for (directories.push(new SubAccessData(inode.asDirectory(), resourcePath, inodes, inodeAttrs)); !directories.isEmpty(); ) {
SubAccessData data = directories.pop();
ReadOnlyList<INode> cList = data.dir.getChildrenList(snapshotId);
if (!(cList.isEmpty() && ignoreEmptyDir)) {
INodeAttributes dirAttribs = data.dir.getSnapshotINode(snapshotId);
authzStatus = isAccessAllowed(data.dir, dirAttribs, data.resourcePath, subAccess, context);
INodeDirectory dirINode;
int dirAncestorIndex;
INodeAttributes[] dirINodeAttrs;
INode[] dirINodes;
INode dirAncestor;
INode dirParent;
byte[][] dirComponents;
if (data.dir.equals(inode)) {
dirINode = inode.asDirectory();
dirINodeAttrs = inodeAttrs;
dirINodes = inodes;
dirAncestorIndex = ancestorIndex;
dirAncestor = ancestor;
dirParent = parent;
dirComponents = pathByNameArr;
} else {
INodeAttributes[] curINodeAttributes;
INode[] curINodes;
dirINode = data.dir;
curINodeAttributes = data.iNodeAttributes;
curINodes = data.inodes;
int idx;
dirINodes = new INode[curINodes.length + 1];
for (idx = 0; idx < curINodes.length; idx++) {
dirINodes[idx] = curINodes[idx];
}
dirINodes[idx] = dirINode;
dirINodeAttrs = new INodeAttributes[curINodeAttributes.length + 1];
for (idx = 0; idx < curINodeAttributes.length; idx++) {
dirINodeAttrs[idx] = curINodeAttributes[idx];
}
dirINodeAttrs[idx] = dirAttribs;
for (dirAncestorIndex = dirINodes.length - 1; dirAncestorIndex >= 0 && dirINodes[dirAncestorIndex] == null; dirAncestorIndex--) {
// empty
}
dirAncestor = dirINodes.length > dirAncestorIndex && dirAncestorIndex >= 0 ? dirINodes[dirAncestorIndex] : null;
dirParent = dirINodes.length > 1 ? dirINodes[dirINodes.length - 2] : null;
dirComponents = dirINode.getPathComponents();
}
if (authzStatus == AuthzStatus.NOT_DETERMINED && !plugin.isUseLegacySubAccessAuthorization()) {
if (LOG.isDebugEnabled()) {
if (data.dir.equals(inode)) {
LOG.debug("Top level directory being processed for default authorizer call, [{}]", data.resourcePath);
} else {
LOG.debug("Sub directory being processed for default authorizer call, [{}]", data.resourcePath);
}
LOG.debug("Calling default authorizer for hierarchy/subaccess with the following parameters");
LOG.debug("fsOwner={}; superGroup={}, inodesCount={}, snapshotId={}, user={}, provided-path={}, ancestorIndex={}, doCheckOwner={}, ancestorAccess=null, parentAccess=null, access=null, subAccess=null, ignoreEmptyDir={}, operationName={}, callerContext=null",
fsOwner, superGroup, dirINodes != null ? dirINodes.length : 0, snapshotId, ugi != null ? ugi.getShortUserName() : null,
data.resourcePath, dirAncestorIndex, doCheckOwner, ignoreEmptyDir, operationName);
}
authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, dirINodeAttrs, dirINodes, dirComponents, snapshotId, data.resourcePath, dirAncestorIndex, doCheckOwner,
null, null, null, null, ignoreEmptyDir, dirAncestor, dirParent, dirINode, context);
LOG.debug("Default authorizer call returned : [{}]", authzStatus);
}
if (authzStatus != AuthzStatus.ALLOW) {
break;
}
AuthzStatus subDirAuthStatus = AuthzStatus.NOT_DETERMINED;
boolean optimizeSubAccessAuthEnabled = plugin.isOptimizeSubAccessAuthEnabled();
if (optimizeSubAccessAuthEnabled) {
subDirAuthStatus = isAccessAllowedForHierarchy(data.dir, dirAttribs, data.resourcePath, subAccess, context);
}
if (subDirAuthStatus != AuthzStatus.ALLOW) {
for (INode child : cList) {
if (child.isDirectory()) {
if (data.resourcePath.endsWith(Path.SEPARATOR)) {
directories.push(new SubAccessData(child.asDirectory(), data.resourcePath + child.getLocalName(), dirINodes, dirINodeAttrs));
} else {
directories.push(new SubAccessData(child.asDirectory(), data.resourcePath + Path.SEPARATOR_CHAR + child.getLocalName(), dirINodes, dirINodeAttrs));
}
}
}
}
}
}
if (authzStatus == AuthzStatus.NOT_DETERMINED) {
authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
null, null, null, subAccess, ignoreEmptyDir, ancestor, parent, inode, context);
}
}
// checkOwnerAccess
if (authzStatus == AuthzStatus.ALLOW && doCheckOwner) {
INodeAttributes inodeAttribs = inodeAttrs.length > 0 ? inodeAttrs[inodeAttrs.length - 1] : null;
String owner = inodeAttribs != null ? inodeAttribs.getUserName() : null;
authzStatus = StringUtils.equals(context.user, owner) ? AuthzStatus.ALLOW : AuthzStatus.NOT_DETERMINED;
}
}
if (authzStatus == AuthzStatus.NOT_DETERMINED) {
authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
ancestorAccess, parentAccess, access, subAccess, ignoreEmptyDir, ancestor, parent, inode, context);
}
if (authzStatus != AuthzStatus.ALLOW) {
FsAction action = access;
if (action == null) {
if (parentAccess != null) {
action = parentAccess;
} else if (ancestorAccess != null) {
action = ancestorAccess;
} else {
action = FsAction.EXECUTE;
}
}
throw new RangerAccessControlException("Permission denied: user=" + context.user + ", access=" + action + ", inode=\"" + resourcePath + "\"");
}
} finally {
if (context.auditHandler != null) {
context.auditHandler.flushAudit();
}
if (optAuthzContext != null && optAuthzContext != OPT_BYPASS_AUTHZ) {
if (LOG.isDebugEnabled()) {
LOG.debug("Updating OptimizedAuthzContext:[{}] with authzStatus={}]", optAuthzContext, authzStatus.name());
}
optAuthzContext.authzStatus = authzStatus;
}
RangerPerfTracer.log(perf);
LOG.debug("<== RangerAccessControlEnforcer.checkRangerPermission({}, {}, user={}) : {}", resourcePath, access, context.user, authzStatus);
}
}