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/08/24 07:59:48 UTC
(opendal-reqsign) 01/01: fix(aws): configure S3 Express session provider grants
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch xuanwo/s3-express-session-provider-mode
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git
commit daf120784eded0a550fe119d5f5295772b87c1c3
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Mon Aug 24 15:59:36 2026 +0800
fix(aws): configure S3 Express session provider grants
---
.../src/provide_credential/s3_express_session.rs | 96 +++++++++++++++++++---
1 file changed, 86 insertions(+), 10 deletions(-)
diff --git a/services/aws-v4/src/provide_credential/s3_express_session.rs b/services/aws-v4/src/provide_credential/s3_express_session.rs
index 06d8a8a..96d7ad0 100644
--- a/services/aws-v4/src/provide_credential/s3_express_session.rs
+++ b/services/aws-v4/src/provide_credential/s3_express_session.rs
@@ -694,15 +694,15 @@ impl GrantCredential for S3ExpressSessionGranter {
}
}
-/// S3 Express fixed-flow credential provider.
+/// S3 Express session credential provider.
///
-/// This compatibility API loads its source through [`ProvideCredential`] and
-/// creates a `ReadWrite` session on every call. New explicit credential-vending
-/// flows should use [`S3ExpressSessionGranter`] through
-/// [`reqsign_core::Granter`].
+/// This API loads its source through [`ProvideCredential`] and creates a session
+/// on every call. It defaults to an explicit `ReadWrite` session for compatibility;
+/// use [`Self::with_grant`] to select another mode.
pub struct S3ExpressSessionProvider {
bucket: String,
region: Option<String>,
+ grant: S3ExpressSessionGrantSelection,
base_provider: Box<dyn ProvideCredentialDyn<Credential = Credential>>,
}
@@ -714,7 +714,7 @@ impl Debug for S3ExpressSessionProvider {
}
impl S3ExpressSessionProvider {
- /// Create a fixed `ReadWrite` session provider for a directory bucket.
+ /// Create a session provider that defaults to `ReadWrite` for a directory bucket.
///
/// The legacy constructor infers the Region from well-known AWS Zone ID
/// prefixes. Use [`S3ExpressSessionProvider::with_region`] when the Zone is
@@ -727,6 +727,7 @@ impl S3ExpressSessionProvider {
Self {
bucket: bucket.into(),
region: None,
+ grant: S3ExpressSessionGrant::new(S3ExpressSessionMode::ReadWrite).into(),
base_provider: Box::new(provider),
}
}
@@ -737,6 +738,12 @@ impl S3ExpressSessionProvider {
self
}
+ /// Select how S3 Express determines the `CreateSession` permission mode.
+ pub fn with_grant(mut self, grant: impl Into<S3ExpressSessionGrantSelection>) -> Self {
+ self.grant = grant.into();
+ self
+ }
+
fn operation(&self) -> Result<S3ExpressSessionGranter> {
let zone_id = zone_id_from_bucket(&self.bucket)?;
let region = match &self.region {
@@ -748,10 +755,7 @@ impl S3ExpressSessionProvider {
})?,
};
let config = S3ExpressSessionConfig::from_bucket(&self.bucket, region)?;
- Ok(S3ExpressSessionGranter::new(
- config,
- S3ExpressSessionGrant::new(S3ExpressSessionMode::ReadWrite),
- ))
+ Ok(S3ExpressSessionGranter::new(config, self.grant.clone()))
}
}
@@ -2298,6 +2302,78 @@ mod tests {
);
}
+ #[tokio::test]
+ async fn configurable_provider_refreshes_maximum_allowed_sessions_for_signer() {
+ let first_expiration = (Timestamp::now() + Duration::from_secs(110)).format_rfc3339_zulu();
+ let second_expiration = (Timestamp::now() + Duration::from_secs(300)).format_rfc3339_zulu();
+ let http = MockHttpSend::new([
+ success_response(
+ "first-session-access-key",
+ "first-session-secret-key",
+ "first-session-token",
+ &first_expiration,
+ ),
+ success_response(
+ "second-session-access-key",
+ "second-session-secret-key",
+ "second-session-token",
+ &second_expiration,
+ ),
+ ]);
+ let ctx = Context::new().with_http_send(http.clone());
+ let (source_provider, source_calls) = FixedCredentialProvider::new(source_credential());
+ let provider = S3ExpressSessionProvider::new("example--usw2-az1--x-s3", source_provider)
+ .with_grant(S3ExpressSessionGrant::maximum_allowed());
+ let signer = Signer::new(
+ ctx,
+ provider,
+ crate::RequestSigner::new("s3express", "us-west-2"),
+ );
+
+ let mut first = Request::get(
+ "https://example--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com/first",
+ )
+ .body(())
+ .expect("first request must build")
+ .into_parts()
+ .0;
+ signer
+ .sign(&mut first, None)
+ .await
+ .expect("first request must use a maximum-allowed session");
+ assert_eq!(
+ first.headers["x-amz-s3session-token"],
+ "first-session-token"
+ );
+
+ let mut second = Request::get(
+ "https://example--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com/second",
+ )
+ .body(())
+ .expect("second request must build")
+ .into_parts()
+ .0;
+ signer
+ .sign(&mut second, None)
+ .await
+ .expect("second request must refresh the maximum-allowed session");
+ assert_eq!(
+ second.headers["x-amz-s3session-token"],
+ "second-session-token"
+ );
+
+ assert_eq!(source_calls.load(Ordering::SeqCst), 2);
+ assert_eq!(http.calls.load(Ordering::SeqCst), 2);
+ for index in 0..2 {
+ assert!(
+ !http
+ .request(index)
+ .headers
+ .contains_key("x-amz-create-session-mode")
+ );
+ }
+ }
+
#[tokio::test]
async fn compatibility_provider_uses_explicit_region_for_new_zone_prefix() {
let response = success_response(