Repository: sentry Updated Branches: refs/heads/master 48422f4cc -> 266857472
SENTRY-2174: Sentry authorization provider should now generate ACL for users. (Kalyan Kumar kalvagadda, reviewed-by Na Li and Sergio Pena) Project: http://git-wip-us.apache.org/repos/asf/sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/26685747 Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/26685747 Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/26685747 Branch: refs/heads/master Commit: 266857472e49bccb02a70328a3d1f45eb01863a4 Parents: 48422f4 Author: Kalyan Kumar Kalvagadda <[email protected]> Authored: Thu May 17 12:01:15 2018 -0500 Committer: Kalyan Kumar Kalvagadda <[email protected]> Committed: Thu May 17 12:01:15 2018 -0500 ---------------------------------------------------------------------- .../apache/sentry/hdfs/SentryPermissions.java | 164 ++++++++++++++++--- .../sentry/hdfs/TestSentryPermissions.java | 118 +++++++++++++ 2 files changed, 260 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sentry/blob/26685747/sentry-hdfs/sentry-hdfs-namenode-plugin/src/main/java/org/apache/sentry/hdfs/SentryPermissions.java ---------------------------------------------------------------------- diff --git a/sentry-hdfs/sentry-hdfs-namenode-plugin/src/main/java/org/apache/sentry/hdfs/SentryPermissions.java b/sentry-hdfs/sentry-hdfs-namenode-plugin/src/main/java/org/apache/sentry/hdfs/SentryPermissions.java index a88d8e2..c162ec1 100644 --- a/sentry-hdfs/sentry-hdfs-namenode-plugin/src/main/java/org/apache/sentry/hdfs/SentryPermissions.java +++ b/sentry-hdfs/sentry-hdfs-namenode-plugin/src/main/java/org/apache/sentry/hdfs/SentryPermissions.java @@ -25,6 +25,8 @@ import org.apache.hadoop.fs.permission.AclEntryType; import org.apache.hadoop.fs.permission.FsAction; import org.apache.sentry.hdfs.service.thrift.TPrivilegeEntity; import org.apache.sentry.hdfs.service.thrift.TPrivilegeEntityType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SentryPermissions implements AuthzPermissions { @@ -85,12 +87,89 @@ public class SentryPermissions implements AuthzPermissions { } } + /** + * Defines HDFS ACL entity to which ACL's are assigned. + */ + public static class HdfsAclEntity { + private final AclEntryType type; + private final String value; + + private HdfsAclEntity(AclEntryType type, String value) throws IllegalArgumentException { + if(type == AclEntryType.USER || type == AclEntryType.GROUP) { + this.type = type; + this.value = value; + } else { + throw new IllegalArgumentException("Invalid AclEntryType"); + } + } + public static HdfsAclEntity constructAclEntityForUser(String user) { + return new HdfsAclEntity(AclEntryType.USER, user); + } + + public static HdfsAclEntity constructAclEntityForGroup(String group) { + return new HdfsAclEntity(AclEntryType.GROUP, group); + } + + public AclEntryType getType() { + return type; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + HdfsAclEntity other = (HdfsAclEntity) obj; + if (type == null) { + if (other.type != null) { + return false; + } + } else if (!type.equals(other.type)) { + return false; + } + + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + + return result; + } + } + // Comparison of authorizable object should be case insensitive. private final Map<String, PrivilegeInfo> privileges = new TreeMap<String, PrivilegeInfo>(String.CASE_INSENSITIVE_ORDER); private Map<String, Set<String>> authzObjChildren = new TreeMap<String, Set<String>>(String.CASE_INSENSITIVE_ORDER); // RoleInfo should be case insensitive. private final Map<String, RoleInfo> roles = new TreeMap<String, RoleInfo>(String.CASE_INSENSITIVE_ORDER); + private static Logger LOG = + LoggerFactory.getLogger(SentryINodeAttributesProvider.class); + String getParentAuthzObject(String authzObject) { if (authzObject != null) { @@ -127,37 +206,56 @@ public class SentryPermissions implements AuthzPermissions { } } - private Map<String, FsAction> getGroupPerms(String authzObj) { - Map<String, FsAction> groupPerms; + /** + * Retrieves all the permissions granted to the object directly and inherited from + * the parents. + * @param authzObj Object name for which permissions are needed. + * @return Sentry Permissions + */ + private Map<HdfsAclEntity, FsAction> getPerms(String authzObj) { + Map<HdfsAclEntity, FsAction> perms; String parent = getParentAuthzObject(authzObj); if (parent == null || parent.equals(authzObj)) { - groupPerms = new HashMap<String, FsAction>(); + perms = new HashMap<HdfsAclEntity, FsAction>(); } else { - groupPerms = getGroupPerms(parent); + perms = getPerms(parent); } PrivilegeInfo privilegeInfo = privileges.get(authzObj); if (privilegeInfo != null) { for (Map.Entry<TPrivilegeEntity, FsAction> privs : privilegeInfo .getAllPermissions().entrySet()) { - if(privs.getKey().getType() == TPrivilegeEntityType.ROLE) { - constructAclEntry(privs.getKey().getValue(), privs.getValue(), groupPerms); - } + constructHdfsPermissions(privs.getKey(), privs.getValue(), perms); } } - return groupPerms; + return perms; } + /** + * Constructs HDFS ACL's based on the permissions granted to the object directly + * and inherited from the parents. + * @param authzObj Object name for which ACL are needed + * @return HDFS ACL's + */ @Override public List<AclEntry> getAcls(String authzObj) { - Map<String, FsAction> groupPerms = getGroupPerms(authzObj); + Map<HdfsAclEntity, FsAction> permissions = getPerms(authzObj); + List<AclEntry> retList = new LinkedList<AclEntry>(); - for (Map.Entry<String, FsAction> groupPerm : groupPerms.entrySet()) { + for (Map.Entry<HdfsAclEntity, FsAction> permission : permissions.entrySet()) { AclEntry.Builder builder = new AclEntry.Builder(); - builder.setName(groupPerm.getKey()); - builder.setType(AclEntryType.GROUP); + if(permission.getKey().getType() == AclEntryType.GROUP) { + builder.setName(permission.getKey().getValue()); + builder.setType(AclEntryType.GROUP); + } else if (permission.getKey().getType() == AclEntryType.USER){ + builder.setName(permission.getKey().getValue()); + builder.setType(AclEntryType.USER); + } else { + LOG.warn("Permissions for Invalid AclEntryType: %s", permission.getKey().getType()); + continue; + } builder.setScope(AclEntryScope.ACCESS); - FsAction action = groupPerm.getValue(); + FsAction action = permission.getValue(); if (action == FsAction.READ || action == FsAction.WRITE || action == FsAction.READ_WRITE) { action = action.or(FsAction.EXECUTE); @@ -168,17 +266,39 @@ public class SentryPermissions implements AuthzPermissions { return retList; } - private void constructAclEntry(String role, FsAction permission, - Map<String, FsAction> groupPerms) { - RoleInfo roleInfo = roles.get(role); - if (roleInfo != null) { - for (String group : roleInfo.groups) { - FsAction fsAction = groupPerms.get(group); - if (fsAction == null) { - fsAction = FsAction.NONE; + /** + * Constructs HDFS Permissions entry based on the privileges granted. + * @param privilegeEntity Privilege Entity + * @param permission Permission granted + * @param perms + */ + private void constructHdfsPermissions(TPrivilegeEntity privilegeEntity, FsAction permission, + Map<HdfsAclEntity, FsAction> perms) { + HdfsAclEntity aclEntry; + FsAction fsAction; + if(privilegeEntity.getType() == TPrivilegeEntityType.ROLE) { + RoleInfo roleInfo = roles.get(privilegeEntity.getValue()); + if (roleInfo != null) { + for (String group : roleInfo.groups) { + aclEntry = HdfsAclEntity.constructAclEntityForGroup(group); + // fsAction is an aggregate of permissions granted to + // the group on the object and it's parents. + fsAction = perms.get(aclEntry); + if (fsAction == null) { + fsAction = FsAction.NONE; + } + perms.put(aclEntry, fsAction.or(permission)); } - groupPerms.put(group, fsAction.or(permission)); } + } else if(privilegeEntity.getType() == TPrivilegeEntityType.USER) { + aclEntry = HdfsAclEntity.constructAclEntityForUser(privilegeEntity.getValue()); + // fsAction is an aggregate of permissions granted to + // the user on the object and it's parents. + fsAction = perms.get(aclEntry); + if (fsAction == null) { + fsAction = FsAction.NONE; + } + perms.put(aclEntry, fsAction.or(permission)); } } http://git-wip-us.apache.org/repos/asf/sentry/blob/26685747/sentry-hdfs/sentry-hdfs-namenode-plugin/src/test/java/org/apache/sentry/hdfs/TestSentryPermissions.java ---------------------------------------------------------------------- diff --git a/sentry-hdfs/sentry-hdfs-namenode-plugin/src/test/java/org/apache/sentry/hdfs/TestSentryPermissions.java b/sentry-hdfs/sentry-hdfs-namenode-plugin/src/test/java/org/apache/sentry/hdfs/TestSentryPermissions.java index dbce405..f0ca787 100644 --- a/sentry-hdfs/sentry-hdfs-namenode-plugin/src/test/java/org/apache/sentry/hdfs/TestSentryPermissions.java +++ b/sentry-hdfs/sentry-hdfs-namenode-plugin/src/test/java/org/apache/sentry/hdfs/TestSentryPermissions.java @@ -19,6 +19,13 @@ package org.apache.sentry.hdfs; +import java.util.List; + +import org.apache.hadoop.fs.permission.AclEntryType; +import org.apache.hadoop.fs.permission.FsAction; +import org.apache.sentry.hdfs.service.thrift.TPrivilegeEntity; +import org.apache.sentry.hdfs.service.thrift.TPrivilegeEntityType; +import org.apache.hadoop.fs.permission.AclEntry; import org.junit.Assert; import org.junit.Test; @@ -37,4 +44,115 @@ public class TestSentryPermissions { Assert.assertNotNull(perm.getRoleInfo("admin")); Assert.assertNull(perm.getRoleInfo("doesNotExist")); } + + /** + * Adds group permissions and role info and check is the ACL are properly generated. + */ + @Test + public void testSentryRolePermissions() { + String authorizable = "db1.tb1"; + FsAction fsAction = FsAction.ALL; + SentryPermissions perms = new SentryPermissions(); + SentryPermissions.RoleInfo roleInfo = new SentryPermissions.RoleInfo("role1"); + roleInfo.addGroup("group1"); + roleInfo.addGroup("group2"); + TPrivilegeEntity roleEntity = new TPrivilegeEntity(TPrivilegeEntityType.ROLE, "role1"); + + perms.addRoleInfo(roleInfo); + + SentryPermissions.PrivilegeInfo pInfo = new SentryPermissions.PrivilegeInfo(authorizable); + pInfo.setPermission(roleEntity, fsAction); + + perms.addPrivilegeInfo(pInfo); + + List<AclEntry> acls = perms.getAcls(authorizable); + Assert.assertEquals("Unexpected number of ACL entries received", 2, acls.size()); + Assert.assertEquals("Unexpected permission", fsAction, acls.get(0).getPermission()); + } + + /** + * Adds user permissions and check is the ACL are properly generated. + */ + @Test + public void testSentryUserPermissions() { + String authorizable = "db1.tb1"; + FsAction fsAction = FsAction.ALL; + TPrivilegeEntity userEntity = new TPrivilegeEntity(TPrivilegeEntityType.USER, "user1"); + + SentryPermissions perms = new SentryPermissions(); + SentryPermissions.PrivilegeInfo pInfo = new SentryPermissions.PrivilegeInfo(authorizable); + pInfo.setPermission(userEntity, fsAction); + + perms.addPrivilegeInfo(pInfo); + + List<AclEntry> acls = perms.getAcls(authorizable); + Assert.assertEquals("Unexpected number of ACL entries received", 1, acls.size()); + Assert.assertEquals("Unexpected permission", fsAction, acls.get(0).getPermission()); + } + + /** + * Adds aggregated user permissions and check is the ACL are properly generated. + */ + @Test + public void testSentryAggregatedUserPermissions() { + String authorizable = null; + // Add read permission for database + authorizable = "db1"; + TPrivilegeEntity userEntity = new TPrivilegeEntity(TPrivilegeEntityType.USER, "user1"); + + SentryPermissions perms = new SentryPermissions(); + SentryPermissions.PrivilegeInfo pInfo = new SentryPermissions.PrivilegeInfo(authorizable); + pInfo.setPermission(userEntity, FsAction.READ_EXECUTE); + perms.addPrivilegeInfo(pInfo); + + // Add write permission for a particular table in the database. + authorizable = "db1.tb1"; + pInfo = new SentryPermissions.PrivilegeInfo(authorizable); + pInfo.setPermission(userEntity, FsAction.WRITE_EXECUTE); + perms.addPrivilegeInfo(pInfo); + + List<AclEntry> acls = perms.getAcls(authorizable); + Assert.assertEquals("Unexpected number of ACL entries received", 1, acls.size()); + Assert.assertEquals("Unexpected permission", FsAction.ALL, acls.get(0).getPermission()); + } + + /** + * Adds user and group permissions and role info and check is the ACL are properly generated. + */ + @Test + public void testSentryPermissions() { + String authorizable = "db1.tb1"; + FsAction fsAction = FsAction.ALL; + SentryPermissions perms = new SentryPermissions(); + SentryPermissions.RoleInfo roleInfo = new SentryPermissions.RoleInfo("role1"); + roleInfo.addGroup("group1"); + roleInfo.addGroup("group2"); + TPrivilegeEntity roleEntity = new TPrivilegeEntity(TPrivilegeEntityType.ROLE, "role1"); + TPrivilegeEntity userEntity = new TPrivilegeEntity(TPrivilegeEntityType.USER, "user1"); + + perms.addRoleInfo(roleInfo); + + SentryPermissions.PrivilegeInfo pInfo = new SentryPermissions.PrivilegeInfo(authorizable); + pInfo.setPermission(roleEntity, fsAction); + pInfo.setPermission(userEntity, fsAction); + + perms.addPrivilegeInfo(pInfo); + + List<AclEntry> acls = perms.getAcls(authorizable); + Assert.assertEquals("Unexpected number of ACL entries received", 3, acls.size()); + Assert.assertEquals("Unexpected permission", fsAction, acls.get(0).getPermission()); + + int userAclCount = 0; + int groupAclCount = 0; + + for (AclEntry entry : acls) { + if(entry.getType() == AclEntryType.GROUP) { + groupAclCount++; + } else if (entry.getType() == AclEntryType.USER) { + userAclCount++; + } + } + Assert.assertEquals("Unexpected number of User ACL", 1, userAclCount); + Assert.assertEquals("Unexpected number of Group ACL", 2, groupAclCount); + } }
