This is an automated email from the ASF dual-hosted git repository.

sodonnell 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 6ff971244c3 HDDS-14681. [STS] Support StringLike Condition operator in 
IAM session policy and handle certain errors more gracefully (#9795)
6ff971244c3 is described below

commit 6ff971244c34e94aeabda0cf7b68112a50813c1c
Author: fmorg-git <[email protected]>
AuthorDate: Fri Feb 20 13:57:50 2026 -0800

    HDDS-14681. [STS] Support StringLike Condition operator in IAM session 
policy and handle certain errors more gracefully (#9795)
    
    Co-authored-by: Fabian Morgan <[email protected]>
---
 .../security/acl/iam/IamSessionPolicyResolver.java | 60 +++++++++--------
 .../acl/iam/TestIamSessionPolicyResolver.java      | 78 ++++++++++++----------
 .../ozone/security/acl/iam/package-info.java       | 21 ++++++
 .../apache/hadoop/ozone/om/OmMetadataReader.java   | 17 ++++-
 .../apache/hadoop/ozone/s3sts/S3STSEndpoint.java   | 16 +++++
 5 files changed, 128 insertions(+), 64 deletions(-)

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 b90bb43c193..0da9781e8af 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
@@ -18,6 +18,7 @@
 package org.apache.hadoop.ozone.security.acl.iam;
 
 import static java.util.Collections.singleton;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INTERNAL_ERROR;
 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.CREATE;
@@ -62,9 +63,9 @@
  * The only supported ResourceArn has prefix arn:aws:s3::: - all others will 
throw
  * OMException with NOT_SUPPORTED_OPERATION.
  * <p>
- * The only supported Condition operator is StringEquals - all others will 
throw
+ * The only supported Condition operators are StringEquals and StringLike - 
all others will throw
  * OMException with NOT_SUPPORTED_OPERATION.  Furthermore, only one Condition 
is supported in a
- * statement.  The value StringEquals is case-sensitive per the
+ * statement.  The value for both StringEquals and StringLike is 
case-sensitive per the
  * <a 
href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html";>
  * AWS spec</a>.
  * <p>
@@ -95,6 +96,8 @@ public final class IamSessionPolicyResolver {
   // Used to group actions into s3:Get*, s3:Put*, s3:List*, s3:Delete*, 
s3:Create*
   private static final String[] S3_ACTION_PREFIXES = {"s3:Get", "s3:Put", 
"s3:List", "s3:Delete", "s3:Create"};
 
+  private static final String ERROR_PREFIX = "IAM session policy: ";
+
   @VisibleForTesting
   static final Map<String, Set<S3Action>> S3_ACTION_MAP_CI = 
buildCaseInsensitiveS3ActionMap();
 
@@ -166,18 +169,19 @@ public static Set<AssumeRoleRequest.OzoneGrant> 
resolve(String policyJson, Strin
   private static void validateInputParameters(String policyJson, String 
volumeName,
       AuthorizerType authorizerType) throws OMException {
     if (StringUtils.isBlank(policyJson)) {
-      throw new OMException("The IAM session policy JSON is required", 
INVALID_REQUEST);
+      throw new OMException(ERROR_PREFIX + "The IAM session policy JSON is 
required", INTERNAL_ERROR);
     }
 
     if (StringUtils.isBlank(volumeName)) {
-      throw new OMException("The volume name is required", INVALID_REQUEST);
+      throw new OMException(ERROR_PREFIX + "The volume name is required", 
INTERNAL_ERROR);
     }
 
     Objects.requireNonNull(authorizerType, "The authorizer type is required");
 
     if (policyJson.length() > MAX_JSON_LENGTH) {
-      throw new OMException("Invalid policy JSON - exceeds maximum length of " 
+
-          MAX_JSON_LENGTH + " characters", INVALID_REQUEST);
+      throw new OMException(
+          ERROR_PREFIX + "Invalid policy JSON - exceeds maximum length of " + 
MAX_JSON_LENGTH + " characters",
+          INVALID_REQUEST);
     }
   }
 
@@ -189,12 +193,13 @@ private static Set<JsonNode> 
parseJsonAndRetrieveStatements(String policyJson) t
     try {
       root = MAPPER.readTree(policyJson);
     } catch (Exception e) {
-      throw new OMException("Invalid policy JSON (most likely JSON structure 
is incorrect)", e, INVALID_REQUEST);
+      throw new OMException(
+          ERROR_PREFIX + "Invalid policy JSON (most likely JSON structure is 
incorrect)", e, INVALID_REQUEST);
     }
 
     final JsonNode statementsNode = root.path("Statement");
     if (statementsNode.isMissingNode()) {
-      throw new OMException("Invalid policy JSON - missing Statement", 
INVALID_REQUEST);
+      throw new OMException(ERROR_PREFIX + "Invalid policy JSON - missing 
Statement", INVALID_REQUEST);
     }
 
     final Set<JsonNode> statements = new HashSet<>();
@@ -216,16 +221,16 @@ private static void 
validateEffectInJsonStatement(JsonNode statement) throws OME
       if (effectNode.isTextual()) {
         final String effect = effectNode.asText();
         if (!"Allow".equals(effect)) {
-          throw new OMException("Unsupported Effect - " + effect, 
NOT_SUPPORTED_OPERATION);
+          throw new OMException(ERROR_PREFIX + "Unsupported Effect - " + 
effect, NOT_SUPPORTED_OPERATION);
         }
         return;
       }
 
       throw new OMException(
-          "Invalid Effect in JSON policy (must be a String) - " + effectNode, 
INVALID_REQUEST);
+          ERROR_PREFIX + "Invalid Effect in JSON policy (must be a String) - " 
+ effectNode, INVALID_REQUEST);
     }
 
-    throw new OMException("Effect is missing from JSON policy", 
INVALID_REQUEST);
+    throw new OMException(ERROR_PREFIX + "Effect is missing from JSON policy", 
INVALID_REQUEST);
   }
 
   /**
@@ -265,32 +270,34 @@ private static Set<String> 
parsePrefixesFromConditions(JsonNode stmt) throws OME
     final JsonNode cond = stmt.get("Condition");
     if (cond != null && !cond.isMissingNode() && !cond.isNull()) {
       if (cond.size() != 1) {
-        throw new OMException("Only one Condition is supported", 
NOT_SUPPORTED_OPERATION);
+        throw new OMException(ERROR_PREFIX + "Only one Condition is 
supported", NOT_SUPPORTED_OPERATION);
       }
 
       if (!cond.isObject()) {
         throw new OMException(
-            "Invalid Condition (must have operator StringEquals " + "and key 
name s3:prefix) - " +
-            cond, INVALID_REQUEST);
+            ERROR_PREFIX + "Invalid Condition (must have operator StringEquals 
or StringLike " +
+            "and key name s3:prefix) - " + cond, INVALID_REQUEST);
       }
 
       final String operator = cond.fieldNames().next();
-      if (!"StringEquals".equals(operator)) {
-        throw new OMException("Unsupported Condition operator - " + operator, 
NOT_SUPPORTED_OPERATION);
+      if (!"StringEquals".equals(operator) && !"StringLike".equals(operator)) {
+        throw new OMException(ERROR_PREFIX + "Unsupported Condition operator - 
" + operator, NOT_SUPPORTED_OPERATION);
       }
 
-      final JsonNode operatorValue = cond.get("StringEquals");
+      final JsonNode operatorValue = cond.get(operator);
       if ("null".equals(operatorValue.asText())) {
-        throw new OMException("Missing Condition operator - StringEquals", 
INVALID_REQUEST);
+        throw new OMException(
+            ERROR_PREFIX + "Missing Condition operator value for " + operator, 
INVALID_REQUEST);
       }
 
       if (!operatorValue.isObject()) {
-        throw new OMException("Invalid Condition operator value structure - " 
+ operatorValue, INVALID_REQUEST);
+        throw new OMException(
+            ERROR_PREFIX + "Invalid Condition operator value structure - " + 
operatorValue, INVALID_REQUEST);
       }
 
       final String keyName = operatorValue.fieldNames().hasNext() ? 
operatorValue.fieldNames().next() : null;
       if (!"s3:prefix".equalsIgnoreCase(keyName)) {
-        throw new OMException("Unsupported Condition key name - " + keyName, 
NOT_SUPPORTED_OPERATION);
+        throw new OMException(ERROR_PREFIX + "Unsupported Condition key name - 
" + keyName, NOT_SUPPORTED_OPERATION);
       }
 
       prefixes = readStringOrArray(operatorValue.get(keyName));
@@ -356,7 +363,8 @@ private static void 
validateNativeAuthorizerBucketPattern(AuthorizerType authori
       throws OMException {
     if (authorizerType == AuthorizerType.NATIVE && bucket.contains("*")) {
       throw new OMException(
-          "Wildcard bucket patterns are not supported for Ozone native 
authorizer", NOT_SUPPORTED_OPERATION);
+          ERROR_PREFIX + "Wildcard bucket patterns are not supported for Ozone 
native authorizer",
+          NOT_SUPPORTED_OPERATION);
     }
   }
 
@@ -374,7 +382,7 @@ static Set<ResourceSpec> 
validateAndCategorizeResources(AuthorizerType authorize
       Set<String> resources) throws OMException {
     final Set<ResourceSpec> resourceSpecs = new HashSet<>();
     if (resources.isEmpty()) {
-      throw new OMException("No Resource(s) found in policy", INVALID_REQUEST);
+      throw new OMException(ERROR_PREFIX + "No Resource(s) found in policy", 
INVALID_REQUEST);
     }
     for (String resource : resources) {
       if ("*".equals(resource)) {
@@ -384,12 +392,12 @@ static Set<ResourceSpec> 
validateAndCategorizeResources(AuthorizerType authorize
       }
 
       if (!resource.startsWith(AWS_S3_ARN_PREFIX)) {
-        throw new OMException("Unsupported Resource Arn - " + resource, 
NOT_SUPPORTED_OPERATION);
+        throw new OMException(ERROR_PREFIX + "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);
+        throw new OMException(ERROR_PREFIX + "Invalid Resource Arn - " + 
resource, INVALID_REQUEST);
       }
 
       ResourceSpec spec = parseResourceSpec(suffix);
@@ -404,8 +412,8 @@ static Set<ResourceSpec> 
validateAndCategorizeResources(AuthorizerType authorize
           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);
+              ERROR_PREFIX + "Wildcard prefix patterns are not supported for 
Ozone native authorizer if " +
+              "wildcard is not at the end", NOT_SUPPORTED_OPERATION);
         }
       }
       resourceSpecs.add(spec);
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 b885c5130ec..4ca478156a3 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
@@ -69,12 +69,13 @@ public void testUnsupportedConditionOperatorThrows() {
         "    \"Effect\": \"Allow\",\n" +
         "    \"Action\": \"s3:ListBucket\",\n" +
         "    \"Resource\": \"arn:aws:s3:::b\",\n" +
-        "    \"Condition\": { \"StringLike\": { \"s3:prefix\": \"x/*\" } }\n" +
+        "    \"Condition\": { \"StringNotEqualsIgnoreCase\": { \"s3:prefix\": 
\"x/*\" } }\n" +
         "  }]\n" +
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Unsupported Condition operator - StringLike", 
NOT_SUPPORTED_OPERATION);
+        json, "IAM session policy: Unsupported Condition operator - 
StringNotEqualsIgnoreCase",
+        NOT_SUPPORTED_OPERATION);
   }
 
   @Test
@@ -89,7 +90,7 @@ public void testUnsupportedConditionAttributeThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Unsupported Condition key name - aws:SourceArn", 
NOT_SUPPORTED_OPERATION);
+        json, "IAM session policy: Unsupported Condition key name - 
aws:SourceArn", NOT_SUPPORTED_OPERATION);
   }
 
   @Test
@@ -103,7 +104,7 @@ public void testUnsupportedEffectThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Unsupported Effect - Deny", NOT_SUPPORTED_OPERATION);
+        json, "IAM session policy: Unsupported Effect - Deny", 
NOT_SUPPORTED_OPERATION);
   }
 
   @Test
@@ -118,7 +119,7 @@ public void testInvalidJsonWithoutStatementThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Invalid policy JSON - missing Statement", INVALID_REQUEST);
+        json, "IAM session policy: Invalid policy JSON - missing Statement", 
INVALID_REQUEST);
   }
 
   @Test
@@ -132,7 +133,7 @@ public void testInvalidEffectThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Invalid Effect in JSON policy (must be a String) - [\"Allow\"]",
+        json, "IAM session policy: Invalid Effect in JSON policy (must be a 
String) - [\"Allow\"]",
         INVALID_REQUEST);
   }
 
@@ -146,7 +147,7 @@ public void testMissingEffectInStatementThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Effect is missing from JSON policy", INVALID_REQUEST);
+        json, "IAM session policy: Effect is missing from JSON policy", 
INVALID_REQUEST);
   }
 
   @Test
@@ -174,7 +175,7 @@ public void testInvalidNumberOfConditionsThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Only one Condition is supported", NOT_SUPPORTED_OPERATION);
+        json, "IAM session policy: Only one Condition is supported", 
NOT_SUPPORTED_OPERATION);
   }
 
   @Test
@@ -191,7 +192,7 @@ public void testInvalidConditionThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Invalid Condition (must have operator StringEquals and key name 
" +
+        json, "IAM session policy: Invalid Condition (must have operator 
StringEquals or StringLike and key name " +
         "s3:prefix) - [\"RandomCondition\"]", INVALID_REQUEST);
   }
 
@@ -207,7 +208,8 @@ public void 
testInvalidConditionAttributeMissingStringEqualsThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Missing Condition operator - StringEquals", INVALID_REQUEST);
+        json, "IAM session policy: Missing Condition operator value for 
StringEquals",
+        INVALID_REQUEST);
   }
 
   @Test
@@ -222,7 +224,7 @@ public void testInvalidConditionAttributeStructureThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Invalid Condition operator value structure - 
[{\"s3:prefix\":\"folder/\"}]",
+        json, "IAM session policy: Invalid Condition operator value structure 
- [{\"s3:prefix\":\"folder/\"}]",
         INVALID_REQUEST);
   }
 
@@ -231,7 +233,7 @@ public void testInvalidJsonThrows() {
     final String invalidJson = "{[{{}]\"\"";
 
     expectResolveThrowsForBothAuthorizers(
-        invalidJson, "Invalid policy JSON (most likely JSON structure is 
incorrect)",
+        invalidJson, "IAM session policy: Invalid policy JSON (most likely 
JSON structure is incorrect)",
         INVALID_REQUEST);
   }
 
@@ -240,7 +242,7 @@ public void testJsonExceedsMaxLengthThrows() {
     final String json = createJsonStringLargerThan2048Characters();
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Invalid policy JSON - exceeds maximum length of 2048 
characters", INVALID_REQUEST);
+        json, "IAM session policy: Invalid policy JSON - exceeds maximum 
length of 2048 characters", INVALID_REQUEST);
   }
 
   @Test
@@ -282,7 +284,7 @@ public void testEffectMustBeCaseSensitive() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Unsupported Effect - aLLOw", NOT_SUPPORTED_OPERATION);
+        json, "IAM session policy: Unsupported Effect - aLLOw", 
NOT_SUPPORTED_OPERATION);
   }
 
   @Test
@@ -434,7 +436,8 @@ public void 
testMapPolicyActionsToS3ActionsWithS3StarIgnoresOtherActions() {
   public void testValidateAndCategorizeResourcesWithWildcard() throws 
OMException {
     expectOMExceptionWithCode(
         () -> validateAndCategorizeResources(NATIVE, 
Collections.singleton("*")),
-        "Wildcard bucket patterns are not supported for Ozone native 
authorizer", NOT_SUPPORTED_OPERATION);
+        "IAM session policy: Wildcard bucket patterns are not supported for 
Ozone native authorizer",
+        NOT_SUPPORTED_OPERATION);
 
     final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger = 
validateAndCategorizeResources(
         RANGER, Collections.singleton("*"));
@@ -463,7 +466,7 @@ public void 
testValidateAndCategorizeResourcesWithBucketWildcard() throws OMExce
 
     expectOMExceptionWithCode(
         () -> validateAndCategorizeResources(NATIVE, 
Collections.singleton("arn:aws:s3:::my-bucket*")),
-        "Wildcard bucket patterns are not supported for Ozone native 
authorizer",
+        "IAM session policy: Wildcard bucket patterns are not supported for 
Ozone native authorizer",
         NOT_SUPPORTED_OPERATION);
 
     final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger = 
validateAndCategorizeResources(
@@ -478,7 +481,7 @@ public void 
testValidateAndCategorizeResourcesWithBucketWildcardAndExactObjectKe
 
     expectOMExceptionWithCode(
         () -> validateAndCategorizeResources(NATIVE, 
Collections.singleton("arn:aws:s3:::*/myKey.txt")),
-        "Wildcard bucket patterns are not supported for Ozone native 
authorizer",
+        "IAM session policy: Wildcard bucket patterns are not supported for 
Ozone native authorizer",
         NOT_SUPPORTED_OPERATION);
 
     final Set<IamSessionPolicyResolver.ResourceSpec> resultRanger = 
validateAndCategorizeResources(
@@ -490,7 +493,7 @@ public void 
testValidateAndCategorizeResourcesWithBucketWildcardAndExactObjectKe
   public void 
testValidateAndCategorizeResourcesWithBucketWildcardAndObjectWildcard() throws 
OMException {
     expectOMExceptionWithCode(
         () -> validateAndCategorizeResources(NATIVE, 
Collections.singleton("arn:aws:s3:::*/*")),
-        "Wildcard bucket patterns are not supported for Ozone native 
authorizer",
+        "IAM session policy: Wildcard bucket patterns are not supported for 
Ozone native authorizer",
         NOT_SUPPORTED_OPERATION);
 
     final IamSessionPolicyResolver.ResourceSpec expectedResourceSpec = new 
IamSessionPolicyResolver.ResourceSpec(
@@ -592,8 +595,8 @@ public void 
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixAndNonEmp
   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);
+        "IAM session policy: 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);
@@ -606,8 +609,8 @@ public void 
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixWildcardN
   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);
+        "IAM session policy: 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);
@@ -621,8 +624,8 @@ public void 
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixWildcardO
       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);
+        "IAM session policy: 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);
@@ -636,8 +639,8 @@ public void 
testValidateAndCategorizeResourcesWithBucketAndObjectPrefixWildcardO
       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);
+        "IAM session policy: 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);
@@ -668,28 +671,30 @@ public void 
testValidateAndCategorizeResourcesWithInvalidArnThrows() {
     final String invalidArn = "arn:aws:ec2:::bucket";
     expectOMExceptionWithCode(
         () -> validateAndCategorizeResources(NATIVE, 
Collections.singleton(invalidArn)),
-        "Unsupported Resource Arn - " + invalidArn, NOT_SUPPORTED_OPERATION);
+        "IAM session policy: Unsupported Resource Arn - " + invalidArn, 
NOT_SUPPORTED_OPERATION);
     expectOMExceptionWithCode(
         () -> validateAndCategorizeResources(RANGER, 
Collections.singleton(invalidArn)),
-        "Unsupported Resource Arn - " + invalidArn, NOT_SUPPORTED_OPERATION);
+        "IAM session policy: 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);
+        "IAM session policy: Invalid Resource Arn - arn:aws:s3:::", 
INVALID_REQUEST);
     expectOMExceptionWithCode(
         () -> validateAndCategorizeResources(RANGER, 
Collections.singleton("arn:aws:s3:::")),
-        "Invalid Resource Arn - arn:aws:s3:::", INVALID_REQUEST);
+        "IAM session policy: Invalid Resource Arn - arn:aws:s3:::", 
INVALID_REQUEST);
   }
 
   @Test
   public void testValidateAndCategorizeResourcesWithNoResourcesThrows() {
     expectOMExceptionWithCode(
-        () -> validateAndCategorizeResources(NATIVE, emptySet()), "No 
Resource(s) found in policy", INVALID_REQUEST);
+        () -> validateAndCategorizeResources(NATIVE, emptySet()), "IAM session 
policy: No Resource(s) found in policy",
+        INVALID_REQUEST);
     expectOMExceptionWithCode(
-        () -> validateAndCategorizeResources(RANGER, emptySet()), "No 
Resource(s) found in policy", INVALID_REQUEST);
+        () -> validateAndCategorizeResources(RANGER, emptySet()), "IAM session 
policy: No Resource(s) found in policy",
+        INVALID_REQUEST);
   }
 
   @Test
@@ -1431,7 +1436,7 @@ public void testUnsupportedResourceArnThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Unsupported Resource Arn - " +
+        json, "IAM session policy: Unsupported Resource Arn - " +
         "arn:aws:dynamodb:us-east-2:123456789012:table/example-table", 
NOT_SUPPORTED_OPERATION);
   }
 
@@ -1583,7 +1588,7 @@ public void testObjectResourceWithWildcardInMiddle() 
throws OMException {
 
     // Wildcards in middle of object resource are not supported for Native 
authorizer
     expectResolveThrows(
-        json, NATIVE, "Wildcard prefix patterns are not supported for Ozone 
native " +
+        json, NATIVE, "IAM session policy: Wildcard prefix patterns are not 
supported for Ozone native " +
         "authorizer if wildcard is not at the end", NOT_SUPPORTED_OPERATION);
 
     final Set<OzoneGrant> resolvedFromRangerAuthorizer = resolve(json, VOLUME, 
RANGER);
@@ -1920,7 +1925,7 @@ public void testInvalidResourceArnThrows() {
         "}";
 
     expectResolveThrowsForBothAuthorizers(
-        json, "Invalid Resource Arn - arn:aws:s3:::", INVALID_REQUEST);
+        json, "IAM session policy: Invalid Resource Arn - arn:aws:s3:::", 
INVALID_REQUEST);
   }
 
   private static void expectIllegalArgumentException(Runnable runnable, String 
expectedMessage) {
@@ -2029,7 +2034,8 @@ private static void 
expectBucketWildcardUnsupportedExceptionForNativeAuthorizer(
       resolve(json, VOLUME, NATIVE);
       throw new AssertionError("Expected exception not thrown");
     } catch (OMException ex) {
-      assertThat(ex.getMessage()).isEqualTo("Wildcard bucket patterns are not 
supported for Ozone native authorizer");
+      assertThat(ex.getMessage()).isEqualTo(
+          "IAM session policy: Wildcard bucket patterns are not supported for 
Ozone native authorizer");
       assertThat(ex.getResult()).isEqualTo(NOT_SUPPORTED_OPERATION);
     }
   }
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/package-info.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/package-info.java
new file mode 100644
index 00000000000..c5e8264687b
--- /dev/null
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Unit tests related to IAM policies.
+ */
+package org.apache.hadoop.ozone.security.acl.iam;
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
index e25cd3d4271..8d123784197 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
@@ -601,12 +601,25 @@ public boolean checkAcls(OzoneObj obj, RequestContext 
context,
                 "Bucket:" + obj.getBucketName() + " " : "";
         String keyName = obj.getKeyName() != null ?
                 "Key:" + obj.getKeyName() : "";
+        // For STS tokens, make clear that the user is using an assumed role, 
otherwise the access denied
+        // message could be confusing
+        String user = 
normalizedRequestContext.getClientUgi().getShortUserName();
+        final STSTokenIdentifier stsTokenIdentifier = 
OzoneManager.getStsTokenIdentifier();
+        if (stsTokenIdentifier != null) {
+          final StringBuilder builder = new StringBuilder(user);
+          builder.append(" (STS assumed role arn = ");
+          builder.append(stsTokenIdentifier.getRoleArn());
+          builder.append(", tempAccessKeyId = ");
+          builder.append(stsTokenIdentifier.getTempAccessKeyId());
+          builder.append(')');
+          user = builder.toString();
+        }
         log.warn("User {} doesn't have {} permission to access {} {}{}{}",
-            normalizedRequestContext.getClientUgi().getShortUserName(),
+            user,
             normalizedRequestContext.getAclRights(),
             obj.getResourceType(), volumeName, bucketName, keyName);
         throw new OMException(
-            "User " + 
normalizedRequestContext.getClientUgi().getShortUserName() +
+            "User " + user +
             " doesn't have " + normalizedRequestContext.getAclRights() +
             " permission to access " + obj.getResourceType() + " " +
             volumeName  + bucketName + keyName, ResultCodes.PERMISSION_DENIED);
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
index e4da7b604c7..e2dee7dc6cf 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
@@ -43,6 +43,7 @@
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.ozone.audit.S3GAction;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
@@ -86,6 +87,7 @@ public class S3STSEndpoint extends S3STSEndpointBase {
   private static final String INTERNAL_FAILURE = "InternalFailure";
   private static final String ACCESS_DENIED = "AccessDenied";
   private static final String INVALID_CLIENT_TOKEN_ID = "InvalidClientTokenId";
+  private static final String UNSUPPORTED_OPERATION = "UnsupportedOperation";
 
   @Inject
   private RequestIdentifier requestIdentifier;
@@ -223,6 +225,11 @@ private Response handleAssumeRole(String roleArn, String 
roleSessionName, Intege
     }
 
     try {
+      if (LOG.isDebugEnabled() && StringUtils.isNotEmpty(awsIamSessionPolicy)) 
{
+        LOG.debug(
+            "AssumeRole requestId={} received Policy(len={}): {}", requestId, 
awsIamSessionPolicy.length(),
+            awsIamSessionPolicy);
+      }
       S3STSUtils.validateSessionPolicy(awsIamSessionPolicy);
     } catch (OMException e) {
       validationErrors.add(e.getMessage());
@@ -275,6 +282,15 @@ private Response handleAssumeRole(String roleArn, String 
roleSessionName, Intege
               INVALID_CLIENT_TOKEN_ID, "The security token included in the 
request is invalid.",
               FORBIDDEN.getStatusCode());
         }
+        if (omException.getResult() == 
OMException.ResultCodes.NOT_SUPPORTED_OPERATION ||
+            omException.getResult() == 
OMException.ResultCodes.FEATURE_NOT_ENABLED) {
+          throw new OSTSException(
+              UNSUPPORTED_OPERATION, omException.getMessage(), 
NOT_IMPLEMENTED.getStatusCode());
+        }
+        if (omException.getResult() == 
OMException.ResultCodes.INVALID_REQUEST) {
+          throw new OSTSException(
+              VALIDATION_ERROR, omException.getMessage(), 
BAD_REQUEST.getStatusCode());
+        }
       }
       throw new OSTSException(
           INTERNAL_FAILURE, "An internal error has occurred.", 
INTERNAL_SERVER_ERROR.getStatusCode(), "Receiver");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to