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]