diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java index 285a11c550f..7421aab3253 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java @@ -26,6 +26,7 @@ import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.LIST; import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ; import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ_ACL; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.WRITE; import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.WRITE_ACL; import com.fasterxml.jackson.databind.JsonNode; @@ -868,24 +869,22 @@ enum S3Action { EnumSet.noneOf(ACLType.class)), GET_BUCKET_ACL("s3:GetBucketAcl", ActionKind.BUCKET, EnumSet.of(READ), EnumSet.of(READ, READ_ACL), EnumSet.noneOf(ACLType.class)), - GET_BUCKET_LOCATION("s3:GetBucketLocation", ActionKind.BUCKET, EnumSet.of(READ), EnumSet.of(READ), - EnumSet.noneOf(ACLType.class)), // Used for HeadBucket, ListObjects and ListObjectsV2 apis LIST_BUCKET("s3:ListBucket", ActionKind.BUCKET, EnumSet.of(READ), EnumSet.of(READ, LIST), EnumSet.of(READ)), // Used for ListMultipartUploads API LIST_BUCKET_MULTIPART_UPLOADS("s3:ListBucketMultipartUploads", ActionKind.BUCKET, EnumSet.of(READ), EnumSet.of(READ, LIST), EnumSet.noneOf(ACLType.class)), - PUT_BUCKET_ACL("s3:PutBucketAcl", ActionKind.BUCKET, EnumSet.of(READ), EnumSet.of(WRITE_ACL), + PUT_BUCKET_ACL("s3:PutBucketAcl", ActionKind.BUCKET, EnumSet.of(READ), EnumSet.of(READ, READ_ACL, WRITE_ACL), EnumSet.noneOf(ACLType.class)), // Object-scope ABORT_MULTIPART_UPLOAD("s3:AbortMultipartUpload", ActionKind.OBJECT, EnumSet.of(READ), EnumSet.of(READ), - EnumSet.of(DELETE)), + EnumSet.of(WRITE)), // Used for DeleteObject (when versionId parameter is not supplied), // DeleteObjects (when versionId parameter is not supplied) APIs DELETE_OBJECT("s3:DeleteObject", ActionKind.OBJECT, EnumSet.of(READ), EnumSet.of(READ), EnumSet.of(DELETE)), DELETE_OBJECT_TAGGING("s3:DeleteObjectTagging", ActionKind.OBJECT, EnumSet.of(READ), EnumSet.of(READ), - EnumSet.of(DELETE)), + EnumSet.of(WRITE)), // Used for HeadObject, CopyObject (for source bucket), GetObject (without versionId parameter) APIs GET_OBJECT("s3:GetObject", ActionKind.OBJECT, EnumSet.of(READ), EnumSet.of(READ), EnumSet.of(READ)), GET_OBJECT_TAGGING("s3:GetObjectTagging", ActionKind.OBJECT, EnumSet.of(READ), EnumSet.of(READ), EnumSet.of(READ)), diff --git a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java index 0a8d1c47412..1d976514632 100644 --- a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java +++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java @@ -307,7 +307,7 @@ public void testBuildCaseInsensitiveS3ActionMap() { // Verify s3:Get* contains Get actions final Set getActions = caseInsensitiveS3ActionMap.get("s3:get*"); assertThat(getActions).containsOnly( - S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_BUCKET_LOCATION, S3Action.GET_OBJECT_TAGGING); + S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_OBJECT_TAGGING); // Verify s3:Put* contains Put actions final Set putActions = caseInsensitiveS3ActionMap.get("s3:put*"); @@ -380,13 +380,11 @@ public void testMapPolicyActionsToS3ActionsWithMultipleActionsMapAllCorrectly() @Test public void testMapPolicyActionsToS3ActionsWithWildcardExpansion() { final Set result = mapPolicyActionsToS3Actions(Collections.singleton("s3:Get*")); - assertThat(result).containsOnly(S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_BUCKET_LOCATION, - S3Action.GET_OBJECT_TAGGING); + assertThat(result).containsOnly(S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_OBJECT_TAGGING); // Ensure it is case-insensitive final Set resultCi = mapPolicyActionsToS3Actions(Collections.singleton("s3:gET*")); - assertThat(resultCi).containsOnly(S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_BUCKET_LOCATION, - S3Action.GET_OBJECT_TAGGING); + assertThat(resultCi).containsOnly(S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_OBJECT_TAGGING); } @Test @@ -415,15 +413,15 @@ public void testMapPolicyActionsToS3ActionsWithOnlyUnsupportedActionsReturnsEmpt @Test public void testMapPolicyActionsToS3ActionsDeduplicatesResults() { final Set result = mapPolicyActionsToS3Actions(strSet("s3:Get*", "s3:GetObject", "s3:GetBucketAcl")); - assertThat(result).containsOnly(S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_BUCKET_LOCATION, - S3Action.GET_OBJECT_TAGGING); + assertThat(result).containsOnly(S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_OBJECT_TAGGING); } @Test public void testMapPolicyActionsToS3ActionsHandlesMultipleWildcards() { final Set result = mapPolicyActionsToS3Actions(strSet("s3:Get*", "s3:Put*")); - assertThat(result).containsOnly(S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_BUCKET_LOCATION, - S3Action.GET_OBJECT_TAGGING, S3Action.PUT_OBJECT, S3Action.PUT_OBJECT_TAGGING, S3Action.PUT_BUCKET_ACL); + assertThat(result).containsOnly( + S3Action.GET_OBJECT, S3Action.GET_BUCKET_ACL, S3Action.GET_OBJECT_TAGGING, S3Action.PUT_OBJECT, + S3Action.PUT_OBJECT_TAGGING, S3Action.PUT_BUCKET_ACL); } @Test @@ -768,7 +766,7 @@ public void testCreatePathsAndPermissionsWithBucketWildcardResource() { final Set actions = Collections.singleton(IamSessionPolicyResolver.S3Action.PUT_BUCKET_ACL); final Set resourceSpecs = Collections.singleton( new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET_WILDCARD, "bucket1*", null, null)); - final Set writeAclObject = objSet(bucket("bucket1*")); + final Set readReadAclAndWriteAclObject = objSet(bucket("bucket1*")); final Set readVolume = objSet(volume()); expectIllegalArgumentException( @@ -779,7 +777,8 @@ public void testCreatePathsAndPermissionsWithBucketWildcardResource() { createPathsAndPermissions(VOLUME, RANGER, actions, resourceSpecs, null, objToAclsMapRanger); final Set resultRanger = groupObjectsByAcls(objToAclsMapRanger); assertThat(resultRanger).containsExactlyInAnyOrder( - new OzoneGrant(writeAclObject, acls(WRITE_ACL)), new OzoneGrant(readVolume, acls(READ))); + new OzoneGrant(readReadAclAndWriteAclObject, acls(READ, READ_ACL, WRITE_ACL)), + new OzoneGrant(readVolume, acls(READ))); } @Test @@ -827,6 +826,48 @@ public void testCreatePathsAndPermissionsWithObjectExactResource() { assertThat(resultRanger).containsExactly(new OzoneGrant(readObjects, acls(READ))); } + @Test + public void testCreatePathsAndPermissionsWithDeleteObjectGrantsDeleteOnKey() { + final Set actions = Collections.singleton(S3Action.DELETE_OBJECT); + final Set resourceSpecs = Collections.singleton( + new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_EXACT, "bucket1", null, "key.txt")); + final Set readVolumeAndBucket = objSet(volume(), bucket("bucket1")); + final Set deleteKey = objSet(key("bucket1", "key.txt")); + + final Map> objToAclsMapNative = new LinkedHashMap<>(); + createPathsAndPermissions(VOLUME, NATIVE, actions, resourceSpecs, null, objToAclsMapNative); + final Set resultNative = groupObjectsByAcls(objToAclsMapNative); + assertThat(resultNative).containsExactlyInAnyOrder( + new OzoneGrant(readVolumeAndBucket, acls(READ)), new OzoneGrant(deleteKey, acls(DELETE))); + + final Map> objToAclsMapRanger = new LinkedHashMap<>(); + createPathsAndPermissions(VOLUME, RANGER, actions, resourceSpecs, null, objToAclsMapRanger); + final Set resultRanger = groupObjectsByAcls(objToAclsMapRanger); + assertThat(resultRanger).containsExactlyInAnyOrder( + new OzoneGrant(readVolumeAndBucket, acls(READ)), new OzoneGrant(deleteKey, acls(DELETE))); + } + + @Test + public void testCreatePathsAndPermissionsWithAbortMultipartUploadGrantsWriteOnKey() { + final Set actions = Collections.singleton(S3Action.ABORT_MULTIPART_UPLOAD); + final Set resourceSpecs = Collections.singleton( + new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_EXACT, "bucket1", null, "key.txt")); + final Set readVolumeAndBucket = objSet(volume(), bucket("bucket1")); + final Set writeKey = objSet(key("bucket1", "key.txt")); + + final Map> objToAclsMapNative = new LinkedHashMap<>(); + createPathsAndPermissions(VOLUME, NATIVE, actions, resourceSpecs, null, objToAclsMapNative); + final Set resultNative = groupObjectsByAcls(objToAclsMapNative); + assertThat(resultNative).containsExactlyInAnyOrder( + new OzoneGrant(readVolumeAndBucket, acls(READ)), new OzoneGrant(writeKey, acls(WRITE))); + + final Map> objToAclsMapRanger = new LinkedHashMap<>(); + createPathsAndPermissions(VOLUME, RANGER, actions, resourceSpecs, null, objToAclsMapRanger); + final Set resultRanger = groupObjectsByAcls(objToAclsMapRanger); + assertThat(resultRanger).containsExactlyInAnyOrder( + new OzoneGrant(readVolumeAndBucket, acls(READ)), new OzoneGrant(writeKey, acls(WRITE))); + } + @Test public void testCreatePathsAndPermissionsWithObjectPrefixResource() { final Set actions = Collections.singleton(S3Action.GET_OBJECT); @@ -983,20 +1024,22 @@ public void testCreatePathsAndPermissionsDeduplicatesAcrossSameResourceTypes() { .collect(Collectors.toSet()); final Set resourceSpecs = Collections.singleton( new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_EXACT, "bucket1", null, "key.txt")); - final Set readAndDeleteObject = objSet(key("bucket1", "key.txt")); + final Set readAndDeleteAndWriteObject = objSet(key("bucket1", "key.txt")); final Set readObjects = objSet(bucket("bucket1"), volume()); final Map> objToAclsMapNative = new LinkedHashMap<>(); createPathsAndPermissions(VOLUME, NATIVE, actions, resourceSpecs, null, objToAclsMapNative); final Set resultNative = groupObjectsByAcls(objToAclsMapNative); assertThat(resultNative).containsExactlyInAnyOrder( - new OzoneGrant(readAndDeleteObject, acls(READ, DELETE)), new OzoneGrant(readObjects, acls(READ))); + new OzoneGrant(readAndDeleteAndWriteObject, acls(READ, DELETE, WRITE)), + new OzoneGrant(readObjects, acls(READ))); final Map> objToAclsMapRanger = new LinkedHashMap<>(); createPathsAndPermissions(VOLUME, RANGER, actions, resourceSpecs, null, objToAclsMapRanger); final Set resultRanger = groupObjectsByAcls(objToAclsMapRanger); assertThat(resultRanger).containsExactlyInAnyOrder( - new OzoneGrant(readAndDeleteObject, acls(READ, DELETE)), new OzoneGrant(readObjects, acls(READ))); + new OzoneGrant(readAndDeleteAndWriteObject, acls(READ, DELETE, WRITE)), + new OzoneGrant(readObjects, acls(READ))); } @Test @@ -1976,9 +2019,9 @@ public void testWildcardActionGroupPutStar() throws OMException { // Ensure what we got is what we expected final Set expectedResolvedNative = new LinkedHashSet<>(); - // Expected for native: bucket READ, WRITE_ACL acl + // Expected for native: bucket READ, READ_ACL, WRITE_ACL acl final Set bucketSet = objSet(bucket("my-bucket")); - final Set bucketAcl = acls(READ, WRITE_ACL); + final Set bucketAcl = acls(READ, READ_ACL, WRITE_ACL); expectedResolvedNative.add(new OzoneGrant(bucketSet, bucketAcl)); // Expected for native: CREATE, WRITE acls on prefix "" under bucket final Set keyPrefixSet = objSet(prefix("my-bucket", "")); @@ -1989,7 +2032,7 @@ public void testWildcardActionGroupPutStar() throws OMException { assertThat(resolvedFromNativeAuthorizer).isEqualTo(expectedResolvedNative); final Set expectedResolvedRanger = new LinkedHashSet<>(); - // Expected for Ranger: bucket READ, WRITE_ACL acl + // Expected for Ranger: bucket READ, READ_ACL, WRITE_ACL acl expectedResolvedRanger.add(new OzoneGrant(bucketSet, bucketAcl)); // Expected for Ranger: CREATE, WRITE key acls for resource type KEY with key name "*" final Set rangerKeySet = objSet(key("my-bucket", "*")); @@ -2017,17 +2060,17 @@ public void testWildcardActionGroupDeleteStar() throws OMException { // Ensure what we got is what we expected final Set expectedResolvedNative = new LinkedHashSet<>(); - // Expected for native: DELETE on prefix "" under bucket; bucket READ, DELETE; volume READ + // Expected for native: DELETE and WRITE on prefix "" under bucket; bucket READ, DELETE; volume READ final Set resourceSetNative = objSet(prefix("my-bucket", "")); - expectedResolvedNative.add(new OzoneGrant(resourceSetNative, acls(DELETE))); + expectedResolvedNative.add(new OzoneGrant(resourceSetNative, acls(DELETE, WRITE))); expectedResolvedNative.add(new OzoneGrant(objSet(bucket("my-bucket")), acls(READ, DELETE))); expectedResolvedNative.add(new OzoneGrant(objSet(volume()), acls(READ))); assertThat(resolvedFromNativeAuthorizer).isEqualTo(expectedResolvedNative); final Set expectedResolvedRanger = new LinkedHashSet<>(); - // Expected for Ranger: DELETE on resource type KEY with key name "*"; bucket READ, DELETE; volume READ + // Expected for Ranger: DELETE and WRITE on resource type KEY with key name "*"; bucket READ, DELETE; volume READ final Set resourceSetRanger = objSet(key("my-bucket", "*")); - expectedResolvedRanger.add(new OzoneGrant(resourceSetRanger, acls(DELETE))); + expectedResolvedRanger.add(new OzoneGrant(resourceSetRanger, acls(DELETE, WRITE))); expectedResolvedRanger.add(new OzoneGrant(objSet(bucket("my-bucket")), acls(READ, DELETE))); expectedResolvedRanger.add(new OzoneGrant(objSet(volume()), acls(READ))); assertThat(resolvedFromRangerAuthorizer).isEqualTo(expectedResolvedRanger);