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]

Reply via email to