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 56da3888204 HDDS-14150. [STS] Connect STS Endpoint to Backend
Processing (#9673)
56da3888204 is described below
commit 56da3888204cbd9ce8e701cfc0f330a92d378a45
Author: fmorg-git <[email protected]>
AuthorDate: Tue Jan 27 13:55:15 2026 -0800
HDDS-14150. [STS] Connect STS Endpoint to Backend Processing (#9673)
---
.../org/apache/hadoop/ozone/OzoneConfigKeys.java | 4 +
.../org/apache/hadoop/ozone/om/OzoneManager.java | 28 ++++
.../om/ratis/utils/OzoneManagerRatisUtils.java | 8 +
.../om/ratis/TestOzoneManagerRatisRequest.java | 58 +++++++
.../apache/hadoop/ozone/om/ratis/package-info.java | 21 +++
.../hadoop/ozone/s3/endpoint/EndpointBase.java | 4 +
.../ozone/s3/signature/AWSSignatureProcessor.java | 39 +++++
.../hadoop/ozone/s3/signature/SignatureInfo.java | 25 ++-
.../ozone/s3sts/S3AssumeRoleResponseXml.java | 179 ++++++++++++++++++++
.../apache/hadoop/ozone/s3sts/S3STSConfigKeys.java | 4 +-
.../apache/hadoop/ozone/s3sts/S3STSEndpoint.java | 180 ++++++++++++---------
.../hadoop/ozone/s3sts/S3STSEndpointBase.java | 5 +
.../s3sts/{TestSTS.java => TestS3STSEndpoint.java} | 92 +++++++++--
.../apache/hadoop/ozone/s3sts/package-info.java | 21 +++
14 files changed, 575 insertions(+), 93 deletions(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
index ceca7d0c882..7aa3a6bdab6 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
@@ -693,6 +693,10 @@ public final class OzoneConfigKeys {
public static final String OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE =
"ozone.client.elastic.byte.buffer.pool.max.size";
public static final String
OZONE_CLIENT_ELASTIC_BYTE_BUFFER_POOL_MAX_SIZE_DEFAULT = "16GB";
+
+ public static final String OZONE_S3G_STS_HTTP_ENABLED_KEY =
+ "ozone.s3g.sts.http.enabled";
+ public static final boolean OZONE_S3G_STS_HTTP_ENABLED_DEFAULT = false;
/**
* There is no need to instantiate this class.
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 7917130950e..cda7c76b38b 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -476,6 +476,7 @@ public final class OzoneManager extends
ServiceRuntimeInfoImpl
private final boolean isS3MultiTenancyEnabled;
private final boolean isStrictS3;
+ private final boolean isS3STSEnabled;
private ExitManager exitManager;
private OzoneManagerPrepareState prepareState;
@@ -685,6 +686,11 @@ private OzoneManager(OzoneConfiguration conf,
StartupOption startupOption)
this.isS3MultiTenancyEnabled =
OMMultiTenantManager.checkAndEnableMultiTenancy(this, conf);
+ // Enable S3 STS if config key is set
+ this.isS3STSEnabled = conf.getBoolean(
+ OzoneConfigKeys.OZONE_S3G_STS_HTTP_ENABLED_KEY,
+ OzoneConfigKeys.OZONE_S3G_STS_HTTP_ENABLED_DEFAULT);
+
metrics = OMMetrics.create();
omSnapshotIntMetrics = OmSnapshotInternalMetrics.create();
perfMetrics = OMPerformanceMetrics.register();
@@ -1137,6 +1143,13 @@ public boolean isStrictS3() {
return isStrictS3;
}
+ /**
+ * Returns true if S3 STS is enabled; false otherwise.
+ */
+ public boolean isS3STSEnabled() {
+ return isS3STSEnabled;
+ }
+
/**
* Throws OMException FEATURE_NOT_ENABLED if S3 multi-tenancy is not enabled.
*/
@@ -1150,6 +1163,21 @@ public void checkS3MultiTenancyEnabled() throws
OMException {
FEATURE_NOT_ENABLED);
}
+ /**
+ * Throws OMException FEATURE_NOT_ENABLED if S3 STS (AssumeRole) is not
enabled.
+ */
+ public void checkS3STSEnabled() throws OMException {
+ if (isS3STSEnabled()) {
+ if (getAccessAuthorizer().isNative()) {
+ throw new OMException("S3 STS is not enabled for Ozone Native
Authorizer", FEATURE_NOT_ENABLED);
+ }
+ return;
+ }
+
+ throw new OMException("S3 STS is not enabled. Please set " +
OzoneConfigKeys.OZONE_S3G_STS_HTTP_ENABLED_KEY +
+ " to true and restart all OMs.", FEATURE_NOT_ENABLED);
+ }
+
/**
* Return config value of {@link OzoneConfigKeys#OZONE_SECURITY_ENABLED_KEY}.
*/
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
index 4f1b2fc952d..bacd0b65258 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
@@ -30,6 +30,8 @@
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Clock;
+import java.time.ZoneOffset;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.security.SecurityConfig;
@@ -68,6 +70,7 @@
import org.apache.hadoop.ozone.om.request.key.acl.prefix.OMPrefixSetAclRequest;
import
org.apache.hadoop.ozone.om.request.s3.multipart.S3ExpiredMultipartUploadsAbortRequest;
import org.apache.hadoop.ozone.om.request.s3.security.OMSetSecretRequest;
+import org.apache.hadoop.ozone.om.request.s3.security.S3AssumeRoleRequest;
import
org.apache.hadoop.ozone.om.request.s3.security.S3DeleteRevokedSTSTokensRequest;
import org.apache.hadoop.ozone.om.request.s3.security.S3GetSecretRequest;
import org.apache.hadoop.ozone.om.request.s3.security.S3RevokeSTSTokenRequest;
@@ -119,6 +122,8 @@ public final class OzoneManagerRatisUtils {
private static final Logger LOG = LoggerFactory
.getLogger(OzoneManagerRatisUtils.class);
+ private static final Clock CLOCK = Clock.system(ZoneOffset.UTC);
+
private OzoneManagerRatisUtils() {
}
@@ -198,6 +203,9 @@ public static OMClientRequest createClientRequest(OMRequest
omRequest,
return new OMSetSecretRequest(omRequest);
case RevokeS3Secret:
return new S3RevokeSecretRequest(omRequest);
+ case AssumeRole:
+ ozoneManager.checkS3STSEnabled();
+ return new S3AssumeRoleRequest(omRequest, CLOCK);
case RevokeSTSToken:
return new S3RevokeSTSTokenRequest(omRequest);
case DeleteRevokedSTSTokens:
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisRequest.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisRequest.java
index fdc9e0f008d..4d3bdef38e3 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisRequest.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerRatisRequest.java
@@ -18,8 +18,10 @@
package org.apache.hadoop.ozone.om.ratis;
import static
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.INVALID_REQUEST;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -44,6 +46,8 @@
import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import
org.apache.hadoop.ozone.protocolPB.OzoneManagerProtocolServerSideTranslatorPB;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.ratis.protocol.ClientId;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -136,4 +140,58 @@ public void testUnknownRequestHandling()
assertEquals(expectedResponse, actualResponse);
}
+
+ @Test
+ public void testAssumeRoleRejectedWhenStsDisabled() {
+ ozoneManager = mock(OzoneManager.class, CALLS_REAL_METHODS);
+ when(ozoneManager.isS3STSEnabled()).thenReturn(false);
+
+ final OzoneManagerProtocolProtos.OMRequest omRequest =
+ OzoneManagerProtocolProtos.OMRequest.newBuilder()
+ .setCmdType(OzoneManagerProtocolProtos.Type.AssumeRole)
+ .setClientId(ClientId.randomId().toString())
+ .build();
+
+ final OMException omException = assertThrows(OMException.class,
+ () -> OzoneManagerRatisUtils.createClientRequest(omRequest,
ozoneManager));
+ assertEquals(OMException.ResultCodes.FEATURE_NOT_ENABLED,
omException.getResult());
+ }
+
+ @Test
+ public void testAssumeRoleRejectedWhenStsEnabledButNativeAuthorizerUsed() {
+ ozoneManager = mock(OzoneManager.class, CALLS_REAL_METHODS);
+ when(ozoneManager.isS3STSEnabled()).thenReturn(true);
+
+ final IAccessAuthorizer authorizer = mock(IAccessAuthorizer.class);
+ when(authorizer.isNative()).thenReturn(true);
+ when(ozoneManager.getAccessAuthorizer()).thenReturn(authorizer);
+
+ final OzoneManagerProtocolProtos.OMRequest omRequest =
+ OzoneManagerProtocolProtos.OMRequest.newBuilder()
+ .setCmdType(OzoneManagerProtocolProtos.Type.AssumeRole)
+ .setClientId(ClientId.randomId().toString())
+ .build();
+
+ final OMException omException = assertThrows(OMException.class,
+ () -> OzoneManagerRatisUtils.createClientRequest(omRequest,
ozoneManager));
+ assertEquals(OMException.ResultCodes.FEATURE_NOT_ENABLED,
omException.getResult());
+ }
+
+ @Test
+ public void testAssumeRoleAllowedWhenStsEnabledAndNativeAuthorizerNotUsed() {
+ ozoneManager = mock(OzoneManager.class, CALLS_REAL_METHODS);
+ when(ozoneManager.isS3STSEnabled()).thenReturn(true);
+
+ final IAccessAuthorizer authorizer = mock(IAccessAuthorizer.class);
+ when(authorizer.isNative()).thenReturn(false);
+ when(ozoneManager.getAccessAuthorizer()).thenReturn(authorizer);
+
+ final OzoneManagerProtocolProtos.OMRequest omRequest =
+ OzoneManagerProtocolProtos.OMRequest.newBuilder()
+ .setCmdType(OzoneManagerProtocolProtos.Type.AssumeRole)
+ .setClientId(ClientId.randomId().toString())
+ .build();
+
+ assertDoesNotThrow(() ->
OzoneManagerRatisUtils.createClientRequest(omRequest, ozoneManager));
+ }
}
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/package-info.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/package-info.java
new file mode 100644
index 00000000000..3dea7af810c
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/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.
+ */
+
+/**
+ * Package contains test classes for OM Ratis server implementation.
+ */
+package org.apache.hadoop.ozone.om.ratis;
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
index a7ef000c672..1f8e0532db8 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
@@ -139,6 +139,10 @@ public void initialization() {
s3Auth = new S3Auth(signatureInfo.getStringToSign(),
signatureInfo.getSignature(),
signatureInfo.getAwsAccessId(), signatureInfo.getAwsAccessId());
+ if (signatureInfo.getSessionToken() != null &&
+ !signatureInfo.getSessionToken().isEmpty()) {
+ s3Auth.setSessionToken(signatureInfo.getSessionToken());
+ }
LOG.debug("S3 access id: {}", s3Auth.getAccessID());
ClientProtocol clientProtocol =
getClient().getObjectStore().getClientProxy();
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java
index 9abf2fc227d..92c2f102c90 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java
@@ -25,6 +25,7 @@
import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_PAYLOAD_HASH_MAX_VALUE;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -68,6 +69,8 @@ public class AWSSignatureProcessor implements
SignatureProcessor {
private static final AuditLogger AUDIT =
new AuditLogger(AuditLoggerType.S3GLOGGER);
+ private static final String X_AMZ_SECURITY_TOKEN = "x-amz-security-token";
+
@Context
private ContainerRequestContext context;
@@ -103,6 +106,15 @@ public SignatureInfo parseSignature() throws OS3Exception,
IOException, NoSuchAl
if (signatureInfo == null) {
signatureInfo = new
SignatureInfo.Builder(Version.NONE).setService("s3").build();
}
+
+ // Capture STS session token if present (header-based or query-based).
+ // - Header-based SigV4: x-amz-security-token
+ // - Query-based (for presigned URLs): X-Amz-Security-Token
+ final String sessionToken = extractSessionToken(headers);
+ if (sessionToken != null && !sessionToken.isEmpty()) {
+ signatureInfo.setSessionToken(sessionToken);
+ }
+
String payloadHash = getPayloadHash(headers, signatureInfo);
signatureInfo.setPayloadHash(payloadHash);
signatureInfo.setUnfilteredURI(
@@ -110,6 +122,33 @@ public SignatureInfo parseSignature() throws OS3Exception,
IOException, NoSuchAl
return signatureInfo;
}
+ private String extractSessionToken(LowerCaseKeyStringMap headers) {
+ // Header-based token
+ final String headerToken = headers.get(X_AMZ_SECURITY_TOKEN);
+ if (headerToken != null && !headerToken.isEmpty()) {
+ return headerToken;
+ }
+
+ // Query-based token - this would be used for presigned URLs
+ final MultivaluedMap<String, String> queryParams =
context.getUriInfo().getQueryParameters();
+ if (queryParams == null) {
+ return null;
+ }
+ for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
+ final String key = entry.getKey();
+ if (Strings.isNullOrEmpty(key)) {
+ continue;
+ }
+ if (key.compareToIgnoreCase(X_AMZ_SECURITY_TOKEN) == 0) {
+ final List<String> values = entry.getValue();
+ if (values != null && !values.isEmpty()) {
+ return values.get(0);
+ }
+ }
+ }
+ return null;
+ }
+
private String getPayloadHash(Map<String, String> headers, SignatureInfo
signatureInfo)
throws OS3Exception, NoSuchAlgorithmException, IOException {
if (signatureInfo.getVersion() == Version.V2) {
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java
index ffe8a8dddd6..52c7b00ccd0 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java
@@ -59,6 +59,13 @@ public class SignatureInfo {
private String service = null;
+ /**
+ * Optional AWS session token (x-amz-security-token / X-Amz-Security-Token).
+ * <p>
+ * This is required for STS temporary credentials when calling S3 APIs.
+ */
+ private String sessionToken = null;
+
public SignatureInfo() { }
private SignatureInfo(Builder b) {
@@ -78,7 +85,8 @@ public void initialize(SignatureInfo signatureInfo) {
.setUnfilteredURI(signatureInfo.getUnfilteredURI())
.setStringToSign(signatureInfo.getStringToSign())
.setPayloadHash(signatureInfo.getPayloadHash())
- .setService(signatureInfo.getService()));
+ .setService(signatureInfo.getService())
+ .setSessionToken(signatureInfo.getSessionToken()));
}
private void initialize(Builder b) {
@@ -95,6 +103,7 @@ private void initialize(Builder b) {
this.stringToSign = b.stringToSign;
this.payloadHash = b.payloadHash;
this.service = b.service;
+ this.sessionToken = b.sessionToken;
}
public String getAwsAccessId() {
@@ -165,6 +174,14 @@ public void setService(String service) {
this.service = service;
}
+ public String getSessionToken() {
+ return sessionToken;
+ }
+
+ public void setSessionToken(String sessionToken) {
+ this.sessionToken = sessionToken;
+ }
+
/**
* Signature version.
*/
@@ -189,6 +206,7 @@ public static class Builder {
private String stringToSign = null;
private String payloadHash = null;
private String service = null;
+ private String sessionToken = null;
public Builder(Version version) {
this.version = version;
@@ -254,6 +272,11 @@ public Builder setService(String service) {
return this;
}
+ public Builder setSessionToken(String sessionToken) {
+ this.sessionToken = sessionToken;
+ return this;
+ }
+
public SignatureInfo build() {
return new SignatureInfo(this);
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3AssumeRoleResponseXml.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3AssumeRoleResponseXml.java
new file mode 100644
index 00000000000..bd4be9a7eaf
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3AssumeRoleResponseXml.java
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.ozone.s3sts;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * JAXB model for AWS STS AssumeRoleResponse.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "AssumeRoleResponse", namespace =
"https://sts.amazonaws.com/doc/2011-06-15/")
+public class S3AssumeRoleResponseXml {
+
+ @XmlElement(name = "AssumeRoleResult")
+ private AssumeRoleResult assumeRoleResult;
+
+ @XmlElement(name = "ResponseMetadata")
+ private ResponseMetadata responseMetadata;
+
+ public AssumeRoleResult getAssumeRoleResult() {
+ return assumeRoleResult;
+ }
+
+ public void setAssumeRoleResult(AssumeRoleResult assumeRoleResult) {
+ this.assumeRoleResult = assumeRoleResult;
+ }
+
+ public ResponseMetadata getResponseMetadata() {
+ return responseMetadata;
+ }
+
+ public void setResponseMetadata(ResponseMetadata responseMetadata) {
+ this.responseMetadata = responseMetadata;
+ }
+
+ /**
+ * AssumeRoleResult element.
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class AssumeRoleResult {
+ @XmlElement(name = "Credentials")
+ private Credentials credentials;
+
+ @XmlElement(name = "AssumedRoleUser")
+ private AssumedRoleUser assumedRoleUser;
+
+ public Credentials getCredentials() {
+ return credentials;
+ }
+
+ public void setCredentials(Credentials credentials) {
+ this.credentials = credentials;
+ }
+
+ public AssumedRoleUser getAssumedRoleUser() {
+ return assumedRoleUser;
+ }
+
+ public void setAssumedRoleUser(AssumedRoleUser assumedRoleUser) {
+ this.assumedRoleUser = assumedRoleUser;
+ }
+ }
+
+ /**
+ * Credentials element.
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Credentials {
+ @XmlElement(name = "AccessKeyId")
+
+ private String accessKeyId;
+ @XmlElement(name = "SecretAccessKey")
+
+ private String secretAccessKey;
+ @XmlElement(name = "SessionToken")
+
+ private String sessionToken;
+ @XmlElement(name = "Expiration")
+
+ private String expiration;
+
+ public String getAccessKeyId() {
+ return accessKeyId;
+ }
+
+ public void setAccessKeyId(String accessKeyId) {
+ this.accessKeyId = accessKeyId;
+ }
+
+ public String getSecretAccessKey() {
+ return secretAccessKey;
+ }
+
+ public void setSecretAccessKey(String secretAccessKey) {
+ this.secretAccessKey = secretAccessKey;
+ }
+
+ public String getSessionToken() {
+ return sessionToken;
+ }
+
+ public void setSessionToken(String sessionToken) {
+ this.sessionToken = sessionToken;
+ }
+
+ public String getExpiration() {
+ return expiration;
+ }
+
+ public void setExpiration(String expiration) {
+ this.expiration = expiration;
+ }
+ }
+
+ /**
+ * AssumedRoleId element.
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class AssumedRoleUser {
+ @XmlElement(name = "AssumedRoleId")
+ private String assumedRoleId;
+
+ @XmlElement(name = "Arn")
+ private String arn;
+
+ public String getAssumedRoleId() {
+ return assumedRoleId;
+ }
+
+ public void setAssumedRoleId(String assumedRoleId) {
+ this.assumedRoleId = assumedRoleId;
+ }
+
+ public String getArn() {
+ return arn;
+ }
+
+ public void setArn(String arn) {
+ this.arn = arn;
+ }
+ }
+
+ /**
+ * ResponseMetadata element.
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class ResponseMetadata {
+ @XmlElement(name = "RequestId")
+ private String requestId;
+
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+ }
+}
+
+
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSConfigKeys.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSConfigKeys.java
index 1512d3fc3c4..aca0cbd470b 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSConfigKeys.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSConfigKeys.java
@@ -17,13 +17,15 @@
package org.apache.hadoop.ozone.s3sts;
+import org.apache.hadoop.ozone.OzoneConfigKeys;
+
/**
* This class contains constants for configuration keys used
* in S3 STS endpoint.
*/
public final class S3STSConfigKeys {
public static final String OZONE_S3G_STS_HTTP_ENABLED_KEY =
- "ozone.s3g.sts.http.enabled";
+ OzoneConfigKeys.OZONE_S3G_STS_HTTP_ENABLED_KEY;
public static final String OZONE_S3G_STS_HTTP_BIND_HOST_KEY =
"ozone.s3g.sts.http-bind-host";
public static final String OZONE_S3G_STS_HTTPS_BIND_HOST_KEY =
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 124581c6f26..e33e9f80552 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
@@ -17,11 +17,12 @@
package org.apache.hadoop.ozone.s3sts;
+import com.google.common.base.Strings;
import java.io.IOException;
+import java.io.StringWriter;
import java.time.Instant;
+import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
-import java.util.Base64;
-import java.util.Random;
import java.util.UUID;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
@@ -31,6 +32,10 @@
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,9 +70,11 @@ public class S3STSEndpoint extends S3STSEndpointBase {
private static final String GET_ACCESS_KEY_INFO_ACTION = "GetAccessKeyInfo";
// Default token duration (in seconds) - AWS default is 3600 (1 hour)
+ // TODO - add these constants and also validations in a common place that
both endpoint and backend can use
private static final int DEFAULT_DURATION_SECONDS = 3600;
private static final int MAX_DURATION_SECONDS = 43200; // 12 hours
private static final int MIN_DURATION_SECONDS = 900; // 15 minutes
+ private static final int MAX_SESSION_POLICY_SIZE = 2048;
/**
* STS endpoint that handles GET requests with query parameters.
@@ -87,9 +94,10 @@ public Response get(
@QueryParam("RoleArn") String roleArn,
@QueryParam("RoleSessionName") String roleSessionName,
@QueryParam("DurationSeconds") Integer durationSeconds,
- @QueryParam("Version") String version) throws OS3Exception {
+ @QueryParam("Version") String version,
+ @QueryParam("Policy") String awsIamSessionPolicy) throws OS3Exception {
- return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version);
+ return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version, awsIamSessionPolicy);
}
/**
@@ -110,13 +118,14 @@ public Response post(
@FormParam("RoleArn") String roleArn,
@FormParam("RoleSessionName") String roleSessionName,
@FormParam("DurationSeconds") Integer durationSeconds,
- @FormParam("Version") String version) throws OS3Exception {
+ @FormParam("Version") String version,
+ @FormParam("Policy") String awsIamSessionPolicy) throws OS3Exception {
- return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version);
+ return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version, awsIamSessionPolicy);
}
private Response handleSTSRequest(String action, String roleArn, String
roleSessionName,
- Integer durationSeconds, String version) throws OS3Exception {
+ Integer durationSeconds, String version, String awsIamSessionPolicy)
throws OS3Exception {
try {
if (action == null) {
return Response.status(Response.Status.BAD_REQUEST)
@@ -140,7 +149,7 @@ private Response handleSTSRequest(String action, String
roleArn, String roleSess
switch (action) {
case ASSUME_ROLE_ACTION:
- return handleAssumeRole(roleArn, roleSessionName, duration);
+ return handleAssumeRole(roleArn, roleSessionName, duration,
awsIamSessionPolicy);
// These operations are not supported yet
case GET_SESSION_TOKEN_ACTION:
case ASSUME_ROLE_WITH_SAML_ACTION:
@@ -180,9 +189,9 @@ private int validateDuration(Integer durationSeconds)
throws IllegalArgumentExce
return durationSeconds;
}
- private Response handleAssumeRole(String roleArn, String roleSessionName,
int duration)
+ private Response handleAssumeRole(String roleArn, String roleSessionName,
int duration, String awsIamSessionPolicy)
throws IOException, OS3Exception {
- // Validate required parameters for AssumeRole. RoleArn is required to
pass the
+ // Validate required parameters for AssumeRole. RoleArn is required
if (roleArn == null || roleArn.isEmpty()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Missing required parameter: " + ROLE_ARN_PARAM)
@@ -203,11 +212,27 @@ private Response handleAssumeRole(String roleArn, String
roleSessionName, int du
.build();
}
- // TODO: Integrate with Ozone Manager to get actual temporary credentials
- // String dummyCredentials =
getClient().getObjectStore().getS3StsToken(userNameFromRequest());
- // Generate AssumeRole response
- String responseXml = generateAssumeRoleResponse(roleArn, roleSessionName,
duration);
+ // Check Policy size if available
+ if (awsIamSessionPolicy != null && awsIamSessionPolicy.length() >
MAX_SESSION_POLICY_SIZE) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Policy length exceeded maximum allowed length of " +
MAX_SESSION_POLICY_SIZE)
+ .build();
+ }
+
+ final String assumedRoleUserArn;
+ try {
+ assumedRoleUserArn = toAssumedRoleUserArn(roleArn, roleSessionName);
+ } catch (IllegalArgumentException e) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(e.getMessage())
+ .build();
+ }
+ final AssumeRoleResponseInfo responseInfo = getClient()
+ .getObjectStore()
+ .assumeRole(roleArn, roleSessionName, duration, awsIamSessionPolicy);
+ // Generate AssumeRole response
+ final String responseXml = generateAssumeRoleResponse(assumedRoleUserArn,
responseInfo);
return Response.ok(responseXml)
.header("Content-Type", "text/xml")
.build();
@@ -222,78 +247,75 @@ private boolean isValidRoleSessionName(String
roleSessionName) {
return roleSessionName.matches("[a-zA-Z0-9+=,.@\\-]+");
}
- // TODO: replace mock implementation with actual logic to generate new
credentials
- private String generateAssumeRoleResponse(String roleArn, String
roleSessionName, int duration) {
- // Generate realistic-looking temporary credentials
- String accessKeyId = "ASIA" + generateRandomAlphanumeric(16); // AWS temp
keys start with ASIA
- String secretAccessKey = generateRandomBase64(40);
- String sessionToken = generateSessionToken();
- String expiration = getExpirationTime(duration);
-
- // Generate AssumedRoleId (format: AROLEID:RoleSessionName)
- String roleId = "AROA" + generateRandomAlphanumeric(16);
- String assumedRoleId = roleId + ":" + roleSessionName;
-
- String requestId = UUID.randomUUID().toString();
-
- return String.format(
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n" +
- "<AssumeRoleResponse
xmlns=\"https://sts.amazonaws.com/doc/2011-06-15/\">%n" +
- " <AssumeRoleResult>%n" +
- " <Credentials>%n" +
- " <AccessKeyId>%s</AccessKeyId>%n" +
- " <SecretAccessKey>%s</SecretAccessKey>%n" +
- " <SessionToken>%s</SessionToken>%n" +
- " <Expiration>%s</Expiration>%n" +
- " </Credentials>%n" +
- " <AssumedRoleUser>%n" +
- " <AssumedRoleId>%s</AssumedRoleId>%n" +
- " <Arn>%s</Arn>%n" +
- " </AssumedRoleUser>%n" +
- " </AssumeRoleResult>%n" +
- " <ResponseMetadata>%n" +
- " <RequestId>%s</RequestId>%n" +
- " </ResponseMetadata>%n" +
- "</AssumeRoleResponse>",
- accessKeyId, secretAccessKey, sessionToken, expiration,
- assumedRoleId, roleArn, requestId);
- }
+ private String generateAssumeRoleResponse(String assumedRoleUserArn,
AssumeRoleResponseInfo responseInfo)
+ throws IOException {
+ final String accessKeyId = responseInfo.getAccessKeyId();
+ final String secretAccessKey = responseInfo.getSecretAccessKey();
+ final String sessionToken = responseInfo.getSessionToken();
+ final String assumedRoleId = responseInfo.getAssumedRoleId();
+
+ final String expiration = DateTimeFormatter.ISO_INSTANT.format(
+
Instant.ofEpochSecond(responseInfo.getExpirationEpochSeconds()).atOffset(ZoneOffset.UTC).toInstant());
- // TODO: this method should be removed once actual credential response from
OM is implemented and used in the endpoint
- private String generateRandomAlphanumeric(int length) {
- String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- StringBuilder sb = new StringBuilder();
- Random random = new Random();
- for (int i = 0; i < length; i++) {
- sb.append(chars.charAt(random.nextInt(chars.length())));
+ final String requestId = UUID.randomUUID().toString();
+
+ try {
+ final S3AssumeRoleResponseXml response = new S3AssumeRoleResponseXml();
+ final S3AssumeRoleResponseXml.AssumeRoleResult result = new
S3AssumeRoleResponseXml.AssumeRoleResult();
+ final S3AssumeRoleResponseXml.Credentials credentials = new
S3AssumeRoleResponseXml.Credentials();
+ credentials.setAccessKeyId(accessKeyId);
+ credentials.setSecretAccessKey(secretAccessKey);
+ credentials.setSessionToken(sessionToken);
+ credentials.setExpiration(expiration);
+ result.setCredentials(credentials);
+ final S3AssumeRoleResponseXml.AssumedRoleUser user = new
S3AssumeRoleResponseXml.AssumedRoleUser();
+ user.setAssumedRoleId(assumedRoleId);
+ user.setArn(assumedRoleUserArn);
+ result.setAssumedRoleUser(user);
+ response.setAssumeRoleResult(result);
+ final S3AssumeRoleResponseXml.ResponseMetadata meta = new
S3AssumeRoleResponseXml.ResponseMetadata();
+ meta.setRequestId(requestId);
+ response.setResponseMetadata(meta);
+
+ final JAXBContext jaxbContext =
JAXBContext.newInstance(S3AssumeRoleResponseXml.class);
+ final Marshaller marshaller = jaxbContext.createMarshaller();
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+ final StringWriter stringWriter = new StringWriter();
+ marshaller.marshal(response, stringWriter);
+ return stringWriter.toString();
+ } catch (JAXBException e) {
+ throw new IOException("Failed to marshal AssumeRole response", e);
}
- return sb.toString();
}
- // TODO: this method should be removed once actual credential response from
OM is implemented and used in the endpoint
- private String generateRandomBase64(int length) {
- String chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- StringBuilder sb = new StringBuilder();
- Random random = new Random();
- for (int i = 0; i < length; i++) {
- sb.append(chars.charAt((random.nextInt(chars.length()))));
+ private String toAssumedRoleUserArn(String roleArn, String roleSessionName) {
+ // RoleArn format: arn:aws:iam::<account-id>:role/<role-name>
+ // Assumed role user arn format:
arn:aws:sts::<account-id>:assumed-role/<role-name>/<role-session-name>
+ // TODO - refactor and reuse AwsRoleArnValidator for validation in future
PR
+ final String errMsg = "Invalid RoleArn: must be in the format
arn:aws:iam::<account-id>:role/<role-name>";
+ final String[] parts = roleArn.split(":", 6);
+ if (parts.length != 6 || !"arn".equals(parts[0]) || parts[1].isEmpty() ||
!"iam".equals(parts[2])) {
+ throw new IllegalArgumentException(errMsg);
}
- return sb.toString();
- }
- // TODO: this method should be removed once actual credential response from
OM is implemented and used in the endpoint
- private String generateSessionToken() {
- byte[] tokenBytes = new byte[128];
- Random random = new Random();
- for (int i = 0; i < tokenBytes.length; i++) {
- tokenBytes[i] = (byte) random.nextInt(256);
+ final String partition = parts[1];
+ final String accountId = parts[4];
+ final String resource = parts[5]; // role/<name>
+
+ if (Strings.isNullOrEmpty(accountId) || Strings.isNullOrEmpty(resource) ||
!resource.startsWith("role/") ||
+ resource.length() == "role/".length()) {
+ throw new IllegalArgumentException(errMsg);
}
- return Base64.getEncoder().encodeToString(tokenBytes);
- }
- // TODO: this method should be removed once actual credential response from
OM is implemented and used in the endpoint
- private String getExpirationTime(int durationSeconds) {
- Instant expiration = Instant.now().plusSeconds(durationSeconds);
- return DateTimeFormatter.ISO_INSTANT.format(expiration);
+ final String roleName = resource.substring("role/".length());
+ final StringBuilder stringBuilder = new StringBuilder("arn:");
+ stringBuilder.append(partition);
+ stringBuilder.append(":sts::");
+ stringBuilder.append(accountId);
+ stringBuilder.append(":assumed-role/");
+ stringBuilder.append(roleName);
+ stringBuilder.append('/');
+ stringBuilder.append(roleSessionName);
+ return stringBuilder.toString();
}
}
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 ef753410f94..0de5e6c1374 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
@@ -56,8 +56,13 @@ public void initialization() {
S3Auth s3Auth = new S3Auth(signatureInfo.getStringToSign(),
signatureInfo.getSignature(),
signatureInfo.getAwsAccessId(), signatureInfo.getAwsAccessId());
+ if (signatureInfo.getSessionToken() != null &&
+ !signatureInfo.getSessionToken().isEmpty()) {
+ s3Auth.setSessionToken(signatureInfo.getSessionToken());
+ }
ClientProtocol clientProtocol =
getClient().getObjectStore().getClientProxy();
clientProtocol.setThreadLocalS3Auth(s3Auth);
+ clientProtocol.setIsS3Request(true);
}
private AuditMessage.Builder auditMessageBaseBuilder(AuditAction op,
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestSTS.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
similarity index 51%
rename from
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestSTS.java
rename to
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
index 7696bd4d3ed..a78c2c394e5 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestSTS.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
@@ -21,25 +21,42 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import java.io.StringReader;
+import java.time.Instant;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Response;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.commons.lang3.RandomStringUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.client.ObjectStore;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
import org.apache.hadoop.ozone.s3.OzoneConfigurationHolder;
import org.apache.hadoop.ozone.s3.signature.SignatureInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
/**
* Test for S3 STS endpoint.
*/
-public class TestSTS {
+public class TestS3STSEndpoint {
private S3STSEndpoint endpoint;
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;
@Mock
private ContainerRequestContext context;
@@ -49,7 +66,19 @@ public void setup() throws Exception {
OzoneConfiguration config = new OzoneConfiguration();
config.set(OZONE_S3_ADMINISTRATORS, "test-user");
OzoneConfigurationHolder.setConfiguration(config);
- OzoneClient clientStub = new OzoneClientStub();
+ OzoneClient clientStub = spy(new OzoneClientStub());
+
+ // Stub assumeRole to return deterministic credentials.
+ ObjectStore objectStore = mock(ObjectStore.class);
+ when(objectStore.assumeRole(anyString(), anyString(), anyInt(), any()))
+ .thenReturn(new AssumeRoleResponseInfo(
+ "ASIA1234567890123456",
+ "mySecretAccessKey",
+ "session-token",
+ Instant.now().plusSeconds(3600).getEpochSecond(),
+ "AROA1234567890123456:test-session"));
+ when(clientStub.getObjectStore()).thenReturn(objectStore);
+
endpoint = new S3STSEndpoint();
endpoint.setClient(clientStub);
endpoint.setContext(context);
@@ -64,24 +93,39 @@ public void setup() throws Exception {
@Test
public void testStsAssumeRole() throws Exception {
Response response = endpoint.get(
- "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15");
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15", null);
assertEquals(200, response.getStatus());
String responseXml = (String) response.getEntity();
assertNotNull(responseXml);
- assertTrue(responseXml.contains("AssumeRoleResponse"));
- assertTrue(responseXml.contains("AccessKeyId"));
- assertTrue(responseXml.contains("SecretAccessKey"));
- assertTrue(responseXml.contains("SessionToken"));
- assertTrue(responseXml.contains("AssumedRoleUser"));
- assertTrue(responseXml.contains(ROLE_ARN));
+
+ // Parse response XML and verify values
+ final DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ final DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
+ final Document doc = documentBuilder.parse(new InputSource(new
StringReader(responseXml)));
+
+ final Element root = doc.getDocumentElement();
+ assertEquals("AssumeRoleResponse", root.getLocalName());
+
+ final String accessKeyId =
doc.getElementsByTagName("AccessKeyId").item(0).getTextContent();
+ assertEquals("ASIA1234567890123456", accessKeyId);
+
+ final String secretAccessKey =
doc.getElementsByTagName("SecretAccessKey").item(0).getTextContent();
+ assertEquals("mySecretAccessKey", secretAccessKey);
+
+ final String sessionToken =
doc.getElementsByTagName("SessionToken").item(0).getTextContent();
+ assertEquals("session-token", sessionToken);
+
+ final String arn =
doc.getElementsByTagName("Arn").item(0).getTextContent();
+ assertEquals(ROLE_USER_ARN, arn);
}
@Test
public void testStsInvalidDuration() throws Exception {
Response response = endpoint.get(
- "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, -1, "2011-06-15");
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, -1, "2011-06-15", null);
assertEquals(400, response.getStatus());
String errorMessage = (String) response.getEntity();
@@ -91,7 +135,7 @@ public void testStsInvalidDuration() throws Exception {
@Test
public void testStsUnsupportedAction() throws Exception {
Response response = endpoint.get(
- "UnsupportedAction", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15");
+ "UnsupportedAction", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15",
null);
assertEquals(400, response.getStatus());
String errorMessage = (String) response.getEntity();
@@ -101,10 +145,34 @@ public void testStsUnsupportedAction() throws Exception {
@Test
public void testStsInvalidVersion() throws Exception {
Response response = endpoint.get(
- "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2000-01-01");
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2000-01-01", null);
assertEquals(400, response.getStatus());
String errorMessage = (String) response.getEntity();
assertTrue(errorMessage.contains("Invalid or missing Version parameter.
Supported version is 2011-06-15."));
}
+
+ @Test
+ public void testStsPolicyTooLarge() throws Exception {
+ final String tooLargePolicy =
RandomStringUtils.insecure().nextAlphanumeric(2049);
+
+ final Response response = endpoint.get(
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15",
tooLargePolicy);
+
+ assertEquals(400, response.getStatus());
+ final String errorMessage = (String) response.getEntity();
+ assertTrue(errorMessage.contains("Policy length exceeded maximum allowed
length of 2048"));
+ }
+
+ @Test
+ public void testStsInvalidRoleArn() throws Exception {
+ final String invalidRoleArn =
"arn:awsNotValid::123456789012:role/test-role";
+ final Response response = endpoint.get(
+ "AssumeRole", invalidRoleArn, ROLE_SESSION_NAME, 3600, "2011-06-15",
null);
+
+ assertEquals(400, response.getStatus());
+ final String errorMessage = (String) response.getEntity();
+ assertTrue(
+ errorMessage.contains("Invalid RoleArn: must be in the format
arn:aws:iam::<account-id>:role/<role-name>"));
+ }
}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/package-info.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/package-info.java
new file mode 100644
index 00000000000..27318a15520
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/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 for the s3 sts endpoint.
+ */
+package org.apache.hadoop.ozone.s3sts;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]