blob: 419ab68e0d03f995c55d229b762453468de47571 [file] [log] [blame]
/**
* 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 permission and
* limitations under the License.
*/
package org.apache.sentry.hdfs;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.hadoop.conf.Configurable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.UnresolvedLinkException;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclEntryScope;
import org.apache.hadoop.fs.permission.AclEntryType;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.server.namenode.AclFeature;
import org.apache.hadoop.hdfs.server.namenode.AuthorizationProvider;
import org.apache.hadoop.security.AccessControlException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
public class SentryAuthorizationProvider
extends AuthorizationProvider implements Configurable {
static class SentryAclFeature extends AclFeature {
public SentryAclFeature(ImmutableList<AclEntry> entries) {
super(entries);
}
}
private static Logger LOG =
LoggerFactory.getLogger(SentryAuthorizationProvider.class);
private boolean started;
private Configuration conf;
private AuthorizationProvider defaultAuthzProvider;
private String user;
private String group;
private FsPermission permission;
private boolean originalAuthzAsAcl;
private SentryAuthorizationInfo authzInfo;
public SentryAuthorizationProvider() {
this(null);
}
@VisibleForTesting
SentryAuthorizationProvider(SentryAuthorizationInfo authzInfo) {
this.authzInfo = authzInfo;
}
@Override
public void setConf(Configuration conf) {
this.conf = conf;
}
@Override
public Configuration getConf() {
return conf;
}
@Override
public synchronized 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");
}
defaultAuthzProvider = AuthorizationProvider.get();
defaultAuthzProvider.start();
// Configuration is read from hdfs-sentry.xml and NN configuration, in
// that order of precedence.
Configuration conf = new Configuration(this.conf);
conf.addResource(SentryAuthorizationConstants.CONFIG_FILE);
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 synchronized void stop() {
LOG.debug("Stopping");
authzInfo.stop();
defaultAuthzProvider.stop();
defaultAuthzProvider = null;
}
@Override
public void setSnaphottableDirs(Map<INodeAuthorizationInfo, Integer>
snapshotableDirs) {
defaultAuthzProvider.setSnaphottableDirs(snapshotableDirs);
}
@Override
public void addSnapshottable(INodeAuthorizationInfo dir) {
defaultAuthzProvider.addSnapshottable(dir);
}
@Override
public void removeSnapshottable(INodeAuthorizationInfo dir) {
defaultAuthzProvider.removeSnapshottable(dir);
}
@Override
public void createSnapshot(INodeAuthorizationInfo dir, int snapshotId)
throws IOException{
defaultAuthzProvider.createSnapshot(dir, snapshotId);
}
@Override
public void removeSnapshot(INodeAuthorizationInfo dir, int snapshotId)
throws IOException {
defaultAuthzProvider.removeSnapshot(dir, snapshotId);
}
@Override
public void checkPermission(String user, Set<String> groups,
INodeAuthorizationInfo[] inodes, int snapshotId,
boolean doCheckOwner, FsAction ancestorAccess, FsAction parentAccess,
FsAction access, FsAction subAccess, boolean ignoreEmptyDir)
throws AccessControlException, UnresolvedLinkException {
defaultAuthzProvider.checkPermission(user, groups, inodes, snapshotId,
doCheckOwner, ancestorAccess, parentAccess, access, subAccess,
ignoreEmptyDir);
}
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private String[] getPathElements(INodeAuthorizationInfo node) {
return getPathElements(node, 0);
}
private String[] getPathElements(INodeAuthorizationInfo node, int idx) {
String[] paths;
INodeAuthorizationInfo parent = node.getParent();
if (parent == null) {
paths = (idx > 0) ? new String[idx] : EMPTY_STRING_ARRAY;
} else {
paths = getPathElements(parent, idx + 1);
paths[paths.length - 1 - idx] = node.getLocalName();
}
return paths;
}
@Override
public void setUser(INodeAuthorizationInfo node, String user) {
defaultAuthzProvider.setUser(node, user);
}
@Override
public String getUser(INodeAuthorizationInfo node, int snapshotId) {
String user;
String[] pathElements = getPathElements(node);
if (!authzInfo.isManaged(pathElements)) {
user = defaultAuthzProvider.getUser(node, snapshotId);
} else if (!authzInfo.doesBelongToAuthzObject(pathElements)) {
user = defaultAuthzProvider.getUser(node, snapshotId);
} else {
user = this.user;
}
return user;
}
@Override
public void setGroup(INodeAuthorizationInfo node, String group) {
defaultAuthzProvider.setGroup(node, group);
}
@Override
public String getGroup(INodeAuthorizationInfo node, int snapshotId) {
String group;
String[] pathElements = getPathElements(node);
if (!authzInfo.isManaged(pathElements)) {
group = getDefaultProviderGroup(node, snapshotId);
} else if (!authzInfo.doesBelongToAuthzObject(pathElements)) {
group = getDefaultProviderGroup(node, snapshotId);
} else {
group = this.group;
}
return group;
}
@Override
public void setPermission(INodeAuthorizationInfo node,
FsPermission permission) {
defaultAuthzProvider.setPermission(node, permission);
}
@Override
public FsPermission getFsPermission(
INodeAuthorizationInfo node, int snapshotId) {
FsPermission permission;
String[] pathElements = getPathElements(node);
if (!authzInfo.isManaged(pathElements)) {
permission = defaultAuthzProvider.getFsPermission(node, snapshotId);
} else if (!authzInfo.doesBelongToAuthzObject(pathElements)) {
permission = defaultAuthzProvider.getFsPermission(node, snapshotId);
}
else {
FsPermission returnPerm = 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;
}
private 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;
}
/*
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(INodeAuthorizationInfo node, int snapshotId) {
AclFeature f = null;
String[] pathElements = getPathElements(node);
String p = Arrays.toString(pathElements);
boolean isManaged = false;
boolean isStale = false;
boolean hasAuthzObj = false;
Map<String, AclEntry> aclMap = null;
if (!authzInfo.isManaged(pathElements)) {
isManaged = false;
f = defaultAuthzProvider.getAclFeature(node, snapshotId);
} else if (!authzInfo.doesBelongToAuthzObject(pathElements)) {
isManaged = true;
f = defaultAuthzProvider.getAclFeature(node, snapshotId);
} else {
isManaged = true;
hasAuthzObj = true;
aclMap = new HashMap<String, AclEntry>();
if (originalAuthzAsAcl) {
String user = defaultAuthzProvider.getUser(node, snapshotId);
String group = getDefaultProviderGroup(node, snapshotId);
FsPermission perm = defaultAuthzProvider.getFsPermission(node, snapshotId);
addToACLMap(aclMap, createAclEntries(user, group, perm));
} else {
addToACLMap(aclMap,
createAclEntries(this.user, this.group, this.permission));
}
if (!authzInfo.isStale()) {
isStale = false;
addToACLMap(aclMap, authzInfo.getAclEntries(pathElements));
f = new SentryAclFeature(ImmutableList.copyOf(aclMap.values()));
} else {
isStale = true;
f = new SentryAclFeature(ImmutableList.copyOf(aclMap.values()));
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("### getAclEntry \n[" + (p == null ? "null" : p) + "] : ["
+ "isManaged=" + isManaged
+ ", isStale=" + isStale
+ ", hasAuthzObj=" + hasAuthzObj
+ ", origAuthzAsAcl=" + originalAuthzAsAcl + "]\n"
+ "[" + (aclMap == null ? "null" : aclMap) + "]\n"
+ "[" + (f == null ? "null" : f.getEntries()) + "]\n");
}
return f;
}
private 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 String getDefaultProviderGroup(INodeAuthorizationInfo node,
int snapshotId) {
String group = defaultAuthzProvider.getGroup(node, snapshotId);
INodeAuthorizationInfo pNode = node.getParent();
while (group == null && pNode != null) {
group = defaultAuthzProvider.getGroup(pNode, snapshotId);
pNode = pNode.getParent();
}
return group;
}
@Override
public void removeAclFeature(INodeAuthorizationInfo node) {
AclFeature aclFeature = node.getAclFeature(CURRENT_STATE_ID);
if (aclFeature.getClass() != SentryAclFeature.class) {
defaultAuthzProvider.removeAclFeature(node);
}
}
@Override
public void addAclFeature(INodeAuthorizationInfo node, AclFeature f) {
String[] pathElements = getPathElements(node);
if (!authzInfo.isManaged(pathElements)) {
defaultAuthzProvider.addAclFeature(node, f);
}
}
}