This is an automated email from the ASF dual-hosted git repository.
ChenSammi 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 dc3b37d5701 HDDS-15984. [STS] Improve s3:prefix Condition handling and
reject unsupported AssumeRole parameters (#10875)
dc3b37d5701 is described below
commit dc3b37d57017d016f194947d469c0983e338cef0
Author: fmorg-git <[email protected]>
AuthorDate: Thu Jul 30 21:43:16 2026 -0700
HDDS-15984. [STS] Improve s3:prefix Condition handling and reject
unsupported AssumeRole parameters (#10875)
---
.../security/acl/iam/IamSessionPolicyResolver.java | 7 +-
.../acl/iam/TestIamSessionPolicyResolver.java | 30 +++
.../apache/hadoop/ozone/s3sts/S3STSEndpoint.java | 173 +++++++++++++---
.../hadoop/ozone/s3sts/S3STSEndpointBase.java | 10 +
.../hadoop/ozone/s3sts/TestS3STSEndpoint.java | 227 ++++++++++++++++++++-
5 files changed, 419 insertions(+), 28 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 4e591d14b5f..415ae5e8af7 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
@@ -309,7 +309,12 @@ private static Condition
parsePrefixesFromConditions(JsonNode stmt) throws OMExc
ERROR_PREFIX + "Invalid Condition operator value structure - " +
operatorValue, MALFORMED_POLICY_DOCUMENT);
}
- final String keyName = operatorValue.fieldNames().hasNext() ?
operatorValue.fieldNames().next() : null;
+ if (operatorValue.size() != 1) {
+ throw new OMException(
+ ERROR_PREFIX + "Only one Condition key is supported per operator",
NOT_SUPPORTED_OPERATION);
+ }
+
+ final String keyName = operatorValue.fieldNames().next();
if (!"s3:prefix".equalsIgnoreCase(keyName)) {
throw new OMException(ERROR_PREFIX + "Unsupported Condition key name -
" + keyName, NOT_SUPPORTED_OPERATION);
}
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 2ac9d195cf5..2902aa4fba0 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
@@ -138,6 +138,36 @@ public void testUnsupportedConditionAttributeThrows() {
json, "IAM session policy: Unsupported Condition key name -
aws:SourceArn", NOT_SUPPORTED_OPERATION);
}
+ @Test
+ public void testMultipleConditionKeysWithPrefixFirstThrows() {
+ final String json = "{\n" +
+ " \"Statement\": [{\n" +
+ " \"Effect\": \"Allow\",\n" +
+ " \"Action\": \"s3:ListBucket\",\n" +
+ " \"Resource\": \"arn:aws:s3:::b\",\n" +
+ " \"Condition\": { \"StringEquals\": { \"s3:prefix\": \"x\",
\"aws:SourceIp\": \"1.2.3.4\" } }\n" +
+ " }]\n" +
+ "}";
+
+ expectResolveThrowsForBothAuthorizers(
+ json, "IAM session policy: Only one Condition key is supported per
operator", NOT_SUPPORTED_OPERATION);
+ }
+
+ @Test
+ public void testMultipleConditionKeysWithUnsupportedKeyFirstThrows() {
+ final String json = "{\n" +
+ " \"Statement\": [{\n" +
+ " \"Effect\": \"Allow\",\n" +
+ " \"Action\": \"s3:ListBucket\",\n" +
+ " \"Resource\": \"arn:aws:s3:::b\",\n" +
+ " \"Condition\": { \"StringEquals\": { \"aws:SourceIp\":
\"1.2.3.4\", \"s3:prefix\": \"x\" } }\n" +
+ " }]\n" +
+ "}";
+
+ expectResolveThrowsForBothAuthorizers(
+ json, "IAM session policy: Only one Condition key is supported per
operator", NOT_SUPPORTED_OPERATION);
+ }
+
@Test
public void testUnsupportedEffectThrows() {
final String json = "{\n" +
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 1cc92230149..365115a4aa9 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
@@ -28,27 +28,34 @@
import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.STS_VALIDATION_ERROR;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
-import javax.ws.rs.FormParam;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
import org.apache.hadoop.ozone.audit.S3GAction;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
@@ -88,6 +95,19 @@ public class S3STSEndpoint extends S3STSEndpointBase {
private static final String EXPECTED_VERSION = "2011-06-15";
+ private static final String SIGV4_PARAM_PREFIX = "X-Amz-";
+
+ private static final Set<String> ASSUME_ROLE_ALLOWED_PARAMS =
ImmutableSet.of(
+ "Action", "RoleArn", "RoleSessionName", "DurationSeconds", "Version",
"Policy");
+
+ private static final String POLICY_ARNS_MEMBER_PREFIX = "PolicyArns.member.";
+ private static final String PROVIDED_CONTEXTS_MEMBER_PREFIX =
"ProvidedContexts.member.";
+ private static final String TAGS_MEMBER_PREFIX = "Tags.member.";
+ private static final String TRANSITIVE_TAG_KEYS_MEMBER_PREFIX =
"TransitiveTagKeys.member.";
+
+ private static final Set<String> AWS_VALID_ASSUME_ROLE_OPTIONAL_PARAMS =
ImmutableSet.of(
+ "ExternalId", "SerialNumber", "SourceIdentity", "TokenCode");
+
// JAXBContext is relatively expensive to create and is threadsafe, so cache
and reuse
private static final JAXBContext JAXB_CONTEXT;
@@ -128,49 +148,51 @@ public Response get(
@QueryParam("Version") String version,
@QueryParam("Policy") String awsIamSessionPolicy) throws OS3Exception {
- return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version, awsIamSessionPolicy);
+ return handleSTSRequest(
+ getQueryParameters().keySet(), action, roleArn, roleSessionName,
durationSeconds, version, awsIamSessionPolicy);
}
/**
* STS endpoint that handles POST requests with form data.
* AWS STS typically uses POST requests with form-encoded parameters.
*
- * @param action The STS action to perform
- * @param roleArn The ARN of the role to assume
- * @param roleSessionName Session name for the role
- * @param durationSeconds Duration of the token validity
- * @param version AWS STS API version
+ * @param form form-encoded request parameters
* @return Response containing STS response XML or error
*/
@POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
- public Response post(
- @FormParam("Action") String action,
- @FormParam("RoleArn") String roleArn,
- @FormParam("RoleSessionName") String roleSessionName,
- @FormParam("DurationSeconds") Integer durationSeconds,
- @FormParam("Version") String version,
- @FormParam("Policy") String awsIamSessionPolicy) throws OS3Exception {
-
- return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version, awsIamSessionPolicy);
+ public Response post(Form form) throws OS3Exception {
+ if (form == null) {
+ return unknownOperationExceptionResponse();
+ }
+
+ final MultivaluedMap<String, String> formParams = form.asMap();
+ final String action = formParams.getFirst("Action");
+ final String roleArn = formParams.getFirst("RoleArn");
+ final String roleSessionName = formParams.getFirst("RoleSessionName");
+ final Integer durationSeconds =
parseIntegerOrNull(formParams.getFirst("DurationSeconds"));
+ final String version = formParams.getFirst("Version");
+ final String awsIamSessionPolicy = formParams.getFirst("Policy");
+
+ return handleSTSRequest(
+ formParams.keySet(), action, roleArn, roleSessionName,
durationSeconds, version, awsIamSessionPolicy);
}
- private Response handleSTSRequest(String action, String roleArn, String
roleSessionName,
- Integer durationSeconds, String version, String awsIamSessionPolicy)
throws OS3Exception {
+ private Response handleSTSRequest(Set<String> paramNamesToValidate, String
action, String roleArn,
+ String roleSessionName, Integer durationSeconds, String version, String
awsIamSessionPolicy) throws OS3Exception {
final String requestId = requestIdentifier.getRequestId();
// NOTE: invalid, missing or unsupported actions are not added to the
audit log
try {
if (action == null) {
// Amazon STS has a different structure for the XML error response
when the action is missing
- return Response.status(BAD_REQUEST)
- .entity("<UnknownOperationException/>")
- .type(MediaType.APPLICATION_XML)
- .build();
+ return unknownOperationExceptionResponse();
}
switch (action) {
case ASSUME_ROLE_ACTION:
- return handleAssumeRole(roleArn, roleSessionName, durationSeconds,
awsIamSessionPolicy, version, requestId);
+ return handleAssumeRole(
+ paramNamesToValidate, roleArn, roleSessionName, durationSeconds,
awsIamSessionPolicy, version, requestId);
// These operations are not supported yet
case GET_SESSION_TOKEN_ACTION:
case ASSUME_ROLE_WITH_SAML_ACTION:
@@ -193,8 +215,8 @@ private Response handleSTSRequest(String action, String
roleArn, String roleSess
}
}
- private Response handleAssumeRole(String roleArn, String roleSessionName,
Integer durationSeconds,
- String awsIamSessionPolicy, String version, String requestId) throws
OSTSException {
+ private Response handleAssumeRole(Set<String> paramNamesToValidate, String
roleArn, String roleSessionName,
+ Integer durationSeconds, String awsIamSessionPolicy, String version,
String requestId) throws OSTSException {
final String action = "AssumeRole";
final Map<String, String> auditParams = getAuditParameters();
S3STSUtils.addAssumeRoleAuditParams(
@@ -211,6 +233,16 @@ private Response handleAssumeRole(String roleArn, String
roleSessionName, Intege
throw exception;
}
+ final AssumeRoleParamValidationResult assumeRoleParamValidationResult =
validateAssumeRoleParameters(
+ paramNamesToValidate);
+ if
(!assumeRoleParamValidationResult.getNotImplementedOptionalParams().isEmpty()) {
+ final OSTSException exception = new
OSTSException(STS_UNSUPPORTED_OPERATION).withMessage(
+ "AssumeRole optional parameter(s) not implemented: " +
+ String.join(", ",
assumeRoleParamValidationResult.getNotImplementedOptionalParams()));
+
getAuditLogger().logWriteFailure(buildAuditMessageForFailure(S3GAction.ASSUME_ROLE,
auditParams, exception));
+ throw exception;
+ }
+
final Set<String> validationErrors = new HashSet<>();
int duration = durationSeconds == null ?
S3STSUtils.DEFAULT_DURATION_SECONDS : durationSeconds;
try {
@@ -242,6 +274,11 @@ private Response handleAssumeRole(String roleArn, String
roleSessionName, Intege
validationErrors.add(e.getMessage());
}
+ if (!assumeRoleParamValidationResult.getUnsupportedParams().isEmpty()) {
+ validationErrors.add("Unsupported AssumeRole parameter(s): " +
String.join(", ",
+ assumeRoleParamValidationResult.getUnsupportedParams()));
+ }
+
final int numValidationErrors = validationErrors.size();
if (numValidationErrors > 0) {
//noinspection StringBufferReplaceableByString
@@ -303,6 +340,92 @@ private Response handleAssumeRole(String roleArn, String
roleSessionName, Intege
}
}
+ private AssumeRoleParamValidationResult
validateAssumeRoleParameters(Set<String> paramNamesToValidate) {
+ if (paramNamesToValidate == null || paramNamesToValidate.isEmpty()) {
+ return AssumeRoleParamValidationResult.empty();
+ }
+
+ final List<String> notImplementedOptionalParams = new ArrayList<>();
+ final List<String> unsupportedParams = new ArrayList<>();
+ for (String paramName : paramNamesToValidate) {
+ if (isAllowedAssumeRoleParameter(paramName)) {
+ continue;
+ }
+
+ if (isAwsValidButNotImplementedAssumeRoleParameter(paramName)) {
+ notImplementedOptionalParams.add(paramName);
+ } else {
+ unsupportedParams.add(paramName);
+ }
+ }
+
+ Collections.sort(notImplementedOptionalParams);
+ Collections.sort(unsupportedParams);
+ return new AssumeRoleParamValidationResult(notImplementedOptionalParams,
unsupportedParams);
+ }
+
+ private static boolean isAllowedAssumeRoleParameter(String paramName) {
+ return ASSUME_ROLE_ALLOWED_PARAMS.contains(paramName) ||
Strings.CI.startsWith(paramName, SIGV4_PARAM_PREFIX);
+ }
+
+ private static Response unknownOperationExceptionResponse() {
+ return Response.status(BAD_REQUEST)
+ .entity("<UnknownOperationException/>")
+ .type(MediaType.APPLICATION_XML)
+ .build();
+ }
+
+ private static boolean isAwsValidButNotImplementedAssumeRoleParameter(String
paramName) {
+ if (StringUtils.isBlank(paramName)) {
+ return false;
+ }
+ if (AWS_VALID_ASSUME_ROLE_OPTIONAL_PARAMS.contains(paramName)) {
+ return true;
+ }
+
+ return Strings.CI.startsWith(paramName, POLICY_ARNS_MEMBER_PREFIX)
+ || Strings.CI.startsWith(paramName, PROVIDED_CONTEXTS_MEMBER_PREFIX)
+ || Strings.CI.startsWith(paramName, TAGS_MEMBER_PREFIX)
+ || Strings.CI.startsWith(paramName, TRANSITIVE_TAG_KEYS_MEMBER_PREFIX);
+ }
+
+ private static Integer parseIntegerOrNull(String value) throws OSTSException
{
+ if (StringUtils.isBlank(value)) {
+ return null;
+ }
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new OSTSException(STS_VALIDATION_ERROR)
+ .withMessage("1 validation error detected: Invalid Value:
DurationSeconds must be a number");
+ }
+ }
+
+ private static final class AssumeRoleParamValidationResult {
+ private static final AssumeRoleParamValidationResult EMPTY = new
AssumeRoleParamValidationResult(
+ Collections.emptyList(), Collections.emptyList());
+
+ private final List<String> notImplementedOptionalParams;
+ private final List<String> unsupportedParams;
+
+ private AssumeRoleParamValidationResult(List<String>
notImplementedOptionalParams, List<String> unsupportedParams) {
+ this.notImplementedOptionalParams = notImplementedOptionalParams;
+ this.unsupportedParams = unsupportedParams;
+ }
+
+ private static AssumeRoleParamValidationResult empty() {
+ return EMPTY;
+ }
+
+ private List<String> getNotImplementedOptionalParams() {
+ return notImplementedOptionalParams;
+ }
+
+ private List<String> getUnsupportedParams() {
+ return unsupportedParams;
+ }
+ }
+
private String generateAssumeRoleResponse(String assumedRoleUserArn,
AssumeRoleResponseInfo responseInfo,
String requestId) throws IOException {
final String accessKeyId = responseInfo.getAccessKeyId();
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
index 027784b0edc..de52a29d6af 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
@@ -23,6 +23,8 @@
import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
import org.apache.hadoop.ozone.audit.AuditAction;
import org.apache.hadoop.ozone.audit.AuditEventStatus;
import org.apache.hadoop.ozone.audit.AuditLogger;
@@ -133,4 +135,12 @@ public void setSignatureInfo(SignatureInfo signatureInfo) {
protected Map<String, String> getAuditParameters() {
return AuditUtils.getAuditParameters(context);
}
+
+ protected MultivaluedMap<String, String> getQueryParameters() {
+ if (context == null || context.getUriInfo() == null) {
+ return new MultivaluedHashMap<>();
+ }
+ final MultivaluedMap<String, String> params =
context.getUriInfo().getQueryParameters();
+ return params == null ? new MultivaluedHashMap<>() : params;
+ }
}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
index 059f54e0993..36adf2359c4 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
@@ -36,6 +36,7 @@
import java.io.StringReader;
import java.time.Instant;
import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Form;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -68,6 +69,8 @@ public class TestS3STSEndpoint {
private S3STSEndpoint endpoint;
private ObjectStore objectStore;
private AuditLogger auditLogger;
+ private MultivaluedHashMap<String, String> queryParameters;
+ private Form formParameters;
private static final String ROLE_ARN =
"arn:aws:iam::123456789012:role/test-role";
private static final String ROLE_SESSION_NAME = "test-session";
private static final String ROLE_USER_ARN =
"arn:aws:sts::123456789012:assumed-role/test-role/" + ROLE_SESSION_NAME;
@@ -87,7 +90,9 @@ public void setup() throws Exception {
final UriInfo uriInfo = mock(UriInfo.class);
when(context.getUriInfo()).thenReturn(uriInfo);
when(uriInfo.getPathParameters()).thenReturn(new MultivaluedHashMap<>());
- when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap<>());
+ queryParameters = new MultivaluedHashMap<>();
+ when(uriInfo.getQueryParameters()).thenReturn(queryParameters);
+ formParameters = new Form();
// Stub assumeRole to return deterministic credentials.
objectStore = mock(ObjectStore.class);
@@ -117,6 +122,184 @@ public void setup() throws Exception {
endpoint.setSignatureInfo(signatureInfo);
}
+ @Test
+ public void testStsAssumeRoleRejectsUnsupportedParameterForGetMethod()
throws Exception {
+ setAssumeRoleQueryParameters("PolicyArns.member.1",
"arn:aws:iam::123456789012:policy/test-policy");
+
+ final OSTSException ex = assertThrows(
+ OSTSException.class, () -> endpoint.get("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null));
+
+ assertEquals(501, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+ verify(objectStore, never()).assumeRole(anyString(), anyString(),
anyInt(), any(), anyString());
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), STS_NS, "Sender", "UnsupportedOperation",
+ "AssumeRole optional parameter(s) not implemented:
PolicyArns.member.1");
+ }
+
+ @Test
+ public void testStsAssumeRoleRejectsUnsupportedParameterForPostMethod()
throws Exception {
+ setBaseAssumeRoleFormParameters();
+ formParameters.param("ExternalId", "external-id");
+
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
endpoint.post(formParameters).close());
+
+ assertEquals(501, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+ verify(objectStore, never()).assumeRole(anyString(), anyString(),
anyInt(), any(), anyString());
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), STS_NS, "Sender", "UnsupportedOperation",
+ "AssumeRole optional parameter(s) not implemented: ExternalId");
+ }
+
+ @Test
+ public void testStsAssumeRoleAllowsSupportedParametersForGetMethod() {
+ setAssumeRoleQueryParameters(
+ "Action", "AssumeRole",
+ "RoleArn", ROLE_ARN,
+ "RoleSessionName", ROLE_SESSION_NAME,
+ "DurationSeconds", "3600",
+ "Version", "2011-06-15");
+
+ final Response response = endpoint.get("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
+
+ assertEquals(200, response.getStatus());
+ verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteFailure(any(AuditMessage.class));
+ }
+
+ @Test
+ public void testStsAssumeRoleAllowsSignatureParametersForGetMethod() {
+ setAssumeRoleQueryParameters(
+ "Action", "AssumeRole",
+ "RoleArn", ROLE_ARN,
+ "RoleSessionName", ROLE_SESSION_NAME,
+ "DurationSeconds", "3600",
+ "Version", "2011-06-15",
+ "X-Amz-Algorithm", "AWS4-HMAC-SHA256",
+ "X-Amz-Credential", "test-user/20260101/us-east-1/sts/aws4_request",
+ "X-Amz-Date", "20260101T000000Z",
+ "X-Amz-Expires", "3600",
+ "X-Amz-SignedHeaders", "host",
+ "X-Amz-Signature",
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
+
+ final Response response = endpoint.get("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
+
+ assertEquals(200, response.getStatus());
+ verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteFailure(any(AuditMessage.class));
+ }
+
+ @Test
+ public void testStsAssumeRoleRejectsUnknownParameterForGetMethod() throws
Exception {
+ setAssumeRoleQueryParameters("TotallyUnknownParam", "x");
+
+ final OSTSException ex = assertThrows(
+ OSTSException.class, () -> endpoint.get("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+ verify(objectStore, never()).assumeRole(anyString(), anyString(),
anyInt(), any(), anyString());
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), STS_NS, "Sender", "ValidationError", "Unsupported
AssumeRole parameter(s): TotallyUnknownParam");
+ }
+
+ @Test
+ public void testStsAssumeRoleRejectsUnknownParameterForPostMethod() throws
Exception {
+ setBaseAssumeRoleFormParameters();
+ formParameters.param("TotallyUnknownParam", "y");
+
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
endpoint.post(formParameters).close());
+
+ assertEquals(400, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+ verify(objectStore, never()).assumeRole(anyString(), anyString(),
anyInt(), any(), anyString());
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), STS_NS, "Sender", "ValidationError",
+ "Unsupported AssumeRole parameter(s): TotallyUnknownParam");
+ }
+
+ @Test
+ public void testStsAssumeRoleRejectsBlankParameterNameForGetMethod() throws
Exception {
+ setAssumeRoleQueryParameters("", "x");
+
+ final OSTSException ex = assertThrows(
+ OSTSException.class, () -> endpoint.get("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+ verify(objectStore, never()).assumeRole(anyString(), anyString(),
anyInt(), any(), anyString());
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), STS_NS, "Sender", "ValidationError", "Unsupported
AssumeRole parameter(s): ");
+ }
+
+ @Test
+ public void testStsAssumeRoleRejectsBlankParameterNameForPostMethod() throws
Exception {
+ setBaseAssumeRoleFormParameters();
+ formParameters.param("", "x");
+
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
endpoint.post(formParameters).close());
+
+ assertEquals(400, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+ verify(objectStore, never()).assumeRole(anyString(), anyString(),
anyInt(), any(), anyString());
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), STS_NS, "Sender", "ValidationError", "Unsupported
AssumeRole parameter(s): ");
+ }
+
+ @Test
+ public void
testStsAssumeRoleIgnoresUnknownQueryStringParameterForPostMethod() {
+ queryParameters.add("foo", "bar");
+
+ // For POST requests, only body (form) parameters should be validated.
+ // Query string parameters should not affect validation results.
+ setBaseAssumeRoleFormParameters();
+ final Response response = endpoint.post(formParameters);
+ response.close();
+
+ assertEquals(200, response.getStatus());
+ verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteFailure(any(AuditMessage.class));
+ }
+
+ @Test
+ public void
testStsAssumeRoleRejectsUnsupportedSigningParametersForPostMethod() throws
Exception {
+ setBaseAssumeRoleFormParameters();
+ formParameters.param("AWSAccessKeyId", "test-user");
+ formParameters.param("Signature", "signature");
+ formParameters.param("Expires", "3600");
+
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
endpoint.post(formParameters).close());
+
+ assertEquals(400, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+ verify(objectStore, never()).assumeRole(anyString(), anyString(),
anyInt(), any(), anyString());
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), STS_NS, "Sender", "ValidationError",
+ "Unsupported AssumeRole parameter(s): AWSAccessKeyId, Expires,
Signature");
+ }
+
@Test
public void testStsAssumeRoleValidForGetMethod() throws Exception {
final Response response = endpoint.get("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
@@ -157,8 +340,9 @@ public void testStsAssumeRoleValidForGetMethod() throws
Exception {
@Test
public void testStsAssumeRoleValidForPostMethod() throws Exception {
+ setBaseAssumeRoleFormParameters();
//noinspection resource
- final Response response = endpoint.post("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
+ final Response response = endpoint.post(formParameters);
assertEquals(200, response.getStatus());
verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
@@ -205,6 +389,22 @@ public void testStsNullAction() throws Exception {
assertEquals("UnknownOperationException", root.getLocalName());
}
+ @Test
+ public void testStsNullFormForPostMethod() throws Exception {
+ final Response response = endpoint.post(null);
+
+ assertEquals(400, response.getStatus());
+ verifyNoInteractions(auditLogger);
+ final String errorMessage = (String) response.getEntity();
+ assertEquals("<UnknownOperationException/>", errorMessage);
+
+ final Document doc = parseXml(errorMessage);
+ final Element root = doc.getDocumentElement();
+ assertEquals("UnknownOperationException", root.getLocalName());
+
+ response.close();
+ }
+
@Test
public void testStsUnsupportedActionWithVersionSupplied() throws Exception {
final OSTSException ex = assertThrows(OSTSException.class, () ->
@@ -563,6 +763,29 @@ public void testStsMultipleValidationErrors() throws
Exception {
"'policy' failed to satisfy constraint: Member must have length less
than or equal to 2048"));
}
+ private void setAssumeRoleQueryParameters(String... nameValuePairs) {
+ queryParameters.clear();
+ for (int i = 0; i < nameValuePairs.length; i += 2) {
+ queryParameters.add(nameValuePairs[i], nameValuePairs[i + 1]);
+ }
+ }
+
+ private void setBaseAssumeRoleFormParameters() {
+ setAssumeRoleFormParameters(
+ "Action", "AssumeRole",
+ "RoleArn", ROLE_ARN,
+ "RoleSessionName", ROLE_SESSION_NAME,
+ "DurationSeconds", "3600",
+ "Version", "2011-06-15");
+ }
+
+ private void setAssumeRoleFormParameters(String... nameValuePairs) {
+ formParameters = new Form();
+ for (int i = 0; i < nameValuePairs.length; i += 2) {
+ formParameters.param(nameValuePairs[i], nameValuePairs[i + 1]);
+ }
+ }
+
private static Document parseXml(String xml) throws Exception {
final DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]