| /* |
| * 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.ranger.authorization.hadoop; |
| |
| import static org.apache.ranger.authorization.hadoop.constants.RangerHadoopConstants.EXECUTE_ACCCESS_TYPE; |
| import static org.apache.ranger.authorization.hadoop.constants.RangerHadoopConstants.HDFS_ROOT_FOLDER_PATH; |
| import static org.apache.ranger.authorization.hadoop.constants.RangerHadoopConstants.READ_ACCCESS_TYPE; |
| import static org.apache.ranger.authorization.hadoop.constants.RangerHadoopConstants.WRITE_ACCCESS_TYPE; |
| |
| import java.net.InetAddress; |
| import java.security.SecureRandom; |
| import java.util.*; |
| |
| import org.apache.commons.lang.ArrayUtils; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.fs.permission.FsAction; |
| import org.apache.hadoop.hdfs.DFSUtil; |
| import org.apache.hadoop.hdfs.server.namenode.INode; |
| import org.apache.hadoop.hdfs.server.namenode.INodeAttributeProvider; |
| import org.apache.hadoop.hdfs.server.namenode.INodeAttributes; |
| import org.apache.hadoop.hdfs.server.namenode.INodeDirectory; |
| import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; |
| import org.apache.hadoop.hdfs.util.ReadOnlyList; |
| import org.apache.hadoop.ipc.Server; |
| import org.apache.hadoop.security.AccessControlException; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.ranger.audit.model.AuthzAuditEvent; |
| import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig; |
| import org.apache.ranger.authorization.hadoop.constants.RangerHadoopConstants; |
| import org.apache.ranger.authorization.hadoop.exceptions.RangerAccessControlException; |
| import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler; |
| import org.apache.ranger.plugin.policyengine.RangerAccessRequest; |
| import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; |
| import org.apache.ranger.plugin.policyengine.RangerAccessResource; |
| import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; |
| import org.apache.ranger.plugin.policyengine.RangerAccessResult; |
| import org.apache.ranger.plugin.resourcematcher.RangerPathResourceMatcher; |
| import org.apache.ranger.plugin.service.RangerBasePlugin; |
| import org.apache.ranger.plugin.util.RangerPerfTracer; |
| |
| import com.google.common.collect.Sets; |
| |
| import org.apache.ranger.plugin.util.RangerAccessRequestUtil; |
| |
| public class RangerHdfsAuthorizer extends INodeAttributeProvider { |
| public static final String KEY_FILENAME = "FILENAME"; |
| public static final String KEY_BASE_FILENAME = "BASE_FILENAME"; |
| public static final String DEFAULT_FILENAME_EXTENSION_SEPARATOR = "."; |
| |
| public static final String KEY_RESOURCE_PATH = "path"; |
| |
| public static final String RANGER_FILENAME_EXTENSION_SEPARATOR_PROP = "ranger.plugin.hdfs.filename.extension.separator"; |
| |
| private static final Log LOG = LogFactory.getLog(RangerHdfsAuthorizer.class); |
| private static final Log PERF_HDFSAUTH_REQUEST_LOG = RangerPerfTracer.getPerfLogger("hdfsauth.request"); |
| |
| private RangerHdfsPlugin rangerPlugin = null; |
| private Map<FsAction, Set<String>> access2ActionListMapper = new HashMap<FsAction, Set<String>>(); |
| private final Path addlConfigFile; |
| |
| public RangerHdfsAuthorizer() { |
| this(null); |
| } |
| |
| public RangerHdfsAuthorizer(Path addlConfigFile) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuthorizer.RangerHdfsAuthorizer()"); |
| } |
| |
| this.addlConfigFile = addlConfigFile; |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuthorizer.RangerHdfsAuthorizer()"); |
| } |
| } |
| |
| public void start() { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuthorizer.start()"); |
| } |
| |
| RangerHdfsPlugin plugin = new RangerHdfsPlugin(addlConfigFile); |
| |
| plugin.init(); |
| |
| if (plugin.isOptimizeSubAccessAuthEnabled()) { |
| LOG.info(RangerHadoopConstants.RANGER_OPTIMIZE_SUBACCESS_AUTHORIZATION_PROP + " is enabled"); |
| } |
| |
| access2ActionListMapper.put(FsAction.NONE, new HashSet<String>()); |
| access2ActionListMapper.put(FsAction.ALL, Sets.newHashSet(READ_ACCCESS_TYPE, WRITE_ACCCESS_TYPE, EXECUTE_ACCCESS_TYPE)); |
| access2ActionListMapper.put(FsAction.READ, Sets.newHashSet(READ_ACCCESS_TYPE)); |
| access2ActionListMapper.put(FsAction.READ_WRITE, Sets.newHashSet(READ_ACCCESS_TYPE, WRITE_ACCCESS_TYPE)); |
| access2ActionListMapper.put(FsAction.READ_EXECUTE, Sets.newHashSet(READ_ACCCESS_TYPE, EXECUTE_ACCCESS_TYPE)); |
| access2ActionListMapper.put(FsAction.WRITE, Sets.newHashSet(WRITE_ACCCESS_TYPE)); |
| access2ActionListMapper.put(FsAction.WRITE_EXECUTE, Sets.newHashSet(WRITE_ACCCESS_TYPE, EXECUTE_ACCCESS_TYPE)); |
| access2ActionListMapper.put(FsAction.EXECUTE, Sets.newHashSet(EXECUTE_ACCCESS_TYPE)); |
| |
| rangerPlugin = plugin; |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuthorizer.start()"); |
| } |
| } |
| |
| public void stop() { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuthorizer.stop()"); |
| } |
| |
| RangerHdfsPlugin plugin = rangerPlugin; |
| rangerPlugin = null; |
| |
| if(plugin != null) { |
| plugin.cleanup(); |
| } |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuthorizer.stop()"); |
| } |
| } |
| |
| @Override |
| public INodeAttributes getAttributes(String fullPath, INodeAttributes inode) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuthorizer.getAttributes(" + fullPath + ")"); |
| } |
| |
| INodeAttributes ret = inode; // return default attributes |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuthorizer.getAttributes(" + fullPath + "): " + ret); |
| } |
| |
| return ret; |
| } |
| |
| @Override |
| public INodeAttributes getAttributes(String[] pathElements, INodeAttributes inode) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuthorizer.getAttributes(pathElementsCount=" + (pathElements == null ? 0 : pathElements.length) + ")"); |
| } |
| |
| INodeAttributes ret = inode; // return default attributes |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuthorizer.getAttributes(pathElementsCount=" + (pathElements == null ? 0 : pathElements.length) + "): " + ret); |
| } |
| |
| return ret; |
| } |
| |
| @Override |
| public AccessControlEnforcer getExternalAccessControlEnforcer(AccessControlEnforcer defaultEnforcer) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuthorizer.getExternalAccessControlEnforcer()"); |
| } |
| |
| RangerAccessControlEnforcer rangerAce = new RangerAccessControlEnforcer(defaultEnforcer); |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuthorizer.getExternalAccessControlEnforcer()"); |
| } |
| |
| return rangerAce; |
| } |
| |
| // for testing |
| public Configuration getConfig() { |
| return rangerPlugin.getConfig(); |
| } |
| |
| private enum AuthzStatus { ALLOW, DENY, NOT_DETERMINED }; |
| |
| class RangerAccessControlEnforcer implements AccessControlEnforcer { |
| private INodeAttributeProvider.AccessControlEnforcer defaultEnforcer = null; |
| |
| public RangerAccessControlEnforcer(AccessControlEnforcer defaultEnforcer) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerAccessControlEnforcer.RangerAccessControlEnforcer()"); |
| } |
| |
| this.defaultEnforcer = defaultEnforcer; |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerAccessControlEnforcer.RangerAccessControlEnforcer()"); |
| } |
| } |
| |
| class SubAccessData { |
| final INodeDirectory dir; |
| final String resourcePath; |
| |
| SubAccessData(INodeDirectory dir, String resourcePath) { |
| this.dir = dir; |
| this.resourcePath = resourcePath; |
| } |
| } |
| |
| @Override |
| public void checkPermission(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) throws AccessControlException { |
| AuthzStatus authzStatus = AuthzStatus.NOT_DETERMINED; |
| RangerHdfsPlugin plugin = rangerPlugin; |
| RangerHdfsAuditHandler auditHandler = null; |
| String user = ugi != null ? ugi.getShortUserName() : null; |
| Set<String> groups = ugi != null ? Sets.newHashSet(ugi.getGroupNames()) : null; |
| String resourcePath = path; |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerAccessControlEnforcer.checkPermission(" |
| + "fsOwner=" + fsOwner + "; superGroup=" + superGroup + ", inodesCount=" + (inodes != null ? inodes.length : 0) |
| + ", snapshotId=" + snapshotId + ", user=" + user + ", provided-path=" + path + ", ancestorIndex=" + ancestorIndex |
| + ", doCheckOwner="+ doCheckOwner + ", ancestorAccess=" + ancestorAccess + ", parentAccess=" + parentAccess |
| + ", access=" + access + ", subAccess=" + subAccess + ", ignoreEmptyDir=" + ignoreEmptyDir + ")"); |
| } |
| |
| RangerPerfTracer perf = null; |
| |
| if(RangerPerfTracer.isPerfTraceEnabled(PERF_HDFSAUTH_REQUEST_LOG)) { |
| perf = RangerPerfTracer.getPerfTracer(PERF_HDFSAUTH_REQUEST_LOG, "RangerHdfsAuthorizer.checkPermission(provided-path=" + path + ")"); |
| } |
| |
| try { |
| final boolean isTraverseOnlyCheck = access == null && parentAccess == null && ancestorAccess == null && subAccess == null; |
| INode ancestor = null; |
| INode parent = null; |
| INode inode = null; |
| |
| boolean useDefaultAuthorizerOnly = false; |
| boolean doNotGenerateAuditRecord = false; |
| |
| if(plugin != null && !ArrayUtils.isEmpty(inodes)) { |
| int sz = inodeAttrs.length; |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Size of INodeAttrs array:[" + sz + "]"); |
| LOG.debug("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) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Input INodeAttributes array contains null at position " + i); |
| LOG.debug("Will use only first [" + i + "] components"); |
| } |
| } |
| |
| if (sz == 1 && inodes.length == 1 && inodes[0].getParent() != null) { |
| |
| doNotGenerateAuditRecord = true; |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Using the only inode in the array to figure out path to resource. No audit record will be generated for this authorization request"); |
| } |
| |
| resourcePath = inodes[0].getFullPathName(); |
| |
| if (snapshotId != Snapshot.CURRENT_STATE_ID) { |
| |
| useDefaultAuthorizerOnly = true; |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("path:[" + resourcePath + "] is for a snapshot, id=[" + snapshotId +"], default Authorizer will be used to authorize this request"); |
| } |
| } else { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("path:[" + resourcePath + "] is not for a snapshot, id=[" + snapshotId +"]. It will be used to authorize this request"); |
| } |
| } |
| } else { |
| |
| resourcePath = DFSUtil.byteArray2PathString(components, 0, i); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("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--); |
| |
| authzStatus = useDefaultAuthorizerOnly ? AuthzStatus.NOT_DETERMINED : AuthzStatus.ALLOW; |
| |
| 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 |
| |
| auditHandler = doNotGenerateAuditRecord ? null : new RangerHdfsAuditHandler(resourcePath, isTraverseOnlyCheck, rangerPlugin.getHadoopModuleName(), rangerPlugin.getExcludedUsers()); |
| |
| /* 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 && isTraverseOnlyCheck) { |
| authzStatus = traverseOnlyCheck(inode, inodeAttrs, resourcePath, components, parent, ancestor, ancestorIndex, user, groups, plugin, auditHandler); |
| } |
| |
| // 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(), user) || StringUtils.equals(inode.getUserName(), 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, user, groups, plugin, auditHandler); |
| if (authzStatus == AuthzStatus.NOT_DETERMINED) { |
| authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, |
| pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner, |
| ancestorAccess, null, null, null, ignoreEmptyDir, |
| isTraverseOnlyCheck, ancestor, parent, inode, auditHandler); |
| } |
| } |
| |
| // 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, user, groups, plugin, auditHandler); |
| if (authzStatus == AuthzStatus.NOT_DETERMINED) { |
| authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, |
| pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner, |
| null, parentAccess, null, null, ignoreEmptyDir, |
| isTraverseOnlyCheck, ancestor, parent, inode, auditHandler); |
| } |
| } |
| |
| // 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, user, groups, plugin, auditHandler); |
| if (authzStatus == AuthzStatus.NOT_DETERMINED) { |
| authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, |
| pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner, |
| null, null, access, null, ignoreEmptyDir, |
| isTraverseOnlyCheck, ancestor, parent, inode, auditHandler); |
| } |
| } |
| |
| // checkSubAccess |
| if(authzStatus == AuthzStatus.ALLOW && subAccess != null && inode != null && inode.isDirectory()) { |
| Stack<SubAccessData> directories = new Stack<>(); |
| |
| for(directories.push(new SubAccessData(inode.asDirectory(), resourcePath)); !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, user, groups, plugin, auditHandler); |
| |
| if(authzStatus != AuthzStatus.ALLOW) { |
| break; |
| } |
| |
| AuthzStatus subDirAuthStatus = AuthzStatus.NOT_DETERMINED; |
| |
| boolean optimizeSubAccessAuthEnabled = rangerPlugin.isOptimizeSubAccessAuthEnabled(); |
| |
| if (optimizeSubAccessAuthEnabled) { |
| subDirAuthStatus = isAccessAllowedForHierarchy(data.dir, dirAttribs, data.resourcePath, subAccess, user, groups, plugin); |
| } |
| |
| if (subDirAuthStatus != AuthzStatus.ALLOW) { |
| for(INode child : cList) { |
| if (child.isDirectory()) { |
| directories.push(new SubAccessData(child.asDirectory(), resourcePath + org.apache.hadoop.fs.Path.SEPARATOR_CHAR + child.getLocalName())); |
| } |
| } |
| } |
| } |
| } |
| if (authzStatus == AuthzStatus.NOT_DETERMINED) { |
| |
| authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, |
| pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner, |
| null, null, null, subAccess, ignoreEmptyDir, |
| isTraverseOnlyCheck, ancestor, parent, inode, auditHandler); |
| |
| } |
| } |
| |
| // 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(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, |
| isTraverseOnlyCheck, ancestor, parent, inode, auditHandler); |
| } |
| |
| 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=" + user + ", access=" + action + ", inode=\"" + resourcePath + "\""); |
| } |
| } finally { |
| if(auditHandler != null) { |
| auditHandler.flushAudit(); |
| } |
| |
| RangerPerfTracer.log(perf); |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerAccessControlEnforcer.checkPermission(" + resourcePath + ", " + access + ", user=" + user + ") : " + authzStatus); |
| } |
| } |
| } |
| |
| /* |
| Check if parent or ancestor of the file being accessed is denied EXECUTE permission. If not, assume that Ranger-acls |
| allowed EXECUTE access. Do not audit this authorization check if resource is a file unless access is explicitly denied |
| */ |
| private AuthzStatus traverseOnlyCheck(INode inode, INodeAttributes[] inodeAttrs, String path, byte[][] components, INode parent, INode ancestor, int ancestorIndex, |
| String user, Set<String> groups, RangerHdfsPlugin plugin, RangerHdfsAuditHandler auditHandler) { |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerAccessControlEnforcer.traverseOnlyCheck(" |
| + "path=" + path + ", user=" + user + ", groups=" + groups + ")"); |
| } |
| final AuthzStatus ret; |
| |
| INode nodeToCheck = inode; |
| INodeAttributes nodeAttribs = inodeAttrs.length > 0 ? inodeAttrs[inodeAttrs.length - 1] : null; |
| boolean skipAuditOnAllow = false; |
| |
| String resourcePath = path; |
| if (nodeToCheck == null || nodeToCheck.isFile()) { |
| skipAuditOnAllow = true; |
| if (parent != null) { |
| nodeToCheck = parent; |
| nodeAttribs = inodeAttrs.length > 1 ? inodeAttrs[inodeAttrs.length - 2] : null; |
| resourcePath = inodeAttrs.length > 0 ? DFSUtil.byteArray2PathString(components, 0, inodeAttrs.length - 1) : HDFS_ROOT_FOLDER_PATH; |
| } else if (ancestor != null) { |
| nodeToCheck = ancestor; |
| nodeAttribs = inodeAttrs.length > ancestorIndex ? inodeAttrs[ancestorIndex] : null; |
| resourcePath = nodeAttribs != null ? DFSUtil.byteArray2PathString(components, 0, ancestorIndex+1) : HDFS_ROOT_FOLDER_PATH; |
| } |
| } |
| |
| if (nodeToCheck != null) { |
| if (resourcePath.length() > 1) { |
| if (resourcePath.endsWith(HDFS_ROOT_FOLDER_PATH)) { |
| resourcePath = resourcePath.substring(0, resourcePath.length()-1); |
| } |
| } |
| ret = isAccessAllowedForTraversal(nodeToCheck, nodeAttribs, resourcePath, user, groups, plugin, auditHandler, skipAuditOnAllow); |
| } else { |
| ret = AuthzStatus.ALLOW; |
| } |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerAccessControlEnforcer.traverseOnlyCheck(" |
| + "path=" + path + ", resourcePath=" + resourcePath + ", user=" + user + ", groups=" + groups + ") : " + ret); |
| } |
| return ret; |
| } |
| |
| private AuthzStatus isAccessAllowedForTraversal(INode inode, INodeAttributes inodeAttribs, String path, String user, Set<String> groups, RangerHdfsPlugin plugin, RangerHdfsAuditHandler auditHandler, boolean skipAuditOnAllow) { |
| final AuthzStatus ret; |
| String pathOwner = inodeAttribs != null ? inodeAttribs.getUserName() : null; |
| FsAction access = FsAction.EXECUTE; |
| |
| |
| if (pathOwner == null) { |
| pathOwner = inode.getUserName(); |
| } |
| |
| if (RangerHadoopConstants.HDFS_ROOT_FOLDER_PATH_ALT.equals(path)) { |
| path = HDFS_ROOT_FOLDER_PATH; |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerAccessControlEnforcer.isAccessAllowedForTraversal(" + path + ", " + access + ", " + user + ", " + skipAuditOnAllow + ")"); |
| } |
| |
| RangerHdfsAccessRequest request = new RangerHdfsAccessRequest(inode, path, pathOwner, access, EXECUTE_ACCCESS_TYPE, user, groups); |
| |
| RangerAccessResult result = plugin.isAccessAllowed(request, null); |
| |
| if (result != null && result.getIsAccessDetermined() && !result.getIsAllowed()) { |
| ret = AuthzStatus.DENY; |
| } else { |
| ret = AuthzStatus.ALLOW; |
| } |
| |
| if (ret == AuthzStatus.DENY || (!skipAuditOnAllow && result != null && result.getIsAccessDetermined())) { |
| auditHandler.processResult(result); |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerAccessControlEnforcer.isAccessAllowedForTraversal(" + path + ", " + access + ", " + user + ", " + skipAuditOnAllow + "): " + ret); |
| } |
| |
| return ret; |
| } |
| |
| private AuthzStatus checkDefaultEnforcer(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, |
| boolean isTraverseOnlyCheck, INode ancestor, |
| INode parent, INode inode, RangerHdfsAuditHandler auditHandler |
| ) throws AccessControlException { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerAccessControlEnforcer.checkDefaultEnforcer(" |
| + "fsOwner=" + fsOwner + "; superGroup=" + superGroup + ", inodesCount=" + (inodes != null ? inodes.length : 0) |
| + ", snapshotId=" + snapshotId + ", path=" + path + ", ancestorIndex=" + ancestorIndex |
| + ", doCheckOwner=" + doCheckOwner + ", ancestorAccess=" + ancestorAccess + ", parentAccess=" + parentAccess |
| + ", access=" + access + ", subAccess=" + subAccess + ", ignoreEmptyDir=" + ignoreEmptyDir |
| + ", isTraverseOnlyCheck=" + isTraverseOnlyCheck + ",ancestor=" + (ancestor == null ? null : ancestor.getFullPathName()) |
| + ", parent=" + (parent == null ? null : parent.getFullPathName()) + ", inode=" + (inode == null ? null : inode.getFullPathName()) |
| + ")"); |
| } |
| |
| AuthzStatus authzStatus = AuthzStatus.NOT_DETERMINED; |
| if(rangerPlugin.isHadoopAuthEnabled() && defaultEnforcer != null) { |
| |
| RangerPerfTracer hadoopAuthPerf = null; |
| |
| if(RangerPerfTracer.isPerfTraceEnabled(PERF_HDFSAUTH_REQUEST_LOG)) { |
| hadoopAuthPerf = RangerPerfTracer.getPerfTracer(PERF_HDFSAUTH_REQUEST_LOG, "RangerAccessControlEnforcer.checkDefaultEnforcer(path=" + path + ")"); |
| } |
| |
| try { |
| defaultEnforcer.checkPermission(fsOwner, superGroup, ugi, inodeAttrs, inodes, |
| pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner, |
| ancestorAccess, parentAccess, access, subAccess, ignoreEmptyDir); |
| |
| authzStatus = AuthzStatus.ALLOW; |
| } finally { |
| if (auditHandler != null) { |
| INode nodeChecked = inode; |
| FsAction action = access; |
| if (isTraverseOnlyCheck) { |
| if (nodeChecked == null || nodeChecked.isFile()) { |
| if (parent != null) { |
| nodeChecked = parent; |
| } else if (ancestor != null) { |
| nodeChecked = ancestor; |
| } |
| } |
| |
| action = FsAction.EXECUTE; |
| } else if (action == null || action == FsAction.NONE) { |
| if (parentAccess != null && parentAccess != FsAction.NONE) { |
| nodeChecked = parent; |
| action = parentAccess; |
| } else if (ancestorAccess != null && ancestorAccess != FsAction.NONE) { |
| nodeChecked = ancestor; |
| action = ancestorAccess; |
| } else if (subAccess != null && subAccess != FsAction.NONE) { |
| action = subAccess; |
| } |
| } |
| |
| String pathChecked = nodeChecked != null ? nodeChecked.getFullPathName() : path; |
| |
| auditHandler.logHadoopEvent(pathChecked, action, authzStatus == AuthzStatus.ALLOW); |
| } |
| RangerPerfTracer.log(hadoopAuthPerf); |
| } |
| } |
| LOG.debug("<== RangerAccessControlEnforcer.checkDefaultEnforcer(" |
| + "fsOwner=" + fsOwner + "; superGroup=" + superGroup + ", inodesCount=" + (inodes != null ? inodes.length : 0) |
| + ", snapshotId=" + snapshotId + ", path=" + path + ", ancestorIndex=" + ancestorIndex |
| + ", doCheckOwner="+ doCheckOwner + ", ancestorAccess=" + ancestorAccess + ", parentAccess=" + parentAccess |
| + ", access=" + access + ", subAccess=" + subAccess + ", ignoreEmptyDir=" + ignoreEmptyDir |
| + ", isTraverseOnlyCheck=" + isTraverseOnlyCheck + ",ancestor=" + (ancestor == null ? null : ancestor.getFullPathName()) |
| + ", parent=" + (parent == null ? null : parent.getFullPathName()) + ", inode=" + (inode == null ? null : inode.getFullPathName()) |
| + ") : " + authzStatus ); |
| |
| return authzStatus; |
| } |
| |
| private AuthzStatus isAccessAllowed(INode inode, INodeAttributes inodeAttribs, String path, FsAction access, String user, Set<String> groups, RangerHdfsPlugin plugin, RangerHdfsAuditHandler auditHandler) { |
| AuthzStatus ret = null; |
| String pathOwner = inodeAttribs != null ? inodeAttribs.getUserName() : null; |
| |
| if(pathOwner == null && inode != null) { |
| pathOwner = inode.getUserName(); |
| } |
| |
| if (RangerHadoopConstants.HDFS_ROOT_FOLDER_PATH_ALT.equals(path)) { |
| path = HDFS_ROOT_FOLDER_PATH; |
| } |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerAccessControlEnforcer.isAccessAllowed(" + path + ", " + access + ", " + user + ")"); |
| } |
| |
| Set<String> accessTypes = access2ActionListMapper.get(access); |
| |
| if(accessTypes == null) { |
| LOG.warn("RangerAccessControlEnforcer.isAccessAllowed(" + path + ", " + access + ", " + user + "): no Ranger accessType found for " + access); |
| |
| accessTypes = access2ActionListMapper.get(FsAction.NONE); |
| } |
| |
| for(String accessType : accessTypes) { |
| RangerHdfsAccessRequest request = new RangerHdfsAccessRequest(inode, path, pathOwner, access, accessType, user, groups); |
| |
| RangerAccessResult result = plugin.isAccessAllowed(request, auditHandler); |
| |
| if (result == null || !result.getIsAccessDetermined()) { |
| ret = AuthzStatus.NOT_DETERMINED; |
| // don't break yet; subsequent accessType could be denied |
| } else if(! result.getIsAllowed()) { // explicit deny |
| ret = AuthzStatus.DENY; |
| break; |
| } else { // allowed |
| if(!AuthzStatus.NOT_DETERMINED.equals(ret)) { // set to ALLOW only if there was no NOT_DETERMINED earlier |
| ret = AuthzStatus.ALLOW; |
| } |
| } |
| } |
| |
| if(ret == null) { |
| ret = AuthzStatus.NOT_DETERMINED; |
| } |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerAccessControlEnforcer.isAccessAllowed(" + path + ", " + access + ", " + user + "): " + ret); |
| } |
| |
| return ret; |
| } |
| |
| private AuthzStatus isAccessAllowedForHierarchy(INode inode, INodeAttributes inodeAttribs, String path, FsAction access, String user, Set<String> groups, RangerHdfsPlugin plugin) { |
| AuthzStatus ret = null; |
| String pathOwner = inodeAttribs != null ? inodeAttribs.getUserName() : null; |
| |
| if (pathOwner == null && inode != null) { |
| pathOwner = inode.getUserName(); |
| } |
| |
| if (RangerHadoopConstants.HDFS_ROOT_FOLDER_PATH_ALT.equals(path)) { |
| path = HDFS_ROOT_FOLDER_PATH; |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerAccessControlEnforcer.isAccessAllowedForHierarchy(" + path + ", " + access + ", " + user + ")"); |
| } |
| |
| if (path != null) { |
| |
| Set<String> accessTypes = access2ActionListMapper.get(access); |
| |
| if (accessTypes == null) { |
| LOG.warn("RangerAccessControlEnforcer.isAccessAllowedForHierarchy(" + path + ", " + access + ", " + user + "): no Ranger accessType found for " + access); |
| |
| accessTypes = access2ActionListMapper.get(FsAction.NONE); |
| } |
| |
| String subDirPath = path; |
| if (subDirPath.charAt(subDirPath.length() - 1) != org.apache.hadoop.fs.Path.SEPARATOR_CHAR) { |
| subDirPath = subDirPath + Character.toString(org.apache.hadoop.fs.Path.SEPARATOR_CHAR); |
| } |
| subDirPath = subDirPath + rangerPlugin.getRandomizedWildcardPathName(); |
| |
| for (String accessType : accessTypes) { |
| RangerHdfsAccessRequest request = new RangerHdfsAccessRequest(null, subDirPath, pathOwner, access, accessType, user, groups); |
| |
| RangerAccessResult result = plugin.isAccessAllowed(request, null); |
| |
| if (result == null || !result.getIsAccessDetermined()) { |
| ret = AuthzStatus.NOT_DETERMINED; |
| // don't break yet; subsequent accessType could be denied |
| } else if(! result.getIsAllowed()) { // explicit deny |
| ret = AuthzStatus.DENY; |
| break; |
| } else { // allowed |
| if(!AuthzStatus.NOT_DETERMINED.equals(ret)) { // set to ALLOW only if there was no NOT_DETERMINED earlier |
| ret = AuthzStatus.ALLOW; |
| } |
| } |
| } |
| } |
| |
| if(ret == null) { |
| ret = AuthzStatus.NOT_DETERMINED; |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerAccessControlEnforcer.isAccessAllowedForHierarchy(" + path + ", " + access + ", " + user + "): " + ret); |
| } |
| |
| return ret; |
| } |
| } |
| } |
| |
| |
| class RangerHdfsPlugin extends RangerBasePlugin { |
| private static final Log LOG = LogFactory.getLog(RangerHdfsPlugin.class); |
| |
| private static String fileNameExtensionSeparator = RangerHdfsAuthorizer.DEFAULT_FILENAME_EXTENSION_SEPARATOR; |
| |
| private final boolean hadoopAuthEnabled; |
| private final boolean optimizeSubAccessAuthEnabled; |
| private final String randomizedWildcardPathName; |
| private final String hadoopModuleName; |
| private final Set<String> excludeUsers = new HashSet<>(); |
| |
| public RangerHdfsPlugin(Path addlConfigFile) { |
| super("hdfs", "hdfs"); |
| |
| RangerPluginConfig config = getConfig(); |
| |
| if (addlConfigFile != null) { |
| config.addResource(addlConfigFile); |
| } |
| |
| String random = generateString("^&#@!%()-_+=@:;'<>`~abcdefghijklmnopqrstuvwxyz01234567890"); |
| |
| RangerHdfsPlugin.fileNameExtensionSeparator = config.get(RangerHdfsAuthorizer.RANGER_FILENAME_EXTENSION_SEPARATOR_PROP, RangerHdfsAuthorizer.DEFAULT_FILENAME_EXTENSION_SEPARATOR); |
| |
| this.hadoopAuthEnabled = config.getBoolean(RangerHadoopConstants.RANGER_ADD_HDFS_PERMISSION_PROP, RangerHadoopConstants.RANGER_ADD_HDFS_PERMISSION_DEFAULT); |
| this.optimizeSubAccessAuthEnabled = config.getBoolean(RangerHadoopConstants.RANGER_OPTIMIZE_SUBACCESS_AUTHORIZATION_PROP, RangerHadoopConstants.RANGER_OPTIMIZE_SUBACCESS_AUTHORIZATION_DEFAULT); |
| this.randomizedWildcardPathName = RangerPathResourceMatcher.WILDCARD_ASTERISK + random + RangerPathResourceMatcher.WILDCARD_ASTERISK; |
| this.hadoopModuleName = config.get(RangerHadoopConstants.AUDITLOG_HADOOP_MODULE_ACL_NAME_PROP , RangerHadoopConstants.DEFAULT_HADOOP_MODULE_ACL_NAME); |
| |
| String excludeUserList = config.get(RangerHadoopConstants.AUDITLOG_HDFS_EXCLUDE_LIST_PROP, RangerHadoopConstants.AUDITLOG_EMPTY_STRING); |
| |
| if (excludeUserList != null && excludeUserList.trim().length() > 0) { |
| for(String excludeUser : excludeUserList.trim().split(",")) { |
| excludeUser = excludeUser.trim(); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Adding exclude user [" + excludeUser + "]"); |
| } |
| |
| excludeUsers.add(excludeUser); |
| } |
| } |
| } |
| |
| // Build random string of length between 56 and 112 characters |
| private static String generateString(String source) |
| { |
| SecureRandom rng = new SecureRandom(); |
| |
| byte[] bytes = new byte[1]; |
| rng.nextBytes(bytes); |
| int length = bytes[0]; |
| length = length < 56 ? 56 : length; |
| length = length > 112 ? 112 : length; |
| |
| char[] text = new char[length]; |
| |
| for (int i = 0; i < length; i++) |
| { |
| text[i] = source.charAt(rng.nextInt(source.length())); |
| } |
| return new String(text); |
| } |
| |
| public static String getFileNameExtensionSeparator() { |
| return fileNameExtensionSeparator; |
| } |
| |
| public boolean isHadoopAuthEnabled() { |
| return hadoopAuthEnabled; |
| } |
| public boolean isOptimizeSubAccessAuthEnabled() { |
| return optimizeSubAccessAuthEnabled; |
| } |
| public String getRandomizedWildcardPathName() { |
| return randomizedWildcardPathName; |
| } |
| public String getHadoopModuleName() { return hadoopModuleName; } |
| public Set<String> getExcludedUsers() { return excludeUsers; } |
| } |
| |
| class RangerHdfsResource extends RangerAccessResourceImpl { |
| |
| public RangerHdfsResource(String path, String owner) { |
| super.setValue(RangerHdfsAuthorizer.KEY_RESOURCE_PATH, path); |
| super.setOwnerUser(owner); |
| } |
| } |
| |
| class RangerHdfsAccessRequest extends RangerAccessRequestImpl { |
| |
| public RangerHdfsAccessRequest(INode inode, String path, String pathOwner, FsAction access, String accessType, String user, Set<String> groups) { |
| super.setResource(new RangerHdfsResource(path, pathOwner)); |
| super.setAccessType(accessType); |
| super.setUser(user); |
| super.setUserGroups(groups); |
| super.setAccessTime(new Date()); |
| super.setClientIPAddress(getRemoteIp()); |
| super.setAction(access.toString()); |
| super.setForwardedAddresses(null); |
| super.setRemoteIPAddress(getRemoteIp()); |
| |
| if (inode != null) { |
| buildRequestContext(inode); |
| } |
| } |
| |
| private static String getRemoteIp() { |
| String ret = null; |
| InetAddress ip = Server.getRemoteIp(); |
| if (ip != null) { |
| ret = ip.getHostAddress(); |
| } |
| return ret; |
| } |
| private void buildRequestContext(final INode inode) { |
| if (inode.isFile()) { |
| String fileName = inode.getLocalName(); |
| RangerAccessRequestUtil.setTokenInContext(getContext(), RangerHdfsAuthorizer.KEY_FILENAME, fileName); |
| int lastExtensionSeparatorIndex = fileName.lastIndexOf(RangerHdfsPlugin.getFileNameExtensionSeparator()); |
| if (lastExtensionSeparatorIndex != -1) { |
| String baseFileName = fileName.substring(0, lastExtensionSeparatorIndex); |
| RangerAccessRequestUtil.setTokenInContext(getContext(), RangerHdfsAuthorizer.KEY_BASE_FILENAME, baseFileName); |
| } |
| } |
| } |
| } |
| |
| class RangerHdfsAuditHandler extends RangerDefaultAuditHandler { |
| private static final Log LOG = LogFactory.getLog(RangerHdfsAuditHandler.class); |
| |
| private boolean isAuditEnabled = false; |
| private AuthzAuditEvent auditEvent = null; |
| private final String pathToBeValidated; |
| private final boolean auditOnlyIfDenied; |
| |
| private final String hadoopModuleName; |
| private final Set<String> excludeUsers; |
| |
| public RangerHdfsAuditHandler(String pathToBeValidated, boolean auditOnlyIfDenied, String hadoopModuleName, Set<String> excludedUsers) { |
| this.pathToBeValidated = pathToBeValidated; |
| this.auditOnlyIfDenied = auditOnlyIfDenied; |
| this.hadoopModuleName = hadoopModuleName; |
| this.excludeUsers = excludedUsers; |
| } |
| |
| @Override |
| public void processResult(RangerAccessResult result) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuditHandler.logAudit(" + result + ")"); |
| } |
| |
| if(! isAuditEnabled && result.getIsAudited()) { |
| isAuditEnabled = true; |
| } |
| |
| if (auditEvent == null) { |
| auditEvent = super.getAuthzEvents(result); |
| } |
| |
| if (auditEvent != null) { |
| RangerAccessRequest request = result.getAccessRequest(); |
| RangerAccessResource resource = request.getResource(); |
| String resourcePath = resource != null ? resource.getAsString() : null; |
| |
| // Overwrite fields in original auditEvent |
| auditEvent.setEventTime(request.getAccessTime() != null ? request.getAccessTime() : new Date()); |
| auditEvent.setAccessType(request.getAction()); |
| auditEvent.setResourcePath(this.pathToBeValidated); |
| auditEvent.setResultReason(resourcePath); |
| |
| auditEvent.setAccessResult((short) (result.getIsAllowed() ? 1 : 0)); |
| auditEvent.setPolicyId(result.getPolicyId()); |
| auditEvent.setPolicyVersion(result.getPolicyVersion()); |
| |
| Set<String> tags = getTags(request); |
| if (tags != null) { |
| auditEvent.setTags(tags); |
| } |
| } |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuditHandler.logAudit(" + result + "): " + auditEvent); |
| } |
| } |
| |
| public void logHadoopEvent(String path, FsAction action, boolean accessGranted) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuditHandler.logHadoopEvent(" + path + ", " + action + ", " + accessGranted + ")"); |
| } |
| |
| if(auditEvent != null) { |
| auditEvent.setResultReason(path); |
| auditEvent.setAccessResult((short) (accessGranted ? 1 : 0)); |
| auditEvent.setAccessType(action == null ? null : action.toString()); |
| auditEvent.setAclEnforcer(hadoopModuleName); |
| auditEvent.setPolicyId(-1); |
| } |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuditHandler.logHadoopEvent(" + path + ", " + action + ", " + accessGranted + "): " + auditEvent); |
| } |
| } |
| |
| public void flushAudit() { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerHdfsAuditHandler.flushAudit(" + isAuditEnabled + ", " + auditEvent + ")"); |
| } |
| |
| if(isAuditEnabled && auditEvent != null && !StringUtils.isEmpty(auditEvent.getAccessType())) { |
| String username = auditEvent.getUser(); |
| |
| boolean skipLog = (username != null && excludeUsers != null && excludeUsers.contains(username)) || (auditOnlyIfDenied && auditEvent.getAccessResult() != 0); |
| |
| if (! skipLog) { |
| super.logAuthzAudit(auditEvent); |
| } |
| } |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerHdfsAuditHandler.flushAudit(" + isAuditEnabled + ", " + auditEvent + ")"); |
| } |
| } |
| } |
| |