This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git
The following commit(s) were added to refs/heads/main by this push:
new 41f5df7 feat: Allow configure from default chain (#620)
41f5df7 is described below
commit 41f5df7fcb7ec79b253dccf93dae167e151b88a6
Author: Xuanwo <[email protected]>
AuthorDate: Thu Sep 11 17:25:37 2025 +0800
feat: Allow configure from default chain (#620)
**This PR was primarily authored with Codex using GPT-5 and then
hand-reviewed by me. I AM responsible for every change made in this PR.
I aimed to keep it aligned with our goals, though I may have missed
minor issues. Please flag anything that feels off, I'll fix it
quickly.**
---------
Signed-off-by: Xuanwo <[email protected]>
---
.../provide_credential/assume_role_with_oidc.rs | 22 +-
.../aliyun-oss/src/provide_credential/default.rs | 86 ++++-
services/aliyun-oss/src/provide_credential/env.rs | 2 +-
services/aliyun-oss/src/provide_credential/mod.rs | 2 +-
.../assume_role_with_web_identity.rs | 14 +-
services/aws-v4/src/provide_credential/default.rs | 370 ++++++++++++++++++--
services/aws-v4/src/provide_credential/env.rs | 2 +-
services/aws-v4/src/provide_credential/imds.rs | 36 +-
services/aws-v4/src/provide_credential/mod.rs | 2 +-
services/aws-v4/src/provide_credential/profile.rs | 2 +-
.../src/provide_credential/azure_cli.rs | 2 +-
.../src/provide_credential/client_secret.rs | 29 +-
.../src/provide_credential/default.rs | 275 ++++++++++++++-
.../azure-storage/src/provide_credential/imds.rs | 14 +-
.../azure-storage/src/provide_credential/mod.rs | 2 +-
.../src/provide_credential/workload_identity.rs | 14 +-
services/google/src/provide_credential/default.rs | 376 ++++++++++++++-------
services/google/src/provide_credential/mod.rs | 3 +-
.../google/src/provide_credential/vm_metadata.rs | 15 +-
.../tests/credential_providers/external_account.rs | 28 +-
.../impersonated_service_account.rs | 42 ++-
.../src/provide_credential/default.rs | 58 +++-
.../huaweicloud-obs/src/provide_credential/env.rs | 6 +-
.../huaweicloud-obs/src/provide_credential/mod.rs | 2 +-
.../oracle/src/provide_credential/config_file.rs | 6 +-
services/oracle/src/provide_credential/default.rs | 88 ++++-
services/oracle/src/provide_credential/env.rs | 6 +-
services/oracle/src/provide_credential/mod.rs | 2 +-
.../assume_role_with_web_identity.rs | 2 +-
.../tencent-cos/src/provide_credential/default.rs | 93 ++++-
services/tencent-cos/src/provide_credential/env.rs | 6 +-
services/tencent-cos/src/provide_credential/mod.rs | 2 +-
32 files changed, 1342 insertions(+), 267 deletions(-)
diff --git
a/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
b/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
index d8ff060..f30c751 100644
--- a/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
+++ b/services/aliyun-oss/src/provide_credential/assume_role_with_oidc.rs
@@ -29,17 +29,29 @@ use serde::Deserialize;
/// - `ALIBABA_CLOUD_OIDC_PROVIDER_ARN`: The ARN of the OIDC provider
/// - `ALIBABA_CLOUD_OIDC_TOKEN_FILE`: Path to the OIDC token file
/// - `ALIBABA_CLOUD_STS_ENDPOINT`: Optional custom STS endpoint
-#[derive(Debug, Default)]
-pub struct AssumeRoleWithOidcCredentialProvider {}
+#[derive(Debug, Default, Clone)]
+pub struct AssumeRoleWithOidcCredentialProvider {
+ sts_endpoint: Option<String>,
+}
impl AssumeRoleWithOidcCredentialProvider {
/// Create a new `AssumeRoleWithOidcCredentialProvider` instance.
/// This will read configuration from environment variables at runtime.
pub fn new() -> Self {
- Self {}
+ Self::default()
+ }
+
+ /// Set the STS endpoint.
+ pub fn with_sts_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+ self.sts_endpoint = Some(endpoint.into());
+ self
}
- fn get_sts_endpoint(envs: &std::collections::HashMap<String, String>) ->
String {
+ fn get_sts_endpoint(&self, envs: &std::collections::HashMap<String,
String>) -> String {
+ if let Some(endpoint) = &self.sts_endpoint {
+ return endpoint.clone();
+ }
+
match envs.get(ALIBABA_CLOUD_STS_ENDPOINT) {
Some(endpoint) => format!("https://{endpoint}"),
None => "https://sts.aliyuncs.com".to_string(),
@@ -71,7 +83,7 @@ impl ProvideCredential for
AssumeRoleWithOidcCredentialProvider {
// Construct request to Aliyun STS Service.
let url = format!(
"{}/?Action=AssumeRoleWithOIDC&OIDCProviderArn={}&RoleArn={}&RoleSessionName={}&Format=JSON&Version=2015-04-01&Timestamp={}&OIDCToken={}",
- Self::get_sts_endpoint(&envs),
+ self.get_sts_endpoint(&envs),
provider_arn,
role_arn,
role_session_name,
diff --git a/services/aliyun-oss/src/provide_credential/default.rs
b/services/aliyun-oss/src/provide_credential/default.rs
index 878d7af..d6f7190 100644
--- a/services/aliyun-oss/src/provide_credential/default.rs
+++ b/services/aliyun-oss/src/provide_credential/default.rs
@@ -38,13 +38,14 @@ impl Default for DefaultCredentialProvider {
}
impl DefaultCredentialProvider {
- /// Create a new `DefaultCredentialProvider` instance.
- pub fn new() -> Self {
- let chain = ProvideCredentialChain::new()
- .push(EnvCredentialProvider::new())
- .push(AssumeRoleWithOidcCredentialProvider::new());
+ /// Create a builder to configure the default credential chain.
+ pub fn builder() -> DefaultCredentialProviderBuilder {
+ DefaultCredentialProviderBuilder::default()
+ }
- Self { chain }
+ /// Create a new `DefaultCredentialProvider` instance using the default
chain.
+ pub fn new() -> Self {
+ Self::builder().build()
}
/// Create with a custom credential chain.
@@ -74,6 +75,79 @@ impl DefaultCredentialProvider {
}
}
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_*` to customize providers and `disable_*(bool)` to control
+/// participation in the chain. Finally call `build()` to create the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+ env: Option<EnvCredentialProvider>,
+ assume_role: Option<AssumeRoleWithOidcCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+ /// Create a new builder with default state.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Configure the environment credential provider.
+ pub fn configure_env<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+ {
+ let p = self.env.take().unwrap_or_default();
+ self.env = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the environment provider.
+ pub fn disable_env(mut self, disable: bool) -> Self {
+ if disable {
+ self.env = None;
+ } else if self.env.is_none() {
+ self.env = Some(EnvCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the OIDC assume-role credential provider.
+ pub fn configure_assume_role<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(AssumeRoleWithOidcCredentialProvider) ->
AssumeRoleWithOidcCredentialProvider,
+ {
+ let p = self.assume_role.take().unwrap_or_default();
+ self.assume_role = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the assume-role provider.
+ pub fn disable_assume_role(mut self, disable: bool) -> Self {
+ if disable {
+ self.assume_role = None;
+ } else if self.assume_role.is_none() {
+ self.assume_role =
Some(AssumeRoleWithOidcCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Build the `DefaultCredentialProvider` with the configured options.
+ pub fn build(self) -> DefaultCredentialProvider {
+ let mut chain = ProvideCredentialChain::new();
+ if let Some(p) = self.env {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(EnvCredentialProvider::new());
+ }
+ if let Some(p) = self.assume_role {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(AssumeRoleWithOidcCredentialProvider::new());
+ }
+ DefaultCredentialProvider::with_chain(chain)
+ }
+}
+
#[async_trait]
impl ProvideCredential for DefaultCredentialProvider {
type Credential = Credential;
diff --git a/services/aliyun-oss/src/provide_credential/env.rs
b/services/aliyun-oss/src/provide_credential/env.rs
index d0650d1..b360802 100644
--- a/services/aliyun-oss/src/provide_credential/env.rs
+++ b/services/aliyun-oss/src/provide_credential/env.rs
@@ -25,7 +25,7 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// - `ALIBABA_CLOUD_ACCESS_KEY_ID`: The Alibaba Cloud access key ID
/// - `ALIBABA_CLOUD_ACCESS_KEY_SECRET`: The Alibaba Cloud access key secret
/// - `ALIBABA_CLOUD_SECURITY_TOKEN`: The Alibaba Cloud security token
(optional)
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
pub struct EnvCredentialProvider;
impl EnvCredentialProvider {
diff --git a/services/aliyun-oss/src/provide_credential/mod.rs
b/services/aliyun-oss/src/provide_credential/mod.rs
index e7ffa1a..8bb7fe2 100644
--- a/services/aliyun-oss/src/provide_credential/mod.rs
+++ b/services/aliyun-oss/src/provide_credential/mod.rs
@@ -19,7 +19,7 @@ mod assume_role_with_oidc;
pub use assume_role_with_oidc::AssumeRoleWithOidcCredentialProvider;
mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
mod env;
pub use env::EnvCredentialProvider;
diff --git
a/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
b/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
index ac87f41..7bef29f 100644
--- a/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
+++ b/services/aws-v4/src/provide_credential/assume_role_with_web_identity.rs
@@ -31,7 +31,7 @@ use std::path::PathBuf;
/// This provider reads configuration from:
/// 1. Constructor parameters (if provided)
/// 2. Environment variables (when constructor parameters are not set)
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
pub struct AssumeRoleWithWebIdentityCredentialProvider {
// Web Identity configuration
role_arn: Option<String>,
@@ -60,6 +60,18 @@ impl AssumeRoleWithWebIdentityCredentialProvider {
}
}
+ /// Set the role ARN.
+ pub fn with_role_arn(mut self, role_arn: impl Into<String>) -> Self {
+ self.role_arn = Some(role_arn.into());
+ self
+ }
+
+ /// Set the web identity token file path.
+ pub fn with_web_identity_token_file(mut self, token_file: impl
Into<PathBuf>) -> Self {
+ self.web_identity_token_file = Some(token_file.into());
+ self
+ }
+
/// Set the role session name.
pub fn with_role_session_name(mut self, name: String) -> Self {
self.role_session_name = Some(name);
diff --git a/services/aws-v4/src/provide_credential/default.rs
b/services/aws-v4/src/provide_credential/default.rs
index c8e43d4..0b388f4 100644
--- a/services/aws-v4/src/provide_credential/default.rs
+++ b/services/aws-v4/src/provide_credential/default.rs
@@ -48,29 +48,14 @@ impl Default for DefaultCredentialProvider {
}
impl DefaultCredentialProvider {
- /// Create a new `DefaultCredentialProvider` instance.
- pub fn new() -> Self {
- let mut chain = ProvideCredentialChain::new()
- .push(EnvCredentialProvider::new())
- .push(ProfileCredentialProvider::new());
-
- #[cfg(not(target_arch = "wasm32"))]
- {
- chain = chain.push(SSOCredentialProvider::new());
- }
-
- chain = chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
-
- #[cfg(not(target_arch = "wasm32"))]
- {
- chain = chain.push(ProcessCredentialProvider::new());
- }
-
- chain = chain
- .push(ECSCredentialProvider::new())
- .push(IMDSv2CredentialProvider::new());
+ /// Create a builder to configure the default credential chain.
+ pub fn builder() -> DefaultCredentialProviderBuilder {
+ DefaultCredentialProviderBuilder::default()
+ }
- Self { chain }
+ /// Create a new `DefaultCredentialProvider` instance using the default
chain.
+ pub fn new() -> Self {
+ Self::builder().build()
}
/// Create with a custom credential chain.
@@ -100,6 +85,251 @@ impl DefaultCredentialProvider {
}
}
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_*` to customize provider behavior and `disable_*(bool)` to
+/// include or exclude providers from the default chain. Call `build()` to
+/// construct the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+ env: Option<EnvCredentialProvider>,
+ profile: Option<ProfileCredentialProvider>,
+ #[cfg(not(target_arch = "wasm32"))]
+ sso: Option<SSOCredentialProvider>,
+ assume_role: Option<AssumeRoleWithWebIdentityCredentialProvider>,
+ #[cfg(not(target_arch = "wasm32"))]
+ process: Option<ProcessCredentialProvider>,
+ ecs: Option<ECSCredentialProvider>,
+ imds: Option<IMDSv2CredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+ /// Create a new builder with default state.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Configure the environment credential provider.
+ pub fn configure_env<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+ {
+ let p = self.env.take().unwrap_or_default();
+ self.env = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the environment provider.
+ pub fn disable_env(mut self, disable: bool) -> Self {
+ if disable {
+ self.env = None;
+ } else if self.env.is_none() {
+ self.env = Some(EnvCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the profile credential provider.
+ pub fn configure_profile<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(ProfileCredentialProvider) -> ProfileCredentialProvider,
+ {
+ let p = self.profile.take().unwrap_or_default();
+ self.profile = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the profile provider.
+ pub fn disable_profile(mut self, disable: bool) -> Self {
+ if disable {
+ self.profile = None;
+ } else if self.profile.is_none() {
+ self.profile = Some(ProfileCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the SSO credential provider.
+ ///
+ /// This customizes how AWS SSO (IAM Identity Center) credentials are
+ /// discovered and exchanged from local SSO caches. Typical use cases
+ /// include setting an alternative endpoint for testing, or overriding the
+ /// profile-derived values. This method is only available for non-wasm32
+ /// targets where process and filesystem access is supported.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn configure_sso<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(SSOCredentialProvider) -> SSOCredentialProvider,
+ {
+ let p = self.sso.take().unwrap_or_default();
+ self.sso = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the SSO provider.
+ ///
+ /// Use this to explicitly remove SSO from the default credential
+ /// resolution chain (disable = true) or ensure it participates (disable =
false).
+ /// This is useful in controlled environments (e.g., CI) or when SSO is
+ /// not configured. Only available on non-wasm32 targets.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn disable_sso(mut self, disable: bool) -> Self {
+ if disable {
+ self.sso = None;
+ } else if self.sso.is_none() {
+ self.sso = Some(SSOCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the web-identity assume-role credential provider.
+ pub fn configure_assume_role<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(
+ AssumeRoleWithWebIdentityCredentialProvider,
+ ) -> AssumeRoleWithWebIdentityCredentialProvider,
+ {
+ let p = self.assume_role.take().unwrap_or_default();
+ self.assume_role = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the assume role provider.
+ pub fn disable_assume_role(mut self, disable: bool) -> Self {
+ if disable {
+ self.assume_role = None;
+ } else if self.assume_role.is_none() {
+ self.assume_role =
Some(AssumeRoleWithWebIdentityCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the external process credential provider.
+ ///
+ /// This allows overriding the profile-derived command used to obtain
+ /// credentials via `credential_process`. Only available on non-wasm32
+ /// targets.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn configure_process<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(ProcessCredentialProvider) -> ProcessCredentialProvider,
+ {
+ let p = self.process.take().unwrap_or_default();
+ self.process = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the process provider.
+ ///
+ /// Use this to explicitly remove the external process credential source
+ /// (disable = true) or ensure it participates (disable = false). This is
+ /// only meaningful on non-wasm32 targets.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn disable_process(mut self, disable: bool) -> Self {
+ if disable {
+ self.process = None;
+ } else if self.process.is_none() {
+ self.process = Some(ProcessCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the ECS (container/task) credential provider.
+ pub fn configure_ecs<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(ECSCredentialProvider) -> ECSCredentialProvider,
+ {
+ let p = self.ecs.take().unwrap_or_default();
+ self.ecs = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the ECS provider.
+ pub fn disable_ecs(mut self, disable: bool) -> Self {
+ if disable {
+ self.ecs = None;
+ } else if self.ecs.is_none() {
+ self.ecs = Some(ECSCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the EC2 IMDSv2 credential provider.
+ pub fn configure_imds<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(IMDSv2CredentialProvider) -> IMDSv2CredentialProvider,
+ {
+ let p = self.imds.take().unwrap_or_default();
+ self.imds = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the IMDSv2 provider.
+ pub fn disable_imds(mut self, disable: bool) -> Self {
+ if disable {
+ self.imds = None;
+ } else if self.imds.is_none() {
+ self.imds = Some(IMDSv2CredentialProvider::new());
+ }
+ self
+ }
+
+ /// Build the `DefaultCredentialProvider` with the configured options.
+ pub fn build(self) -> DefaultCredentialProvider {
+ let mut chain = ProvideCredentialChain::new();
+
+ if let Some(p) = self.env {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(EnvCredentialProvider::new());
+ }
+
+ if let Some(p) = self.profile {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(ProfileCredentialProvider::new());
+ }
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ if let Some(p) = self.sso {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(SSOCredentialProvider::new());
+ }
+ }
+
+ if let Some(p) = self.assume_role {
+ chain = chain.push(p);
+ } else {
+ chain =
chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+ }
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ if let Some(p) = self.process {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(ProcessCredentialProvider::new());
+ }
+ }
+
+ if let Some(p) = self.ecs {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(ECSCredentialProvider::new());
+ }
+
+ if let Some(p) = self.imds {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(IMDSv2CredentialProvider::new());
+ }
+
+ DefaultCredentialProvider::with_chain(chain)
+ }
+}
+
#[async_trait]
impl ProvideCredential for DefaultCredentialProvider {
type Credential = Credential;
@@ -280,6 +510,102 @@ mod tests {
assert_eq!("static_secret_key", cred.secret_access_key);
}
+ #[tokio::test]
+ async fn test_default_credential_provider_configure_imds() {
+ let _ = env_logger::builder().is_test(true).try_init();
+
+ let ctx = Context::new()
+ .with_file_read(TokioFileRead)
+ .with_http_send(ReqwestHttpSend::default())
+ .with_env(OsEnv);
+ let ctx = ctx.with_env(StaticEnv {
+ home_dir: None,
+ envs: HashMap::new(),
+ });
+
+ // Build a custom chain with IMDS disabled
+ let mut chain = ProvideCredentialChain::new()
+ .push(EnvCredentialProvider::new())
+ .push(ProfileCredentialProvider::new());
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ chain = chain.push(SSOCredentialProvider::new());
+ }
+
+ chain = chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ chain = chain.push(ProcessCredentialProvider::new());
+ }
+
+ chain = chain.push(ECSCredentialProvider::new());
+
+ let provider = DefaultCredentialProvider::with_chain(chain);
+
+ // Even though IMDS is the last provider, it should return None when
disabled
+ let cred = provider
+ .provide_credential(&ctx)
+ .await
+ .expect("load must succeed");
+ assert!(cred.is_none());
+ }
+
+ #[tokio::test]
+ async fn test_default_credential_provider_configure_profile() {
+ let _ = env_logger::builder().is_test(true).try_init();
+
+ let ctx = Context::new()
+ .with_file_read(TokioFileRead)
+ .with_http_send(ReqwestHttpSend::default())
+ .with_env(OsEnv);
+ let ctx = ctx.with_env(StaticEnv {
+ home_dir: None,
+ envs: HashMap::new(),
+ });
+
+ // Build a custom chain with Profile provider using a custom config
file
+ let custom_config = format!(
+ "{}/testdata/default_config",
+ env::current_dir()
+ .expect("current_dir must exist")
+ .to_string_lossy()
+ );
+
+ let mut chain =
ProvideCredentialChain::new().push(EnvCredentialProvider::new());
+
+ chain =
chain.push(ProfileCredentialProvider::new().with_config_file(custom_config));
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ chain = chain.push(SSOCredentialProvider::new());
+ }
+
+ chain = chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ chain = chain.push(ProcessCredentialProvider::new());
+ }
+
+ chain = chain
+ .push(ECSCredentialProvider::new())
+ .push(IMDSv2CredentialProvider::new());
+
+ let provider = DefaultCredentialProvider::with_chain(chain);
+
+ // Should load from the custom config
+ let cred = provider
+ .provide_credential(&ctx)
+ .await
+ .expect("load must succeed");
+ // The testdata/default_config has credentials
+ let cred = cred.expect("credential should exist");
+ assert_eq!("config_access_key_id", cred.access_key_id);
+ assert_eq!("config_secret_access_key", cred.secret_access_key);
+ }
+
/// AWS_SHARED_CREDENTIALS_FILE should be taken first.
#[tokio::test]
async fn test_credential_profile_loader_from_both() {
diff --git a/services/aws-v4/src/provide_credential/env.rs
b/services/aws-v4/src/provide_credential/env.rs
index 631ba7f..0256b3c 100644
--- a/services/aws-v4/src/provide_credential/env.rs
+++ b/services/aws-v4/src/provide_credential/env.rs
@@ -25,7 +25,7 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// - `AWS_ACCESS_KEY_ID`: The AWS access key ID
/// - `AWS_SECRET_ACCESS_KEY`: The AWS secret access key
/// - `AWS_SESSION_TOKEN`: The AWS session token (optional)
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
pub struct EnvCredentialProvider;
impl EnvCredentialProvider {
diff --git a/services/aws-v4/src/provide_credential/imds.rs
b/services/aws-v4/src/provide_credential/imds.rs
index 5de8409..656e12e 100644
--- a/services/aws-v4/src/provide_credential/imds.rs
+++ b/services/aws-v4/src/provide_credential/imds.rs
@@ -28,14 +28,14 @@ use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct IMDSv2CredentialProvider {
- disabled: Option<bool>,
+ endpoint: Option<String>,
token: Arc<Mutex<(String, DateTime)>>,
}
impl Default for IMDSv2CredentialProvider {
fn default() -> Self {
Self {
- disabled: None,
+ endpoint: None,
token: Arc::new(Mutex::new((String::new(), DateTime::default()))),
}
}
@@ -47,19 +47,22 @@ impl IMDSv2CredentialProvider {
Self::default()
}
- /// Disable the provider.
- pub fn disabled(mut self) -> Self {
- self.disabled = Some(true);
+ /// Set the endpoint for the metadata service.
+ pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+ self.endpoint = Some(endpoint.into());
self
}
}
impl IMDSv2CredentialProvider {
fn get_endpoint(&self, ctx: &Context) -> String {
- ctx.env_vars()
- .get("AWS_EC2_METADATA_SERVICE_ENDPOINT")
- .cloned()
- .unwrap_or_else(|| "http://169.254.169.254".into())
+ // First check configured endpoint, then environment, then default
+ self.endpoint.clone().unwrap_or_else(|| {
+ ctx.env_vars()
+ .get("AWS_EC2_METADATA_SERVICE_ENDPOINT")
+ .cloned()
+ .unwrap_or_else(|| "http://169.254.169.254".into())
+ })
}
async fn load_ec2_metadata_token(&self, ctx: &Context) -> Result<String> {
@@ -118,15 +121,14 @@ impl ProvideCredential for IMDSv2CredentialProvider {
type Credential = Credential;
async fn provide_credential(&self, ctx: &Context) ->
Result<Option<Self::Credential>> {
- // Check if disabled, first from config, then from environment
- let disabled = self.disabled.unwrap_or_else(|| {
- ctx.env_vars()
- .get("AWS_EC2_METADATA_DISABLED")
- .map(|v| v == "true")
- .unwrap_or(false)
- });
+ // Check if disabled via environment
+ let disabled_env = ctx
+ .env_vars()
+ .get("AWS_EC2_METADATA_DISABLED")
+ .map(|v| v == "true")
+ .unwrap_or(false);
- if disabled {
+ if disabled_env {
return Ok(None);
}
diff --git a/services/aws-v4/src/provide_credential/mod.rs
b/services/aws-v4/src/provide_credential/mod.rs
index 4c4a83d..90bd07a 100644
--- a/services/aws-v4/src/provide_credential/mod.rs
+++ b/services/aws-v4/src/provide_credential/mod.rs
@@ -25,7 +25,7 @@ mod cognito;
pub use cognito::CognitoIdentityCredentialProvider;
mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
mod ecs;
pub use ecs::ECSCredentialProvider;
diff --git a/services/aws-v4/src/provide_credential/profile.rs
b/services/aws-v4/src/provide_credential/profile.rs
index 85005b4..7d9d6ae 100644
--- a/services/aws-v4/src/provide_credential/profile.rs
+++ b/services/aws-v4/src/provide_credential/profile.rs
@@ -37,7 +37,7 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// 1. The `AWS_PROFILE` environment variable
/// 2. The profile specified via `with_profile()`
/// 3. Default to "default"
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct ProfileCredentialProvider {
profile: String,
config_file: Option<String>,
diff --git a/services/azure-storage/src/provide_credential/azure_cli.rs
b/services/azure-storage/src/provide_credential/azure_cli.rs
index 850bc50..ce6dc82 100644
--- a/services/azure-storage/src/provide_credential/azure_cli.rs
+++ b/services/azure-storage/src/provide_credential/azure_cli.rs
@@ -30,7 +30,7 @@ pub struct AzureCliCredentialProvider {}
impl AzureCliCredentialProvider {
pub fn new() -> Self {
- Self {}
+ Self::default()
}
/// Execute `az account get-access-token` command
diff --git a/services/azure-storage/src/provide_credential/client_secret.rs
b/services/azure-storage/src/provide_credential/client_secret.rs
index 4c79695..7294f7c 100644
--- a/services/azure-storage/src/provide_credential/client_secret.rs
+++ b/services/azure-storage/src/provide_credential/client_secret.rs
@@ -26,13 +26,28 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// a client ID and client secret.
///
/// Reference:
<https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow>
-#[derive(Debug, Default)]
-pub struct ClientSecretCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct ClientSecretCredentialProvider {
+ tenant_id: Option<String>,
+ client_id: Option<String>,
+}
impl ClientSecretCredentialProvider {
/// Create a new client secret loader.
pub fn new() -> Self {
- Self
+ Self::default()
+ }
+
+ /// Set the tenant ID.
+ pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
+ self.tenant_id = Some(tenant_id.into());
+ self
+ }
+
+ /// Set the client ID.
+ pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
+ self.client_id = Some(client_id.into());
+ self
}
}
@@ -43,8 +58,12 @@ impl ProvideCredential for ClientSecretCredentialProvider {
async fn provide_credential(&self, ctx: &Context) ->
Result<Option<Self::Credential>> {
let envs = ctx.env_vars();
- // Check if all required parameters are available from environment
- let tenant_id = match envs.get("AZURE_TENANT_ID") {
+ // Check if all required parameters are available from environment or
config
+ let tenant_id = match self
+ .tenant_id
+ .as_ref()
+ .or_else(|| envs.get("AZURE_TENANT_ID"))
+ {
Some(id) if !id.is_empty() => id,
_ => return Ok(None),
};
diff --git a/services/azure-storage/src/provide_credential/default.rs
b/services/azure-storage/src/provide_credential/default.rs
index 06e6376..02ab0a9 100644
--- a/services/azure-storage/src/provide_credential/default.rs
+++ b/services/azure-storage/src/provide_credential/default.rs
@@ -35,29 +35,30 @@ use reqsign_core::{Context, ProvideCredential,
ProvideCredentialChain, Result};
/// 5. Azure Pipelines (workload identity)
/// 6. Workload identity (federated credentials)
/// 7. IMDS (Azure VM managed identity)
-#[derive(Debug, Default)]
+#[derive(Debug)]
pub struct DefaultCredentialProvider {
chain: ProvideCredentialChain<Credential>,
}
+impl Default for DefaultCredentialProvider {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl DefaultCredentialProvider {
+ /// Create a builder to configure the default credential chain.
+ pub fn builder() -> DefaultCredentialProviderBuilder {
+ DefaultCredentialProviderBuilder::default()
+ }
+
/// Create a new default loader.
pub fn new() -> Self {
- let mut chain =
ProvideCredentialChain::new().push(EnvCredentialProvider::new());
-
- #[cfg(not(target_arch = "wasm32"))]
- {
- chain = chain
- .push(AzureCliCredentialProvider::new())
- .push(ClientCertificateCredentialProvider::new());
- }
-
- chain = chain
- .push(ClientSecretCredentialProvider::new())
- .push(AzurePipelinesCredentialProvider::new())
- .push(WorkloadIdentityCredentialProvider::new())
- .push(ImdsCredentialProvider::new());
+ Self::builder().build()
+ }
+ /// Create with a custom credential chain.
+ pub fn with_chain(chain: ProvideCredentialChain<Credential>) -> Self {
Self { chain }
}
@@ -83,6 +84,250 @@ impl DefaultCredentialProvider {
}
}
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_*` to adjust provider behavior and `disable_*(bool)` to
+/// control participation in the chain. Then call `build()` to construct the
+/// final provider that resolves credentials in the documented order.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+ env: Option<EnvCredentialProvider>,
+ #[cfg(not(target_arch = "wasm32"))]
+ azure_cli: Option<AzureCliCredentialProvider>,
+ #[cfg(not(target_arch = "wasm32"))]
+ client_certificate: Option<ClientCertificateCredentialProvider>,
+ client_secret: Option<ClientSecretCredentialProvider>,
+ azure_pipelines: Option<AzurePipelinesCredentialProvider>,
+ workload_identity: Option<WorkloadIdentityCredentialProvider>,
+ imds: Option<ImdsCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn configure_env<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+ {
+ let p = self.env.take().unwrap_or_default();
+ self.env = Some(f(p));
+ self
+ }
+
+ pub fn disable_env(mut self, disable: bool) -> Self {
+ if disable {
+ self.env = None;
+ } else if self.env.is_none() {
+ self.env = Some(EnvCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the Azure CLI credential provider.
+ ///
+ /// This is typically used for local development where Azure CLI is
+ /// available and authenticated. You can tweak behavior (like alternative
+ /// token acquisition strategies) via the provided closure. Only available
+ /// on non-wasm32 targets.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn configure_azure_cli<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(AzureCliCredentialProvider) -> AzureCliCredentialProvider,
+ {
+ let p = self.azure_cli.take().unwrap_or_default();
+ self.azure_cli = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the Azure CLI provider.
+ ///
+ /// Useful in CI or constrained environments where Azure CLI is not
+ /// present, or to explicitly opt out. Only available on non-wasm32
targets.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn disable_azure_cli(mut self, disable: bool) -> Self {
+ if disable {
+ self.azure_cli = None;
+ } else if self.azure_cli.is_none() {
+ self.azure_cli = Some(AzureCliCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the client certificate credential provider.
+ ///
+ /// Customize certificate-based SPN authentication (tenant/client/cert
+ /// parameters). Only available on non-wasm32 targets.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn configure_client_certificate<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(ClientCertificateCredentialProvider) ->
ClientCertificateCredentialProvider,
+ {
+ let p = self.client_certificate.take().unwrap_or_default();
+ self.client_certificate = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the client certificate
provider.
+ ///
+ /// Use to explicitly remove certificate-based auth from the chain or to
+ /// ensure participation. Only available on non-wasm32 targets.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn disable_client_certificate(mut self, disable: bool) -> Self {
+ if disable {
+ self.client_certificate = None;
+ } else if self.client_certificate.is_none() {
+ self.client_certificate =
Some(ClientCertificateCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the client secret credential provider.
+ ///
+ /// Customize tenant/client parameters used to exchange a client secret
+ /// for an access token suitable for Azure Storage.
+ pub fn configure_client_secret<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(ClientSecretCredentialProvider) ->
ClientSecretCredentialProvider,
+ {
+ let p = self.client_secret.take().unwrap_or_default();
+ self.client_secret = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the client secret provider.
+ pub fn disable_client_secret(mut self, disable: bool) -> Self {
+ if disable {
+ self.client_secret = None;
+ } else if self.client_secret.is_none() {
+ self.client_secret = Some(ClientSecretCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the Azure Pipelines workload identity provider.
+ ///
+ /// Allows customizing how OIDC tokens from Azure Pipelines are exchanged
+ /// for Azure AD access tokens.
+ pub fn configure_azure_pipelines<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(AzurePipelinesCredentialProvider) ->
AzurePipelinesCredentialProvider,
+ {
+ let p = self.azure_pipelines.take().unwrap_or_default();
+ self.azure_pipelines = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the Azure Pipelines provider.
+ pub fn disable_azure_pipelines(mut self, disable: bool) -> Self {
+ if disable {
+ self.azure_pipelines = None;
+ } else if self.azure_pipelines.is_none() {
+ self.azure_pipelines =
Some(AzurePipelinesCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the Kubernetes workload identity provider.
+ ///
+ /// Allows customizing tenant or other parameters used to exchange
+ /// federated tokens for Azure AD access tokens.
+ pub fn configure_workload_identity<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(WorkloadIdentityCredentialProvider) ->
WorkloadIdentityCredentialProvider,
+ {
+ let p = self.workload_identity.take().unwrap_or_default();
+ self.workload_identity = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the workload identity
provider.
+ pub fn disable_workload_identity(mut self, disable: bool) -> Self {
+ if disable {
+ self.workload_identity = None;
+ } else if self.workload_identity.is_none() {
+ self.workload_identity =
Some(WorkloadIdentityCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the Azure IMDS provider.
+ ///
+ /// Allows setting an alternate metadata endpoint or other parameters used
+ /// to obtain managed identity tokens on Azure VMs.
+ pub fn configure_imds<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(ImdsCredentialProvider) -> ImdsCredentialProvider,
+ {
+ let p = self.imds.take().unwrap_or_default();
+ self.imds = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the IMDS provider.
+ pub fn disable_imds(mut self, disable: bool) -> Self {
+ if disable {
+ self.imds = None;
+ } else if self.imds.is_none() {
+ self.imds = Some(ImdsCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Build the `DefaultCredentialProvider` with the configured options.
+ pub fn build(self) -> DefaultCredentialProvider {
+ let mut chain = ProvideCredentialChain::new();
+
+ if let Some(p) = self.env {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(EnvCredentialProvider::new());
+ }
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ if let Some(p) = self.azure_cli {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(AzureCliCredentialProvider::new());
+ }
+
+ if let Some(p) = self.client_certificate {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(ClientCertificateCredentialProvider::new());
+ }
+ }
+
+ if let Some(p) = self.client_secret {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(ClientSecretCredentialProvider::new());
+ }
+
+ if let Some(p) = self.azure_pipelines {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(AzurePipelinesCredentialProvider::new());
+ }
+
+ if let Some(p) = self.workload_identity {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(WorkloadIdentityCredentialProvider::new());
+ }
+
+ if let Some(p) = self.imds {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(ImdsCredentialProvider::new());
+ }
+
+ DefaultCredentialProvider::with_chain(chain)
+ }
+}
+
#[async_trait]
impl ProvideCredential for DefaultCredentialProvider {
type Credential = Credential;
diff --git a/services/azure-storage/src/provide_credential/imds.rs
b/services/azure-storage/src/provide_credential/imds.rs
index ae79ab4..a09a41f 100644
--- a/services/azure-storage/src/provide_credential/imds.rs
+++ b/services/azure-storage/src/provide_credential/imds.rs
@@ -25,13 +25,21 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// which is available on Azure VMs and other Azure compute resources.
///
/// Reference:
<https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal,http#using-the-rest-protocol>
-#[derive(Debug, Default)]
-pub struct ImdsCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct ImdsCredentialProvider {
+ endpoint: Option<String>,
+}
impl ImdsCredentialProvider {
/// Create a new IMDS loader.
pub fn new() -> Self {
- Self
+ Self::default()
+ }
+
+ /// Set the IMDS endpoint.
+ pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+ self.endpoint = Some(endpoint.into());
+ self
}
}
diff --git a/services/azure-storage/src/provide_credential/mod.rs
b/services/azure-storage/src/provide_credential/mod.rs
index fe046b4..90acf1f 100644
--- a/services/azure-storage/src/provide_credential/mod.rs
+++ b/services/azure-storage/src/provide_credential/mod.rs
@@ -22,7 +22,7 @@ mod static_provider;
pub use static_provider::StaticCredentialProvider;
mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
mod imds;
pub use imds::ImdsCredentialProvider;
diff --git a/services/azure-storage/src/provide_credential/workload_identity.rs
b/services/azure-storage/src/provide_credential/workload_identity.rs
index 42a0983..17d0328 100644
--- a/services/azure-storage/src/provide_credential/workload_identity.rs
+++ b/services/azure-storage/src/provide_credential/workload_identity.rs
@@ -26,13 +26,21 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// using a federated token.
///
/// Reference:
<https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview>
-#[derive(Debug, Default)]
-pub struct WorkloadIdentityCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct WorkloadIdentityCredentialProvider {
+ tenant_id: Option<String>,
+}
impl WorkloadIdentityCredentialProvider {
/// Create a new workload identity loader.
pub fn new() -> Self {
- Self
+ Self::default()
+ }
+
+ /// Set the tenant ID.
+ pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
+ self.tenant_id = Some(tenant_id.into());
+ self
}
}
diff --git a/services/google/src/provide_credential/default.rs
b/services/google/src/provide_credential/default.rs
index dac70af..7b74479 100644
--- a/services/google/src/provide_credential/default.rs
+++ b/services/google/src/provide_credential/default.rs
@@ -17,7 +17,7 @@
use log::debug;
-use reqsign_core::{Context, ProvideCredential, Result};
+use reqsign_core::{Context, ProvideCredential, ProvideCredentialChain, Result};
use crate::constants::{DEFAULT_SCOPE, GOOGLE_APPLICATION_CREDENTIALS,
GOOGLE_SCOPE};
use crate::credential::{Credential, CredentialFile};
@@ -29,71 +29,165 @@ use super::{
vm_metadata::VmMetadataCredentialProvider,
};
-/// DefaultCredentialProvider tries to load credentials from multiple sources
in order.
+/// Default credential provider for Google Cloud Storage (GCS).
///
-/// It follows the Google Application Default Credentials (ADC) strategy:
-/// 1. GOOGLE_APPLICATION_CREDENTIALS environment variable
-/// 2. gcloud credential file
(~/.config/gcloud/application_default_credentials.json)
-/// 3. Metadata server (for GCE/Cloud Functions/App Engine)
-///
-/// The provider automatically handles all credential types including:
-/// - Service Account
-/// - External Account (Workload Identity)
-/// - Impersonated Service Account
-/// - Authorized User (OAuth2)
-#[derive(Debug, Clone, Default)]
+/// Resolution order follows ADC (Application Default Credentials):
+/// 1. Env var `GOOGLE_APPLICATION_CREDENTIALS`
+/// 2. Well-known location
(`~/.config/gcloud/application_default_credentials.json`)
+/// 3. VM metadata service (GCE / Cloud Functions / App Engine)
+#[derive(Debug)]
pub struct DefaultCredentialProvider {
- scope: Option<String>,
+ chain: ProvideCredentialChain<Credential>,
+}
+
+impl Default for DefaultCredentialProvider {
+ fn default() -> Self {
+ Self::new()
+ }
}
impl DefaultCredentialProvider {
- /// Create a new DefaultCredentialProvider.
+ /// Create a builder to configure the default ADC chain for GCS.
+ pub fn builder() -> DefaultCredentialProviderBuilder {
+ DefaultCredentialProviderBuilder::default()
+ }
+
+ /// Create a new DefaultCredentialProvider with the default chain:
+ /// env ADC -> well-known ADC -> VM metadata
pub fn new() -> Self {
- Self { scope: None }
+ Self::builder().build()
}
- /// Set the OAuth2 scope.
- pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
- self.scope = Some(scope.into());
- self
+ /// Create with a custom credential chain.
+ pub fn with_chain(chain: ProvideCredentialChain<Credential>) -> Self {
+ Self { chain }
}
/// Add a credential provider to the front of the default chain.
- ///
- /// Note: Google's DefaultCredentialProvider doesn't use
ProvideCredentialChain internally,
- /// but this method is provided for API consistency with other providers.
- /// The custom provider will be tried first before the default ADC flow.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use reqsign_google::{DefaultCredentialProvider,
StaticCredentialProvider};
- ///
- /// let provider = DefaultCredentialProvider::new()
- /// .push_front(StaticCredentialProvider::new("service_account_json"));
- /// ```
pub fn push_front(
- self,
- _provider: impl ProvideCredential<Credential = Credential> + 'static,
+ mut self,
+ provider: impl ProvideCredential<Credential = Credential> + 'static,
) -> Self {
- // Note: This implementation would need refactoring to support
chain-based approach
- // For now, we keep the method for API consistency
- log::warn!("push_front is not yet implemented for Google
DefaultCredentialProvider");
+ self.chain = self.chain.push_front(provider);
self
}
- /// Try to load credentials from GOOGLE_APPLICATION_CREDENTIALS
environment variable.
- async fn try_env_credentials(&self, ctx: &Context) ->
Result<Option<Credential>> {
- let Some(path) = ctx.env_var(GOOGLE_APPLICATION_CREDENTIALS) else {
+ /// Set the OAuth2 scope for ADC providers (deprecated).
+ ///
+ /// This helper configures the scope used by the environment and
+ /// well-known ADC providers, as well as the VM metadata provider, by
+ /// constructing a new chain with the provided scope. Prefer configuring
+ /// scope via specific providers (e.g.,
`VmMetadataCredentialProvider::with_scope`)
+ /// or using the `GOOGLE_SCOPE` environment variable.
+ #[deprecated(
+ since = "1.0.0",
+ note = "Configure scope via specific providers or GOOGLE_SCOPE env var"
+ )]
+ pub fn with_scope(self, scope: impl Into<String>) -> Self {
+ let s = scope.into();
+ let chain = ProvideCredentialChain::new()
+ .push(EnvAdcCredentialProvider::new().with_scope(s.clone()))
+ .push(WellKnownAdcCredentialProvider::new().with_scope(s.clone()))
+ .push(VmMetadataCredentialProvider::new().with_scope(s));
+ Self { chain }
+ }
+
+ #[deprecated(
+ since = "1.0.0",
+ note = "Use
DefaultCredentialProvider::builder().disable_env(skip).build() instead"
+ )]
+ pub fn skip_env_credentials(self, skip: bool) -> Self {
+ DefaultCredentialProvider::builder()
+ .disable_env(skip)
+ .build()
+ }
+
+ #[deprecated(
+ since = "1.0.0",
+ note = "Use
DefaultCredentialProvider::builder().disable_well_known(skip).build() instead"
+ )]
+ pub fn skip_well_known_location(self, skip: bool) -> Self {
+ DefaultCredentialProvider::builder()
+ .disable_well_known(skip)
+ .build()
+ }
+}
+
+#[async_trait::async_trait]
+impl ProvideCredential for DefaultCredentialProvider {
+ type Credential = Credential;
+
+ async fn provide_credential(&self, ctx: &Context) ->
Result<Option<Self::Credential>> {
+ self.chain.provide_credential(ctx).await
+ }
+}
+
+#[derive(Default, Clone, Debug)]
+struct EnvAdcCredentialProvider {
+ disabled: Option<bool>,
+ scope: Option<String>,
+}
+
+impl EnvAdcCredentialProvider {
+ fn new() -> Self {
+ Self::default()
+ }
+
+ /// Set the OAuth2 scope to request when exchanging ADC credentials.
+ fn with_scope(mut self, scope: impl Into<String>) -> Self {
+ self.scope = Some(scope.into());
+ self
+ }
+}
+
+#[async_trait::async_trait]
+impl ProvideCredential for EnvAdcCredentialProvider {
+ type Credential = Credential;
+
+ async fn provide_credential(&self, ctx: &Context) ->
Result<Option<Self::Credential>> {
+ if self.disabled.unwrap_or(false) {
return Ok(None);
+ }
+
+ let path = match ctx.env_var(GOOGLE_APPLICATION_CREDENTIALS) {
+ Some(path) if !path.is_empty() => path,
+ _ => return Ok(None),
};
debug!("trying to load credential from env
GOOGLE_APPLICATION_CREDENTIALS: {path}");
- self.load_credential_from_path(ctx, &path).await
+
+ let content = ctx.file_read(&path).await?;
+ parse_credential_bytes(ctx, &content, self.scope.clone()).await
}
+}
+
+#[derive(Default, Clone, Debug)]
+struct WellKnownAdcCredentialProvider {
+ disabled: Option<bool>,
+ scope: Option<String>,
+}
+
+impl WellKnownAdcCredentialProvider {
+ fn new() -> Self {
+ Self::default()
+ }
+
+ /// Set the OAuth2 scope to request when exchanging ADC credentials.
+ fn with_scope(mut self, scope: impl Into<String>) -> Self {
+ self.scope = Some(scope.into());
+ self
+ }
+}
+
+#[async_trait::async_trait]
+impl ProvideCredential for WellKnownAdcCredentialProvider {
+ type Credential = Credential;
+
+ async fn provide_credential(&self, ctx: &Context) ->
Result<Option<Self::Credential>> {
+ if self.disabled.unwrap_or(false) {
+ return Ok(None);
+ }
- /// Try to load credentials from gcloud default location.
- async fn try_well_known_location(&self, ctx: &Context) ->
Result<Option<Credential>> {
let config_dir = if let Some(v) = ctx.env_var("APPDATA") {
v
} else if let Some(v) = ctx.env_var("XDG_CONFIG_HOME") {
@@ -107,94 +201,139 @@ impl DefaultCredentialProvider {
let path =
format!("{config_dir}/gcloud/application_default_credentials.json");
debug!("trying to load credential from well-known location: {path}");
- match self.load_credential_from_path(ctx, &path).await {
- Ok(cred) => Ok(cred),
- Err(_) => Ok(None), // Ignore errors for well-known location
+ let content = match ctx.file_read(&path).await {
+ Ok(v) => v,
+ Err(_) => return Ok(None),
+ };
+
+ match parse_credential_bytes(ctx, &content, self.scope.clone()).await {
+ Ok(v) => Ok(v),
+ Err(_) => Ok(None),
}
}
+}
- /// Try to load credentials from metadata server.
- async fn try_metadata_server(&self, ctx: &Context) ->
Result<Option<Credential>> {
- debug!("trying to load credential from metadata server");
+async fn parse_credential_bytes(
+ ctx: &Context,
+ content: &[u8],
+ scope_override: Option<String>,
+) -> Result<Option<Credential>> {
+ let cred_file = CredentialFile::from_slice(content)?;
+
+ let scope = scope_override
+ .or_else(|| ctx.env_var(GOOGLE_SCOPE))
+ .unwrap_or_else(|| DEFAULT_SCOPE.to_string());
+
+ match cred_file {
+ CredentialFile::ServiceAccount(sa) => {
+ debug!("loaded service account credential");
+ Ok(Some(Credential::with_service_account(sa)))
+ }
+ CredentialFile::ExternalAccount(ea) => {
+ debug!("loaded external account credential, exchanging for token");
+ let provider =
ExternalAccountCredentialProvider::new(ea).with_scope(&scope);
+ provider.provide_credential(ctx).await
+ }
+ CredentialFile::ImpersonatedServiceAccount(isa) => {
+ debug!("loaded impersonated service account credential, exchanging
for token");
+ let provider =
+
ImpersonatedServiceAccountCredentialProvider::new(isa).with_scope(&scope);
+ provider.provide_credential(ctx).await
+ }
+ CredentialFile::AuthorizedUser(au) => {
+ debug!("loaded authorized user credential, exchanging for token");
+ let provider = AuthorizedUserCredentialProvider::new(au);
+ provider.provide_credential(ctx).await
+ }
+ }
+}
- let provider = match &self.scope {
- Some(scope) =>
VmMetadataCredentialProvider::new().with_scope(scope),
- None => VmMetadataCredentialProvider::new(),
- };
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_vm_metadata` to customize VM metadata behavior and
+/// `disable_env` / `disable_well_known` / `disable_vm_metadata` to control
+/// participation. Call `build()` to construct the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+ env_adc: Option<EnvAdcCredentialProvider>,
+ well_known_adc: Option<WellKnownAdcCredentialProvider>,
+ vm_metadata: Option<VmMetadataCredentialProvider>,
+}
- provider.provide_credential(ctx).await
- }
-
- /// Load credential from a file path and handle all credential types.
- async fn load_credential_from_path(
- &self,
- ctx: &Context,
- path: &str,
- ) -> Result<Option<Credential>> {
- let content = ctx.file_read(path).await.map_err(|err| {
- debug!("failed to read credential file {path}: {err:?}");
- err
- })?;
-
- let cred_file = CredentialFile::from_slice(&content).map_err(|err| {
- debug!("failed to parse credential file {path}: {err:?}");
- err
- })?;
-
- // Get scope from instance, environment, or use default
- let scope = self
- .scope
- .clone()
- .or_else(|| ctx.env_var(GOOGLE_SCOPE))
- .unwrap_or_else(|| DEFAULT_SCOPE.to_string());
-
- match cred_file {
- CredentialFile::ServiceAccount(sa) => {
- debug!("loaded service account credential");
- Ok(Some(Credential::with_service_account(sa)))
- }
- CredentialFile::ExternalAccount(ea) => {
- debug!("loaded external account credential, exchanging for
token");
- let provider =
ExternalAccountCredentialProvider::new(ea).with_scope(&scope);
- provider.provide_credential(ctx).await
- }
- CredentialFile::ImpersonatedServiceAccount(isa) => {
- debug!("loaded impersonated service account credential,
exchanging for token");
- let provider =
-
ImpersonatedServiceAccountCredentialProvider::new(isa).with_scope(&scope);
- provider.provide_credential(ctx).await
- }
- CredentialFile::AuthorizedUser(au) => {
- debug!("loaded authorized user credential, exchanging for
token");
- let provider = AuthorizedUserCredentialProvider::new(au);
- provider.provide_credential(ctx).await
- }
+impl DefaultCredentialProviderBuilder {
+ /// Create a new builder with default state.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ // No global scope configurator; configure scope on specific providers if
needed.
+
+ /// Configure the VM metadata provider.
+ ///
+ /// This allows setting a custom endpoint or other options for retrieving
+ /// tokens when running on Google Compute Engine or compatible
environments.
+ pub fn configure_vm_metadata<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(VmMetadataCredentialProvider) ->
VmMetadataCredentialProvider,
+ {
+ let p = self.vm_metadata.take().unwrap_or_default();
+ self.vm_metadata = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the env-based ADC provider.
+ pub fn disable_env(mut self, disable: bool) -> Self {
+ if disable {
+ self.env_adc = None;
+ } else if self.env_adc.is_none() {
+ self.env_adc = Some(EnvAdcCredentialProvider::new());
}
+ self
}
-}
-#[async_trait::async_trait]
-impl ProvideCredential for DefaultCredentialProvider {
- type Credential = Credential;
+ /// Disable (true) or ensure enabled (false) the well-known ADC provider.
+ pub fn disable_well_known(mut self, disable: bool) -> Self {
+ if disable {
+ self.well_known_adc = None;
+ } else if self.well_known_adc.is_none() {
+ self.well_known_adc = Some(WellKnownAdcCredentialProvider::new());
+ }
+ self
+ }
- async fn provide_credential(&self, ctx: &Context) ->
Result<Option<Self::Credential>> {
- // 1. Try environment variable
- if let Some(cred) = self.try_env_credentials(ctx).await? {
- return Ok(Some(cred));
+ /// Disable (true) or ensure enabled (false) the VM metadata provider.
+ pub fn disable_vm_metadata(mut self, disable: bool) -> Self {
+ if disable {
+ self.vm_metadata = None;
+ } else if self.vm_metadata.is_none() {
+ self.vm_metadata = Some(VmMetadataCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Build the `DefaultCredentialProvider` with the configured options.
+ pub fn build(self) -> DefaultCredentialProvider {
+ let mut chain = ProvideCredentialChain::new();
+
+ if let Some(p) = self.env_adc {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(EnvAdcCredentialProvider::new());
}
- // 2. Try well-known location
- if let Some(cred) = self.try_well_known_location(ctx).await? {
- return Ok(Some(cred));
+ if let Some(p) = self.well_known_adc {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(WellKnownAdcCredentialProvider::new());
}
- // 3. Try metadata server
- if let Some(cred) = self.try_metadata_server(ctx).await? {
- return Ok(Some(cred));
+ if let Some(p) = self.vm_metadata {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(VmMetadataCredentialProvider::new());
}
- debug!("no valid credential source found");
- Ok(None)
+ DefaultCredentialProvider::with_chain(chain)
}
}
@@ -240,8 +379,7 @@ mod tests {
#[tokio::test]
async fn test_default_provider_with_scope() {
- let provider = DefaultCredentialProvider::new()
-
.with_scope("https://www.googleapis.com/auth/devstorage.read_only");
+ let provider = DefaultCredentialProvider::builder().build();
// Even without valid credentials, this should not panic
let ctx = Context::new()
diff --git a/services/google/src/provide_credential/mod.rs
b/services/google/src/provide_credential/mod.rs
index ecb5773..d181afe 100644
--- a/services/google/src/provide_credential/mod.rs
+++ b/services/google/src/provide_credential/mod.rs
@@ -16,7 +16,8 @@
// under the License.
mod default;
-pub use default::DefaultCredentialProvider;
+#[allow(unused_imports)]
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
mod vm_metadata;
pub use vm_metadata::VmMetadataCredentialProvider;
diff --git a/services/google/src/provide_credential/vm_metadata.rs
b/services/google/src/provide_credential/vm_metadata.rs
index 0ddae76..c3993b7 100644
--- a/services/google/src/provide_credential/vm_metadata.rs
+++ b/services/google/src/provide_credential/vm_metadata.rs
@@ -33,12 +33,13 @@ struct VmMetadataTokenResponse {
#[derive(Debug, Clone, Default)]
pub struct VmMetadataCredentialProvider {
scope: Option<String>,
+ endpoint: Option<String>,
}
impl VmMetadataCredentialProvider {
/// Create a new VmMetadataCredentialProvider.
pub fn new() -> Self {
- Self { scope: None }
+ Self::default()
}
/// Set the OAuth2 scope.
@@ -46,6 +47,12 @@ impl VmMetadataCredentialProvider {
self.scope = Some(scope.into());
self
}
+
+ /// Set the metadata endpoint.
+ pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
+ self.endpoint = Some(endpoint.into());
+ self
+ }
}
#[async_trait::async_trait]
@@ -69,8 +76,10 @@ impl ProvideCredential for VmMetadataCredentialProvider {
);
// Allow overriding metadata host for testing
- let metadata_host = ctx
- .env_var("GCE_METADATA_HOST")
+ let metadata_host = self
+ .endpoint
+ .clone()
+ .or_else(|| ctx.env_var("GCE_METADATA_HOST"))
.unwrap_or_else(|| "metadata.google.internal".to_string());
let url = format!(
diff --git a/services/google/tests/credential_providers/external_account.rs
b/services/google/tests/credential_providers/external_account.rs
index 7944151..0058f26 100644
--- a/services/google/tests/credential_providers/external_account.rs
+++ b/services/google/tests/credential_providers/external_account.rs
@@ -41,13 +41,15 @@ async fn test_external_account_credential_provider() ->
Result<()> {
"Credential file must be external_account type"
);
- let ctx = create_test_context_with_env(HashMap::from_iter([(
- "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
- cred_path,
- )]));
+ let ctx = create_test_context_with_env(HashMap::from_iter([
+ ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+ (
+ "GOOGLE_SCOPE".to_string(),
+
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+ ),
+ ]));
- let provider = DefaultCredentialProvider::new()
- .with_scope("https://www.googleapis.com/auth/devstorage.read_write");
+ let provider = DefaultCredentialProvider::new();
let credential = provider
.provide_credential(&ctx)
@@ -83,13 +85,15 @@ async fn test_external_account_with_workload_identity() ->
Result<()> {
"Credential file must be external_account type for workload identity"
);
- let ctx = create_test_context_with_env(HashMap::from_iter([(
- "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
- cred_path,
- )]));
+ let ctx = create_test_context_with_env(HashMap::from_iter([
+ ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+ (
+ "GOOGLE_SCOPE".to_string(),
+
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+ ),
+ ]));
- let provider = DefaultCredentialProvider::new()
- .with_scope("https://www.googleapis.com/auth/devstorage.read_write");
+ let provider = DefaultCredentialProvider::new();
let credential = provider
.provide_credential(&ctx)
diff --git
a/services/google/tests/credential_providers/impersonated_service_account.rs
b/services/google/tests/credential_providers/impersonated_service_account.rs
index bd2b090..dab620e 100644
--- a/services/google/tests/credential_providers/impersonated_service_account.rs
+++ b/services/google/tests/credential_providers/impersonated_service_account.rs
@@ -42,13 +42,15 @@ async fn
test_impersonated_service_account_credential_provider() -> Result<()> {
"Credential file must be impersonated_service_account type"
);
- let ctx = create_test_context_with_env(HashMap::from_iter([(
- "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
- cred_path,
- )]));
+ let ctx = create_test_context_with_env(HashMap::from_iter([
+ ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+ (
+ "GOOGLE_SCOPE".to_string(),
+
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+ ),
+ ]));
- let provider = DefaultCredentialProvider::new()
- .with_scope("https://www.googleapis.com/auth/devstorage.read_write");
+ let provider = DefaultCredentialProvider::new();
let credential = provider
.provide_credential(&ctx)
@@ -88,13 +90,15 @@ async fn
test_impersonated_service_account_with_real_credentials() -> Result<()>
"Credential file must be impersonated_service_account type"
);
- let ctx = create_test_context_with_env(HashMap::from_iter([(
- "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
- cred_path,
- )]));
+ let ctx = create_test_context_with_env(HashMap::from_iter([
+ ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+ (
+ "GOOGLE_SCOPE".to_string(),
+
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+ ),
+ ]));
- let provider = DefaultCredentialProvider::new()
- .with_scope("https://www.googleapis.com/auth/devstorage.read_write");
+ let provider = DefaultCredentialProvider::new();
let credential = provider
.provide_credential(&ctx)
@@ -134,13 +138,15 @@ async fn
test_impersonated_service_account_with_delegates() -> Result<()> {
"Credential file must contain delegation chain"
);
- let ctx = create_test_context_with_env(HashMap::from_iter([(
- "GOOGLE_APPLICATION_CREDENTIALS".to_string(),
- cred_path,
- )]));
+ let ctx = create_test_context_with_env(HashMap::from_iter([
+ ("GOOGLE_APPLICATION_CREDENTIALS".to_string(), cred_path),
+ (
+ "GOOGLE_SCOPE".to_string(),
+
"https://www.googleapis.com/auth/devstorage.read_write".to_string(),
+ ),
+ ]));
- let provider = DefaultCredentialProvider::new()
- .with_scope("https://www.googleapis.com/auth/devstorage.read_write");
+ let provider = DefaultCredentialProvider::new();
let credential = provider
.provide_credential(&ctx)
diff --git a/services/huaweicloud-obs/src/provide_credential/default.rs
b/services/huaweicloud-obs/src/provide_credential/default.rs
index 497cc53..f341041 100644
--- a/services/huaweicloud-obs/src/provide_credential/default.rs
+++ b/services/huaweicloud-obs/src/provide_credential/default.rs
@@ -39,11 +39,14 @@ impl Default for DefaultCredentialProvider {
}
impl DefaultCredentialProvider {
- /// Create a new DefaultCredentialProvider
- pub fn new() -> Self {
- let chain =
ProvideCredentialChain::new().push(EnvCredentialProvider::new());
+ /// Create a builder to configure the default credential chain.
+ pub fn builder() -> DefaultCredentialProviderBuilder {
+ DefaultCredentialProviderBuilder::default()
+ }
- Self { chain }
+ /// Create a new DefaultCredentialProvider using the default chain.
+ pub fn new() -> Self {
+ Self::builder().build()
}
/// Create with a custom credential chain.
@@ -73,6 +76,53 @@ impl DefaultCredentialProvider {
}
}
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_env` to customize environment loading and
+/// `disable_env(bool)` to control participation, then `build()` to create the
provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+ env: Option<EnvCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+ /// Create a new builder with default state.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Configure the environment credential provider.
+ pub fn configure_env<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+ {
+ let p = self.env.take().unwrap_or_default();
+ self.env = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the environment provider.
+ pub fn disable_env(mut self, disable: bool) -> Self {
+ if disable {
+ self.env = None;
+ } else if self.env.is_none() {
+ self.env = Some(EnvCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Build the `DefaultCredentialProvider` with the configured options.
+ pub fn build(self) -> DefaultCredentialProvider {
+ let mut chain = ProvideCredentialChain::new();
+ if let Some(p) = self.env {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(EnvCredentialProvider::new());
+ }
+ DefaultCredentialProvider::with_chain(chain)
+ }
+}
+
#[async_trait]
impl ProvideCredential for DefaultCredentialProvider {
type Credential = Credential;
diff --git a/services/huaweicloud-obs/src/provide_credential/env.rs
b/services/huaweicloud-obs/src/provide_credential/env.rs
index aebc9d5..38616f8 100644
--- a/services/huaweicloud-obs/src/provide_credential/env.rs
+++ b/services/huaweicloud-obs/src/provide_credential/env.rs
@@ -25,13 +25,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// - `HUAWEI_CLOUD_ACCESS_KEY_ID`: The Huawei Cloud access key ID
/// - `HUAWEI_CLOUD_SECRET_ACCESS_KEY`: The Huawei Cloud secret access key
/// - `HUAWEI_CLOUD_SECURITY_TOKEN`: The Huawei Cloud security token (optional)
-#[derive(Debug, Default)]
-pub struct EnvCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct EnvCredentialProvider {}
impl EnvCredentialProvider {
/// Create a new EnvCredentialProvider.
pub fn new() -> Self {
- Self
+ Self {}
}
}
diff --git a/services/huaweicloud-obs/src/provide_credential/mod.rs
b/services/huaweicloud-obs/src/provide_credential/mod.rs
index 3a5102e..abb9bb3 100644
--- a/services/huaweicloud-obs/src/provide_credential/mod.rs
+++ b/services/huaweicloud-obs/src/provide_credential/mod.rs
@@ -24,7 +24,7 @@ mod config;
pub use config::ConfigCredentialProvider;
mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
mod env;
pub use env::EnvCredentialProvider;
diff --git a/services/oracle/src/provide_credential/config_file.rs
b/services/oracle/src/provide_credential/config_file.rs
index 2b02a77..2ea4f2f 100644
--- a/services/oracle/src/provide_credential/config_file.rs
+++ b/services/oracle/src/provide_credential/config_file.rs
@@ -29,13 +29,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// The config file path and profile name can be overridden using environment
variables:
/// - `OCI_CONFIG_FILE`: Override the config file path
/// - `OCI_PROFILE`: Override the profile name (default is "DEFAULT")
-#[derive(Debug, Default)]
-pub struct ConfigFileCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct ConfigFileCredentialProvider {}
impl ConfigFileCredentialProvider {
/// Create a new ConfigFileCredentialProvider.
pub fn new() -> Self {
- Self
+ Self {}
}
}
diff --git a/services/oracle/src/provide_credential/default.rs
b/services/oracle/src/provide_credential/default.rs
index ae86c29..83c7c3c 100644
--- a/services/oracle/src/provide_credential/default.rs
+++ b/services/oracle/src/provide_credential/default.rs
@@ -31,12 +31,18 @@ pub struct DefaultCredentialProvider {
}
impl DefaultCredentialProvider {
- /// Create a new DefaultCredentialProvider
+ /// Create a builder to configure the default credential chain.
+ pub fn builder() -> DefaultCredentialProviderBuilder {
+ DefaultCredentialProviderBuilder::default()
+ }
+
+ /// Create a new DefaultCredentialProvider using the default chain.
pub fn new() -> Self {
- let chain = ProvideCredentialChain::new()
- .push(EnvCredentialProvider::new())
- .push(ConfigFileCredentialProvider::new());
+ Self::builder().build()
+ }
+ /// Create with a custom credential chain.
+ pub fn with_chain(chain: ProvideCredentialChain<Credential>) -> Self {
Self { chain }
}
@@ -62,6 +68,80 @@ impl DefaultCredentialProvider {
}
}
+/// Builder for `DefaultCredentialProvider`.
+///
+/// Use `configure_env` / `configure_config_file` to customize providers, and
+/// `disable_env(bool)` / `disable_config_file(bool)` to control participation.
+/// Finish with `build()` to construct the provider.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+ env: Option<EnvCredentialProvider>,
+ config_file: Option<ConfigFileCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+ /// Create a new builder with default state.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Configure the environment credential provider.
+ pub fn configure_env<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+ {
+ let p = self.env.take().unwrap_or_default();
+ self.env = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the environment provider.
+ pub fn disable_env(mut self, disable: bool) -> Self {
+ if disable {
+ self.env = None;
+ } else if self.env.is_none() {
+ self.env = Some(EnvCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the config-file credential provider.
+ pub fn configure_config_file<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(ConfigFileCredentialProvider) ->
ConfigFileCredentialProvider,
+ {
+ let p = self.config_file.take().unwrap_or_default();
+ self.config_file = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the config-file provider.
+ pub fn disable_config_file(mut self, disable: bool) -> Self {
+ if disable {
+ self.config_file = None;
+ } else if self.config_file.is_none() {
+ self.config_file = Some(ConfigFileCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Build the `DefaultCredentialProvider` with the configured options.
+ pub fn build(self) -> DefaultCredentialProvider {
+ let mut chain = ProvideCredentialChain::new();
+ if let Some(p) = self.env {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(EnvCredentialProvider::new());
+ }
+ if let Some(p) = self.config_file {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(ConfigFileCredentialProvider::new());
+ }
+ DefaultCredentialProvider::with_chain(chain)
+ }
+}
+
#[async_trait]
impl ProvideCredential for DefaultCredentialProvider {
type Credential = Credential;
diff --git a/services/oracle/src/provide_credential/env.rs
b/services/oracle/src/provide_credential/env.rs
index 05357b6..f92dbd8 100644
--- a/services/oracle/src/provide_credential/env.rs
+++ b/services/oracle/src/provide_credential/env.rs
@@ -26,13 +26,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// - `OCI_TENANCY`: The Oracle Cloud tenancy ID
/// - `OCI_KEY_FILE`: The path to the private key file
/// - `OCI_FINGERPRINT`: The fingerprint of the key
-#[derive(Debug, Default)]
-pub struct EnvCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct EnvCredentialProvider {}
impl EnvCredentialProvider {
/// Create a new EnvCredentialProvider.
pub fn new() -> Self {
- Self
+ Self {}
}
}
diff --git a/services/oracle/src/provide_credential/mod.rs
b/services/oracle/src/provide_credential/mod.rs
index 67f98f0..fb5b63f 100644
--- a/services/oracle/src/provide_credential/mod.rs
+++ b/services/oracle/src/provide_credential/mod.rs
@@ -32,4 +32,4 @@ mod config_file;
pub use config_file::ConfigFileCredentialProvider;
mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
diff --git
a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
index 61e3ea0..c0ee0dc 100644
---
a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
+++
b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs
@@ -26,7 +26,7 @@ use reqsign_core::{Context, ProvideCredential};
use serde::{Deserialize, Serialize};
/// Loader that loads credential via AssumeRoleWithWebIdentity.
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
pub struct AssumeRoleWithWebIdentityCredentialProvider {}
impl AssumeRoleWithWebIdentityCredentialProvider {
diff --git a/services/tencent-cos/src/provide_credential/default.rs
b/services/tencent-cos/src/provide_credential/default.rs
index 9e1fbbe..1dcf191 100644
--- a/services/tencent-cos/src/provide_credential/default.rs
+++ b/services/tencent-cos/src/provide_credential/default.rs
@@ -15,6 +15,9 @@
// specific language governing permissions and limitations
// under the License.
+use crate::provide_credential::{
+ AssumeRoleWithWebIdentityCredentialProvider, EnvCredentialProvider,
+};
use crate::Credential;
use async_trait::async_trait;
use reqsign_core::{Context, ProvideCredential, ProvideCredentialChain, Result};
@@ -36,13 +39,14 @@ impl Default for DefaultCredentialProvider {
}
impl DefaultCredentialProvider {
- /// Create a new DefaultCredentialProvider
- pub fn new() -> Self {
- let chain = ProvideCredentialChain::new()
- .push(super::EnvCredentialProvider::new())
- .push(super::AssumeRoleWithWebIdentityCredentialProvider::new());
+ /// Create a builder to configure the default credential chain.
+ pub fn builder() -> DefaultCredentialProviderBuilder {
+ DefaultCredentialProviderBuilder::default()
+ }
- Self { chain }
+ /// Create a new DefaultCredentialProvider using the default chain.
+ pub fn new() -> Self {
+ Self::builder().build()
}
/// Create with a custom credential chain.
@@ -72,6 +76,83 @@ impl DefaultCredentialProvider {
}
}
+/// Builder for `DefaultCredentialProvider`.
+///
+/// - Use `configure_*` to customize a provider.
+/// - Use `disable_*(bool)` to disable (true) or ensure enabled (false).
+/// - Call `build()` to construct the provider in the default order.
+#[derive(Default)]
+pub struct DefaultCredentialProviderBuilder {
+ env: Option<EnvCredentialProvider>,
+ assume_role: Option<AssumeRoleWithWebIdentityCredentialProvider>,
+}
+
+impl DefaultCredentialProviderBuilder {
+ /// Create a new builder with default state.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Configure the environment credential provider.
+ pub fn configure_env<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
+ {
+ let p = self.env.take().unwrap_or_default();
+ self.env = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the environment provider.
+ pub fn disable_env(mut self, disable: bool) -> Self {
+ if disable {
+ self.env = None;
+ } else if self.env.is_none() {
+ self.env = Some(EnvCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Configure the web-identity assume-role credential provider.
+ pub fn configure_assume_role<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(
+ AssumeRoleWithWebIdentityCredentialProvider,
+ ) -> AssumeRoleWithWebIdentityCredentialProvider,
+ {
+ let p = self.assume_role.take().unwrap_or_default();
+ self.assume_role = Some(f(p));
+ self
+ }
+
+ /// Disable (true) or ensure enabled (false) the web-identity assume-role
provider.
+ pub fn disable_assume_role(mut self, disable: bool) -> Self {
+ if disable {
+ self.assume_role = None;
+ } else if self.assume_role.is_none() {
+ self.assume_role =
Some(AssumeRoleWithWebIdentityCredentialProvider::new());
+ }
+ self
+ }
+
+ /// Build the `DefaultCredentialProvider` with the configured options.
+ pub fn build(self) -> DefaultCredentialProvider {
+ let mut chain = ProvideCredentialChain::new();
+ if let Some(p) = self.env {
+ chain = chain.push(p);
+ } else {
+ chain = chain.push(EnvCredentialProvider::new());
+ }
+ if let Some(p) = self.assume_role {
+ chain = chain.push(p);
+ } else {
+ chain =
chain.push(AssumeRoleWithWebIdentityCredentialProvider::new());
+ }
+
+ DefaultCredentialProvider::with_chain(chain)
+ }
+}
+
#[async_trait]
impl ProvideCredential for DefaultCredentialProvider {
type Credential = Credential;
diff --git a/services/tencent-cos/src/provide_credential/env.rs
b/services/tencent-cos/src/provide_credential/env.rs
index 3617bce..ba61f6f 100644
--- a/services/tencent-cos/src/provide_credential/env.rs
+++ b/services/tencent-cos/src/provide_credential/env.rs
@@ -25,13 +25,13 @@ use reqsign_core::{Context, ProvideCredential, Result};
/// - `TENCENTCLOUD_SECRET_ID` or `TKE_SECRET_ID`: The Tencent Cloud secret ID
/// - `TENCENTCLOUD_SECRET_KEY` or `TKE_SECRET_KEY`: The Tencent Cloud secret
key
/// - `TENCENTCLOUD_TOKEN`, `TENCENTCLOUD_SECURITY_TOKEN`, or
`QCLOUD_SECRET_TOKEN`: The security token (optional)
-#[derive(Debug, Default)]
-pub struct EnvCredentialProvider;
+#[derive(Debug, Default, Clone)]
+pub struct EnvCredentialProvider {}
impl EnvCredentialProvider {
/// Create a new EnvCredentialProvider.
pub fn new() -> Self {
- Self
+ Self {}
}
}
diff --git a/services/tencent-cos/src/provide_credential/mod.rs
b/services/tencent-cos/src/provide_credential/mod.rs
index ecc9142..e5171b9 100644
--- a/services/tencent-cos/src/provide_credential/mod.rs
+++ b/services/tencent-cos/src/provide_credential/mod.rs
@@ -24,7 +24,7 @@ mod config;
pub use config::ConfigCredentialProvider;
mod default;
-pub use default::DefaultCredentialProvider;
+pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
mod assume_role_with_web_identity;
pub use
assume_role_with_web_identity::AssumeRoleWithWebIdentityCredentialProvider;