This is an automated email from the ASF dual-hosted git repository.
pifta 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 bf1453d2ec4 HDDS-13345. STS port and endpoint skeleton (#9343)
bf1453d2ec4 is described below
commit bf1453d2ec48525556258e2781713a6691eda357
Author: len548 <[email protected]>
AuthorDate: Mon Jan 19 16:02:41 2026 +0100
HDDS-13345. STS port and endpoint skeleton (#9343)
---
.../common/src/main/resources/ozone-default.xml | 48 ++++
.../src/main/compose/ozonesecure/docker-config | 1 +
.../hadoop/ozone/TestOzoneConfigurationFields.java | 2 +
.../java/org/apache/hadoop/ozone/s3/Gateway.java | 4 +
.../apache/hadoop/ozone/s3/S3STSHttpServer.java | 105 ++++++++
.../hadoop/ozone/s3/exception/S3ErrorTable.java | 4 +
.../ozone/s3/signature/AWSSignatureProcessor.java | 75 +++++-
.../s3/signature/AuthorizationV4HeaderParser.java | 1 +
.../s3/signature/AuthorizationV4QueryParser.java | 2 +
.../hadoop/ozone/s3/signature/SignatureInfo.java | 38 ++-
.../ozone/s3/signature/SignatureProcessor.java | 4 +-
.../ozone/s3/signature/StringToSignProducer.java | 50 +---
.../Application.java} | 29 +-
.../apache/hadoop/ozone/s3sts/S3STSConfigKeys.java | 51 ++++
.../S3STSEnabled.java} | 32 +--
.../s3sts/S3STSEnabledEndpointRequestFilter.java | 53 ++++
.../apache/hadoop/ozone/s3sts/S3STSEndpoint.java | 299 +++++++++++++++++++++
.../hadoop/ozone/s3sts/S3STSEndpointBase.java | 109 ++++++++
.../package-info.java} | 26 +-
.../main/resources/webapps/s3g-sts/WEB-INF/web.xml | 33 +++
.../hadoop/ozone/s3/TestAuthorizationFilter.java | 26 ++
.../s3/signature/TestStringToSignProducer.java | 1 +
.../org/apache/hadoop/ozone/s3sts/TestSTS.java | 110 ++++++++
23 files changed, 992 insertions(+), 111 deletions(-)
diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml
b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index f3ea84abb4a..630af4d397b 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -2022,6 +2022,54 @@
will be used for http authentication.
</description>
</property>
+
+ <property>
+ <name>ozone.s3g.sts.http.enabled</name>
+ <value>false</value>
+ <tag>OZONE, S3GATEWAY</tag>
+ <description>
+ The boolean which enables the Ozone S3Gateway STS endpoint.
+ </description>
+ </property>
+ <property>
+ <name>ozone.s3g.sts.http-bind-host</name>
+ <value>0.0.0.0</value>
+ <tag>OZONE, S3GATEWAY</tag>
+ <description>
+ The bind host for the S3 Gateway STS HTTP server.
+ If this optional address is set, it overrides only the hostname portion
of
+ ozone.s3g.sts.http-address.
+ If not set, the value of ozone.s3g.http-bind-host is used.
+ </description>
+ </property>
+ <property>
+ <name>ozone.s3g.sts.http-address</name>
+ <value>0.0.0.0:9880</value>
+ <tag>OZONE, S3GATEWAY</tag>
+ <description>
+ The HTTP address for the S3 Gateway STS endpoint.
+ </description>
+ </property>
+ <property>
+ <name>ozone.s3g.sts.https-bind-host</name>
+ <value>0.0.0.0</value>
+ <tag>OZONE, S3GATEWAY</tag>
+ <description>
+ The bind host for the S3 Gateway STS HTTPS server.
+ If this optional address is set, it overrides only the hostname portion
of
+ ozone.s3g.sts.http-address.
+ If not set, the value of ozone.s3g.https-bind-host is used.
+ </description>
+ </property>
+ <property>
+ <name>ozone.s3g.sts.https-address</name>
+ <value>0.0.0.0:9881</value>
+ <tag>OZONE, S3GATEWAY</tag>
+ <description>
+ The HTTPS address for the S3 Gateway STS endpoint.
+ </description>
+ </property>
+
<property>
<name>ozone.s3g.metrics.percentiles.intervals.seconds</name>
<value>60</value>
diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config
b/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config
index 5daf6c11fc9..3d5ebba9223 100644
--- a/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config
+++ b/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config
@@ -93,6 +93,7 @@
OZONE-SITE.XML_hdds.datanode.kerberos.keytab.file=/etc/security/keytabs/dn.keyta
OZONE-SITE.XML_ozone.security.http.kerberos.enabled=true
OZONE-SITE.XML_ozone.s3g.secret.http.enabled=true
+OZONE-SITE.XML_ozone.s3g.sts.http.enabled=true
OZONE-SITE.XML_ozone.http.filter.initializers=org.apache.hadoop.security.AuthenticationFilterInitializer
OZONE-SITE.XML_ozone.om.http.auth.type=kerberos
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java
index 98ccd8fac8b..edb1484fd0f 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/TestOzoneConfigurationFields.java
@@ -30,6 +30,7 @@
import org.apache.hadoop.ozone.recon.ReconServerConfigKeys;
import org.apache.hadoop.ozone.s3.S3GatewayConfigKeys;
import org.apache.hadoop.ozone.s3secret.S3SecretConfigKeys;
+import org.apache.hadoop.ozone.s3sts.S3STSConfigKeys;
/**
* Tests if configuration constants documented in ozone-defaults.xml.
@@ -45,6 +46,7 @@ public void initializeMemberVariables() {
ReconConfigKeys.class, ReconServerConfigKeys.class,
S3GatewayConfigKeys.class,
S3SecretConfigKeys.class,
+ S3STSConfigKeys.class,
SCMHTTPServerConfig.class,
SCMHTTPServerConfig.ConfigStrings.class,
ScmConfig.ConfigStrings.class
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/Gateway.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/Gateway.java
index 9126d8ed4a2..1f28dbf39bd 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/Gateway.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/Gateway.java
@@ -60,6 +60,7 @@ public class Gateway extends GenericCli implements
Callable<Void> {
private S3GatewayHttpServer httpServer;
/** Servlets and static content on separate port. */
private BaseHttpServer contentServer;
+ private BaseHttpServer stsServer;
private S3GatewayMetrics metrics;
private final JvmPauseMonitor jvmPauseMonitor = newJvmPauseMonitor("S3G");
@@ -80,6 +81,7 @@ public Void call() throws Exception {
setHttpBaseDir(OzoneConfigurationHolder.configuration());
httpServer = new
S3GatewayHttpServer(OzoneConfigurationHolder.configuration(), "s3gateway");
contentServer = new
S3GatewayWebAdminServer(OzoneConfigurationHolder.configuration(), "s3g-web");
+ stsServer = new S3STSHttpServer(OzoneConfigurationHolder.configuration(),
"s3g-sts");
metrics =
S3GatewayMetrics.create(OzoneConfigurationHolder.configuration());
start();
@@ -104,12 +106,14 @@ public void start() throws IOException {
jvmPauseMonitor.start();
httpServer.start();
contentServer.start();
+ stsServer.start();
}
public void stop() throws Exception {
LOG.info("Stopping Ozone S3 gateway");
httpServer.stop();
contentServer.stop();
+ stsServer.stop();
jvmPauseMonitor.stop();
S3GatewayMetrics.unRegister();
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3STSHttpServer.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3STSHttpServer.java
new file mode 100644
index 00000000000..f100e408942
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3STSHttpServer.java
@@ -0,0 +1,105 @@
+/*
+ * 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.s3;
+
+import static
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_HTTP_AUTH_CONFIG_PREFIX;
+import static
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_HTTP_AUTH_TYPE;
+import static
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_HTTP_BIND_HOST_DEFAULT;
+import static
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_KEYTAB_FILE;
+import static
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTPS_ADDRESS_KEY;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTPS_BIND_HOST_KEY;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTPS_BIND_PORT_DEFAULT;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTP_ADDRESS_KEY;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTP_BIND_HOST_KEY;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTP_BIND_PORT_DEFAULT;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTP_ENABLED_KEY;
+
+import java.io.IOException;
+import org.apache.hadoop.hdds.conf.MutableConfigurationSource;
+import org.apache.hadoop.hdds.server.http.BaseHttpServer;
+
+/**
+ * HTTP server for the S3 Gateway STS endpoint.
+ */
+public class S3STSHttpServer extends BaseHttpServer {
+
+ S3STSHttpServer(MutableConfigurationSource conf, String name) throws
IOException {
+ super(conf, name);
+ }
+
+ @Override
+ protected String getHttpAddressKey() {
+ return OZONE_S3G_STS_HTTP_ADDRESS_KEY;
+ }
+
+ @Override
+ protected String getHttpBindHostKey() {
+ return OZONE_S3G_STS_HTTP_BIND_HOST_KEY;
+ }
+
+ @Override
+ protected String getHttpsAddressKey() {
+ return OZONE_S3G_STS_HTTPS_ADDRESS_KEY;
+ }
+
+ @Override
+ protected String getHttpsBindHostKey() {
+ return OZONE_S3G_STS_HTTPS_BIND_HOST_KEY;
+ }
+
+ @Override
+ protected String getBindHostDefault() {
+ return OZONE_S3G_HTTP_BIND_HOST_DEFAULT;
+ }
+
+ @Override
+ protected int getHttpBindPortDefault() {
+ return OZONE_S3G_STS_HTTP_BIND_PORT_DEFAULT;
+ }
+
+ @Override
+ protected int getHttpsBindPortDefault() {
+ return OZONE_S3G_STS_HTTPS_BIND_PORT_DEFAULT;
+ }
+
+ @Override
+ protected String getKeytabFile() {
+ return OZONE_S3G_KEYTAB_FILE;
+ }
+
+ @Override
+ protected String getSpnegoPrincipal() {
+ return OZONE_S3G_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL;
+ }
+
+ @Override
+ protected String getEnabledKey() {
+ return OZONE_S3G_STS_HTTP_ENABLED_KEY;
+ }
+
+ @Override
+ protected String getHttpAuthType() {
+ return OZONE_S3G_HTTP_AUTH_TYPE;
+ }
+
+ @Override
+ protected String getHttpAuthConfigPrefix() {
+ return OZONE_S3G_HTTP_AUTH_CONFIG_PREFIX;
+ }
+}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
index 060ed83d1bc..3c9ecdb768a 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
@@ -160,6 +160,10 @@ public final class S3ErrorTable {
"Access Denied", "User doesn't have permission to access this resource
due to a " +
"bucket ownership mismatch.", HTTP_FORBIDDEN);
+ public static final OS3Exception PAYLOAD_TOO_LARGE = new OS3Exception(
+ "PayloadTooLarge", "Your request body size was too large.",
HTTP_BAD_REQUEST
+ );
+
private static Function<Exception, OS3Exception> generateInternalError =
e -> new OS3Exception("InternalError", e.getMessage(),
HTTP_INTERNAL_ERROR);
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 7d8f2fe04e9..9abf2fc227d 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
@@ -18,8 +18,19 @@
package org.apache.hadoop.ozone.s3.signature;
import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_HEADER;
+import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.PAYLOAD_TOO_LARGE;
+import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.UNSIGNED_PAYLOAD;
+import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_PAYLOAD_HASH_MAX_VALUE;
import com.google.common.annotations.VisibleForTesting;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -39,6 +50,7 @@
import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
import org.apache.hadoop.ozone.s3.signature.SignatureInfo.Version;
import org.apache.hadoop.ozone.s3.util.AuditUtils;
+import org.apache.kerby.util.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,7 +72,7 @@ public class AWSSignatureProcessor implements
SignatureProcessor {
private ContainerRequestContext context;
@Override
- public SignatureInfo parseSignature() throws OS3Exception {
+ public SignatureInfo parseSignature() throws OS3Exception, IOException,
NoSuchAlgorithmException {
LowerCaseKeyStringMap headers =
LowerCaseKeyStringMap.fromHeaderMap(context.getHeaders());
@@ -89,13 +101,57 @@ public SignatureInfo parseSignature() throws OS3Exception {
}
}
if (signatureInfo == null) {
- signatureInfo = new SignatureInfo.Builder(Version.NONE).build();
+ signatureInfo = new
SignatureInfo.Builder(Version.NONE).setService("s3").build();
}
+ String payloadHash = getPayloadHash(headers, signatureInfo);
+ signatureInfo.setPayloadHash(payloadHash);
signatureInfo.setUnfilteredURI(
context.getUriInfo().getRequestUri().getPath());
return signatureInfo;
}
+ private String getPayloadHash(Map<String, String> headers, SignatureInfo
signatureInfo)
+ throws OS3Exception, NoSuchAlgorithmException, IOException {
+ if (signatureInfo.getVersion() == Version.V2) {
+ throw S3_AUTHINFO_CREATION_ERROR;
+ }
+ if (signatureInfo.getService().equals("s3")) {
+ if (!signatureInfo.isSignPayload()) {
+ // According to AWS Signature V4 documentation using Query Parameters
+ //
https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
+ return UNSIGNED_PAYLOAD;
+ }
+ String contentSignatureHeaderValue = headers.get(X_AMZ_CONTENT_SHA256);
+ // According to AWS Signature V4 documentation using Authorization Header
+ //
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ // The x-amz-content-sha256 header is required
+ // for all AWS Signature Version 4 requests using Authorization header.
+ if (contentSignatureHeaderValue == null) {
+ LOG.error("The request must include " + X_AMZ_CONTENT_SHA256
+ + " header for signed payload");
+ throw S3_AUTHINFO_CREATION_ERROR;
+ }
+ // Simply return the header value of x-amz-content-sha256 as the payload
hash
+ // These are the possible cases:
+ // 1. Actual payload checksum for single chunk upload
+ // 2. Unsigned payloads for multiple chunks upload
+ // - UNSIGNED-PAYLOAD
+ // - STREAMING-UNSIGNED-PAYLOAD-TRAILER
+ // 3. Signed payloads for multiple chunks upload
+ // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD
+ // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER
+ // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD
+ // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER
+ return contentSignatureHeaderValue;
+ }
+ // For STS payload hash is calculated over the body
+ InputStream in = context.getEntityStream();
+ byte[] body = readAllBytes(in);
+ String payloadHash =
Hex.encode(MessageDigest.getInstance("SHA-256").digest(body));
+ context.setEntityStream(new ByteArrayInputStream(body));
+ return payloadHash;
+ }
+
private AuditMessage buildAuthFailureMessage(MalformedResourceException e) {
AuditMessage message = new AuditMessage.Builder()
.forOperation(AuthOperation.fromContext(context))
@@ -107,6 +163,21 @@ private AuditMessage
buildAuthFailureMessage(MalformedResourceException e) {
return message;
}
+ private byte[] readAllBytes(InputStream in) throws OS3Exception, IOException
{
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ byte[] chunk = new byte[8192];
+ int totalRead = 0;
+ int n;
+ while ((n = in.read(chunk)) != -1) {
+ if (totalRead + n > OZONE_S3G_STS_PAYLOAD_HASH_MAX_VALUE) {
+ throw PAYLOAD_TOO_LARGE;
+ }
+ buffer.write(chunk, 0, n);
+ totalRead += n;
+ }
+ return buffer.toByteArray();
+ }
+
@VisibleForTesting
public void setContext(ContainerRequestContext context) {
this.context = context;
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java
index b327280d070..ed4ef4b13dd 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java
@@ -90,6 +90,7 @@ public SignatureInfo parseSignature() throws
MalformedResourceException {
.setCredentialScope(credentialObj.createScope())
.setAlgorithm(algorithm)
.setSignPayload(true)
+ .setService(credentialObj.getAwsService())
.build();
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
index a7031127181..04d0d8cc525 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
@@ -91,7 +91,9 @@ public SignatureInfo parseSignature() throws
MalformedResourceException {
.setSignedHeaders(queryParameters.get("X-Amz-SignedHeaders"))
.setCredentialScope(credential.createScope())
.setAlgorithm(queryParameters.get("X-Amz-Algorithm"))
+ .setService(credential.getAwsService())
.setSignPayload(false)
+ .setPayloadHash("UNSIGNED-PAYLOAD")
.build();
}
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 730481bce21..ffe8a8dddd6 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
@@ -55,6 +55,10 @@ public class SignatureInfo {
private String stringToSign = null;
+ private String payloadHash = null;
+
+ private String service = null;
+
public SignatureInfo() { }
private SignatureInfo(Builder b) {
@@ -72,7 +76,9 @@ public void initialize(SignatureInfo signatureInfo) {
.setAlgorithm(signatureInfo.getAlgorithm())
.setSignPayload(signatureInfo.isSignPayload())
.setUnfilteredURI(signatureInfo.getUnfilteredURI())
- .setStringToSign(signatureInfo.getStringToSign()));
+ .setStringToSign(signatureInfo.getStringToSign())
+ .setPayloadHash(signatureInfo.getPayloadHash())
+ .setService(signatureInfo.getService()));
}
private void initialize(Builder b) {
@@ -87,6 +93,8 @@ private void initialize(Builder b) {
this.signPayload = b.signPayload;
this.unfilteredURI = b.unfilteredURI;
this.stringToSign = b.stringToSign;
+ this.payloadHash = b.payloadHash;
+ this.service = b.service;
}
public String getAwsAccessId() {
@@ -141,6 +149,22 @@ public void setStrToSign(String strToSign) {
this.stringToSign = strToSign;
}
+ public String getPayloadHash() {
+ return this.payloadHash;
+ }
+
+ public void setPayloadHash(String payloadHash) {
+ this.payloadHash = payloadHash;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public void setService(String service) {
+ this.service = service;
+ }
+
/**
* Signature version.
*/
@@ -163,6 +187,8 @@ public static class Builder {
private boolean signPayload = true;
private String unfilteredURI = null;
private String stringToSign = null;
+ private String payloadHash = null;
+ private String service = null;
public Builder(Version version) {
this.version = version;
@@ -218,6 +244,16 @@ public Builder setStringToSign(String stringToSign) {
return this;
}
+ public Builder setPayloadHash(String payloadHash) {
+ this.payloadHash = payloadHash;
+ return this;
+ }
+
+ public Builder setService(String service) {
+ this.service = service;
+ return this;
+ }
+
public SignatureInfo build() {
return new SignatureInfo(this);
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
index 32213e41daf..36788b4e4ea 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
@@ -17,6 +17,8 @@
package org.apache.hadoop.ozone.s3.signature;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
import java.time.format.DateTimeFormatter;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
@@ -39,5 +41,5 @@ public interface SignatureProcessor {
/**
* API to return string to sign.
*/
- SignatureInfo parseSignature() throws OS3Exception;
+ SignatureInfo parseSignature() throws OS3Exception, IOException,
NoSuchAlgorithmException;
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
index e2f8d64a4d1..d9500c19632 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java
@@ -19,7 +19,6 @@
import static java.time.temporal.ChronoUnit.SECONDS;
import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR;
-import static org.apache.hadoop.ozone.s3.util.S3Consts.UNSIGNED_PAYLOAD;
import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
import com.google.common.annotations.VisibleForTesting;
@@ -118,7 +117,6 @@ public static String createSignatureBase(
}
strToSign.append(signatureInfo.getDateTime()).append(NEWLINE);
strToSign.append(credentialScope).append(NEWLINE);
-
String canonicalRequest = buildCanonicalRequest(
scheme,
method,
@@ -126,7 +124,7 @@ public static String createSignatureBase(
signatureInfo.getSignedHeaders(),
headers,
queryParams,
- !signatureInfo.isSignPayload());
+ signatureInfo.getPayloadHash());
strToSign.append(hash(canonicalRequest));
if (LOG.isDebugEnabled()) {
LOG.debug("canonicalRequest:[{}]", canonicalRequest);
@@ -160,7 +158,7 @@ public static String buildCanonicalRequest(
String signedHeaders,
Map<String, String> headers,
Map<String, String> queryParams,
- boolean unsignedPayload
+ String payloadHash
) throws OS3Exception {
Iterable<String> parts = split("/", uri);
@@ -179,6 +177,9 @@ public static String buildCanonicalRequest(
canonicalHeaders.append(':');
if (headers.containsKey(header)) {
String headerValue = headers.get(header);
+ if (header.equals("content-type")) {
+ headerValue = headerValue.toLowerCase();
+ }
canonicalHeaders.append(headerValue);
canonicalHeaders.append(NEWLINE);
@@ -197,10 +198,7 @@ public static String buildCanonicalRequest(
}
}
- validateCanonicalHeaders(canonicalHeaders.toString(), headers,
- unsignedPayload);
-
- String payloadHash = getPayloadHash(headers, unsignedPayload);
+ validateCanonicalHeaders(canonicalHeaders.toString(), headers);
return method + NEWLINE
+ canonicalUri + NEWLINE
@@ -210,37 +208,6 @@ public static String buildCanonicalRequest(
+ payloadHash;
}
- private static String getPayloadHash(Map<String, String> headers, boolean
isUsingQueryParameter)
- throws OS3Exception {
- if (isUsingQueryParameter) {
- // According to AWS Signature V4 documentation using Query Parameters
- //
https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
- return UNSIGNED_PAYLOAD;
- }
- String contentSignatureHeaderValue = headers.get(X_AMZ_CONTENT_SHA256);
- // According to AWS Signature V4 documentation using Authorization Header
- //
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
- // The x-amz-content-sha256 header is required
- // for all AWS Signature Version 4 requests using Authorization header.
- if (contentSignatureHeaderValue == null) {
- LOG.error("The request must include " + X_AMZ_CONTENT_SHA256
- + " header for signed payload");
- throw S3_AUTHINFO_CREATION_ERROR;
- }
- // Simply return the header value of x-amz-content-sha256 as the payload
hash
- // These are the possible cases:
- // 1. Actual payload checksum for single chunk upload
- // 2. Unsigned payloads for multiple chunks upload
- // - UNSIGNED-PAYLOAD
- // - STREAMING-UNSIGNED-PAYLOAD-TRAILER
- // 3. Signed payloads for multiple chunks upload
- // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD
- // - STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER
- // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD
- // - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER
- return contentSignatureHeaderValue;
- }
-
/**
* String join that also works with empty strings.
*
@@ -357,9 +324,8 @@ static void validateSignedHeader(
*/
private static void validateCanonicalHeaders(
String canonicalHeaders,
- Map<String, String> headers,
- Boolean unsignedPaylod
- ) throws OS3Exception {
+ Map<String, String> headers)
+ throws OS3Exception {
if (!canonicalHeaders.contains(HOST + ":")) {
LOG.error("The SignedHeaders list must include HTTP Host header");
throw S3_AUTHINFO_CREATION_ERROR;
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/Application.java
similarity index 57%
copy from
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
copy to
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/Application.java
index 32213e41daf..65081d5d47f 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/Application.java
@@ -15,29 +15,16 @@
* limitations under the License.
*/
-package org.apache.hadoop.ozone.s3.signature;
+package org.apache.hadoop.ozone.s3sts;
-import java.time.format.DateTimeFormatter;
-import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.glassfish.jersey.server.ResourceConfig;
/**
- * Parser to request auth parser for http request.
+ * JAX-RS application for the STS endpoint.
*/
-public interface SignatureProcessor {
-
- String CONTENT_TYPE = "content-type";
-
- String CONTENT_MD5 = "content-md5";
-
- String AWS4_SIGNING_ALGORITHM = "AWS4-HMAC-SHA256";
-
- String HOST_HEADER = "Host";
-
- DateTimeFormatter DATE_FORMATTER =
- DateTimeFormatter.ofPattern("yyyyMMdd");
-
- /**
- * API to return string to sign.
- */
- SignatureInfo parseSignature() throws OS3Exception;
+public class Application extends ResourceConfig {
+ public Application() {
+ packages("org.apache.hadoop.ozone.s3sts");
+ register(org.apache.hadoop.ozone.s3.AuthorizationFilter.class);
+ }
}
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
new file mode 100644
index 00000000000..1512d3fc3c4
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSConfigKeys.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * 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";
+ 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 =
+ "ozone.s3g.sts.https-bind-host";
+ public static final String OZONE_S3G_STS_HTTP_ADDRESS_KEY =
+ "ozone.s3g.sts.http-address";
+ public static final String OZONE_S3G_STS_HTTPS_ADDRESS_KEY =
+ "ozone.s3g.sts.https-address";
+ public static final int OZONE_S3G_STS_HTTP_BIND_PORT_DEFAULT = 9880;
+ public static final int OZONE_S3G_STS_HTTPS_BIND_PORT_DEFAULT = 9881;
+ // Max payload default size for STS AssumeRole API calls (32 KB)
+ // as STS AssumeRole has these parameters required in payload:
+ // Action=AssumeRole&RoleArn=...&RoleSessionName=...&DurationSeconds=...
+ // where RoleArn max length is 2048 and max bytes per character in UTF-8
encoding is 12
+ // (2048 * 12 = 24576) + other parameters and overheads, so setting to 32 KB
+ // this limit can be adjusted via configuration if needed.
+ public static final int OZONE_S3G_STS_PAYLOAD_HASH_MAX_VALUE = 32768;
+
+ /**
+ * Never constructed.
+ */
+ private S3STSConfigKeys() {
+
+ }
+}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEnabled.java
similarity index 58%
copy from
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
copy to
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEnabled.java
index 32213e41daf..84712a5fd1f 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEnabled.java
@@ -15,29 +15,21 @@
* limitations under the License.
*/
-package org.apache.hadoop.ozone.s3.signature;
+package org.apache.hadoop.ozone.s3sts;
-import java.time.format.DateTimeFormatter;
-import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.ws.rs.NameBinding;
/**
- * Parser to request auth parser for http request.
+ * Annotation to disable S3 STS Endpoint.
*/
-public interface SignatureProcessor {
-
- String CONTENT_TYPE = "content-type";
-
- String CONTENT_MD5 = "content-md5";
-
- String AWS4_SIGNING_ALGORITHM = "AWS4-HMAC-SHA256";
-
- String HOST_HEADER = "Host";
+@NameBinding
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface S3STSEnabled {
+}
- DateTimeFormatter DATE_FORMATTER =
- DateTimeFormatter.ofPattern("yyyyMMdd");
- /**
- * API to return string to sign.
- */
- SignatureInfo parseSignature() throws OS3Exception;
-}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEnabledEndpointRequestFilter.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEnabledEndpointRequestFilter.java
new file mode 100644
index 00000000000..50157ea75b0
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEnabledEndpointRequestFilter.java
@@ -0,0 +1,53 @@
+/*
+ * 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 static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_HTTP_ENABLED_KEY;
+
+import java.io.IOException;
+import javax.inject.Inject;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+
+/**
+ * Filter that disables all endpoints annotated with {@link S3STSEnabled}.
+ * Condition is based on the value of the configuration key
+ * ozone.s3g.s3sts.http.enabled.
+ */
+@S3STSEnabled
+@Provider
+public class S3STSEnabledEndpointRequestFilter implements
ContainerRequestFilter {
+ @Inject
+ private OzoneConfiguration ozoneConfiguration;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws
IOException {
+ boolean isSTSEnabled = ozoneConfiguration.getBoolean(
+ OZONE_S3G_STS_HTTP_ENABLED_KEY, false);
+ if (!isSTSEnabled) {
+ requestContext.abortWith(Response.status(Response.Status.NOT_IMPLEMENTED)
+ .entity("STS endpoint is disabled.")
+ .type(MediaType.APPLICATION_XML_TYPE)
+ .build());
+ }
+ }
+}
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
new file mode 100644
index 00000000000..124581c6f26
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
@@ -0,0 +1,299 @@
+/*
+ * 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 java.io.IOException;
+import java.time.Instant;
+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;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * AWS STS (Security Token Service) compatible endpoint for Ozone S3 Gateway.
+ * <p>
+ * This endpoint provides temporary security credentials compatible with
+ * AWS STS API, exposed on the port 9880 or 9881.
+ * <p>
+ * Currently supports only AssumeRole operation. Other STS operations will
+ * return appropriate error responses.
+ *
+ * @see <a href="https://docs.aws.amazon.com/STS/latest/APIReference/">AWS STS
API Reference</a>
+ */
+@Path("/")
+@S3STSEnabled
+public class S3STSEndpoint extends S3STSEndpointBase {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(S3STSEndpoint.class);
+
+ // STS API constants
+ private static final String STS_ACTION_PARAM = "Action";
+ private static final String ASSUME_ROLE_ACTION = "AssumeRole";
+ private static final String ROLE_ARN_PARAM = "RoleArn";
+ private static final String ROLE_DURATION_SECONDS_PARAM = "DurationSeconds";
+ private static final String GET_SESSION_TOKEN_ACTION = "GetSessionToken";
+ private static final String ASSUME_ROLE_WITH_SAML_ACTION =
"AssumeRoleWithSAML";
+ private static final String ASSUME_ROLE_WITH_WEB_IDENTITY_ACTION =
"AssumeRoleWithWebIdentity";
+ private static final String GET_CALLER_IDENTITY_ACTION = "GetCallerIdentity";
+ private static final String DECODE_AUTHORIZATION_MESSAGE_ACTION =
"DecodeAuthorizationMessage";
+ private static final String GET_ACCESS_KEY_INFO_ACTION = "GetAccessKeyInfo";
+
+ // Default token duration (in seconds) - AWS default is 3600 (1 hour)
+ 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
+
+ /**
+ * STS endpoint that handles GET requests with query parameters.
+ * AWS STS supports both GET and POST requests.
+ *
+ * @param action The STS action to perform (AssumeRole, GetSessionToken,
etc.)
+ * @param roleArn The ARN of the role to assume (for AssumeRole)
+ * @param roleSessionName Session name for the role (for AssumeRole)
+ * @param durationSeconds Duration of the token validity in seconds
+ * @param version AWS STS API version (should be "2011-06-15")
+ * @return Response containing STS response XML or error
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_XML)
+ public Response get(
+ @QueryParam("Action") String action,
+ @QueryParam("RoleArn") String roleArn,
+ @QueryParam("RoleSessionName") String roleSessionName,
+ @QueryParam("DurationSeconds") Integer durationSeconds,
+ @QueryParam("Version") String version) throws OS3Exception {
+
+ return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version);
+ }
+
+ /**
+ * 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
+ * @return Response containing STS response XML or error
+ */
+ @POST
+ @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) throws OS3Exception {
+
+ return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds,
version);
+ }
+
+ private Response handleSTSRequest(String action, String roleArn, String
roleSessionName,
+ Integer durationSeconds, String version) throws OS3Exception {
+ try {
+ if (action == null) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Missing required parameter: " + STS_ACTION_PARAM)
+ .build();
+ }
+ int duration;
+ try {
+ duration = validateDuration(durationSeconds);
+ } catch (IllegalArgumentException e) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(e.getMessage())
+ .build();
+ }
+
+ if (version == null || !version.equals("2011-06-15")) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Invalid or missing Version parameter. Supported version
is 2011-06-15.")
+ .build();
+ }
+
+ switch (action) {
+ case ASSUME_ROLE_ACTION:
+ return handleAssumeRole(roleArn, roleSessionName, duration);
+ // These operations are not supported yet
+ case GET_SESSION_TOKEN_ACTION:
+ case ASSUME_ROLE_WITH_SAML_ACTION:
+ case ASSUME_ROLE_WITH_WEB_IDENTITY_ACTION:
+ case GET_CALLER_IDENTITY_ACTION:
+ case DECODE_AUTHORIZATION_MESSAGE_ACTION:
+ case GET_ACCESS_KEY_INFO_ACTION:
+ return Response.status(Response.Status.NOT_IMPLEMENTED)
+ .entity("Operation " + action + " is not supported yet.")
+ .build();
+ default:
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Unsupported Action: " + action)
+ .build();
+ }
+ } catch (OS3Exception s3e) {
+ // Handle known S3 exceptions
+ LOG.error("S3 Error during STS request: {}", s3e.toXml());
+ throw s3e;
+ } catch (Exception ex) {
+ LOG.error("Unexpected error during STS request", ex);
+ return Response.serverError().build();
+ }
+ }
+
+ private int validateDuration(Integer durationSeconds) throws
IllegalArgumentException, OS3Exception {
+ if (durationSeconds == null) {
+ return DEFAULT_DURATION_SECONDS;
+ }
+
+ if (durationSeconds < MIN_DURATION_SECONDS || durationSeconds >
MAX_DURATION_SECONDS) {
+ throw new IllegalArgumentException(
+ "Invalid Value: " + ROLE_DURATION_SECONDS_PARAM + " must be between
" + MIN_DURATION_SECONDS +
+ " and " + MAX_DURATION_SECONDS + " seconds");
+ }
+
+ return durationSeconds;
+ }
+
+ private Response handleAssumeRole(String roleArn, String roleSessionName,
int duration)
+ throws IOException, OS3Exception {
+ // Validate required parameters for AssumeRole. RoleArn is required to
pass the
+ if (roleArn == null || roleArn.isEmpty()) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Missing required parameter: " + ROLE_ARN_PARAM)
+ .build();
+ }
+
+ if (roleSessionName == null || roleSessionName.isEmpty()) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Missing required parameter: RoleSessionName")
+ .build();
+ }
+
+ // Validate role session name format (AWS requirements)
+ if (!isValidRoleSessionName(roleSessionName)) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity("Invalid RoleSessionName: must be 2-64 characters long and "
+
+ "contain only alphanumeric characters, +, =, ,, ., @, -")
+ .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);
+
+ return Response.ok(responseXml)
+ .header("Content-Type", "text/xml")
+ .build();
+ }
+
+ private boolean isValidRoleSessionName(String roleSessionName) {
+ if (roleSessionName.length() < 2 || roleSessionName.length() > 64) {
+ return false;
+ }
+
+ // AWS allows: alphanumeric, +, =, ,, ., @, -
+ 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);
+ }
+
+ // 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())));
+ }
+ 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()))));
+ }
+ 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);
+ }
+ 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);
+ }
+}
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
new file mode 100644
index 00000000000..ef753410f94
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.google.common.annotations.VisibleForTesting;
+import java.util.Map;
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Context;
+import org.apache.hadoop.ozone.audit.AuditAction;
+import org.apache.hadoop.ozone.audit.AuditEventStatus;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.audit.AuditLoggerType;
+import org.apache.hadoop.ozone.audit.AuditMessage;
+import org.apache.hadoop.ozone.audit.Auditor;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
+import org.apache.hadoop.ozone.om.protocol.S3Auth;
+import org.apache.hadoop.ozone.s3.signature.SignatureInfo;
+import org.apache.hadoop.ozone.s3.util.AuditUtils;
+
+/**
+ * Base class for STS endpoints.
+ */
+public class S3STSEndpointBase implements Auditor {
+
+ @Context
+ private ContainerRequestContext context;
+
+ @Inject
+ private OzoneClient client;
+ @Inject
+ private SignatureInfo signatureInfo;
+
+ protected static final AuditLogger AUDIT =
+ new AuditLogger(AuditLoggerType.S3GLOGGER);
+
+ @PostConstruct
+ public void initialization() {
+ S3Auth s3Auth = new S3Auth(signatureInfo.getStringToSign(),
+ signatureInfo.getSignature(),
+ signatureInfo.getAwsAccessId(), signatureInfo.getAwsAccessId());
+ ClientProtocol clientProtocol =
getClient().getObjectStore().getClientProxy();
+ clientProtocol.setThreadLocalS3Auth(s3Auth);
+ }
+
+ private AuditMessage.Builder auditMessageBaseBuilder(AuditAction op,
+ Map<String, String> auditMap) {
+ AuditMessage.Builder builder = new AuditMessage.Builder()
+ .forOperation(op)
+ .withParams(auditMap);
+ if (context != null) {
+ builder.atIp(AuditUtils.getClientIpAddress(context));
+ }
+ return builder;
+ }
+
+ @Override
+ public AuditMessage buildAuditMessageForSuccess(AuditAction op,
+ Map<String, String> auditMap) {
+ AuditMessage.Builder builder = auditMessageBaseBuilder(op, auditMap)
+ .withResult(AuditEventStatus.SUCCESS);
+ return builder.build();
+ }
+
+ @Override
+ public AuditMessage buildAuditMessageForFailure(AuditAction op,
+ Map<String, String> auditMap, Throwable throwable) {
+ AuditMessage.Builder builder = auditMessageBaseBuilder(op, auditMap)
+ .withResult(AuditEventStatus.FAILURE)
+ .withException(throwable);
+ return builder.build();
+ }
+
+ public OzoneClient getClient() {
+ return client;
+ }
+
+ @VisibleForTesting
+ public void setClient(OzoneClient ozoneClient) {
+ this.client = ozoneClient;
+ }
+
+ @VisibleForTesting
+ public void setContext(ContainerRequestContext context) {
+ this.context = context;
+ }
+
+ @VisibleForTesting
+ public void setSignatureInfo(SignatureInfo signatureInfo) {
+ this.signatureInfo = signatureInfo;
+ }
+}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/package-info.java
similarity index 57%
copy from
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
copy to
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/package-info.java
index 32213e41daf..76f77800182 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureProcessor.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/package-info.java
@@ -15,29 +15,7 @@
* limitations under the License.
*/
-package org.apache.hadoop.ozone.s3.signature;
-
-import java.time.format.DateTimeFormatter;
-import org.apache.hadoop.ozone.s3.exception.OS3Exception;
-
/**
- * Parser to request auth parser for http request.
+ * This package contains the AWS STS (Security Token Service) compatible API
for S3 Gateway.
*/
-public interface SignatureProcessor {
-
- String CONTENT_TYPE = "content-type";
-
- String CONTENT_MD5 = "content-md5";
-
- String AWS4_SIGNING_ALGORITHM = "AWS4-HMAC-SHA256";
-
- String HOST_HEADER = "Host";
-
- DateTimeFormatter DATE_FORMATTER =
- DateTimeFormatter.ofPattern("yyyyMMdd");
-
- /**
- * API to return string to sign.
- */
- SignatureInfo parseSignature() throws OS3Exception;
-}
+package org.apache.hadoop.ozone.s3sts;
diff --git
a/hadoop-ozone/s3gateway/src/main/resources/webapps/s3g-sts/WEB-INF/web.xml
b/hadoop-ozone/s3gateway/src/main/resources/webapps/s3g-sts/WEB-INF/web.xml
new file mode 100644
index 00000000000..d6dcf626dcc
--- /dev/null
+++ b/hadoop-ozone/s3gateway/src/main/resources/webapps/s3g-sts/WEB-INF/web.xml
@@ -0,0 +1,33 @@
+<!--
+ Licensed 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. See accompanying LICENSE file.
+-->
+<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
+ <servlet>
+ <servlet-name>sts-jaxrs</servlet-name>
+
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.apache.hadoop.ozone.s3sts.Application</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>sts-jaxrs</servlet-name>
+ <url-pattern>/sts/*</url-pattern>
+ </servlet-mapping>
+ <listener>
+
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
+ </listener>
+</web-app>
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java
index 6df57448cad..5171138710e 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java
@@ -20,6 +20,7 @@
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_HEADER;
+import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.PAYLOAD_TOO_LARGE;
import static
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR;
import static
org.apache.hadoop.ozone.s3.signature.AWSSignatureProcessor.DATE_FORMATTER;
import static
org.apache.hadoop.ozone.s3.signature.SignatureParser.AUTHORIZATION_HEADER;
@@ -28,6 +29,7 @@
import static
org.apache.hadoop.ozone.s3.signature.SignatureProcessor.HOST_HEADER;
import static
org.apache.hadoop.ozone.s3.signature.StringToSignProducer.X_AMAZ_DATE;
import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
+import static
org.apache.hadoop.ozone.s3sts.S3STSConfigKeys.OZONE_S3G_STS_PAYLOAD_HASH_MAX_VALUE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.fail;
@@ -35,6 +37,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
@@ -117,6 +121,24 @@ public class TestAuthorizationFilter {
"application/octet-stream",
"/",
S3_AUTHINFO_CREATION_ERROR.getErrorMessage()
+ ),
+ // Too huge payload for signature V4 of STS request
+ arguments(
+ "POST",
+ "AWS4-HMAC-SHA256 Credential=testuser1/" + CURDATE +
+ "/us-east-1/sts/aws4_request, " +
+ "SignedHeaders=content-type;host;" +
+ "x-amz-date, " +
+ "Signature" +
+ "=56ec73ba1974f8feda8365c3caef89c5d4a688d5f9baccf47" +
+ "65f46a14cd745ad",
+ "Content-SHA",
+ "s3g:9880",
+ "Content-SHA",
+ DATETIME,
+ "application/x-www-form-urlencoded; charset=utf-8",
+ "/sts",
+ PAYLOAD_TOO_LARGE.getErrorMessage()
)
);
}
@@ -133,6 +155,10 @@ void testAuthFilterFailures(
ContainerRequestContext context = setupContext(method, authHeader,
contentMd5, host, amzContentSha256, date, contentType, path);
+ byte[] payloadBytes = new byte[OZONE_S3G_STS_PAYLOAD_HASH_MAX_VALUE + 1];
+ InputStream payLoadStream = new ByteArrayInputStream(payloadBytes);
+ when(context.getEntityStream()).thenReturn(payLoadStream);
+
AWSSignatureProcessor awsSignatureProcessor = new
AWSSignatureProcessor();
awsSignatureProcessor.setContext(context);
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java
index cbce030ef69..1d9b89eb23c 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java
@@ -94,6 +94,7 @@ public void validateDateRange(Credential credentialObj) {
//NOOP
}
}.parseSignature();
+ signatureInfo.setPayloadHash("Content-SHA");
signatureInfo.setUnfilteredURI("/buckets");
headers.fixContentType();
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/TestSTS.java
new file mode 100644
index 00000000000..7696bd4d3ed
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestSTS.java
@@ -0,0 +1,110 @@
+/*
+ * 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 static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_S3_ADMINISTRATORS;
+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 javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientStub;
+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;
+
+/**
+ * Test for S3 STS endpoint.
+ */
+public class TestSTS {
+ 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";
+
+ @Mock
+ private ContainerRequestContext context;
+
+ @BeforeEach
+ public void setup() throws Exception {
+ OzoneConfiguration config = new OzoneConfiguration();
+ config.set(OZONE_S3_ADMINISTRATORS, "test-user");
+ OzoneConfigurationHolder.setConfiguration(config);
+ OzoneClient clientStub = new OzoneClientStub();
+ endpoint = new S3STSEndpoint();
+ endpoint.setClient(clientStub);
+ endpoint.setContext(context);
+ SignatureInfo signatureInfo = new
SignatureInfo.Builder(SignatureInfo.Version.V4)
+ .setAwsAccessId("test-user")
+ .setSignature("some-signature")
+ .setStringToSign("dummy-string")
+ .build();
+ endpoint.setSignatureInfo(signatureInfo);
+ }
+
+ @Test
+ public void testStsAssumeRole() throws Exception {
+ Response response = endpoint.get(
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15");
+
+ 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));
+ }
+
+ @Test
+ public void testStsInvalidDuration() throws Exception {
+ Response response = endpoint.get(
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, -1, "2011-06-15");
+
+ assertEquals(400, response.getStatus());
+ String errorMessage = (String) response.getEntity();
+ assertTrue(errorMessage.contains("Invalid Value: DurationSeconds"));
+ }
+
+ @Test
+ public void testStsUnsupportedAction() throws Exception {
+ Response response = endpoint.get(
+ "UnsupportedAction", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15");
+
+ assertEquals(400, response.getStatus());
+ String errorMessage = (String) response.getEntity();
+ assertTrue(errorMessage.contains("Unsupported Action"));
+ }
+
+ @Test
+ public void testStsInvalidVersion() throws Exception {
+ Response response = endpoint.get(
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2000-01-01");
+
+ assertEquals(400, response.getStatus());
+ String errorMessage = (String) response.getEntity();
+ assertTrue(errorMessage.contains("Invalid or missing Version parameter.
Supported version is 2011-06-15."));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]