| /** |
| * 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 |
| * <p> |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * <p> |
| * 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 permission and |
| * limitations under the License. |
| */ |
| package org.apache.sentry.hdfs; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import org.apache.hadoop.conf.Configurable; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.permission.*; |
| import org.apache.hadoop.hdfs.DFSConfigKeys; |
| import org.apache.hadoop.hdfs.DFSUtil; |
| |
| import org.apache.hadoop.hdfs.server.namenode.AclEntryStatusFormat; |
| import org.apache.hadoop.hdfs.server.namenode.AclFeature; |
| import org.apache.hadoop.hdfs.server.namenode.INodeAttributeProvider; |
| import org.apache.hadoop.hdfs.server.namenode.INodeAttributes; |
| import org.apache.hadoop.hdfs.server.namenode.INode; |
| import org.apache.hadoop.hdfs.server.namenode.INodeDirectory; |
| import org.apache.hadoop.hdfs.server.namenode.XAttrFeature; |
| import org.apache.hadoop.security.AccessControlException; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.*; |
| |
| public class SentryINodeAttributesProvider extends INodeAttributeProvider |
| implements Configurable { |
| |
| private static Logger LOG = |
| LoggerFactory.getLogger(SentryINodeAttributesProvider.class); |
| |
| static class SentryAclFeature extends AclFeature { |
| public SentryAclFeature(ImmutableList<AclEntry> entries) { |
| super(AclEntryStatusFormat.toInt(entries)); |
| } |
| } |
| |
| class SentryPermissionEnforcer implements AccessControlEnforcer { |
| private final AccessControlEnforcer ace; |
| |
| SentryPermissionEnforcer(INodeAttributeProvider.AccessControlEnforcer ace) { |
| this.ace = ace; |
| } |
| |
| @Override |
| public void checkPermission(String fsOwner, String supergroup, |
| UserGroupInformation callerUgi, |
| 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 { |
| String[] pathElems = getPathElems(pathByNameArr); |
| if (pathElems != null && (pathElems.length > 1) && ("".equals(pathElems[0]))) { |
| pathElems = Arrays.copyOfRange(pathElems, 1, pathElems.length); |
| } |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Enforcing Permission : + " + Lists |
| .newArrayList(fsOwner, supergroup, callerUgi.getShortUserName(), |
| Arrays.toString(callerUgi.getGroupNames()), |
| Arrays.toString(pathElems), ancestorAccess, |
| parentAccess, access, subAccess, ignoreEmptyDir)); |
| } |
| ace.checkPermission(fsOwner, supergroup, callerUgi, |
| inodeAttrs, inodes, |
| pathByNameArr, snapshotId, path, ancestorIndex, |
| doCheckOwner, |
| ancestorAccess, parentAccess, access, subAccess, |
| ignoreEmptyDir); |
| } |
| |
| private String[] getPathElems(byte[][] pathByName) { |
| String[] retVal = new String[pathByName.length]; |
| for (int i = 0; i < pathByName.length; i++) { |
| retVal[i] = (pathByName[i] != null) ? DFSUtil.bytes2String |
| (pathByName[i]) : ""; |
| } |
| return retVal; |
| } |
| } |
| |
| public class SentryINodeAttributes implements INodeAttributes { |
| |
| private final INodeAttributes defaultAttributes; |
| private final String[] pathElements; |
| |
| public SentryINodeAttributes(INodeAttributes defaultAttributes, String[] |
| pathElements) { |
| this.defaultAttributes = defaultAttributes; |
| this.pathElements = pathElements; |
| } |
| |
| @Override |
| public boolean isDirectory() { |
| return defaultAttributes.isDirectory(); |
| } |
| |
| @Override |
| public byte[] getLocalNameBytes() { |
| return defaultAttributes.getLocalNameBytes(); |
| } |
| |
| @Override |
| public String getUserName() { |
| return isSentryManaged(pathElements)? |
| SentryINodeAttributesProvider.this.user : defaultAttributes.getUserName(); |
| } |
| |
| @Override |
| public String getGroupName() { |
| return isSentryManaged(pathElements)? |
| SentryINodeAttributesProvider.this.group : defaultAttributes.getGroupName(); |
| } |
| |
| @Override |
| public FsPermission getFsPermission() { |
| FsPermission permission; |
| |
| if (!isSentryManaged(pathElements)) { |
| permission = defaultAttributes.getFsPermission(); |
| } else { |
| FsPermission returnPerm = SentryINodeAttributesProvider.this.permission; |
| // Handle case when prefix directory is itself associated with an |
| // authorizable object (default db directory in hive) |
| // An executable permission needs to be set on the the prefix directory |
| // in this case.. else, subdirectories (which map to other dbs) will |
| // not be travesible. |
| for (String [] prefixPath : authzInfo.getPathPrefixes()) { |
| if (Arrays.equals(prefixPath, pathElements)) { |
| returnPerm = FsPermission.createImmutable((short)(returnPerm.toShort() | 0x01)); |
| break; |
| } |
| } |
| permission = returnPerm; |
| } |
| return permission; |
| } |
| |
| @Override |
| public short getFsPermissionShort() { |
| return getFsPermission().toShort(); |
| } |
| |
| @Override |
| public long getPermissionLong() { |
| PermissionStatus permissionStatus = new PermissionStatus(getUserName(), |
| getGroupName(), getFsPermission()); |
| // No other way to get the long permission currently |
| return new INodeDirectory(0L, null, permissionStatus, 0L) |
| .getPermissionLong(); |
| } |
| |
| /** |
| * Returns hadoop acls if |
| * - Not managed |
| * - Not stale and not an auth obj |
| * Returns hive:hive |
| * - If stale |
| * Returns sentry acls |
| * - Otherwise, if not stale and auth obj |
| **/ |
| @Override |
| public AclFeature getAclFeature() { |
| AclFeature aclFeature; |
| String p = Arrays.toString(pathElements); |
| boolean isPrefixed = false; |
| boolean isStale = false; |
| boolean hasAuthzObj = false; |
| Map<String, AclEntry> aclMap = null; |
| |
| // If path is not under prefix, return hadoop acls. |
| if (!authzInfo.isUnderPrefix(pathElements)) { |
| isPrefixed = false; |
| aclFeature = defaultAttributes.getAclFeature(); |
| } else if (!authzInfo.doesBelongToAuthzObject(pathElements)) { |
| // If path is not managed, return hadoop acls. |
| isPrefixed = true; |
| aclFeature = defaultAttributes.getAclFeature(); |
| } else { |
| // If path is managed, add original hadoop permission if originalAuthzAsAcl true. |
| isPrefixed = true; |
| hasAuthzObj = true; |
| aclMap = new HashMap<String, AclEntry>(); |
| if (originalAuthzAsAcl) { |
| String user = defaultAttributes.getUserName(); |
| String group = defaultAttributes.getGroupName(); |
| FsPermission perm = defaultAttributes.getFsPermission(); |
| addToACLMap(aclMap, createAclEntries(user, group, perm)); |
| } else { |
| // else add hive:hive |
| addToACLMap(aclMap, createAclEntries(user, group, permission)); |
| } |
| if (!authzInfo.isStale()) { |
| // if not stale return sentry acls. |
| isStale = false; |
| addToACLMap(aclMap, authzInfo.getAclEntries(pathElements)); |
| aclFeature = new SentryAclFeature(ImmutableList.copyOf(aclMap.values())); |
| } else { |
| // if stale return hive:hive |
| isStale = true; |
| aclFeature = new SentryAclFeature(ImmutableList.copyOf(aclMap.values())); |
| } |
| } |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("### getAclEntry \n[" + (p == null ? "null" : p) + "] : [" |
| + "isPreifxed=" + isPrefixed |
| + ", isStale=" + isStale |
| + ", hasAuthzObj=" + hasAuthzObj |
| + ", origAuthzAsAcl=" + originalAuthzAsAcl + "]\n" |
| + "[" + (aclMap == null ? "null" : aclMap) + "]\n"); |
| } |
| return aclFeature; |
| } |
| |
| @Override |
| public XAttrFeature getXAttrFeature() { |
| return defaultAttributes.getXAttrFeature(); |
| } |
| |
| @Override |
| public long getModificationTime() { |
| return defaultAttributes.getModificationTime(); |
| } |
| |
| @Override |
| public long getAccessTime() { |
| return defaultAttributes.getAccessTime(); |
| } |
| } |
| |
| private boolean started; |
| private SentryAuthorizationInfo authzInfo; |
| private String user; |
| private String group; |
| private FsPermission permission; |
| private boolean originalAuthzAsAcl; |
| private Configuration conf; |
| |
| public SentryINodeAttributesProvider() { |
| } |
| |
| private boolean isSentryManaged(final String[] pathElements) { |
| return authzInfo.isSentryManaged(pathElements); |
| } |
| |
| @VisibleForTesting |
| SentryINodeAttributesProvider(SentryAuthorizationInfo authzInfo) { |
| this.authzInfo = authzInfo; |
| } |
| |
| @Override |
| public void setConf(Configuration conf) { |
| this.conf = conf; |
| } |
| |
| @Override |
| public Configuration getConf() { |
| return conf; |
| } |
| |
| |
| @Override |
| public void start() { |
| if (started) { |
| throw new IllegalStateException("Provider already started"); |
| } |
| started = true; |
| try { |
| if (!conf.getBoolean(DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_KEY, |
| false)) { |
| throw new RuntimeException("HDFS ACLs must be enabled"); |
| } |
| Configuration conf = new Configuration(this.conf); |
| conf.addResource(SentryAuthorizationConstants.CONFIG_FILE, true); |
| user = conf.get(SentryAuthorizationConstants.HDFS_USER_KEY, |
| SentryAuthorizationConstants.HDFS_USER_DEFAULT); |
| group = conf.get(SentryAuthorizationConstants.HDFS_GROUP_KEY, |
| SentryAuthorizationConstants.HDFS_GROUP_DEFAULT); |
| permission = FsPermission.createImmutable( |
| (short) conf.getLong(SentryAuthorizationConstants |
| .HDFS_PERMISSION_KEY, |
| SentryAuthorizationConstants.HDFS_PERMISSION_DEFAULT) |
| ); |
| originalAuthzAsAcl = conf.getBoolean( |
| SentryAuthorizationConstants.INCLUDE_HDFS_AUTHZ_AS_ACL_KEY, |
| SentryAuthorizationConstants.INCLUDE_HDFS_AUTHZ_AS_ACL_DEFAULT); |
| |
| LOG.info("Starting"); |
| LOG.info("Config: hdfs-user[{}] hdfs-group[{}] hdfs-permission[{}] " + |
| "include-hdfs-authz-as-acl[{}]", new Object[] |
| {user, group, permission, originalAuthzAsAcl}); |
| |
| if (authzInfo == null) { |
| authzInfo = new SentryAuthorizationInfo(conf); |
| } |
| authzInfo.start(); |
| } catch (Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| @Override |
| public void stop() { |
| LOG.debug(getClass().getSimpleName() + ": Stopping"); |
| authzInfo.stop(); |
| } |
| |
| @Override |
| public INodeAttributes getAttributes(String[] pathElements, |
| INodeAttributes inode) { |
| Preconditions.checkNotNull(pathElements); |
| |
| if (pathElements.length == 0) { |
| return inode; |
| } |
| |
| pathElements = "".equals(pathElements[0]) && pathElements.length > 1 ? |
| Arrays.copyOfRange(pathElements, 1, pathElements.length) : |
| pathElements; |
| return isSentryManaged(pathElements) ? new SentryINodeAttributes |
| (inode, pathElements) : inode; |
| } |
| |
| @Override |
| public AccessControlEnforcer getExternalAccessControlEnforcer |
| (AccessControlEnforcer defaultEnforcer) { |
| return new SentryPermissionEnforcer(defaultEnforcer); |
| } |
| |
| private static void addToACLMap(Map<String, AclEntry> map, |
| Collection<AclEntry> entries) { |
| for (AclEntry ent : entries) { |
| String key = (ent.getName() == null ? "" : ent.getName()) |
| + ent.getScope() + ent.getType(); |
| AclEntry aclEntry = map.get(key); |
| if (aclEntry == null) { |
| map.put(key, ent); |
| } else { |
| map.put(key, |
| new AclEntry.Builder(). |
| setName(ent.getName()). |
| setScope(ent.getScope()). |
| setType(ent.getType()). |
| setPermission(ent.getPermission().or(aclEntry |
| .getPermission())). |
| build()); |
| } |
| } |
| } |
| |
| private static List<AclEntry> createAclEntries(String user, String group, |
| FsPermission permission) { |
| List<AclEntry> list = new ArrayList<AclEntry>(); |
| AclEntry.Builder builder = new AclEntry.Builder(); |
| FsPermission fsPerm = new FsPermission(permission); |
| builder.setName(user); |
| builder.setType(AclEntryType.USER); |
| builder.setScope(AclEntryScope.ACCESS); |
| builder.setPermission(fsPerm.getUserAction()); |
| list.add(builder.build()); |
| builder.setName(group); |
| builder.setType(AclEntryType.GROUP); |
| builder.setScope(AclEntryScope.ACCESS); |
| builder.setPermission(fsPerm.getGroupAction()); |
| list.add(builder.build()); |
| builder.setName(null); |
| return list; |
| } |
| } |