adoroszlai commented on code in PR #10266:
URL: https://github.com/apache/ozone/pull/10266#discussion_r3257044289


##########
hadoop-hdds/common/src/main/resources/ozone-default.xml:
##########
@@ -2142,6 +2142,117 @@
      true, hadoop.security.authentication should be Kerberos.
     </description>
   </property>
+  <property>
+    <name>ozone.sts.web.identity.enabled</name>

Review Comment:
   Please add these new config properties as `@Config` annotations in 
`OidcConfig` instead of XML + constants split between two files.



##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleWithWebIdentityRequest.java:
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.security.acl;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Represents an STS AssumeRoleWithWebIdentity request that has already been
+ * authenticated by validating the web identity token.
+ *
+ * The web identity token itself must not be stored in this object. OM is the
+ * authoritative validator for the token and passes only normalized identity
+ * attributes to the authorizer.
+ */
+@Immutable
+public class AssumeRoleWithWebIdentityRequest {
+
+  public static final String ACTION = "AssumeRoleWithWebIdentity";
+
+  private final String host;
+  private final InetAddress ip;
+  private final String user;
+  private final Set<String> groups;
+  private final Set<String> roles;
+  private final String roleArn;
+  private final String roleSessionName;
+  private final String issuer;
+  private final String subject;
+  private final String audience;
+  private final String providerId;
+  private final Set<AssumeRoleRequest.OzoneGrant> grants;
+
+  @SuppressWarnings("checkstyle:ParameterNumber")
+  public AssumeRoleWithWebIdentityRequest(String host, InetAddress ip,
+      String user, Set<String> groups, Set<String> roles, String roleArn,
+      String roleSessionName, String issuer, String subject, String audience,
+      String providerId, Set<AssumeRoleRequest.OzoneGrant> grants) {

Review Comment:
   Please add a `Builder` class, `newBuilder` method, change the constructor to 
accept only the `Builder` instead of all properties, and make the constructor 
`private`.



##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/oidc/CachingJwksProvider.java:
##########
@@ -0,0 +1,153 @@
+/*
+ * 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.security.oidc;
+
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import java.io.IOException;
+import java.text.ParseException;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Thread-safe JWKS cache with refresh-on-unknown-kid semantics.

Review Comment:
   `common` is for code shared between client and server components.  These 
classes in `org.apache.hadoop.ozone.security.oidc` are not needed in clients, 
please move them to `ozone-manager` (along with additional dependency 
`nimbus-jose-jwt`).



##########
hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto:
##########
@@ -2434,6 +2452,53 @@ message UpdateAssumeRoleRequest {
   required string roleId                  = 8;
 }
 
+message AssumeRoleWithWebIdentityRequest {
+  required string roleArn                 = 1;
+  required string roleSessionName         = 2;

Review Comment:
   Please add all single-value fields as `optional`, not `required`.
   
   https://protobuf.dev/programming-guides/style/#required
   https://protobuf.dev/programming-guides/proto2/#required-deprecated



##########
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSWebIdentityAuthBypassFilter.java:
##########
@@ -0,0 +1,183 @@
+/*
+ * 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_STS_WEB_IDENTITY_ENABLED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.UriInfo;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.s3.AuthorizationFilter;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for the STS-only WebIdentity auth bypass filter.
+ */
+public class TestS3STSWebIdentityAuthBypassFilter {
+
+  @Test
+  public void enabledGetWebIdentityRequestSkipsAwsAuth() throws Exception {
+    S3STSWebIdentityAuthBypassFilter filter = filter(true);
+    ContainerRequestContext context = context(HttpMethod.GET,
+        "AssumeRoleWithWebIdentity", null);
+
+    filter.filter(context);
+
+    verify(context).setProperty(AuthorizationFilter.SKIP_AWS_AUTH_PROPERTY,
+        Boolean.TRUE);
+  }
+
+  @Test
+  public void disabledWebIdentityRequestDoesNotSkipAwsAuth()
+      throws Exception {
+    S3STSWebIdentityAuthBypassFilter filter = filter(false);
+    ContainerRequestContext context = context(HttpMethod.GET,
+        "AssumeRoleWithWebIdentity", null);
+
+    filter.filter(context);
+
+    verify(context, never()).setProperty(anyString(), any());
+  }
+
+  @Test
+  public void otherStsActionDoesNotSkipAwsAuth() throws Exception {
+    S3STSWebIdentityAuthBypassFilter filter = filter(true);
+    ContainerRequestContext context = context(HttpMethod.GET, "AssumeRole",
+        null);
+
+    filter.filter(context);
+
+    verify(context, never()).setProperty(anyString(), any());
+  }
+
+  @Test
+  public void postWebIdentityRequestSkipsAwsAuthAndRestoresBody()
+      throws Exception {
+    S3STSWebIdentityAuthBypassFilter filter = filter(true);
+    String body = "Action=AssumeRoleWithWebIdentity"
+        + "&WebIdentityToken=sensitive-token-material";
+    ContainerRequestContext context = context(HttpMethod.POST, null, body);
+
+    filter.filter(context);
+
+    verify(context).setProperty(AuthorizationFilter.SKIP_AWS_AUTH_PROPERTY,
+        Boolean.TRUE);
+    ArgumentCaptor<InputStream> streamCaptor =
+        ArgumentCaptor.forClass(InputStream.class);
+    verify(context).setEntityStream(streamCaptor.capture());
+    assertEquals(body, read(streamCaptor.getValue()));
+  }
+
+  @Test
+  public void adversarialActionValuesDoNotSkipAwsAuth() throws Exception {
+    assertDoesNotSkipPost("Action=AssumeRoleWithWebIdentity%20");
+    assertDoesNotSkipPost("action=assumerolewithwebidentity");
+    assertDoesNotSkipPost("Action=AssumeRoleWithWebIdentity%00");
+    assertDoesNotSkipPost("Action=AssumeRoleWithWebIdentity"
+        + "&Action=AssumeRole");
+    assertDoesNotSkipPost("{\"Action\":\"AssumeRoleWithWebIdentity\"}");
+    assertDoesNotSkipPost("Version=2011-06-15");
+    assertDoesNotSkipPost("Action=");
+  }
+
+  @Test
+  public void duplicateQueryActionDoesNotSkipAwsAuth() throws Exception {
+    S3STSWebIdentityAuthBypassFilter filter = filter(true);
+    ContainerRequestContext context = contextWithQueryActions(
+        "AssumeRoleWithWebIdentity", "AssumeRole");
+
+    filter.filter(context);
+
+    verify(context, never()).setProperty(anyString(), any());
+  }
+
+  private static S3STSWebIdentityAuthBypassFilter filter(boolean enabled) {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    conf.setBoolean(OZONE_STS_WEB_IDENTITY_ENABLED, enabled);
+    S3STSWebIdentityAuthBypassFilter filter =
+        new S3STSWebIdentityAuthBypassFilter();
+    filter.setOzoneConfiguration(conf);
+    return filter;
+  }
+
+  private static void assertDoesNotSkipPost(String body) throws Exception {
+    S3STSWebIdentityAuthBypassFilter filter = filter(true);
+    ContainerRequestContext context = context(HttpMethod.POST, null, body);
+
+    filter.filter(context);
+
+    verify(context, never()).setProperty(anyString(), any());
+  }
+
+  private static ContainerRequestContext context(String method,
+      String queryAction, String body) {
+    ContainerRequestContext context = mock(ContainerRequestContext.class);
+    UriInfo uriInfo = mock(UriInfo.class);
+    MultivaluedHashMap<String, String> queryParams =
+        new MultivaluedHashMap<>();
+    if (queryAction != null) {
+      queryParams.putSingle("Action", queryAction);
+    }
+    when(context.getMethod()).thenReturn(method);
+    when(context.getUriInfo()).thenReturn(uriInfo);
+    when(uriInfo.getQueryParameters()).thenReturn(queryParams);
+    if (body != null) {
+      when(context.getEntityStream()).thenReturn(new ByteArrayInputStream(
+          body.getBytes(StandardCharsets.UTF_8)));
+    }
+    return context;
+  }
+
+  private static ContainerRequestContext contextWithQueryActions(
+      String... queryActions) {
+    ContainerRequestContext context = mock(ContainerRequestContext.class);
+    UriInfo uriInfo = mock(UriInfo.class);
+    MultivaluedHashMap<String, String> queryParams =
+        new MultivaluedHashMap<>();
+    queryParams.put("Action", Arrays.asList(queryActions));
+    when(context.getMethod()).thenReturn(HttpMethod.GET);
+    when(context.getUriInfo()).thenReturn(uriInfo);
+    when(uriInfo.getQueryParameters()).thenReturn(queryParams);
+    return context;
+  }
+
+  private static String read(InputStream stream) throws Exception {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    byte[] buffer = new byte[256];
+    int read;
+    while ((read = stream.read(buffer)) != -1) {
+      out.write(buffer, 0, read);
+    }
+    return new String(out.toByteArray(), StandardCharsets.UTF_8);
+  }

Review Comment:
   Please use `IOUtils`.



##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSWebIdentityAuthBypassFilter.java:
##########
@@ -0,0 +1,74 @@
+/*
+ * 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_STS_WEB_IDENTITY_ENABLED;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ENABLED_DEFAULT;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.ext.Provider;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.s3.AuthorizationFilter;
+import org.apache.hadoop.ozone.s3.OzoneConfigurationHolder;
+
+/**
+ * Allows unauthenticated bootstrap only for STS WebIdentity requests.
+ * <p>
+ * This filter is registered only in the STS JAX-RS application. It does not
+ * validate or trust the JWT; OM remains the authoritative WebIdentity
+ * validator and temporary credential issuer.
+ */
+@Provider
+@PreMatching
+@Priority(AuthorizationFilter.PRIORITY - 1)
+public class S3STSWebIdentityAuthBypassFilter
+    implements ContainerRequestFilter {
+
+  @Inject
+  private OzoneConfiguration ozoneConfiguration;
+
+  @Override
+  public void filter(ContainerRequestContext context) throws IOException {
+    if (isWebIdentityEnabled()
+        && S3STSWebIdentityRequestParser.isAssumeRoleWithWebIdentity(
+            context)) {
+      context.setProperty(AuthorizationFilter.SKIP_AWS_AUTH_PROPERTY,
+          Boolean.TRUE);
+    }
+  }
+
+  private boolean isWebIdentityEnabled() {
+    OzoneConfiguration conf = ozoneConfiguration;
+    if (conf == null) {
+      conf = OzoneConfigurationHolder.configuration();
+    }
+    return conf != null && conf.getBoolean(OZONE_STS_WEB_IDENTITY_ENABLED,
+        OZONE_STS_WEB_IDENTITY_ENABLED_DEFAULT);
+  }
+
+  @VisibleForTesting
+  void setOzoneConfiguration(OzoneConfiguration conf) {
+    this.ozoneConfiguration = conf;
+  }

Review Comment:
   I think field injection can be replaced with constructor injection, making 
test-specific mutator unnecessary.



##########
pom.xml:
##########
@@ -224,6 +224,7 @@
     <swagger-annotations-version>1.5.4</swagger-annotations-version>
     <test.build.data>${test.build.dir}</test.build.data>
     <test.build.dir>${project.build.directory}/test-dir</test.build.dir>
+    <testcontainers.version>1.21.3</testcontainers.version>

Review Comment:
   Can we use latest version 
([2.0.5](https://github.com/testcontainers/testcontainers-java/releases/tag/2.0.5))?



##########
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSWebIdentityRequestParser.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.s3sts;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.container.ContainerRequestContext;
+
+/**
+ * Parses the STS action without logging request parameters.
+ */
+final class S3STSWebIdentityRequestParser {
+
+  static final String ACTION = "Action";
+  static final String ASSUME_ROLE_WITH_WEB_IDENTITY =
+      "AssumeRoleWithWebIdentity";
+
+  private S3STSWebIdentityRequestParser() {
+  }
+
+  static boolean isAssumeRoleWithWebIdentity(ContainerRequestContext context)
+      throws IOException {
+    String action = getAction(context);
+    return ASSUME_ROLE_WITH_WEB_IDENTITY.equals(action);
+  }
+
+  private static String getAction(ContainerRequestContext context)
+      throws IOException {
+    String method = context.getMethod();
+    if (HttpMethod.GET.equalsIgnoreCase(method)) {
+      return singleAction(context.getUriInfo().getQueryParameters()
+          .get(ACTION));
+    }
+    if (HttpMethod.POST.equalsIgnoreCase(method)) {
+      return getFormAction(context);
+    }
+    return null;
+  }
+
+  private static String getFormAction(ContainerRequestContext context)
+      throws IOException {
+    InputStream stream = context.getEntityStream();
+    if (stream == null) {
+      return null;
+    }
+
+    byte[] body = readFully(stream);
+    context.setEntityStream(new ByteArrayInputStream(body));
+    String form = new String(body, StandardCharsets.UTF_8);
+    String action = null;
+    for (String pair : form.split("&")) {
+      int equals = pair.indexOf('=');
+      if (equals < 0) {
+        continue;
+      }
+      String name = decode(pair.substring(0, equals));
+      if (ACTION.equals(name)) {
+        if (action != null) {
+          return null;
+        }
+        action = decode(pair.substring(equals + 1));
+      }
+    }
+    return action;
+  }
+
+  private static byte[] readFully(InputStream stream) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    byte[] buffer = new byte[4096];
+    int read;
+    while ((read = stream.read(buffer)) != -1) {
+      out.write(buffer, 0, read);
+    }
+    return out.toByteArray();
+  }

Review Comment:
   Can reuse `org.apache.commons.io.IOUtils.readFully`?



##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/oidc/OidcConfig.java:
##########
@@ -0,0 +1,395 @@
+/*
+ * 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.security.oidc;
+
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ALLOW_INSECURE_HTTP_FOR_TESTS;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ALLOW_INSECURE_HTTP_FOR_TESTS_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_AUDIENCE;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_AUDIENCE_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_CLOCK_SKEW;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_CLOCK_SKEW_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ENABLED;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ENABLED_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_GROUPS_CLAIM;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_GROUPS_CLAIM_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ISSUER_URI;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ISSUER_URI_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_CONNECT_TIMEOUT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_CONNECT_TIMEOUT_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_READ_TIMEOUT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_READ_TIMEOUT_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_REFRESH_INTERVAL;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_REFRESH_INTERVAL_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_SIZE_LIMIT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_SIZE_LIMIT_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_URI;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_JWKS_URI_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_REQUIRE_HTTPS;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_REQUIRE_HTTPS_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ROLES_CLAIM;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_ROLES_CLAIM_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_SUBJECT_CLAIM;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_SUBJECT_CLAIM_DEFAULT;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_USERNAME_CLAIM;
+import static 
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_STS_WEB_IDENTITY_USERNAME_CLAIM_DEFAULT;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hdds.conf.ConfigurationSource;
+import org.apache.hadoop.hdds.conf.StorageUnit;
+
+/**
+ * Configuration for the experimental OIDC identity provider.
+ */
+public final class OidcConfig {
+
+  private final boolean enabled;
+  private final String issuerUri;
+  private final String jwksUri;
+  private final String audience;
+  private final String usernameClaim;
+  private final String subjectClaim;
+  private final String groupsClaim;
+  private final String rolesClaim;
+  private final Duration clockSkew;
+  private final Duration jwksRefreshInterval;
+  private final Duration jwksConnectTimeout;
+  private final Duration jwksReadTimeout;
+  private final int jwksSizeLimit;
+  private final boolean requireHttps;
+  private final boolean allowInsecureHttpForTests;
+
+  private OidcConfig(Builder builder) {
+    this.enabled = builder.enabled;
+    this.issuerUri = trimToEmpty(builder.issuerUri);
+    this.jwksUri = trimToEmpty(builder.jwksUri);
+    this.audience = trimToEmpty(builder.audience);
+    this.usernameClaim = requireNonBlank(builder.usernameClaim,
+        OZONE_STS_WEB_IDENTITY_USERNAME_CLAIM);
+    this.subjectClaim = requireNonBlank(builder.subjectClaim,
+        OZONE_STS_WEB_IDENTITY_SUBJECT_CLAIM);
+    this.groupsClaim = requireNonBlank(builder.groupsClaim,
+        OZONE_STS_WEB_IDENTITY_GROUPS_CLAIM);
+    this.rolesClaim = requireNonBlank(builder.rolesClaim,
+        OZONE_STS_WEB_IDENTITY_ROLES_CLAIM);
+    this.clockSkew = requireNonNegative(builder.clockSkew,
+        OZONE_STS_WEB_IDENTITY_CLOCK_SKEW);
+    this.jwksRefreshInterval = requireNonNegative(builder.jwksRefreshInterval,
+        OZONE_STS_WEB_IDENTITY_JWKS_REFRESH_INTERVAL);
+    this.jwksConnectTimeout = requirePositive(builder.jwksConnectTimeout,
+        OZONE_STS_WEB_IDENTITY_JWKS_CONNECT_TIMEOUT);
+    this.jwksReadTimeout = requirePositive(builder.jwksReadTimeout,
+        OZONE_STS_WEB_IDENTITY_JWKS_READ_TIMEOUT);
+    this.jwksSizeLimit = requirePositive(builder.jwksSizeLimit,
+        OZONE_STS_WEB_IDENTITY_JWKS_SIZE_LIMIT);
+    this.requireHttps = builder.requireHttps;
+    this.allowInsecureHttpForTests = builder.allowInsecureHttpForTests;
+  }
+
+  public static OidcConfig from(ConfigurationSource conf) {
+    OidcConfig config = newBuilder()
+        .setEnabled(conf.getBoolean(OZONE_STS_WEB_IDENTITY_ENABLED,
+            OZONE_STS_WEB_IDENTITY_ENABLED_DEFAULT))
+        .setIssuerUri(conf.getTrimmed(OZONE_STS_WEB_IDENTITY_ISSUER_URI,
+            OZONE_STS_WEB_IDENTITY_ISSUER_URI_DEFAULT))
+        .setJwksUri(conf.getTrimmed(OZONE_STS_WEB_IDENTITY_JWKS_URI,
+            OZONE_STS_WEB_IDENTITY_JWKS_URI_DEFAULT))
+        .setAudience(conf.getTrimmed(OZONE_STS_WEB_IDENTITY_AUDIENCE,
+            OZONE_STS_WEB_IDENTITY_AUDIENCE_DEFAULT))
+        .setUsernameClaim(conf.getTrimmed(
+            OZONE_STS_WEB_IDENTITY_USERNAME_CLAIM,
+            OZONE_STS_WEB_IDENTITY_USERNAME_CLAIM_DEFAULT))
+        .setSubjectClaim(conf.getTrimmed(
+            OZONE_STS_WEB_IDENTITY_SUBJECT_CLAIM,
+            OZONE_STS_WEB_IDENTITY_SUBJECT_CLAIM_DEFAULT))
+        .setGroupsClaim(conf.getTrimmed(OZONE_STS_WEB_IDENTITY_GROUPS_CLAIM,
+            OZONE_STS_WEB_IDENTITY_GROUPS_CLAIM_DEFAULT))
+        .setRolesClaim(conf.getTrimmed(OZONE_STS_WEB_IDENTITY_ROLES_CLAIM,
+            OZONE_STS_WEB_IDENTITY_ROLES_CLAIM_DEFAULT))
+        .setClockSkew(duration(conf, OZONE_STS_WEB_IDENTITY_CLOCK_SKEW,
+            OZONE_STS_WEB_IDENTITY_CLOCK_SKEW_DEFAULT))
+        .setJwksRefreshInterval(duration(conf,
+            OZONE_STS_WEB_IDENTITY_JWKS_REFRESH_INTERVAL,
+            OZONE_STS_WEB_IDENTITY_JWKS_REFRESH_INTERVAL_DEFAULT))
+        .setJwksConnectTimeout(duration(conf,
+            OZONE_STS_WEB_IDENTITY_JWKS_CONNECT_TIMEOUT,
+            OZONE_STS_WEB_IDENTITY_JWKS_CONNECT_TIMEOUT_DEFAULT))
+        .setJwksReadTimeout(duration(conf,
+            OZONE_STS_WEB_IDENTITY_JWKS_READ_TIMEOUT,
+            OZONE_STS_WEB_IDENTITY_JWKS_READ_TIMEOUT_DEFAULT))
+        .setJwksSizeLimit(storageSize(conf,
+            OZONE_STS_WEB_IDENTITY_JWKS_SIZE_LIMIT,
+            OZONE_STS_WEB_IDENTITY_JWKS_SIZE_LIMIT_DEFAULT))
+        .setRequireHttps(conf.getBoolean(OZONE_STS_WEB_IDENTITY_REQUIRE_HTTPS,
+            OZONE_STS_WEB_IDENTITY_REQUIRE_HTTPS_DEFAULT))
+        .setAllowInsecureHttpForTests(conf.getBoolean(
+            OZONE_STS_WEB_IDENTITY_ALLOW_INSECURE_HTTP_FOR_TESTS,
+            OZONE_STS_WEB_IDENTITY_ALLOW_INSECURE_HTTP_FOR_TESTS_DEFAULT))
+        .build();
+    if (config.isEnabled()) {
+      config.validateForProvider();
+    }
+    return config;
+  }
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public String getIssuerUri() {
+    return issuerUri;
+  }
+
+  public String getJwksUri() {
+    return jwksUri;
+  }
+
+  public String getAudience() {
+    return audience;
+  }
+
+  public String getUsernameClaim() {
+    return usernameClaim;
+  }
+
+  public String getSubjectClaim() {
+    return subjectClaim;
+  }
+
+  public String getGroupsClaim() {
+    return groupsClaim;
+  }
+
+  public String getRolesClaim() {
+    return rolesClaim;
+  }
+
+  public Duration getClockSkew() {
+    return clockSkew;
+  }
+
+  public Duration getJwksRefreshInterval() {
+    return jwksRefreshInterval;
+  }
+
+  public Duration getJwksConnectTimeout() {
+    return jwksConnectTimeout;
+  }
+
+  public Duration getJwksReadTimeout() {
+    return jwksReadTimeout;
+  }
+
+  public int getJwksSizeLimit() {
+    return jwksSizeLimit;
+  }
+
+  public boolean isRequireHttps() {
+    return requireHttps;
+  }
+
+  public boolean isAllowInsecureHttpForTests() {
+    return allowInsecureHttpForTests;
+  }
+
+  void validateForProvider() {
+    requireNonBlank(issuerUri, OZONE_STS_WEB_IDENTITY_ISSUER_URI);
+    requireNonBlank(audience, OZONE_STS_WEB_IDENTITY_AUDIENCE);
+
+    if (requireHttps && !allowInsecureHttpForTests) {

Review Comment:
   Why do we need two config properties/flags?  `allowInsecureHttpForTests` 
seems to be the test-specific version of `requireHttps` (inverted).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to