This is an automated email from the ASF dual-hosted git repository.
sammichen pushed a commit to branch HDDS-13323-sts
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-13323-sts by this push:
new 934f4c18842 HDDS-13926. [STS] Part 3 - Create utility to convert IAM
policy to groupings of OzoneObj and Acls (#9306)
934f4c18842 is described below
commit 934f4c188425c5639d7c339a1f1bc51050a8ea39
Author: fmorg-git <[email protected]>
AuthorDate: Thu Dec 4 01:19:56 2025 -0800
HDDS-13926. [STS] Part 3 - Create utility to convert IAM policy to
groupings of OzoneObj and Acls (#9306)
---
.../ozone/security/acl/AssumeRoleRequest.java | 5 +
.../security/acl/iam/IamSessionPolicyResolver.java | 440 ++++++++++++++-
.../acl/iam/TestIamSessionPolicyResolver.java | 595 ++++++++++++++++++++-
3 files changed, 1019 insertions(+), 21 deletions(-)
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
index 1272d5422ec..03d093b5aef 100644
---
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
@@ -123,5 +123,10 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(objects, permissions);
}
+
+ @Override
+ public String toString() {
+ return "OzoneGrant{" + "objects=" + objects + ", permissions=" +
permissions + '}';
+ }
}
}
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 dbf3dc9e076..7e10d566b59 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
@@ -30,6 +30,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
@@ -42,6 +43,9 @@
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
+import org.apache.hadoop.ozone.security.acl.IOzoneObj;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
/**
* Resolves a limited subset of AWS IAM session policies into Ozone ACL grants,
@@ -73,7 +77,7 @@
* <a
href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_effect.html">AWS
spec</a>.
* <p>
* If a (currently) unsupported S3 action is requested, such as
s3:GetAccelerateConfiguration,
- * it will be silently ignored.
+ * it will be silently ignored. Similarly, if an invalid S3 action is
requested, it will be silently ignored.
* <p>
* Supported wildcard expansions in Actions are: s3:*, s3:Get*, s3:Put*,
s3:List*,
* s3:Create*, and s3:Delete*.
@@ -82,6 +86,8 @@ public final class IamSessionPolicyResolver {
private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final String AWS_S3_ARN_PREFIX = "arn:aws:s3:::";
+
// JSON length is limited per AWS policy. See
https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
// under Policy section.
private static final int MAX_JSON_LENGTH = 2048;
@@ -343,6 +349,17 @@ static Set<S3Action>
mapPolicyActionsToS3Actions(Set<String> actions) {
return mappedActions;
}
+ /**
+ * Validates that wildcard bucket patterns are not used with native
authorizer.
+ */
+ private static void validateNativeAuthorizerBucketPattern(AuthorizerType
authorizerType, String bucket)
+ throws OMException {
+ if (authorizerType == AuthorizerType.NATIVE && bucket.contains("*")) {
+ throw new OMException(
+ "Wildcard bucket patterns are not supported for Ozone native
authorizer", NOT_SUPPORTED_OPERATION);
+ }
+ }
+
/**
* Iterates over resources in IAM policy and determines whether it is a
bucket resource,
* an object resource, a prefix or a wildcard. The categorization can be
different
@@ -352,21 +369,276 @@ static Set<S3Action>
mapPolicyActionsToS3Actions(Set<String> actions) {
* <p>
* It also validates that the Resource Arn(s) are valid and supported.
*/
- private static Set<ResourceSpec>
validateAndCategorizeResources(AuthorizerType authorizerType,
+ @VisibleForTesting
+ static Set<ResourceSpec> validateAndCategorizeResources(AuthorizerType
authorizerType,
Set<String> resources) throws OMException {
- // TODO implement in future PR
- return Collections.emptySet();
+ final Set<ResourceSpec> resourceSpecs = new HashSet<>();
+ if (resources.isEmpty()) {
+ throw new OMException("No Resource(s) found in policy", INVALID_REQUEST);
+ }
+ for (String resource : resources) {
+ if ("*".equals(resource)) {
+ validateNativeAuthorizerBucketPattern(authorizerType, "*");
+ resourceSpecs.add(ResourceSpec.any());
+ continue;
+ }
+
+ if (!resource.startsWith(AWS_S3_ARN_PREFIX)) {
+ throw new OMException("Unsupported Resource Arn - " + resource,
NOT_SUPPORTED_OPERATION);
+ }
+
+ final String suffix = resource.substring(AWS_S3_ARN_PREFIX.length());
+ if (suffix.isEmpty()) {
+ throw new OMException("Invalid Resource Arn - " + resource,
INVALID_REQUEST);
+ }
+
+ ResourceSpec spec = parseResourceSpec(suffix);
+
+ // This scenario can happen in the case of arn:aws:s3:::*/* or
arn:aws:s3:::*/test.txt for
+ // examples
+ validateNativeAuthorizerBucketPattern(authorizerType, spec.bucket);
+
+ if (authorizerType == AuthorizerType.NATIVE && spec.type ==
S3ResourceType.OBJECT_PREFIX_WILDCARD) {
+ final String specPrefixExceptLastChar = spec.prefix.substring(0,
spec.prefix.length() - 1);
+ if (spec.prefix.endsWith("*") &&
!specPrefixExceptLastChar.contains("*")) {
+ spec = ResourceSpec.objectPrefix(spec.bucket,
specPrefixExceptLastChar);
+ } else {
+ throw new OMException(
+ "Wildcard prefix patterns are not supported for Ozone native
authorizer if wildcard is not at the end",
+ NOT_SUPPORTED_OPERATION);
+ }
+ }
+ resourceSpecs.add(spec);
+ }
+ return resourceSpecs;
}
/**
* Iterates over all resources, finds applicable actions (if any) and
constructs
* entries pairing sets of IOzoneObjs with the requisite permissions granted
(if any).
*/
- private static Set<AssumeRoleRequest.OzoneGrant>
createPathsAndPermissions(String volumeName,
- AuthorizerType authorizerType, Set<S3Action> mappedS3Actions,
Set<ResourceSpec> resourceSpecs,
- Set<String> prefixes) {
- // TODO implement in future PR
- return Collections.emptySet();
+ @VisibleForTesting
+ static Set<AssumeRoleRequest.OzoneGrant> createPathsAndPermissions(String
volumeName, AuthorizerType authorizerType,
+ Set<S3Action> mappedS3Actions, Set<ResourceSpec> resourceSpecs,
Set<String> prefixes) {
+ // Create map to collect IOzoneObj to ACLType mappings
+ final Map<IOzoneObj, Set<ACLType>> objToAclsMap = new LinkedHashMap<>();
+
+ // Process each resource spec with the given actions
+ for (ResourceSpec resourceSpec : resourceSpecs) {
+ processResourceSpecWithActions(volumeName, authorizerType,
mappedS3Actions, resourceSpec, prefixes, objToAclsMap);
+ }
+
+ // Group objects by their ACL sets to create proper entries
+ return groupObjectsByAcls(objToAclsMap);
+ }
+
+ /**
+ * Groups objects by their ACL sets.
+ */
+ private static Set<AssumeRoleRequest.OzoneGrant>
groupObjectsByAcls(Map<IOzoneObj, Set<ACLType>> objToAclsMap) {
+ final Map<Set<ACLType>, Set<IOzoneObj>> groupMap = new LinkedHashMap<>();
+
+ // Group objects by their ACL sets only (across resource types)
+ objToAclsMap.forEach((obj, acls) ->
+ groupMap.computeIfAbsent(acls, k -> new LinkedHashSet<>()).add(obj));
+
+ // Convert to result format, filtering out entries with empty ACLs
+ final Set<AssumeRoleRequest.OzoneGrant> result = new LinkedHashSet<>();
+ groupMap.forEach((key, objs) -> {
+ if (!key.isEmpty()) {
+ result.add(new AssumeRoleRequest.OzoneGrant(objs, key));
+ }
+ });
+
+ return result;
+ }
+
+ /**
+ * Processes a single ResourceSpec with given actions and adds resulting
+ * IOzoneObj to ACLType mappings to the provided map.
+ */
+ private static void processResourceSpecWithActions(String volumeName,
AuthorizerType authorizerType,
+ Set<S3Action> mappedS3Actions, ResourceSpec resourceSpec, Set<String>
prefixes,
+ Map<IOzoneObj, Set<ACLType>> objToAclsMap) {
+
+ // Process based on ResourceSpec type
+ switch (resourceSpec.type) {
+ case ANY:
+ Preconditions.checkArgument(
+ authorizerType != AuthorizerType.NATIVE,
+ "ResourceSpec type ANY not supported for OzoneNativeAuthorizer");
+ processResourceTypeAny(volumeName, mappedS3Actions, objToAclsMap);
+ break;
+ case BUCKET:
+ processBucketResource(volumeName, mappedS3Actions, resourceSpec,
prefixes, authorizerType, objToAclsMap);
+ break;
+ case BUCKET_WILDCARD:
+ Preconditions.checkArgument(
+ authorizerType != AuthorizerType.NATIVE,
+ "ResourceSpec type BUCKET_WILDCARD not supported for
OzoneNativeAuthorizer");
+ processBucketResource(volumeName, mappedS3Actions, resourceSpec,
prefixes, authorizerType, objToAclsMap);
+ break;
+ case OBJECT_EXACT:
+ processObjectExactResource(volumeName, mappedS3Actions, resourceSpec,
objToAclsMap);
+ break;
+ case OBJECT_PREFIX:
+ Preconditions.checkArgument(
+ authorizerType != AuthorizerType.RANGER,
+ "ResourceSpec type OBJECT_PREFIX not supported for
RangerOzoneAuthorizer");
+ processObjectPrefixResource(volumeName, authorizerType, mappedS3Actions,
resourceSpec, objToAclsMap);
+ break;
+ case OBJECT_PREFIX_WILDCARD:
+ Preconditions.checkArgument(
+ authorizerType != AuthorizerType.NATIVE,
+ "ResourceSpec type OBJECT_PREFIX_WILDCARD not supported for
OzoneNativeAuthorizer");
+ processObjectPrefixResource(volumeName, authorizerType, mappedS3Actions,
resourceSpec, objToAclsMap);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected resourceSpec type found: " +
resourceSpec.type);
+ }
+ }
+
+ /**
+ * Handles ResourceType.ANY (*).
+ * Example: "Resource": "*"
+ */
+ private static void processResourceTypeAny(String volumeName, Set<S3Action>
mappedS3Actions,
+ Map<IOzoneObj, Set<ACLType>> objToAclsMap) {
+ for (S3Action action : mappedS3Actions) {
+ addAclsForObj(objToAclsMap, volumeObj(volumeName), action.volumePerms);
+ addAclsForObj(objToAclsMap, bucketObj(volumeName, "*"),
action.bucketPerms);
+ addAclsForObj(objToAclsMap, keyObj(volumeName, "*", "*"),
action.objectPerms);
+ }
+ }
+
+ /**
+ * Handles BUCKET and BUCKET_WILDCARD resource types.
+ * Example: "Resource": "arn:aws:s3:::my-bucket" or "Resource":
"arn:aws:s3:::my-bucket*" or
+ * "Resource": "arn:aws:s3:::*"
+ */
+ private static void processBucketResource(String volumeName, Set<S3Action>
mappedS3Actions,
+ ResourceSpec resourceSpec, Set<String> prefixes, AuthorizerType
authorizerType,
+ Map<IOzoneObj, Set<ACLType>> objToAclsMap) {
+ for (S3Action action : mappedS3Actions) {
+ // The s3:ListAllMyBuckets action can use either "*" or
+ // "arn:aws:s3:::*" as its Resource. The former is already handled via
the
+ // ResourceSpec.ANY path. The latter is parsed as a BUCKET_WILDCARD
with a
+ // bucket name of "*". To align with AWS, make sure that in this
+ // specific case we also grant the volume-level permissions for
volume-scoped
+ // actions (currently s3:ListAllMyBuckets).
+ if (action.kind == ActionKind.BUCKET || action == S3Action.ALL_S3 ||
+ action.kind == ActionKind.VOLUME && "*".equals(resourceSpec.bucket))
{ // this handles s3:ListAllMyBuckets
+ addAclsForObj(objToAclsMap, volumeObj(volumeName), action.volumePerms);
+ addAclsForObj(objToAclsMap, bucketObj(volumeName,
resourceSpec.bucket), action.bucketPerms);
+ }
+
+ if (action == S3Action.LIST_BUCKET) {
+ // If condition prefixes are present, these would constrain the object
permissions if the action
+ // is s3:ListBucket
+ if (prefixes != null && !prefixes.isEmpty()) {
+ for (String prefix : prefixes) {
+ createObjectResourcesFromConditionPrefix(
+ volumeName, authorizerType, resourceSpec, prefix,
objToAclsMap, action.objectPerms);
+ }
+ } else {
+ // No condition prefixes, but we need READ access to all objects, so
use "*" as the prefix
+ createObjectResourcesFromConditionPrefix(
+ volumeName, authorizerType, resourceSpec, "*", objToAclsMap,
action.objectPerms);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles OBJECT_EXACT resource type.
+ * Example: "Resource": "arn:aws:s3:::my-bucket/file.txt"
+ */
+ private static void processObjectExactResource(String volumeName,
Set<S3Action> mappedS3Actions,
+ ResourceSpec resourceSpec, Map<IOzoneObj, Set<ACLType>> objToAclsMap) {
+ for (S3Action action : mappedS3Actions) {
+ addAclsForObj(objToAclsMap, volumeObj(volumeName), action.volumePerms);
+ if (action.kind == ActionKind.OBJECT) {
+ addAclsForObj(objToAclsMap, bucketObj(volumeName,
resourceSpec.bucket), action.bucketPerms);
+ addAclsForObj(objToAclsMap, keyObj(volumeName, resourceSpec.bucket,
resourceSpec.key), action.objectPerms);
+ } else if (action == S3Action.ALL_S3) {
+ // For s3:*, ALL should only apply at the object level; grant READ at
bucket level for navigation
+ addAclsForObj(objToAclsMap, bucketObj(volumeName,
resourceSpec.bucket), EnumSet.of(READ));
+ addAclsForObj(objToAclsMap, keyObj(volumeName, resourceSpec.bucket,
resourceSpec.key), action.objectPerms);
+ }
+ }
+ }
+
+ /**
+ * Handles OBJECT_PREFIX and OBJECT_PREFIX_WILDCARD resource types.
+ * Prefixes can be specified in the Resource itself as in the example below,
or via an s3:prefix Condition.
+ * Example: "Resource": "arn:aws:s3:::my-bucket/path/folder"
+ */
+ private static void processObjectPrefixResource(String volumeName,
AuthorizerType authorizerType,
+ Set<S3Action> mappedS3Actions, ResourceSpec resourceSpec, Map<IOzoneObj,
Set<ACLType>> objToAclsMap) {
+ for (S3Action action : mappedS3Actions) {
+ // Object actions apply to prefix/key resources
+ addAclsForObj(objToAclsMap, volumeObj(volumeName), action.volumePerms);
+ if (action.kind == ActionKind.OBJECT) {
+ addAclsForObj(objToAclsMap, bucketObj(volumeName,
resourceSpec.bucket), action.bucketPerms);
+ } else if (action == S3Action.ALL_S3) {
+ // For s3:*, ALL should only apply at the object/prefix level; grant
READ at bucket level for navigation
+ addAclsForObj(objToAclsMap, bucketObj(volumeName,
resourceSpec.bucket), EnumSet.of(READ));
+ }
+
+ // Handle the resource prefix itself (e.g., my-bucket/*)
+ createObjectResourcesFromResourcePrefix(
+ volumeName, authorizerType, resourceSpec, objToAclsMap,
action.objectPerms);
+ }
+ }
+
+ /**
+ * Creates object resources from resource prefix (e.g., my-bucket/*).
+ */
+ private static void createObjectResourcesFromResourcePrefix(String
volumeName, AuthorizerType authorizerType,
+ ResourceSpec resourceSpec, Map<IOzoneObj, Set<ACLType>> objToAclsMap,
Set<ACLType> acls) {
+ if (authorizerType == AuthorizerType.NATIVE) {
+ final IOzoneObj prefixObj = prefixObj(volumeName, resourceSpec.bucket,
resourceSpec.prefix);
+ addAclsForObj(objToAclsMap, prefixObj, acls);
+ } else {
+ final IOzoneObj keyObj = keyObj(volumeName, resourceSpec.bucket,
resourceSpec.prefix);
+ addAclsForObj(objToAclsMap, keyObj, acls);
+ }
+ }
+
+ /**
+ * Creates object resources from condition prefixes (i.e. the s3:prefix
conditions).
+ */
+ private static void createObjectResourcesFromConditionPrefix(String
volumeName, AuthorizerType authorizerType,
+ ResourceSpec resourceSpec, String conditionPrefix, Map<IOzoneObj,
Set<ACLType>> objToAclsMap, Set<ACLType> acls) {
+ if (authorizerType == AuthorizerType.NATIVE) {
+ // For native authorizer, use PREFIX resource type with normalized
prefix.
+ // Map "x" in condition list prefix to "x". Map "x/*" in condition list
prefix to "x/".
+ // Map "*" in condition list prefix to "".
+ final String normalizedPrefix;
+ if (conditionPrefix != null && conditionPrefix.endsWith("*")) {
+ normalizedPrefix = conditionPrefix.substring(0,
conditionPrefix.length() - 1);
+ } else {
+ normalizedPrefix = conditionPrefix;
+ }
+ final IOzoneObj prefixObj = prefixObj(volumeName, resourceSpec.bucket,
normalizedPrefix);
+ addAclsForObj(objToAclsMap, prefixObj, acls);
+ } else {
+ // For Ranger authorizer, use KEY resource type with original prefix
+ // Map "x" in condition list prefix to "x". Map "x/*" in condition list
prefix to "x/*".
+ // Map "* in condition list prefix to "*".
+ final IOzoneObj keyObj = keyObj(volumeName, resourceSpec.bucket,
conditionPrefix);
+ addAclsForObj(objToAclsMap, keyObj, acls);
+ }
+ }
+
+ /**
+ * Helper method to add ACLs for an IOzoneObj, merging with existing ACLs if
present.
+ */
+ private static void addAclsForObj(Map<IOzoneObj, Set<ACLType>> objToAclsMap,
IOzoneObj obj, Set<ACLType> acls) {
+ if (acls != null && !acls.isEmpty()) {
+ final OzoneObj ozoneObj = (OzoneObj) obj;
+ objToAclsMap.computeIfAbsent(ozoneObj, k ->
EnumSet.noneOf(ACLType.class)).addAll(acls);
+ }
}
/**
@@ -389,11 +661,105 @@ private enum ActionKind {
ALL
}
+ /**
+ * The categorization possibilities of Resources in the IAM policy.
+ */
+ @VisibleForTesting
+ enum S3ResourceType {
+ ANY, // Ranger authorizer solely uses this
+ BUCKET,
+ BUCKET_WILDCARD, // Ranger authorizer solely uses this
+ OBJECT_PREFIX, // Native authorizer solely uses this
+ OBJECT_PREFIX_WILDCARD, // Ranger authorizer solely uses this. We
initially categorize all resources with
+ // wildcard (*) as OBJECT_PREFIX_WILDCARD, but if
the wildcard is not at the end, and
+ // Native authorizer is being used, an error is
thrown. If the wildcard is at the end,
+ // then the categorization will use OBJECT_PREFIX
for native authorizer instead and remove
+ // the wildcard.
+ OBJECT_EXACT
+ }
+
/**
* Utility to help categorize IAM policy resources, whether for bucket, key,
wildcards, etc.
*/
- private static final class ResourceSpec {
- // TODO implement in future PR
+ @VisibleForTesting
+ static final class ResourceSpec {
+ private final S3ResourceType type;
+ private final String bucket;
+ private final String prefix; // for OBJECT_PREFIX or
OBJECT_PREFIX_WILDCARD only, otherwise null
+ private final String key; // for OBJECT_EXACT only, otherwise null
+
+ @VisibleForTesting
+ ResourceSpec(S3ResourceType type, String bucket, String prefix, String
key) {
+ this.type = type;
+ this.bucket = bucket;
+ this.prefix = prefix;
+ this.key = key;
+ }
+
+ static ResourceSpec any() {
+ return new ResourceSpec(S3ResourceType.ANY, "*", null, null);
+ }
+
+ static ResourceSpec bucket(String bucket) {
+ return new ResourceSpec(
+ bucket.contains("*") ? S3ResourceType.BUCKET_WILDCARD :
S3ResourceType.BUCKET, bucket, null, null);
+ }
+
+ static ResourceSpec objectExact(String bucket, String key) {
+ return new ResourceSpec(S3ResourceType.OBJECT_EXACT, bucket, null, key);
+ }
+
+ static ResourceSpec objectPrefix(String bucket, String prefix) {
+ return new ResourceSpec(
+ prefix.contains("*") ? S3ResourceType.OBJECT_PREFIX_WILDCARD :
S3ResourceType.OBJECT_PREFIX, bucket,
+ prefix, null);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ResourceSpec that = (ResourceSpec) o;
+ return type == that.type && Objects.equals(bucket, that.bucket) &&
Objects.equals(prefix, that.prefix) &&
+ Objects.equals(key, that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, bucket, prefix, key);
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceSpec{" + "type=" + type + ", bucket='" + bucket + '\'' +
", prefix='" + prefix + '\'' +
+ ", key='" + key + '\'' + '}';
+ }
+ }
+
+ /**
+ * Parses and categorizes the ResourceArn.
+ * <p>
+ * Suffix parameter can be:
+ * -> bucket
+ * -> bucket/* (prefix in OzoneNativeAuthorizer or wildcard key in
RangerOzoneAuthorizer)
+ * -> bucket/deep/path/* (prefix in OzoneNativeAuthorizer or wildcard key in
RangerOzoneAuthorizer)
+ * -> bucket/key or bucket/prefix/key (exact key)
+ */
+ private static ResourceSpec parseResourceSpec(String suffix) {
+
+ final int slashIndex = suffix.indexOf('/');
+ if (slashIndex < 0) {
+ return ResourceSpec.bucket(suffix);
+ }
+
+ final String bucket = suffix.substring(0, slashIndex);
+ final String rest = suffix.substring(slashIndex + 1);
+ if (rest.contains("*")) {
+ return ResourceSpec.objectPrefix(bucket, rest);
+ }
+
+ return ResourceSpec.objectExact(bucket, rest);
}
@VisibleForTesting
@@ -413,8 +779,7 @@ enum S3Action {
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.noneOf(ACLType.class)),
+ 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)),
@@ -443,7 +808,7 @@ enum S3Action {
EnumSet.of(ACLType.WRITE)),
// Wildcard all
- ALL_S3("s3:*", ActionKind.ALL, EnumSet.of(ACLType.ALL),
EnumSet.of(ACLType.ALL), EnumSet.of(ACLType.ALL));
+ ALL_S3("s3:*", ActionKind.ALL, EnumSet.of(READ), EnumSet.of(ACLType.ALL),
EnumSet.of(ACLType.ALL));
private final String name;
private final ActionKind kind;
@@ -460,4 +825,51 @@ enum S3Action {
this.objectPerms = objectPerms;
}
}
+
+ /**
+ * Creates an OzoneObjInfo.Builder based on supplied parameters.
+ */
+ private static OzoneObjInfo.Builder obj(OzoneObj.ResourceType type, String
volumeName, String bucketName) {
+ return OzoneObjInfo.Builder.newBuilder()
+ .setResType(type)
+ .setStoreType(OzoneObj.StoreType.OZONE)
+ .setVolumeName(volumeName)
+ .setBucketName(bucketName);
+ }
+
+ /**
+ * Creates IOzoneObj with ResourceType BUCKET.
+ */
+ private static IOzoneObj bucketObj(String volumeName, String bucketName) {
+ return obj(OzoneObj.ResourceType.BUCKET, volumeName, bucketName).build();
+ }
+
+ /**
+ * Creates IOzoneObj with ResourceType KEY.
+ */
+ private static IOzoneObj keyObj(String volumeName, String bucketName, String
keyName) {
+ return obj(OzoneObj.ResourceType.KEY, volumeName, bucketName)
+ .setKeyName(keyName)
+ .build();
+ }
+
+ /**
+ * Creates IOzoneObj with ResourceType PREFIX.
+ */
+ private static IOzoneObj prefixObj(String volumeName, String bucketName,
String prefixName) {
+ return obj(OzoneObj.ResourceType.PREFIX, volumeName, bucketName)
+ .setPrefixName(prefixName)
+ .build();
+ }
+
+ /**
+ * Creates IOzoneObj with ResourceType VOLUME.
+ */
+ private static IOzoneObj volumeObj(String volumeName) {
+ return OzoneObjInfo.Builder.newBuilder()
+ .setResType(OzoneObj.ResourceType.VOLUME)
+ .setStoreType(OzoneObj.StoreType.OZONE)
+ .setVolumeName(volumeName)
+ .build();
+ }
}
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 5721901b19c..9fe5965874c 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
@@ -19,17 +19,32 @@
import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_REQUEST;
import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.NOT_SUPPORTED_OPERATION;
+import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
+import static
org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.CREATE;
+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_ACL;
import static
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.AuthorizerType.NATIVE;
import static
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.AuthorizerType.RANGER;
+import static
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.S3ResourceType;
import static
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.buildCaseInsensitiveS3ActionMap;
+import static
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.createPathsAndPermissions;
import static
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.mapPolicyActionsToS3Actions;
+import static
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.validateAndCategorizeResources;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest;
+import org.apache.hadoop.ozone.security.acl.IOzoneObj;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
import
org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver.S3Action;
import org.junit.jupiter.api.Test;
@@ -218,8 +233,7 @@ public void testJsonExceedsMaxLengthThrows() {
final String json = createJsonStringLargerThan2048Characters();
expectResolveThrowsForBothAuthorizers(
- json, "Invalid policy JSON - exceeds maximum length of 2048
characters",
- INVALID_REQUEST);
+ json, "Invalid policy JSON - exceeds maximum length of 2048
characters", INVALID_REQUEST);
}
@Test
@@ -410,15 +424,584 @@ public void
testMapPolicyActionsToS3ActionsWithS3StarIgnoresOtherActions() {
assertThat(result).containsOnly(S3Action.ALL_S3);
}
+ @Test
+ public void testValidateAndCategorizeResourcesWithWildcard() throws
OMException {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("*")),
+ "Wildcard bucket patterns are not supported for Ozone native
authorizer", NOT_SUPPORTED_OPERATION);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("*"));
+ assertThat(resultRanger).containsOnly(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.ANY, "*",
null, null));
+ }
+
+ @Test
+ public void testValidateAndCategorizeResourcesWithSingleBucket() throws
OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedResourceSpec = new
IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.BUCKET, "my-bucket", null, null);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE, Collections.singleton("arn:aws:s3:::my-bucket"));
+ assertThat(resultNative).containsOnly(expectedResourceSpec);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::my-bucket"));
+ assertThat(resultRanger).containsOnly(expectedResourceSpec);
+ }
+
+ @Test
+ public void testValidateAndCategorizeResourcesWithBucketWildcard() throws
OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedResourceSpec = new
IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.BUCKET_WILDCARD, "my-bucket*", null, null);
+
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::my-bucket*")),
+ "Wildcard bucket patterns are not supported for Ozone native
authorizer",
+ NOT_SUPPORTED_OPERATION);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::my-bucket*"));
+ assertThat(resultRanger).containsOnly(expectedResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketWildcardAndExactObjectKey() throws
OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedResourceSpec = new
IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_EXACT, "*", null, "myKey.txt");
+
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::*/myKey.txt")),
+ "Wildcard bucket patterns are not supported for Ozone native
authorizer",
+ NOT_SUPPORTED_OPERATION);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::*/myKey.txt"));
+ assertThat(resultRanger).containsOnly(expectedResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketWildcardAndObjectWildcard() throws
OMException {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::*/*")),
+ "Wildcard bucket patterns are not supported for Ozone native
authorizer",
+ NOT_SUPPORTED_OPERATION);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedResourceSpec = new
IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "*", "*", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::*/*"));
+ assertThat(resultRanger).containsOnly(expectedResourceSpec);
+ }
+
+ @Test
+ public void testValidateAndCategorizeResourcesWithBucketAndExactObjectKey()
throws OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedResourceSpec = new
IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_EXACT, "bucket1", null, "key.txt");
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE, Collections.singleton("arn:aws:s3:::bucket1/key.txt"));
+ assertThat(resultNative).containsOnly(expectedResourceSpec);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket1/key.txt"));
+ assertThat(resultRanger).containsOnly(expectedResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndExactObjectKeyWithPath() throws
OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedResourceSpec = new
IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_EXACT, "bucket2", null,
"path/folder/nested/key.txt");
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE,
Collections.singleton("arn:aws:s3:::bucket2/path/folder/nested/key.txt"));
+ assertThat(resultNative).containsOnly(expectedResourceSpec);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER,
Collections.singleton("arn:aws:s3:::bucket2/path/folder/nested/key.txt"));
+ assertThat(resultRanger).containsOnly(expectedResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixAndEmpty() throws
OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedNativeResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX, "bucket3", "", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE, Collections.singleton("arn:aws:s3:::bucket3/*"));
+ assertThat(resultNative).containsOnly(expectedNativeResourceSpec);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket3", "*", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket3/*"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixAndEmptyWithPath()
throws OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedNativeResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX, "bucket3", "path/b/", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE, Collections.singleton("arn:aws:s3:::bucket3/path/b/*"));
+ assertThat(resultNative).containsOnly(expectedNativeResourceSpec);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket3", "path/b/*", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket3/path/b/*"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixAndNonEmpty() throws
OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedNativeResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX, "bucket3", "test", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE, Collections.singleton("arn:aws:s3:::bucket3/test*"));
+ assertThat(resultNative).containsOnly(expectedNativeResourceSpec);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket3", "test*", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket3/test*"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixAndNonEmptyWithPath()
throws OMException {
+ final IamSessionPolicyResolver.ResourceSpec expectedNativeResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX, "bucket", "a/b/test", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE, Collections.singleton("arn:aws:s3:::bucket/a/b/test*"));
+ assertThat(resultNative).containsOnly(expectedNativeResourceSpec);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket", "a/b/test*", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket/a/b/test*"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixWildcardNotAtEnd()
throws OMException {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::bucket3/*.log")),
+ "Wildcard prefix patterns are not supported for Ozone native
authorizer if wildcard is not " +
+ "at the end", NOT_SUPPORTED_OPERATION);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket3", "*.log", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket3/*.log"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixWildcardNotAtEndWithPath()
throws OMException {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::bucket/a/q/*.ps")),
+ "Wildcard prefix patterns are not supported for Ozone native
authorizer if wildcard is not " +
+ "at the end", NOT_SUPPORTED_OPERATION);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket", "a/q/*.ps", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket/a/q/*.ps"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixWildcardOneAtEndAndOneNotAtEnd()
+ throws OMException {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::bucket3/*key*")),
+ "Wildcard prefix patterns are not supported for Ozone native
authorizer if wildcard is not " +
+ "at the end", NOT_SUPPORTED_OPERATION);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket3", "*key*", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket3/*key*"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixWildcardOneAtEndAndOneNotAtEndWithPath()
+ throws OMException {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::bucket3/a/b/t/*key*")),
+ "Wildcard prefix patterns are not supported for Ozone native
authorizer if wildcard is not " +
+ "at the end", NOT_SUPPORTED_OPERATION);
+
+ final IamSessionPolicyResolver.ResourceSpec expectedRangerResourceSpec =
new IamSessionPolicyResolver.ResourceSpec(
+ S3ResourceType.OBJECT_PREFIX_WILDCARD, "bucket3", "a/b/t/*key*", null);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, Collections.singleton("arn:aws:s3:::bucket3/a/b/t/*key*"));
+ assertThat(resultRanger).containsOnly(expectedRangerResourceSpec);
+ }
+
+ @Test
+ public void testValidateAndCategorizeResourcesWithMultipleResources() throws
OMException {
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultNative =
validateAndCategorizeResources(
+ NATIVE, strSet("arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2/*",
"arn:aws:s3:::bucket3/key.txt"));
+ assertThat(resultNative).containsOnly(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null),
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_PREFIX, "bucket2",
"", null),
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_EXACT,
"bucket3", null, "key.txt"));
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger =
validateAndCategorizeResources(
+ RANGER, strSet("arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2/*",
"arn:aws:s3:::bucket3/key.txt"));
+ assertThat(resultRanger).containsOnly(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null),
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_PREFIX_WILDCARD,
"bucket2", "*", null),
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_EXACT,
"bucket3", null, "key.txt"));
+ }
+
+ @Test
+ public void testValidateAndCategorizeResourcesWithInvalidArnThrows() {
+ final String invalidArn = "arn:aws:ec2:::bucket";
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton(invalidArn)),
+ "Unsupported Resource Arn - " + invalidArn, NOT_SUPPORTED_OPERATION);
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(RANGER,
Collections.singleton(invalidArn)),
+ "Unsupported Resource Arn - " + invalidArn, NOT_SUPPORTED_OPERATION);
+ }
+
+ @Test
+ public void testValidateAndCategorizeResourcesWithArnWithNoBucketThrows() {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE,
Collections.singleton("arn:aws:s3:::")),
+ "Invalid Resource Arn - arn:aws:s3:::", INVALID_REQUEST);
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(RANGER,
Collections.singleton("arn:aws:s3:::")),
+ "Invalid Resource Arn - arn:aws:s3:::", INVALID_REQUEST);
+ }
+
+ @Test
+ public void testValidateAndCategorizeResourcesWithNoResourcesThrows() {
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(NATIVE, Collections.emptySet()),
+ "No Resource(s) found in policy", INVALID_REQUEST);
+ expectOMExceptionWithCode(
+ () -> validateAndCategorizeResources(RANGER, Collections.emptySet()),
+ "No Resource(s) found in policy", INVALID_REQUEST);
+ }
+
+ @Test
+ public void testCreatePathsAndPermissionsWithResourceAny() {
+ // This also tests that acls are deduplicated across different resource
types
+ final Set<S3Action> actions = Stream.of(S3Action.LIST_ALL_MY_BUCKETS,
S3Action.LIST_BUCKET, S3Action.GET_OBJECT)
+ .collect(Collectors.toSet()); // actions at volume, bucket and key
levels
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.ANY, "*",
null, null));
+
+ expectIllegalArgumentException(
+ () -> createPathsAndPermissions(VOLUME, NATIVE, actions,
resourceSpecs, Collections.emptySet()),
+ "ResourceSpec type ANY not supported for OzoneNativeAuthorizer");
+
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, resourceSpecs, Collections.emptySet());
+ final Set<IOzoneObj> readAndListObjects = objSet(volume(), bucket("*"));
// volume, bucket level have READ, LIST
+ final Set<IOzoneObj> readObject = objSet(key("*", "*")); // key level has
READ
+ assertThat(resultRanger).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(readAndListObjects, acls(READ, LIST)),
+ new AssumeRoleRequest.OzoneGrant(readObject, acls(READ)));
+ }
+
+ @Test
+ public void
testCreatePathsAndPermissionsWithBucketResourceThatIsListBucket() {
+ final Set<S3Action> actions =
Collections.singleton(IamSessionPolicyResolver.S3Action.LIST_BUCKET);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null));
+ final Set<IOzoneObj> readAndListObject = objSet(bucket("bucket1"));
+
+ final Set<IOzoneObj> nativeReadObjects = objSet(volume(),
prefix("bucket1", ""));
+ final Set<AssumeRoleRequest.OzoneGrant> resultNative =
createPathsAndPermissions(
+ VOLUME, NATIVE, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultNative).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(readAndListObject, acls(READ, LIST)),
+ new AssumeRoleRequest.OzoneGrant(nativeReadObjects, acls(READ)));
+
+ final Set<IOzoneObj> rangerReadObjects = objSet(volume(), key("bucket1",
"*"));
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultRanger).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(readAndListObject, acls(READ, LIST)),
+ new AssumeRoleRequest.OzoneGrant(rangerReadObjects, acls(READ)));
+ }
+
+ @Test
+ public void
testCreatePathsAndPermissionsWithBucketResourceThatIsNotListBucket() {
+ final Set<S3Action> actions =
Collections.singleton(S3Action.CREATE_BUCKET);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null));
+ final Set<IOzoneObj> createObject = objSet(bucket("bucket1"));
+ final Set<IOzoneObj> readObject = objSet(volume());
+
+ final Set<AssumeRoleRequest.OzoneGrant> resultNative =
createPathsAndPermissions(
+ VOLUME, NATIVE, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultNative).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(createObject, acls(CREATE)),
+ new AssumeRoleRequest.OzoneGrant(readObject, acls(READ)));
+
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultRanger).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(createObject, acls(CREATE)),
+ new AssumeRoleRequest.OzoneGrant(readObject, acls(READ)));
+ }
+
+ @Test
+ public void testCreatePathsAndPermissionsWithBucketWildcardResource() {
+ final Set<S3Action> actions =
Collections.singleton(IamSessionPolicyResolver.S3Action.PUT_BUCKET_ACL);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET_WILDCARD,
"bucket1*", null, null));
+ final Set<IOzoneObj> writeAclObject = objSet(bucket("bucket1*"));
+ final Set<IOzoneObj> readVolume = objSet(volume());
+
+ expectIllegalArgumentException(
+ () -> createPathsAndPermissions(VOLUME, NATIVE, actions,
resourceSpecs, Collections.emptySet()),
+ "ResourceSpec type BUCKET_WILDCARD not supported for
OzoneNativeAuthorizer");
+
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultRanger).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(writeAclObject, acls(WRITE_ACL)),
+ new AssumeRoleRequest.OzoneGrant(readVolume, acls(READ)));
+ }
+
+ @Test
+ public void testCreatePathsAndPermissionsWithBucketsWildcardResourceAll() {
+ // For AWS IAM, s3:ListAllMyBuckets supports both "*" and "arn:aws:s3:::*"
as
+ // Resource values. The "*" case is covered by
testCreatePathsAndPermissionsWithResourceAny.
+ // This test ensures that "arn:aws:s3:::*" (parsed as BUCKET_WILDCARD with
bucket="*")
+ // also grants the expected volume-level permissions for ListAllMyBuckets.
+ final Set<S3Action> actions = Stream.of(S3Action.LIST_ALL_MY_BUCKETS,
S3Action.LIST_BUCKET)
+ .collect(Collectors.toSet());
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET_WILDCARD, "*",
null, null));
+
+ expectIllegalArgumentException(
+ () -> createPathsAndPermissions(VOLUME, NATIVE, actions,
resourceSpecs, Collections.emptySet()),
+ "ResourceSpec type BUCKET_WILDCARD not supported for
OzoneNativeAuthorizer");
+
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, resourceSpecs, Collections.emptySet());
+
+ // Both the volume and the wildcard bucket should end up with READ + LIST
permissions.
+ // We also need READ access on the keys
+ final Set<IOzoneObj> readAndListObjects = objSet(volume(), bucket("*"));
+ final Set<IOzoneObj> readObjects = objSet(key("*", "*"));
+ assertThat(resultRanger).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(readAndListObjects, acls(READ, LIST)),
+ new AssumeRoleRequest.OzoneGrant(readObjects, acls(READ)));
+ }
+
+ @Test
+ public void testCreatePathsAndPermissionsWithObjectExactResource() {
+ final Set<S3Action> actions = Collections.singleton(S3Action.GET_OBJECT);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_EXACT,
"bucket1", null, "key.txt"));
+ final Set<IOzoneObj> readObjects = objSet(key("bucket1", "key.txt"),
bucket("bucket1"), volume());
+
+ final Set<AssumeRoleRequest.OzoneGrant> resultNative =
createPathsAndPermissions(
+ VOLUME, NATIVE, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultNative).containsExactly(new
AssumeRoleRequest.OzoneGrant(readObjects, acls(READ)));
+
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultRanger).containsExactly(new
AssumeRoleRequest.OzoneGrant(readObjects, acls(READ)));
+ }
+
+ @Test
+ public void testCreatePathsAndPermissionsWithObjectPrefixResource() {
+ final Set<S3Action> actions = Collections.singleton(S3Action.GET_OBJECT);
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_PREFIX, "bucket1",
"prefix/", null));
+ final Set<IOzoneObj> nativeReadObjects = objSet(prefix("bucket1",
"prefix/"), bucket("bucket1"), volume());
+ final Set<AssumeRoleRequest.OzoneGrant> resultNative =
createPathsAndPermissions(
+ VOLUME, NATIVE, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultNative).containsExactly(new
AssumeRoleRequest.OzoneGrant(nativeReadObjects, acls(READ)));
+
+ expectIllegalArgumentException(
+ () -> createPathsAndPermissions(VOLUME, RANGER, actions,
resourceSpecs, Collections.emptySet()),
+ "ResourceSpec type OBJECT_PREFIX not supported for
RangerOzoneAuthorizer");
+ }
+
+ @Test
+ public void testCreatePathsAndPermissionsWithObjectPrefixWildcardResource() {
+ final Set<S3Action> actions = Collections.singleton(S3Action.GET_OBJECT);
+ final Set<IamSessionPolicyResolver.ResourceSpec> resourceSpecs =
Collections.singleton(
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_PREFIX_WILDCARD,
"bucket1", "prefix/*", null));
+
+ expectIllegalArgumentException(
+ () -> createPathsAndPermissions(VOLUME, NATIVE, actions,
resourceSpecs, Collections.emptySet()),
+ "ResourceSpec type OBJECT_PREFIX_WILDCARD not supported for
OzoneNativeAuthorizer");
+
+ final Set<IOzoneObj> rangerReadObjects = objSet(key("bucket1",
"prefix/*"), bucket("bucket1"), volume());
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, resourceSpecs, Collections.emptySet());
+ assertThat(resultRanger).containsExactly(new
AssumeRoleRequest.OzoneGrant(rangerReadObjects, acls(READ)));
+ }
+
+ @Test
+ public void
testCreatePathsAndPermissionsWithConditionPrefixesForObjectActionMustIgnoreConditionPrefixes()
{
+ final Set<S3Action> actions = Collections.singleton(S3Action.GET_OBJECT);
+ final Set<String> prefixes = strSet("folder1/", "folder2/");
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> nativeResourceSpecs =
Collections.singleton(
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_PREFIX, "bucket1",
"", null));
+ final Set<IOzoneObj> nativeReadObjects = objSet(prefix("bucket1", ""),
bucket("bucket1"), volume());
+ final Set<AssumeRoleRequest.OzoneGrant> resultNative =
createPathsAndPermissions(
+ VOLUME, NATIVE, actions, nativeResourceSpecs, prefixes);
+ assertThat(resultNative).containsExactly(new
AssumeRoleRequest.OzoneGrant(nativeReadObjects, acls(READ)));
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> rangerResourceSpecs =
Collections.singleton(
+ new
IamSessionPolicyResolver.ResourceSpec(S3ResourceType.OBJECT_PREFIX_WILDCARD,
"bucket1", "*", null));
+ final Set<IOzoneObj> rangerReadObjects = objSet(key("bucket1", "*"),
bucket("bucket1"), volume());
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, rangerResourceSpecs, prefixes);
+ assertThat(resultRanger).containsExactly(new
AssumeRoleRequest.OzoneGrant(rangerReadObjects, acls(READ)));
+ }
+
+ @Test
+ public void
testCreatePathsAndPermissionsWithConditionPrefixesForBucketActionWhenActionIsListBucket()
{
+ final Set<S3Action> actions = Collections.singleton(S3Action.LIST_BUCKET);
+ final Set<String> prefixes = strSet("folder1/", "folder2/");
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> nativeResourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null));
+ final Set<IOzoneObj> nativeReadObjects = objSet(
+ prefix("bucket1", "folder1/"), prefix("bucket1", "folder2/"),
volume());
+ final Set<IOzoneObj> nativeReadAndListObject = objSet(bucket("bucket1"));
+ final Set<AssumeRoleRequest.OzoneGrant> resultNative =
createPathsAndPermissions(
+ VOLUME, NATIVE, actions, nativeResourceSpecs, prefixes);
+ assertThat(resultNative).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(nativeReadObjects, acls(READ)),
+ new AssumeRoleRequest.OzoneGrant(nativeReadAndListObject, acls(READ,
LIST)));
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> rangerResourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null));
+ final Set<IOzoneObj> rangerReadObjects = objSet(
+ key("bucket1", "folder1/"), key("bucket1", "folder2/"), volume());
+ final Set<IOzoneObj> rangerReadAndListObject = objSet(bucket("bucket1"));
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, rangerResourceSpecs, prefixes);
+ assertThat(resultRanger).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(rangerReadObjects, acls(READ)),
+ new AssumeRoleRequest.OzoneGrant(rangerReadAndListObject, acls(READ,
LIST)));
+ }
+
+ @Test
+ public void
testCreatePathsAndPermissionsWithConditionPrefixesForBucketActionWhenActionIsNotListBucket()
{
+ final Set<S3Action> actions =
Collections.singleton(S3Action.GET_BUCKET_ACL);
+ final Set<String> prefixes = strSet("folder1/", "folder2/");
+ final Set<IOzoneObj> readObject = objSet(volume());
+ final Set<IOzoneObj> readAndReadAclObject = objSet(bucket("bucket1"));
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> nativeResourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null));
+ final Set<AssumeRoleRequest.OzoneGrant> resultNative =
createPathsAndPermissions(
+ VOLUME, NATIVE, actions, nativeResourceSpecs, prefixes);
+ assertThat(resultNative).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(readObject, acls(READ)),
+ new AssumeRoleRequest.OzoneGrant(readAndReadAclObject, acls(READ,
READ_ACL)));
+
+ final Set<IamSessionPolicyResolver.ResourceSpec> rangerResourceSpecs =
Collections.singleton(
+ new IamSessionPolicyResolver.ResourceSpec(S3ResourceType.BUCKET,
"bucket1", null, null));
+ final Set<AssumeRoleRequest.OzoneGrant> resultRanger =
createPathsAndPermissions(
+ VOLUME, RANGER, actions, rangerResourceSpecs, prefixes);
+ assertThat(resultRanger).containsExactlyInAnyOrder(
+ new AssumeRoleRequest.OzoneGrant(readObject, acls(READ)),
+ new AssumeRoleRequest.OzoneGrant(readAndReadAclObject, acls(READ,
READ_ACL)));
+ }
+
+ // TODO sts - add more createPathsAndPermissions tests in the next PR
+
+ private static void expectIllegalArgumentException(Runnable runnable, String
expectedMessage) {
+ try {
+ runnable.run();
+ throw new AssertionError("Expected exception not thrown");
+ } catch (IllegalArgumentException ex) {
+ assertThat(ex.getMessage()).isEqualTo(expectedMessage);
+ }
+ }
+
+ private static void expectOMExceptionWithCode(RunnableThrowingOMException
runnable, String expectedMessage,
+ OMException.ResultCodes expectedCode) {
+ try {
+ runnable.run();
+ throw new AssertionError("Expected exception not thrown");
+ } catch (OMException ex) {
+ assertThat(ex.getMessage()).isEqualTo(expectedMessage);
+ assertThat(ex.getResult()).isEqualTo(expectedCode);
+ }
+ }
+
+ @FunctionalInterface
+ private interface RunnableThrowingOMException {
+ void run() throws OMException;
+ }
+
+ private static IOzoneObj key(String bucket, String key) {
+ return OzoneObjInfo.Builder.newBuilder()
+ .setResType(OzoneObj.ResourceType.KEY)
+ .setStoreType(OzoneObj.StoreType.OZONE)
+ .setVolumeName(VOLUME)
+ .setBucketName(bucket)
+ .setKeyName(key)
+ .build();
+ }
+
+ private static IOzoneObj volume() {
+ return OzoneObjInfo.Builder.newBuilder()
+ .setResType(OzoneObj.ResourceType.VOLUME)
+ .setStoreType(OzoneObj.StoreType.OZONE)
+ .setVolumeName(VOLUME)
+ .build();
+ }
+
+ private static IOzoneObj bucket(String bucket) {
+ return OzoneObjInfo.Builder.newBuilder()
+ .setResType(OzoneObj.ResourceType.BUCKET)
+ .setStoreType(OzoneObj.StoreType.OZONE)
+ .setVolumeName(VOLUME)
+ .setBucketName(bucket)
+ .build();
+ }
+
+ private static IOzoneObj prefix(String bucket, String prefix) {
+ return OzoneObjInfo.Builder.newBuilder()
+ .setResType(OzoneObj.ResourceType.PREFIX)
+ .setStoreType(OzoneObj.StoreType.OZONE)
+ .setVolumeName(VOLUME)
+ .setBucketName(bucket)
+ .setPrefixName(prefix)
+ .build();
+ }
+
+ private static Set<IOzoneObj> objSet(IOzoneObj... objs) {
+ final Set<IOzoneObj> s = new LinkedHashSet<>();
+ Collections.addAll(s, objs);
+ return s;
+ }
+
+ private static Set<ACLType> acls(ACLType... types) {
+ final Set<ACLType> s = new LinkedHashSet<>();
+ Collections.addAll(s, types);
+ return s;
+ }
+
private static Set<String> strSet(String... strs) {
final Set<String> s = new LinkedHashSet<>();
Collections.addAll(s, strs);
return s;
}
- private static void expectResolveThrows(String json,
- IamSessionPolicyResolver.AuthorizerType authorizerType, String
expectedMessage,
- OMException.ResultCodes expectedCode) {
+ private static void expectResolveThrows(String json,
IamSessionPolicyResolver.AuthorizerType authorizerType,
+ String expectedMessage, OMException.ResultCodes expectedCode) {
try {
IamSessionPolicyResolver.resolve(json, VOLUME, authorizerType);
throw new AssertionError("Expected exception not thrown");
@@ -448,7 +1031,6 @@ private static String
createJsonStringLargerThan2048Characters() {
jsonBuilder.append("\"\n");
jsonBuilder.append(" }]\n");
jsonBuilder.append('}');
-
return jsonBuilder.toString();
}
@@ -465,7 +1047,6 @@ private static String create2048CharJsonString() {
jsonBuilder.append('a');
}
jsonBuilder.append("\"\n }]\n}");
-
return jsonBuilder.toString();
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]