You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/03/18 10:57:10 UTC
(opendal-reqsign) branch main updated: refactor(aliyun-oss): migrate default credential provider API (#716)
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 fc1e491 refactor(aliyun-oss): migrate default credential provider API (#716)
fc1e491 is described below
commit fc1e49108f8e3cc3c1c001983ac2e9b03df58c64
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Wed Mar 18 18:57:06 2026 +0800
refactor(aliyun-oss): migrate default credential provider API (#716)
Migrate reqsign-aliyun-oss to the new DefaultCredentialProvider slot API
so the public builder matches the repository design. This removes the
legacy configure/disable surface, renames the OIDC slot explicitly, and
updates Aliyun-specific docs and tests to cover slot removal semantics.
---
services/aliyun-oss/README.md | 114 +++++++-------
services/aliyun-oss/examples/oss_operations.rs | 14 +-
services/aliyun-oss/src/lib.rs | 22 ++-
.../aliyun-oss/src/provide_credential/default.rs | 163 ++++++++++++++++-----
4 files changed, 202 insertions(+), 111 deletions(-)
diff --git a/services/aliyun-oss/README.md b/services/aliyun-oss/README.md
index b56f9ce..b96df82 100644
--- a/services/aliyun-oss/README.md
+++ b/services/aliyun-oss/README.md
@@ -9,32 +9,48 @@ This crate provides signing support for Alibaba Cloud Object Storage Service (OS
## Quick Start
```rust
-use reqsign_aliyun_oss::{Builder, Config, DefaultLoader};
-use reqsign_core::{Context, Signer};
-
-// Create context and signer
-let ctx = Context::default();
-let config = Config::default()
- .region("oss-cn-beijing")
- .from_env();
-let loader = DefaultLoader::new(config);
-let builder = Builder::new();
-let signer = Signer::new(ctx, loader, builder);
-
-// Sign requests
-let mut req = http::Request::get("https://bucket.oss-cn-beijing.aliyuncs.com/object.txt")
- .body(())
- .unwrap()
- .into_parts()
- .0;
-
-signer.sign(&mut req, None).await?;
+use reqsign_aliyun_oss::{
+ AssumeRoleWithOidcCredentialProvider, DefaultCredentialProvider, EnvCredentialProvider,
+ RequestSigner, StaticCredentialProvider,
+};
+use reqsign_core::{Context, Result, Signer};
+use reqsign_file_read_tokio::TokioFileRead;
+use reqsign_http_send_reqwest::ReqwestHttpSend;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let ctx = Context::new()
+ .with_file_read(TokioFileRead)
+ .with_http_send(ReqwestHttpSend::default());
+
+ let loader = DefaultCredentialProvider::builder()
+ .env(EnvCredentialProvider::new())
+ .oidc(AssumeRoleWithOidcCredentialProvider::new())
+ .build();
+
+ // Or use static credentials:
+ // let loader = StaticCredentialProvider::new(
+ // "your-access-key-id",
+ // "your-access-key-secret",
+ // );
+
+ let signer = Signer::new(ctx, loader, RequestSigner::new("bucket"));
+
+ let mut req = http::Request::get("https://bucket.oss-cn-beijing.aliyuncs.com/object.txt")
+ .body(())
+ .unwrap()
+ .into_parts()
+ .0;
+
+ signer.sign(&mut req, None).await?;
+ Ok(())
+}
```
## Features
- **HMAC-SHA1 Signing**: Complete implementation of Aliyun's signing algorithm
-- **Multiple Credential Sources**: Environment, config files, ECS RAM roles
+- **Multiple Credential Sources**: Environment variables and OIDC-based STS exchange
- **STS Support**: Temporary credentials via Security Token Service
- **All OSS Operations**: Object, bucket, and multipart operations
@@ -48,44 +64,21 @@ export ALIBABA_CLOUD_ACCESS_KEY_SECRET=your-access-key-secret
export ALIBABA_CLOUD_SECURITY_TOKEN=your-sts-token # Optional
```
-### Configuration File
-
-Reads from `~/.aliyun/config.json`:
-
-```json
-{
- "current": "default",
- "profiles": [{
- "name": "default",
- "mode": "AK",
- "access_key_id": "your-access-key-id",
- "access_key_secret": "your-access-key-secret",
- "region_id": "cn-beijing"
- }]
-}
-```
-
-### ECS RAM Role
-
-Automatically used when running on Aliyun ECS with RAM role attached:
-
-```rust
-let config = Config::default()
- .region("oss-cn-beijing");
-// Credentials loaded automatically from metadata service
-```
-
### STS AssumeRole with OIDC
For Kubernetes/ACK environments:
```rust
-let config = Config::default()
- .role_arn("acs:ram::123456789012:role/MyRole")
- .oidc_provider_arn("acs:ram::123456789012:oidc-provider/MyProvider")
- .oidc_token_file_path("/var/run/secrets/token");
-
-let loader = AssumeRoleWithOidcLoader::new(config);
+use reqsign_aliyun_oss::{AssumeRoleWithOidcCredentialProvider, DefaultCredentialProvider};
+
+// Set ALIBABA_CLOUD_ROLE_ARN, ALIBABA_CLOUD_OIDC_PROVIDER_ARN,
+// and ALIBABA_CLOUD_OIDC_TOKEN_FILE in the environment.
+let loader = DefaultCredentialProvider::builder()
+ .no_env()
+ .oidc(
+ AssumeRoleWithOidcCredentialProvider::new().with_role_session_name("my-session"),
+ )
+ .build();
```
The session name defaults to `reqsign`. To customize it, set `ALIBABA_CLOUD_ROLE_SESSION_NAME` or use `AssumeRoleWithOidcCredentialProvider::with_role_session_name`.
@@ -201,20 +194,17 @@ Common OSS regions:
### Custom Credentials
```rust
-let config = Config::default()
- .access_key_id("your-access-key-id")
- .access_key_secret("your-access-key-secret")
- .security_token("optional-sts-token")
- .region("oss-cn-beijing");
+use reqsign_aliyun_oss::StaticCredentialProvider;
+
+let loader = StaticCredentialProvider::new("your-access-key-id", "your-access-key-secret");
```
### Force Specific Loader
```rust
-// Use only config loader
-use reqsign_aliyun_oss::ConfigLoader;
+use reqsign_aliyun_oss::DefaultCredentialProvider;
-let loader = ConfigLoader::new(config);
+let loader = DefaultCredentialProvider::builder().no_oidc().build();
```
## License
diff --git a/services/aliyun-oss/examples/oss_operations.rs b/services/aliyun-oss/examples/oss_operations.rs
index 412cc91..3de6745 100644
--- a/services/aliyun-oss/examples/oss_operations.rs
+++ b/services/aliyun-oss/examples/oss_operations.rs
@@ -15,7 +15,10 @@
// specific language governing permissions and limitations
// under the License.
-use reqsign_aliyun_oss::{DefaultCredentialProvider, RequestSigner, StaticCredentialProvider};
+use reqsign_aliyun_oss::{
+ AssumeRoleWithOidcCredentialProvider, DefaultCredentialProvider, EnvCredentialProvider,
+ RequestSigner, StaticCredentialProvider,
+};
use reqsign_core::Result;
use reqsign_core::{Context, OsEnv, Signer};
use reqsign_file_read_tokio::TokioFileRead;
@@ -59,10 +62,11 @@ async fn main() -> Result<()> {
StaticCredentialProvider::new("LTAI4GDemoAccessKeyId", "DemoAccessKeySecretForExample");
Signer::new(ctx.clone(), loader, builder)
} else {
- // This will try multiple sources:
- // 1. Environment variables (ALIBABA_CLOUD_ACCESS_KEY_ID, ALIBABA_CLOUD_ACCESS_KEY_SECRET)
- // 2. Assume Role with OIDC (if configured)
- let loader = DefaultCredentialProvider::new();
+ // Build the default env -> oidc chain explicitly via slot APIs.
+ let loader = DefaultCredentialProvider::builder()
+ .env(EnvCredentialProvider::new())
+ .oidc(AssumeRoleWithOidcCredentialProvider::new())
+ .build();
Signer::new(ctx.clone(), loader, builder)
};
diff --git a/services/aliyun-oss/src/lib.rs b/services/aliyun-oss/src/lib.rs
index 295687a..1cf179a 100644
--- a/services/aliyun-oss/src/lib.rs
+++ b/services/aliyun-oss/src/lib.rs
@@ -29,7 +29,10 @@
//! ## Quick Start
//!
//! ```no_run
-//! use reqsign_aliyun_oss::{RequestSigner, DefaultCredentialProvider, StaticCredentialProvider};
+//! use reqsign_aliyun_oss::{
+//! AssumeRoleWithOidcCredentialProvider, DefaultCredentialProvider, EnvCredentialProvider,
+//! RequestSigner, StaticCredentialProvider,
+//! };
//! use reqsign_core::{Context, Signer, Result};
//! use reqsign_file_read_tokio::TokioFileRead;
//! use reqsign_http_send_reqwest::ReqwestHttpSend;
@@ -41,8 +44,11 @@
//! .with_file_read(TokioFileRead::default())
//! .with_http_send(ReqwestHttpSend::default());
//!
-//! // Create credential loader - uses environment variables by default
-//! let loader = DefaultCredentialProvider::new();
+//! // Create credential loader with the default env -> oidc chain.
+//! let loader = DefaultCredentialProvider::builder()
+//! .env(EnvCredentialProvider::new())
+//! .oidc(AssumeRoleWithOidcCredentialProvider::new())
+//! .build();
//!
//! // Or use static credentials
//! // let loader = StaticCredentialProvider::new(
@@ -131,13 +137,17 @@
//! ### STS AssumeRole
//!
//! ```no_run
-//! use reqsign_aliyun_oss::AssumeRoleWithOidcCredentialProvider;
+//! use reqsign_aliyun_oss::{AssumeRoleWithOidcCredentialProvider, DefaultCredentialProvider};
//!
//! // Use environment variables
//! // Set ALIBABA_CLOUD_ROLE_ARN, ALIBABA_CLOUD_OIDC_PROVIDER_ARN, ALIBABA_CLOUD_OIDC_TOKEN_FILE
//! // Optionally set ALIBABA_CLOUD_ROLE_SESSION_NAME
-//! let loader = AssumeRoleWithOidcCredentialProvider::new()
-//! .with_role_session_name("my-session");
+//! let loader = DefaultCredentialProvider::builder()
+//! .no_env()
+//! .oidc(
+//! AssumeRoleWithOidcCredentialProvider::new().with_role_session_name("my-session"),
+//! )
+//! .build();
//! ```
//!
//! ### Custom Endpoints
diff --git a/services/aliyun-oss/src/provide_credential/default.rs b/services/aliyun-oss/src/provide_credential/default.rs
index fb1b3c8..8e24da7 100644
--- a/services/aliyun-oss/src/provide_credential/default.rs
+++ b/services/aliyun-oss/src/provide_credential/default.rs
@@ -76,12 +76,20 @@ 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)]
+/// Use `slot(provider)` to override a default provider or `no_slot()` to
+/// remove it from the chain before calling `build()`.
pub struct DefaultCredentialProviderBuilder {
env: Option<EnvCredentialProvider>,
- assume_role: Option<AssumeRoleWithOidcCredentialProvider>,
+ oidc: Option<AssumeRoleWithOidcCredentialProvider>,
+}
+
+impl Default for DefaultCredentialProviderBuilder {
+ fn default() -> Self {
+ Self {
+ env: Some(EnvCredentialProvider::new()),
+ oidc: Some(AssumeRoleWithOidcCredentialProvider::new()),
+ }
+ }
}
impl DefaultCredentialProviderBuilder {
@@ -90,43 +98,27 @@ impl DefaultCredentialProviderBuilder {
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));
+ /// Set the environment credential provider slot.
+ pub fn env(mut self, provider: EnvCredentialProvider) -> Self {
+ self.env = Some(provider);
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());
- }
+ /// Remove the environment credential provider slot.
+ pub fn no_env(mut self) -> Self {
+ self.env = None;
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));
+ /// Set the OIDC credential provider slot.
+ pub fn oidc(mut self, provider: AssumeRoleWithOidcCredentialProvider) -> Self {
+ self.oidc = Some(provider);
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());
- }
+ /// Remove the OIDC credential provider slot.
+ pub fn no_oidc(mut self) -> Self {
+ self.oidc = None;
self
}
@@ -135,13 +127,9 @@ impl DefaultCredentialProviderBuilder {
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 {
+ if let Some(p) = self.oidc {
chain = chain.push(p);
- } else {
- chain = chain.push(AssumeRoleWithOidcCredentialProvider::new());
}
DefaultCredentialProvider::with_chain(chain)
}
@@ -158,7 +146,8 @@ impl ProvideCredential for DefaultCredentialProvider {
mod tests {
use super::*;
use crate::constants::*;
- use reqsign_core::{OsEnv, StaticEnv};
+ use bytes::Bytes;
+ use reqsign_core::{FileRead, HttpSend, OsEnv, StaticEnv};
use reqsign_file_read_tokio::TokioFileRead;
use reqsign_http_send_reqwest::ReqwestHttpSend;
use std::collections::HashMap;
@@ -244,4 +233,102 @@ mod tests {
assert_eq!("secret_access_key", credential.access_key_secret);
assert_eq!("security_token", credential.security_token.unwrap());
}
+
+ #[tokio::test]
+ async fn test_builder_no_env_removes_env_provider() {
+ let ctx = Context::new().with_env(StaticEnv {
+ home_dir: None,
+ envs: HashMap::from_iter([
+ (
+ ALIBABA_CLOUD_ACCESS_KEY_ID.to_string(),
+ "access_key_id".to_string(),
+ ),
+ (
+ ALIBABA_CLOUD_ACCESS_KEY_SECRET.to_string(),
+ "secret_access_key".to_string(),
+ ),
+ ]),
+ });
+
+ let credential = DefaultCredentialProvider::builder()
+ .no_oidc()
+ .build()
+ .provide_credential(&ctx)
+ .await
+ .unwrap();
+ assert!(credential.is_some());
+
+ let credential = DefaultCredentialProvider::builder()
+ .no_env()
+ .no_oidc()
+ .build()
+ .provide_credential(&ctx)
+ .await
+ .unwrap();
+ assert!(credential.is_none());
+ }
+
+ #[tokio::test]
+ async fn test_builder_no_oidc_removes_oidc_provider() {
+ #[derive(Debug)]
+ struct TestFileRead;
+
+ impl FileRead for TestFileRead {
+ async fn file_read(&self, path: &str) -> Result<Vec<u8>> {
+ assert_eq!(path, "/mock/token");
+ Ok(b"token".to_vec())
+ }
+ }
+
+ #[derive(Debug)]
+ struct TestHttpSend;
+
+ impl HttpSend for TestHttpSend {
+ async fn http_send(&self, _req: http::Request<Bytes>) -> Result<http::Response<Bytes>> {
+ http::Response::builder()
+ .status(http::StatusCode::OK)
+ .body(Bytes::from_static(br#"{"Credentials":{"SecurityToken":"security_token","Expiration":"2124-05-25T11:45:17Z","AccessKeySecret":"secret_access_key","AccessKeyId":"access_key_id"}}"#))
+ .map_err(Into::into)
+ }
+ }
+
+ let ctx = Context::new()
+ .with_file_read(TestFileRead)
+ .with_http_send(TestHttpSend)
+ .with_env(StaticEnv {
+ home_dir: None,
+ envs: HashMap::from_iter([
+ (
+ ALIBABA_CLOUD_OIDC_TOKEN_FILE.to_string(),
+ "/mock/token".to_string(),
+ ),
+ (
+ ALIBABA_CLOUD_ROLE_ARN.to_string(),
+ "acs:ram::123456789012:role/test-role".to_string(),
+ ),
+ (
+ ALIBABA_CLOUD_OIDC_PROVIDER_ARN.to_string(),
+ "acs:ram::123456789012:oidc-provider/test-provider".to_string(),
+ ),
+ ]),
+ });
+
+ let credential = DefaultCredentialProvider::builder()
+ .no_env()
+ .oidc(AssumeRoleWithOidcCredentialProvider::new())
+ .build()
+ .provide_credential(&ctx)
+ .await
+ .unwrap();
+ assert!(credential.is_some());
+
+ let credential = DefaultCredentialProvider::builder()
+ .no_env()
+ .no_oidc()
+ .build()
+ .provide_credential(&ctx)
+ .await
+ .unwrap();
+ assert!(credential.is_none());
+ }
}