[SPARK-57891][CORE] Add CredentialProvider SPI and ServiceLoader discovery#57191
[SPARK-57891][CORE] Add CredentialProvider SPI and ServiceLoader discovery#57191yadavay-amzn wants to merge 4 commits into
Conversation
…or single candidate; clarify init-once-per-instance
| // Initialize exactly once under the lock (first-conf-wins). | ||
| synchronized (CredentialProviderLoader.class) { | ||
| if (!initializedProviders.contains(selected)) { | ||
| selected.init(conf); |
There was a problem hiding this comment.
I have a security concern here. The full Spark configuration map is passed to init(). Since providers are discovered via ServiceLoader (potentially third-party JARs), a buggy or malicious provider receives all config properties including secrets from other subsystems (e.g., spark.authenticate.secret, database passwords, cloud keys).
Spark has precedent for namespace scoping. DataSourceV2Utils.extractSessionConfigs() filters the full conf to only spark.datasource.<keyPrefix>.* before passing to providers. Similar to this approach, can we filter the map to only pass spark.security.credentials.* keys?
There was a problem hiding this comment.
Thanks for pointing that out, it's a very valid concern and something I missed!
Updated, init() conf now filtered to only spark.security.credentials.* (prefix filter matching the DataSourceV2Utils.extractSessionConfigs precedent).
Also added a test to check spark.authenticate.secret/spark.ssl.keyPassword do not reach init().
| // Initialize exactly once under the lock (first-conf-wins). | ||
| synchronized (CredentialProviderLoader.class) { | ||
| if (!initializedProviders.contains(selected)) { | ||
| selected.init(conf); |
There was a problem hiding this comment.
What happens if init() throws? Currently the provider is not added to initializedProviders, so a subsequent call would retry init(). This retry behavior seems reasonable, but:
- The provider instance may be in a half-initialized state after a failed
init(). Is that safe to retry? - Should the contract document whether
init()is expected to be idempotent-safe (i.e., safe to call again after a previous failure)?
How about:
- Documenting the retry-on-failure behavior in the
init()Javadoc (e.g., "If init() throws, it may be retried on the next resolution attempt. Implementations should be safe to call again after a prior failure."), or - Adding a test verifying this behavior
There was a problem hiding this comment.
Documented the retry behavior and added a test (testInitRetryAfterFailure) to show the loader retries and succeeds.
|
Let's remove the "Design notes (feedback/suggestions welcome)" section from the description because it's used as commit log when this PR is merged. |
…ty.credentials.*, reject empty scheme, document thread-safety and init retry
|
@sarutak Thanks for reviewing! One note about
In the In this per-workload phase I'm assuming this okay to do as a followup but wanted to check if you'd like this incorporated into this PR. |
Yes, it's OK to do as a followup. |
…overy ### What changes were proposed in this pull request? This is task 2 of the OIDC Credential Propagation SPIP ([SPARK-57703](https://issues.apache.org/jira/browse/SPARK-57703)), building on the core types added in SPARK-57890. It introduces the pluggable provider SPI in `org.apache.spark.security` (spark-core): - `CredentialProvider` - a `DeveloperApi` interface that providers (AWS STS, Azure, GCP, etc.) implement to exchange a user identity for a short-lived `ServiceCredential` - `CredentialResolutionException` - a `DeveloperApi` checked exception thrown by `resolve`. - `CredentialProviderLoader` - discovers implementations via `ServiceLoader` and selects a provider per scheme. Scheme keys are normalized to lowercase. Selection follows an explicit-configuration policy: `spark.security.credentials.provider.<scheme>` names the fully-qualified provider class to use for that scheme. If exactly one discovered provider supports a scheme, no configuration is needed; if multiple support it and the conf is unset, a clear error is raised listing the candidates. Each selected provider is initialized exactly once. A `FakeCredentialProvider` (and a second one sharing a scheme) plus a `META-INF/services` registration are added under `core/src/test` to exercise discovery and selection. ### Why are the changes needed? There is currently no pluggable SPI for exchanging an identity token for short-lived service credentials. This is the extension point the credential-propagation framework builds on. See the SPIP design document, Appendix A. ### Does this PR introduce _any_ user-facing change? No behavior change. It adds new `DeveloperApi` types only; nothing uses them until the follow-up (SPARK-57892 / the manager task). ### How was this patch tested? New JUnit `CredentialProviderLoaderSuite` covering the acceptance criteria, ServiceLoader discovery, provider selection/ambiguity, error cases, and null-input guards. ### Was this patch authored or co-authored using generative AI tooling? No. Closes #57191 from yadavay-amzn/SPARK-57891. Authored-by: Anupam Yadav <anupamy030@gmail.com> Signed-off-by: Kousuke Saruta <sarutak@apache.org> (cherry picked from commit 475a771) Signed-off-by: Kousuke Saruta <sarutak@apache.org>
What changes were proposed in this pull request?
This is task 2 of the OIDC Credential Propagation SPIP (SPARK-57703), building on the core types added in SPARK-57890. It introduces the pluggable provider SPI in
org.apache.spark.security(spark-core):CredentialProvider- a@DeveloperApiinterface that providers (AWS STS, Azure, GCP, etc.) implement to exchange a user identity for a short-livedServiceCredentialCredentialResolutionException- a@DeveloperApichecked exception thrown byresolve.CredentialProviderLoader- discovers implementations viaServiceLoaderand selects a provider per scheme. Scheme keys are normalized to lowercase. Selection follows an explicit-configuration policy:spark.security.credentials.provider.<scheme>names the fully-qualified provider class to use for that scheme. If exactly one discovered provider supports a scheme, no configuration is needed; if multiple support it and the conf is unset, a clear error is raised listing the candidates. Each selected provider is initialized exactly once.A
FakeCredentialProvider(and a second one sharing a scheme) plus aMETA-INF/servicesregistration are added undercore/src/testto exercise discovery and selection.Why are the changes needed?
There is currently no pluggable SPI for exchanging an identity token for short-lived service credentials. This is the extension point the credential-propagation framework builds on. See the SPIP design document, Appendix A.
Does this PR introduce any user-facing change?
No behavior change. It adds new
@DeveloperApitypes only; nothing uses them until the follow-up (SPARK-57892 / the manager task).How was this patch tested?
New JUnit
CredentialProviderLoaderSuitecovering the acceptance criteria, ServiceLoader discovery, provider selection/ambiguity, error cases, and null-input guards.Was this patch authored or co-authored using generative AI tooling?
No.