sarutak commented on code in PR #57655:
URL: https://github.com/apache/spark/pull/57655#discussion_r3736717326


##########
connector/credential-aws/src/main/java/org/apache/spark/security/aws/AwsStsCredentialProvider.java:
##########
@@ -18,29 +18,258 @@
 package org.apache.spark.security.aws;
 
 import java.net.URI;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Pattern;
+
+import com.google.common.annotations.VisibleForTesting;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sts.StsClient;
+import software.amazon.awssdk.services.sts.StsClientBuilder;
+import 
software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest;
+import 
software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityResponse;
+import software.amazon.awssdk.services.sts.model.Credentials;
 
-import org.apache.spark.annotation.DeveloperApi;
 import org.apache.spark.security.CredentialProvider;
 import org.apache.spark.security.CredentialResolutionException;
 import org.apache.spark.security.ServiceCredential;
 import org.apache.spark.security.UserContext;
 
 /**
- * :: DeveloperApi ::
- * AWS STS credential provider that exchanges an OIDC identity token for 
temporary
- * AWS credentials via {@code AssumeRoleWithWebIdentity}.
+ * A {@link CredentialProvider} that exchanges an OIDC identity token for 
temporary
+ * AWS credentials via the STS {@code AssumeRoleWithWebIdentity} API.
+ * <p>
+ * The returned {@link ServiceCredential} contains S3A-compatible Hadoop 
configuration
+ * properties ({@code fs.s3a.access.key}, {@code fs.s3a.secret.key},
+ * {@code fs.s3a.session.token}) that can be propagated to executors for 
accessing
+ * S3-compatible storage.
+ * <p>
+ * <b>Configuration keys</b> (passed via {@code spark.security.oidc.*}):
+ * <ul>
+ *   <li>{@code spark.security.oidc.aws.roleArn} (required) -- the ARN of the 
IAM role
+ *       to assume</li>
+ *   <li>{@code spark.security.oidc.aws.sessionName} (optional) -- the role 
session name;
+ *       defaults to a value derived from the user's principal or 
"spark-oidc"</li>
+ *   <li>{@code spark.security.oidc.aws.durationSeconds} (optional) -- 
credential duration
+ *       in seconds (900-43200); if unset, STS uses the role's default 
maximum</li>
+ *   <li>{@code spark.security.oidc.aws.region} (optional) -- the AWS region 
for the STS
+ *       endpoint; defaults to us-east-1 when only {@code stsEndpoint} is set. 
When neither
+ *       {@code region} nor {@code stsEndpoint} is configured, the STS client 
falls back to
+ *       the AWS SDK default region resolution (AWS_REGION / 
AWS_DEFAULT_REGION environment
+ *       variables, then the ~/.aws/config profile).</li>
+ *   <li>{@code spark.security.oidc.aws.stsEndpoint} (optional) -- a custom 
STS endpoint URL
+ *       for non-AWS environments (MinIO, Ceph, LocalStack, etc.)</li>
+ * </ul>
+ * <p>
+ * <b>Security note:</b> The OIDC raw token is never included in log messages 
or
+ * exception messages. It is passed directly to the STS API call and discarded.
  *
  * @since 4.3.0
  */
-@DeveloperApi
 public class AwsStsCredentialProvider implements CredentialProvider {
 
+  // Configuration key constants
+  static final String CONF_ROLE_ARN = "spark.security.oidc.aws.roleArn";
+  static final String CONF_SESSION_NAME = 
"spark.security.oidc.aws.sessionName";
+  static final String CONF_DURATION_SECONDS = 
"spark.security.oidc.aws.durationSeconds";
+  static final String CONF_REGION = "spark.security.oidc.aws.region";
+  static final String CONF_STS_ENDPOINT = 
"spark.security.oidc.aws.stsEndpoint";
+
+  /** Minimum duration allowed by STS AssumeRoleWithWebIdentity (15 minutes). 
*/
+  static final int MIN_DURATION_SECONDS = 900;
+  /** Maximum duration allowed by STS AssumeRoleWithWebIdentity (12 hours). */
+  static final int MAX_DURATION_SECONDS = 43200;
+
+  private static final String DEFAULT_REGION = "us-east-1";
+  private static final String DEFAULT_SESSION_NAME = "spark-oidc";
+
+  /**
+   * Precompiled pattern matching characters that are NOT valid in STS session 
names.
+   * Valid characters are: alphanumeric, underscore, plus, equals, comma, 
period, at, hyphen.
+   */
+  private static final Pattern SESSION_NAME_INVALID_CHARS =
+      Pattern.compile("[^a-zA-Z0-9_+=,.@\\-]");
+
+  /**
+   * Immutable configuration holder that is safely published via the volatile
+   * {@link #config} field. All fields are set once during construction and are
+   * final, ensuring correct visibility across threads after init() completes.
+   */
+  static final class ResolvedConfig {
+    final String roleArn;
+    final String roleSessionName;
+    final Integer durationSeconds;
+    final Region resolvedRegion;
+    final URI endpointOverride;
+    final StsClient stsClient;
+
+    ResolvedConfig(String roleArn, String roleSessionName, Integer 
durationSeconds,
+        Region resolvedRegion, URI endpointOverride, StsClient stsClient) {
+      this.roleArn = roleArn;
+      this.roleSessionName = roleSessionName;
+      this.durationSeconds = durationSeconds;
+      this.resolvedRegion = resolvedRegion;
+      this.endpointOverride = endpointOverride;
+      this.stsClient = stsClient;
+    }
+  }
+
+  /** Safely published via volatile write in init(); read in 
resolve()/suggestedTtl(). */
+  private volatile ResolvedConfig config;
+
+  /**
+   * Default no-arg constructor used by {@link java.util.ServiceLoader}.
+   */
+  public AwsStsCredentialProvider() {
+    // ServiceLoader requires a public no-arg constructor
+  }
+
+  /**
+   * Package-private constructor for testing with an injected STS client.
+   * <p>
+   * This constructor is visible for testing only; production code must use the
+   * no-arg constructor followed by {@link #init(Map)}.
+   *
+   * @param stsClient the STS client to use (must not be null)
+   * @param roleArn the IAM role ARN (must not be null or blank)
+   * @param roleSessionName the session name (may be null for default)
+   * @param durationSeconds the credential duration in seconds (may be null)
+   */
+  @VisibleForTesting
+  AwsStsCredentialProvider(StsClient stsClient, String roleArn, String 
roleSessionName,
+      Integer durationSeconds) {
+    this.config = new ResolvedConfig(roleArn, roleSessionName, durationSeconds,
+        null, null, stsClient);
+  }
+
   @Override
   public void init(Map<String, String> conf) {
-    throw new UnsupportedOperationException(
-        "AwsStsCredentialProvider is a stub. Full implementation in 
SPARK-57898.");
+    if (this.config != null) {
+      throw new IllegalStateException("AwsStsCredentialProvider is already 
initialized");
+    }
+
+    String roleArn = conf.get(CONF_ROLE_ARN);
+    if (roleArn != null) {
+      roleArn = roleArn.trim();
+    }
+    if (roleArn == null || roleArn.isBlank()) {
+      throw new IllegalArgumentException(
+          "Configuration key '" + CONF_ROLE_ARN + "' is required but was not 
set. "
+              + "Specify the ARN of the IAM role to assume via 
AssumeRoleWithWebIdentity.");
+    }
+
+    String roleSessionName = conf.get(CONF_SESSION_NAME);

Review Comment:
   Should we trim `roleSessionName` like as `roleArn`, `regionStr` and 
`stsEndpoint`?



-- 
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