diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index f0d4e6a75299..4f82ce93dfc2 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -2020,6 +2020,14 @@ will be used for http authentication. + + ozone.s3g.sts.http.enabled + false + OZONE, S3GATEWAY + + The boolean which enables the Ozone S3Gateway STS endpoint. + + ozone.s3g.metrics.percentiles.intervals.seconds 60 diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-compose.yaml b/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-compose.yaml index f3e372964bb7..612e188696ec 100644 --- a/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-compose.yaml +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-compose.yaml @@ -94,6 +94,7 @@ services: - ./krb5.conf:/etc/krb5.conf ports: - 9878:9878 + - 19878:19878 env_file: - ./docker-config command: ["/opt/hadoop/bin/ozone","s3g", "-Dozone.om.transport.class=${OZONE_S3_OM_TRANSPORT:-org.apache.hadoop.ozone.om.protocolPB.GrpcOmTransportFactory}"] diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config b/hadoop-ozone/dist/src/main/compose/ozonesecure/docker-config index 5daf6c11fc9b..822e8f0a393c 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 @@ -100,6 +101,7 @@ OZONE-SITE.XML_hdds.scm.http.auth.type=kerberos OZONE-SITE.XML_hdds.datanode.http.auth.type=kerberos OZONE-SITE.XML_ozone.s3g.http.auth.type=kerberos OZONE-SITE.XML_ozone.s3g.secret.http.auth.type=kerberos +OZONE-SITE.XML_ozone.s3g.sts.http.auth.type=kerberos OZONE-SITE.XML_ozone.httpfs.http.auth.type=kerberos OZONE-SITE.XML_ozone.recon.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 c699a6f6fafc..49a18062966a 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 @@ -29,7 +29,8 @@ import org.apache.hadoop.ozone.om.OMConfigKeys; 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.s3web.s3secret.S3SecretConfigKeys; +import org.apache.hadoop.ozone.s3web.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/S3GatewayWebAdminServer.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayWebAdminServer.java index f87343cfb92b..e3117d593000 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayWebAdminServer.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/S3GatewayWebAdminServer.java @@ -29,10 +29,10 @@ import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_WEBADMIN_HTTP_BIND_PORT_DEFAULT; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_WEBADMIN_HTTP_ENABLED_KEY; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL; -import static org.apache.hadoop.ozone.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_AUTH_TYPE_DEFAULT; -import static org.apache.hadoop.ozone.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_AUTH_TYPE_KEY; -import static org.apache.hadoop.ozone.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_ENABLED_KEY; -import static org.apache.hadoop.ozone.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_ENABLED_KEY_DEFAULT; +import static org.apache.hadoop.ozone.s3web.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_AUTH_TYPE_DEFAULT; +import static org.apache.hadoop.ozone.s3web.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_AUTH_TYPE_KEY; +import static org.apache.hadoop.ozone.s3web.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_ENABLED_KEY; +import static org.apache.hadoop.ozone.s3web.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_ENABLED_KEY_DEFAULT; import static org.apache.hadoop.security.authentication.server.AuthenticationFilter.AUTH_TYPE; import com.google.common.base.Strings; 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 ea460f62d494..136f9771ed95 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 @@ -118,7 +118,6 @@ public static String createSignatureBase( } strToSign.append(signatureInfo.getDateTime()).append(NEWLINE); strToSign.append(credentialScope).append(NEWLINE); - String canonicalRequest = buildCanonicalRequest( scheme, method, diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/Application.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/Application.java similarity index 91% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/Application.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/Application.java index 77ba872e5199..1fcaba763bce 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/Application.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/Application.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web; import org.glassfish.jersey.server.ResourceConfig; @@ -24,6 +24,6 @@ */ public class Application extends ResourceConfig { public Application() { - packages("org.apache.hadoop.ozone.s3secret"); + packages(true, "org.apache.hadoop.ozone.s3web"); } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/package-info.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/package-info.java new file mode 100644 index 000000000000..fc06957acedf --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/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. + */ + +/** + * This package contains the top level generic classes of s3 web gateway. + */ +package org.apache.hadoop.ozone.s3web; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3AdminEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3AdminEndpoint.java similarity index 96% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3AdminEndpoint.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3AdminEndpoint.java index d91b1dea4fcb..278f94d2073b 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3AdminEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3AdminEndpoint.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretAdminFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretAdminFilter.java similarity index 97% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretAdminFilter.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretAdminFilter.java index 0130f31e9dd3..556f41914f11 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretAdminFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretAdminFilter.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import java.io.IOException; import java.security.Principal; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretConfigKeys.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretConfigKeys.java similarity index 96% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretConfigKeys.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretConfigKeys.java index ce1070194144..e3067686fec6 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretConfigKeys.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretConfigKeys.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; /** * This class contains constants for configuration keys used diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEnabled.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEnabled.java similarity index 96% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEnabled.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEnabled.java index c0a5084079f6..35ce0a656c11 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEnabled.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEnabled.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEnabledEndpointRequestFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEnabledEndpointRequestFilter.java similarity index 92% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEnabledEndpointRequestFilter.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEnabledEndpointRequestFilter.java index 96d9f4c54258..465ecc3e1261 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEnabledEndpointRequestFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEnabledEndpointRequestFilter.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; -import static org.apache.hadoop.ozone.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_ENABLED_KEY; +import static org.apache.hadoop.ozone.s3web.s3secret.S3SecretConfigKeys.OZONE_S3G_SECRET_HTTP_ENABLED_KEY; import java.io.IOException; import javax.inject.Inject; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEndpointBase.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEndpointBase.java similarity index 98% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEndpointBase.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEndpointBase.java index 9a1a6af21ea4..d53ee2470b2e 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretEndpointBase.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretEndpointBase.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretManagementEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretManagementEndpoint.java similarity index 98% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretManagementEndpoint.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretManagementEndpoint.java index 648c9d26ed00..f46c34b5761b 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretManagementEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretManagementEndpoint.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import static javax.ws.rs.core.Response.Status.NOT_FOUND; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretResponse.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretResponse.java similarity index 97% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretResponse.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretResponse.java index e3fdb4d6fd74..248d2025bc5e 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretResponse.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/S3SecretResponse.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/package-info.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/package-info.java similarity index 94% rename from hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/package-info.java rename to hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/package-info.java index 438d4aff2b32..a1986a03af42 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/package-info.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3secret/package-info.java @@ -18,4 +18,4 @@ /** * This package contains the top level generic classes of s3 secret gateway. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSConfigKeys.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSConfigKeys.java new file mode 100644 index 000000000000..23ee64b2a22b --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSConfigKeys.java @@ -0,0 +1,34 @@ +/* + * 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.s3web.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"; + + /** + * Never constructed. + */ + private S3STSConfigKeys() { + + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEnabled.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEnabled.java new file mode 100644 index 000000000000..6da6ee4293eb --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEnabled.java @@ -0,0 +1,35 @@ +/* + * 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.s3web.s3sts; + +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; + +/** + * Annotation to disable S3 STS Endpoint. + */ +@NameBinding +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface S3STSEnabled { +} + + diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEnabledEndpointRequestFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEnabledEndpointRequestFilter.java new file mode 100644 index 000000000000..08d29430e526 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEnabledEndpointRequestFilter.java @@ -0,0 +1,64 @@ +/* + * 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.s3web.s3sts; + +import static org.apache.hadoop.ozone.s3web.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) { + String errorMessage = "S3 STS endpoint is disabled."; + String errorCode = "AccessDenied"; + String xmlError = "" + + "" + + "Sender" + + "" + errorCode + "" + + "" + errorMessage + "" + + "" + + "" + requestContext.getHeaderString("x-amz-request-id") + "" + + ""; + + requestContext.abortWith(Response.status(Response.Status.BAD_REQUEST) + .entity(xmlError) + .type(MediaType.APPLICATION_XML_TYPE) + .build()); + } + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEndpoint.java new file mode 100644 index 000000000000..f9d38a521991 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEndpoint.java @@ -0,0 +1,298 @@ +/* + * 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.s3web.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. + *

+ * This endpoint provides temporary security credentials compatible with + * AWS STS API, exposed on the webadmin port (19878) at /sts endpoint. + *

+ * Currently supports only AssumeRole operation. Other STS operations will + * return appropriate error responses. + * + * @see AWS STS API Reference + */ +@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(); + } + + 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: Add a validation if a user is not an admin but still allowed to call AssumeRole + // TODO: Convert roleArn to a valid Ozone ACL + // TODO: Validate requested ACLs + // TODO: Create a new S3 credentials for this role session + // TODO: Add validated ACLs for the new credentials + // TODO: How do we handle expired credentials? We don't support renewal? + + // Generate AssumeRole response + String responseXml = generateAssumeRoleResponse(roleArn, roleSessionName, duration); + + return Response.ok(responseXml) + .header("Content-Type", "text/xml") + .build(); + } + + // TODO: implement private List toOzoneAcls(String roleArn) to convert roleArn to Ozone ACLs + // TODO: implement private List checkAclSubset(List requestedAcls) to validate requested ACLs + + 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( + "%n" + + "%n" + + " %n" + + " %n" + + " %s%n" + + " %s%n" + + " %s%n" + + " %s%n" + + " %n" + + " %n" + + " %s%n" + + " %s%n" + + " %n" + + " %n" + + " %n" + + " %s%n" + + " %n" + + "", + accessKeyId, secretAccessKey, sessionToken, expiration, + assumedRoleId, roleArn, requestId); + } + + // Helper methods to generate random alphanumeric and base64 strings for mock credentials. + // TODO: these should be replaced with actual credential generation logic. + 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(); + } + + 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(); + } + + 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); + } + + 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/s3web/s3sts/S3STSEndpointBase.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEndpointBase.java new file mode 100644 index 000000000000..7e80973e1507 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/S3STSEndpointBase.java @@ -0,0 +1,97 @@ +/* + * 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.s3web.s3sts; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Map; +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.s3.util.AuditUtils; + +/** + * Base class for STS endpoints. + */ +public class S3STSEndpointBase implements Auditor { + + @Context + private ContainerRequestContext context; + + @Inject + private OzoneClient client; + + protected static final AuditLogger AUDIT = + new AuditLogger(AuditLoggerType.S3GLOGGER); + + protected String userNameFromRequest() { + return context.getSecurityContext().getUserPrincipal().getName(); + } + + private AuditMessage.Builder auditMessageBaseBuilder(AuditAction op, + Map 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 auditMap) { + AuditMessage.Builder builder = auditMessageBaseBuilder(op, auditMap) + .withResult(AuditEventStatus.SUCCESS); + return builder.build(); + } + + @Override + public AuditMessage buildAuditMessageForFailure(AuditAction op, + Map 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; + } + + protected Map getAuditParameters() { + return AuditUtils.getAuditParameters(context); + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/package-info.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/s3sts/package-info.java new file mode 100644 index 000000000000..f629c2497866 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3web/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. + */ + +/** + * This package contains the AWS STS (Security Token Service) compatible API for S3 Gateway. + */ +package org.apache.hadoop.ozone.s3web.s3sts; diff --git a/hadoop-ozone/s3gateway/src/main/resources/webapps/s3g-web/WEB-INF/web.xml b/hadoop-ozone/s3gateway/src/main/resources/webapps/s3g-web/WEB-INF/web.xml index 092c8a41af5a..41c547dd9be0 100644 --- a/hadoop-ozone/s3gateway/src/main/resources/webapps/s3g-web/WEB-INF/web.xml +++ b/hadoop-ozone/s3gateway/src/main/resources/webapps/s3g-web/WEB-INF/web.xml @@ -15,18 +15,22 @@ 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"> - secret + web org.glassfish.jersey.servlet.ServletContainer javax.ws.rs.Application - org.apache.hadoop.ozone.s3secret.Application + org.apache.hadoop.ozone.s3web.Application 1 - secret + web /secret/* + + web + /sts/* + org.jboss.weld.environment.servlet.Listener diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretGenerate.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3secret/TestSecretGenerate.java similarity index 99% rename from hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretGenerate.java rename to hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3secret/TestSecretGenerate.java index 72879c45f835..0fcc47f1fc7f 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretGenerate.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3secret/TestSecretGenerate.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretRevoke.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3secret/TestSecretRevoke.java similarity index 98% rename from hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretRevoke.java rename to hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3secret/TestSecretRevoke.java index 500cb7272dd6..aae9ea3fb6a3 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretRevoke.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3secret/TestSecretRevoke.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.hadoop.ozone.s3secret; +package org.apache.hadoop.ozone.s3web.s3secret; import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; import static javax.ws.rs.core.Response.Status.NOT_FOUND; diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3sts/TestSTS.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3sts/TestSTS.java new file mode 100644 index 000000000000..6fab567f6d0c --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3web/s3sts/TestSTS.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.s3web.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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.HttpHeaders; +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.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +/** + * Test for S3 STS endpoint. + */ +public class TestSTS { + private OzoneClient clientStub; + private S3STSEndpoint endpoint; + private HttpHeaders httpHeaders; + + @Mock + private ContainerRequestContext context; + + @BeforeEach + public void setup() throws Exception { + OzoneConfiguration config = new OzoneConfiguration(); + config.set(OZONE_S3_ADMINISTRATORS, "test-user"); + OzoneConfigurationHolder.setConfiguration(config); + clientStub = new OzoneClientStub(); + httpHeaders = mock(HttpHeaders.class); + when(httpHeaders.getHeaderString("Authorization")) + .thenReturn("AWS4-HMAC-SHA256 Credential=test-user/20240709/us-east-1/s3/aws4_request, " + + "SignedHeaders=host;x-amz-date, Signature=some-signature"); + endpoint = new S3STSEndpoint(); + endpoint.setClient(clientStub); + endpoint.setContext(context); + } + + @Test + public void testStsAssumeRoleInSecureCluster() throws Exception { + String roleArn = "arn:aws:iam::123456789012:role/test-role"; + String roleSessionName = "test-session"; + + Response response = endpoint.get( + "AssumeRole", roleArn, roleSessionName, 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(roleArn)); + } + + @Test + public void testStsInvalidDuration() throws Exception { + String roleArn = "arn:aws:iam::123456789012:role/test-role"; + String roleSessionName = "test-session"; + + Response response = endpoint.get( + "AssumeRole", roleArn, roleSessionName, -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 { + String roleArn = "arn:aws:iam::123456789012:role/test-role"; + String roleSessionName = "test-session"; + + Response response = endpoint.get( + "UnsupportedAction", roleArn, roleSessionName, 3600, "2011-06-15"); + + assertEquals(400, response.getStatus()); + String errorMessage = (String) response.getEntity(); + assertTrue(errorMessage.contains("Unsupported Action")); + } +}