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]