From 0a75b4183c8a6c4ac11d57efe75b6f8da1110e2f Mon Sep 17 00:00:00 2001 From: ysaito1001 Date: Tue, 20 Feb 2024 13:47:18 -0600 Subject: [PATCH] Add S3 Express identity cache (#3390) ## Motivation and Context Adds a default implementation for S3 Express identity cache. ## Description This PR adds the said cache for S3 Express. This cache is not configurable from outside and solely owned by the default S3 Express identity provider. It is implemented in terms of an LRU cache keyed on a string generated by `sha256hmac(random 64-byte key, access_key_id + secret_key) + bucket_name` (note: `access_key_id` and `secret_key` are for a customer's credentials but not for a retrieved `create_session` API token). Cache values are of type `ExpiringCache` that contains a session token retrieved by S3's `create_session` API. When a customer is trying to use a cached session token but if it has expired, `ExpiringCache` calls the S3's `create_session` API, stores in it a new session token, and returns it to the customer. ## Testing Added unit tests for `S3IdentityCache` and a connection recording test for `list-objects-v2` running against both express and regular buckets to exercise a use case where a customer is switching between those buckets. ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ --------- Co-authored-by: John DiSanti Co-authored-by: Russell Cohen Co-authored-by: AWS SDK Rust Bot Co-authored-by: AWS SDK Rust Bot <97246200+aws-sdk-rust-ci@users.noreply.github.com> Co-authored-by: Zelda Hessler --- aws/rust-runtime/aws-inlineable/Cargo.toml | 4 + .../aws-inlineable/src/s3_express.rs | 461 +++++++++++++- .../customize/s3/S3ExpressDecorator.kt | 59 +- .../s3/tests/data/express/mixed-auths.json | 585 ++++++++++++++++++ aws/sdk/integration-tests/s3/tests/express.rs | 58 ++ .../codegen/core/rustlang/CargoDependency.kt | 3 + 6 files changed, 1132 insertions(+), 38 deletions(-) create mode 100644 aws/sdk/integration-tests/s3/tests/data/express/mixed-auths.json diff --git a/aws/rust-runtime/aws-inlineable/Cargo.toml b/aws/rust-runtime/aws-inlineable/Cargo.toml index 85b6c440cb..401bf4cca6 100644 --- a/aws/rust-runtime/aws-inlineable/Cargo.toml +++ b/aws/rust-runtime/aws-inlineable/Cargo.toml @@ -22,10 +22,14 @@ aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", featur aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types", features = ["http-body-0-4-x"] } bytes = "1" +fastrand = "2.0.0" hex = "0.4.3" http = "0.2.9" http-body = "0.4.5" +hmac = "0.12" +lru = "0.12.2" ring = "0.17.5" +sha2 = "0.10" tokio = "1.23.1" tracing = "0.1" diff --git a/aws/rust-runtime/aws-inlineable/src/s3_express.rs b/aws/rust-runtime/aws-inlineable/src/s3_express.rs index 3388145112..6608707d2e 100644 --- a/aws/rust-runtime/aws-inlineable/src/s3_express.rs +++ b/aws/rust-runtime/aws-inlineable/src/s3_express.rs @@ -93,23 +93,408 @@ pub(crate) mod auth { /// Supporting code for S3 Express identity cache pub(crate) mod identity_cache { + use aws_credential_types::Credentials; + use aws_smithy_async::time::SharedTimeSource; + use aws_smithy_runtime::expiring_cache::ExpiringCache; + use aws_smithy_runtime_api::box_error::BoxError; + use aws_smithy_runtime_api::client::identity::Identity; + use aws_smithy_types::DateTime; + use fastrand::Rng; + use hmac::{digest::FixedOutput, Hmac, Mac}; + use lru::LruCache; + use sha2::Sha256; + use std::fmt; + use std::future::Future; + use std::hash::Hash; + use std::num::NonZeroUsize; + use std::sync::Mutex; + use std::time::{Duration, SystemTime}; + + pub(crate) const DEFAULT_MAX_CACHE_CAPACITY: usize = 100; + pub(crate) const DEFAULT_BUFFER_TIME: Duration = Duration::from_secs(10); + + #[derive(Clone, Eq, PartialEq, Hash)] + pub(crate) struct CacheKey(String); + /// The caching implementation for S3 Express identity. /// /// While customers can either disable S3 Express itself or provide a custom S3 Express identity /// provider, configuring S3 Express identity cache is not supported. Thus, this is _the_ /// implementation of S3 Express identity cache. - #[derive(Debug)] - pub(crate) struct S3ExpressIdentityCache; -} + pub(crate) struct S3ExpressIdentityCache { + inner: Mutex>>, + time_source: SharedTimeSource, + buffer_time: Duration, + random_bytes: [u8; 64], + } + + impl fmt::Debug for S3ExpressIdentityCache { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (size, capacity) = { + let cache = self.inner.lock().unwrap(); + (cache.len(), cache.cap()) + }; + write!( + f, + "S3ExpressIdentityCache {{ time_source: {:?}, buffer_time: {:?} }}, with size/capacity: {}/{}", + self.time_source, &self.buffer_time, size, capacity, + ) + } + } + + impl S3ExpressIdentityCache { + pub(crate) fn new( + capacity: usize, + time_source: SharedTimeSource, + buffer_time: Duration, + ) -> Self { + // It'd be nice to use a cryptographically secure random generator but not necessary. + // The cache is memory only and randomization here is mostly to obfuscate the key and + // make it reasonable length. + let mut rng = Rng::default(); + let mut random_bytes = [0u8; 64]; + rng.fill(&mut random_bytes); + Self { + inner: Mutex::new(LruCache::new(NonZeroUsize::new(capacity).unwrap())), + time_source, + buffer_time, + random_bytes, + } + } + + pub(crate) fn key(&self, bucket_name: &str, creds: &Credentials) -> CacheKey { + CacheKey({ + let mut mac = Hmac::::new_from_slice(self.random_bytes.as_slice()) + .expect("should be created from random 64 bytes"); + let input = format!("{}{}", creds.access_key_id(), creds.secret_access_key()); + mac.update(input.as_ref()); + let mut inner = hex::encode(mac.finalize_fixed()); + inner.push_str(bucket_name); + inner + }) + } + + pub(crate) async fn get_or_load( + &self, + key: CacheKey, + loader: F, + ) -> Result + where + F: FnOnce() -> Fut, + Fut: Future>, + { + let expiring_cache = { + let mut inner = self.inner.lock().unwrap(); + inner + .get_or_insert_mut(key, || ExpiringCache::new(self.buffer_time)) + .clone() + }; + + let now = self.time_source.now(); + + match expiring_cache.yield_or_clear_if_expired(now).await { + Some(identity) => { + tracing::debug!( + buffer_time=?self.buffer_time, + cached_expiration=?identity.expiration(), + now=?now, + "loaded identity from cache" + ); + Ok(identity) + } + None => { + let start_time = self.time_source.now(); + let identity = expiring_cache.get_or_load(loader).await?; + let expiration = identity + .expiration() + .ok_or("SessionCredentials` always has expiration")?; + let printable = DateTime::from(expiration); + tracing::info!( + new_expiration=%printable, + valid_for=?expiration.duration_since(self.time_source.now()).unwrap_or_default(), + "identity cache miss occurred; added new identity (took {:?})", + self.time_source.now().duration_since(start_time).unwrap_or_default() + ); + Ok(identity) + } + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + use aws_smithy_async::rt::sleep::TokioSleep; + use aws_smithy_async::test_util::ManualTimeSource; + use aws_smithy_runtime_api::client::identity::http::Token; + use aws_smithy_runtime_api::client::identity::{ + IdentityFuture, ResolveIdentity, SharedIdentityResolver, + }; + use aws_smithy_runtime_api::client::runtime_components::{ + RuntimeComponents, RuntimeComponentsBuilder, + }; + use aws_smithy_runtime_api::shared::IntoShared; + use aws_smithy_types::config_bag::ConfigBag; + use futures_util::stream::FuturesUnordered; + use std::sync::Arc; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + use tracing::info; + + fn epoch_secs(secs: u64) -> SystemTime { + SystemTime::UNIX_EPOCH + Duration::from_secs(secs) + } + + fn identity_expiring_in(expired_secs: u64) -> Identity { + let expiration = Some(epoch_secs(expired_secs)); + Identity::new(Token::new("test", expiration), expiration) + } + + fn test_identity_resolver( + load_list: Vec>, + ) -> SharedIdentityResolver { + #[derive(Debug)] + struct Resolver(Mutex>>); + impl ResolveIdentity for Resolver { + fn resolve_identity<'a>( + &'a self, + _: &'a RuntimeComponents, + _config_bag: &'a ConfigBag, + ) -> IdentityFuture<'a> { + let mut list = self.0.lock().unwrap(); + if list.len() > 0 { + let next = list.remove(0); + info!("refreshing the identity to {:?}", next); + IdentityFuture::ready(next) + } else { + drop(list); + panic!("no more identities") + } + } + } + + SharedIdentityResolver::new(Resolver(Mutex::new(load_list))) + } + + async fn load( + identity_resolver: SharedIdentityResolver, + runtime_components: &RuntimeComponents, + ) -> Result<(Identity, SystemTime), BoxError> { + let identity = identity_resolver + .resolve_identity(&runtime_components, &ConfigBag::base()) + .await + .unwrap(); + Ok((identity.clone(), identity.expiration().unwrap())) + } + + async fn expect_identity( + expired_secs: u64, + sut: &S3ExpressIdentityCache, + key: CacheKey, + loader: F, + ) where + F: FnOnce() -> Fut, + Fut: Future>, + { + let identity = sut.get_or_load(key, loader).await.unwrap(); + assert_eq!(Some(epoch_secs(expired_secs)), identity.expiration()); + } + + #[tokio::test] + async fn reload_expired_test_identity() { + let time = ManualTimeSource::new(UNIX_EPOCH); + let runtime_components = RuntimeComponentsBuilder::for_tests() + .with_time_source(Some(time.clone())) + .with_sleep_impl(Some(TokioSleep::new())) + .build() + .unwrap(); + + let sut = + S3ExpressIdentityCache::new(1, time.clone().into_shared(), DEFAULT_BUFFER_TIME); + + let identity_resolver = test_identity_resolver(vec![ + Ok(identity_expiring_in(1000)), + Ok(identity_expiring_in(2000)), + ]); + + let key = sut.key( + "test-bucket--usw2-az1--x-s3", + &Credentials::for_tests_with_session_token(), + ); + + // First call to the cache, populating a cache entry. + expect_identity(1000, &sut, key.clone(), || { + let identity_resolver = identity_resolver.clone(); + let runtime_components = runtime_components.clone(); + async move { load(identity_resolver, &runtime_components).await } + }) + .await; + + // Testing for a cache hit by advancing time such that the updated time is before the expiration of the first identity + // i.e. 500 < 1000. + time.set_time(epoch_secs(500)); + + expect_identity(1000, &sut, key.clone(), || async move { + panic!("new identity should not be loaded") + }) + .await; + + // Testing for a cache miss by advancing time such that the updated time is now after the expiration of the first identity + // and before the expiration of the second identity i.e. 1000 < 1500 && 1500 < 2000. + time.set_time(epoch_secs(1500)); + + expect_identity(2000, &sut, key, || async move { + load(identity_resolver, &runtime_components).await + }) + .await; + } + #[test] + fn load_contention() { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_time() + .worker_threads(16) + .build() + .unwrap(); + + let time = ManualTimeSource::new(epoch_secs(0)); + let runtime_components = RuntimeComponentsBuilder::for_tests() + .with_time_source(Some(time.clone())) + .with_sleep_impl(Some(TokioSleep::new())) + .build() + .unwrap(); + + let number_of_buckets = 4; + let sut = Arc::new(S3ExpressIdentityCache::new( + number_of_buckets, + time.clone().into_shared(), + DEFAULT_BUFFER_TIME, + )); + + // Nested for loops below advance time by 200 in total, and each identity has the expiration + // such that no matter what order async tasks are executed, it never expires. + let safe_expiration = number_of_buckets as u64 * 50 + DEFAULT_BUFFER_TIME.as_secs() + 1; + let identity_resolver = test_identity_resolver(vec![ + Ok(identity_expiring_in(safe_expiration)), + Ok(identity_expiring_in(safe_expiration)), + Ok(identity_expiring_in(safe_expiration)), + Ok(identity_expiring_in(safe_expiration)), + ]); + + let mut tasks = Vec::new(); + for i in 0..number_of_buckets { + let key = sut.key( + &format!("test-bucket-{i}-usw2-az1--x-s3"), + &Credentials::for_tests_with_session_token(), + ); + for _ in 0..50 { + let sut = sut.clone(); + let key = key.clone(); + let identity_resolver = identity_resolver.clone(); + let time = time.clone(); + let runtime_components = runtime_components.clone(); + tasks.push(rt.spawn(async move { + let now = time.advance(Duration::from_secs(1)); + let identity: Identity = sut + .get_or_load(key, || async move { + load(identity_resolver, &runtime_components).await + }) + .await + .unwrap(); + + assert!( + identity.expiration().unwrap() >= now, + "{:?} >= {:?}", + identity.expiration(), + now + ); + })); + } + } + let tasks = tasks.into_iter().collect::>(); + for task in tasks { + rt.block_on(task).unwrap(); + } + } + + #[tokio::test] + async fn identity_fetch_triggered_by_lru_eviction() { + let time = ManualTimeSource::new(UNIX_EPOCH); + let runtime_components = RuntimeComponentsBuilder::for_tests() + .with_time_source(Some(time.clone())) + .with_sleep_impl(Some(TokioSleep::new())) + .build() + .unwrap(); + + // Create a cache of size 2. + let sut = S3ExpressIdentityCache::new(2, time.into_shared(), DEFAULT_BUFFER_TIME); + + let identity_resolver = test_identity_resolver(vec![ + Ok(identity_expiring_in(1000)), + Ok(identity_expiring_in(2000)), + Ok(identity_expiring_in(3000)), + Ok(identity_expiring_in(4000)), + ]); + + let [key1, key2, key3] = [1, 2, 3].map(|i| { + sut.key( + &format!("test-bucket-{i}--usw2-az1--x-s3"), + &Credentials::for_tests_with_session_token(), + ) + }); + + // This should pupulate a cache entry for `key1`. + expect_identity(1000, &sut, key1.clone(), || { + let identity_resolver = identity_resolver.clone(); + let runtime_components = runtime_components.clone(); + async move { load(identity_resolver, &runtime_components).await } + }) + .await; + // This immediate next call for `key1` should be a cache hit. + expect_identity(1000, &sut, key1.clone(), || async move { + panic!("new identity should not be loaded") + }) + .await; + + // This should pupulate a cache entry for `key2`. + expect_identity(2000, &sut, key2, || { + let identity_resolver = identity_resolver.clone(); + let runtime_components = runtime_components.clone(); + async move { load(identity_resolver, &runtime_components).await } + }) + .await; + + // This should pupulate a cache entry for `key3`, but evicting a cache entry for `key1` because the cache is full. + expect_identity(3000, &sut, key3.clone(), || { + let identity_resolver = identity_resolver.clone(); + let runtime_components = runtime_components.clone(); + async move { load(identity_resolver, &runtime_components).await } + }) + .await; + + // Attempt to get an identity for `key1` should end up fetching a new one since its cache entry has been evicted. + // This fetch should now evict a cache entry for `key2`. + expect_identity(4000, &sut, key1, || async move { + load(identity_resolver, &runtime_components).await + }) + .await; + + // A cache entry for `key3` should still exist in the cache. + expect_identity(3000, &sut, key3, || async move { + panic!("new identity should not be loaded") + }) + .await; + } + } +} /// Supporting code for S3 Express identity provider pub(crate) mod identity_provider { - use std::time::SystemTime; + use std::time::{Duration, SystemTime}; use crate::s3_express::identity_cache::S3ExpressIdentityCache; use crate::types::SessionCredentials; use aws_credential_types::provider::error::CredentialsError; use aws_credential_types::Credentials; + use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams; use aws_smithy_runtime_api::client::identity::{ @@ -119,16 +504,16 @@ pub(crate) mod identity_provider { use aws_smithy_runtime_api::client::runtime_components::{ GetIdentityResolver, RuntimeComponents, }; + use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::config_bag::ConfigBag; + use super::identity_cache::{DEFAULT_BUFFER_TIME, DEFAULT_MAX_CACHE_CAPACITY}; + #[derive(Debug)] pub(crate) struct DefaultS3ExpressIdentityProvider { - _cache: S3ExpressIdentityCache, + cache: S3ExpressIdentityCache, } - #[derive(Default)] - pub(crate) struct Builder; - impl TryFrom for Credentials { type Error = BoxError; @@ -149,7 +534,7 @@ pub(crate) mod identity_provider { impl DefaultS3ExpressIdentityProvider { pub(crate) fn builder() -> Builder { - Builder + Builder::default() } async fn identity<'a>( @@ -162,20 +547,28 @@ pub(crate) mod identity_provider { let sigv4_identity_resolver = runtime_components .identity_resolver(aws_runtime::auth::sigv4::SCHEME_ID) .ok_or("identity resolver for sigv4 should be set for S3")?; - let _aws_identity = runtime_components + let aws_identity = runtime_components .identity_cache() .resolve_cached_identity(sigv4_identity_resolver, runtime_components, config_bag) .await?; - // TODO(S3Express): use both `bucket_name` and `aws_identity` as part of `S3ExpressIdentityCache` implementation - - let express_session_credentials = self - .express_session_credentials(bucket_name, runtime_components, config_bag) - .await?; - - let data = Credentials::try_from(express_session_credentials)?; - - Ok(Identity::new(data.clone(), data.expiry())) + let credentials = aws_identity.data::().ok_or( + "wrong identity type for SigV4. Expected AWS credentials but got `{identity:?}", + )?; + + let key = self.cache.key(bucket_name, credentials); + self.cache + .get_or_load(key, || async move { + let creds = self + .express_session_credentials(bucket_name, runtime_components, config_bag) + .await?; + let data = Credentials::try_from(creds)?; + Ok(( + Identity::new(data.clone(), data.expiry()), + data.expiry().unwrap(), + )) + }) + .await } fn bucket_name<'a>(&'a self, config_bag: &'a ConfigBag) -> Result<&'a str, BoxError> { @@ -218,10 +611,38 @@ pub(crate) mod identity_provider { } } + #[derive(Default)] + pub(crate) struct Builder { + time_source: Option, + buffer_time: Option, + } + impl Builder { + pub(crate) fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { + self.set_time_source(time_source.into_shared()); + self + } + pub(crate) fn set_time_source(&mut self, time_source: SharedTimeSource) -> &mut Self { + self.time_source = Some(time_source.into_shared()); + self + } + #[allow(dead_code)] + pub(crate) fn buffer_time(mut self, buffer_time: Duration) -> Self { + self.set_buffer_time(Some(buffer_time)); + self + } + #[allow(dead_code)] + pub(crate) fn set_buffer_time(&mut self, buffer_time: Option) -> &mut Self { + self.buffer_time = buffer_time; + self + } pub(crate) fn build(self) -> DefaultS3ExpressIdentityProvider { DefaultS3ExpressIdentityProvider { - _cache: S3ExpressIdentityCache, + cache: S3ExpressIdentityCache::new( + DEFAULT_MAX_CACHE_CAPACITY, + self.time_source.unwrap_or_default(), + self.buffer_time.unwrap_or(DEFAULT_BUFFER_TIME), + ), } } } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExpressDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExpressDecorator.kt index 8529fdfc47..cda1971795 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExpressDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/customize/s3/S3ExpressDecorator.kt @@ -15,12 +15,15 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRunti import software.amazon.smithy.rust.codegen.client.smithy.generators.ServiceRuntimePluginSection import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope +import software.amazon.smithy.rustsdk.AwsCargoDependency import software.amazon.smithy.rustsdk.AwsRuntimeType import software.amazon.smithy.rustsdk.InlineAwsDependency @@ -28,13 +31,11 @@ class S3ExpressDecorator : ClientCodegenDecorator { override val name: String = "S3ExpressDecorator" override val order: Byte = 0 - private fun sigv4S3Express() = + private fun sigv4S3Express(runtimeConfig: RuntimeConfig) = writable { rust( "#T", - RuntimeType.forInlineDependency( - InlineAwsDependency.forRustFile("s3_express"), - ).resolve("auth::SCHEME_ID"), + s3ExpressModule(runtimeConfig).resolve("auth::SCHEME_ID"), ) } @@ -46,7 +47,7 @@ class S3ExpressDecorator : ClientCodegenDecorator { baseAuthSchemeOptions + AuthSchemeOption.StaticAuthSchemeOption( SigV4Trait.ID, - listOf(sigv4S3Express()), + listOf(sigv4S3Express(codegenContext.runtimeConfig)), ) override fun serviceRuntimePluginCustomizations( @@ -67,20 +68,14 @@ private class S3ExpressServiceRuntimePluginCustomization(codegenContext: ClientC private val codegenScope by lazy { arrayOf( "DefaultS3ExpressIdentityProvider" to - RuntimeType.forInlineDependency( - InlineAwsDependency.forRustFile("s3_express"), - ).resolve("identity_provider::DefaultS3ExpressIdentityProvider"), + s3ExpressModule(runtimeConfig).resolve("identity_provider::DefaultS3ExpressIdentityProvider"), "IdentityCacheLocation" to RuntimeType.smithyRuntimeApiClient(runtimeConfig) .resolve("client::identity::IdentityCacheLocation"), "S3ExpressAuthScheme" to - RuntimeType.forInlineDependency( - InlineAwsDependency.forRustFile("s3_express"), - ).resolve("auth::S3ExpressAuthScheme"), + s3ExpressModule(runtimeConfig).resolve("auth::S3ExpressAuthScheme"), "S3_EXPRESS_SCHEME_ID" to - RuntimeType.forInlineDependency( - InlineAwsDependency.forRustFile("s3_express"), - ).resolve("auth::SCHEME_ID"), + s3ExpressModule(runtimeConfig).resolve("auth::SCHEME_ID"), "SharedAuthScheme" to RuntimeType.smithyRuntimeApiClient(runtimeConfig) .resolve("client::auth::SharedAuthScheme"), @@ -112,7 +107,14 @@ private class S3ExpressServiceRuntimePluginCustomization(codegenContext: ClientC rustTemplate("#{S3_EXPRESS_SCHEME_ID}", *codegenScope) }, writable { - rustTemplate("#{DefaultS3ExpressIdentityProvider}::builder().build()", *codegenScope) + rustTemplate( + """ + #{DefaultS3ExpressIdentityProvider}::builder() + .time_source(${section.serviceConfigName}.time_source().unwrap_or_default()) + .build() + """, + *codegenScope, + ) }, ) } @@ -144,9 +146,7 @@ class S3ExpressIdentityProviderConfig(codegenContext: ClientCodegenContext) : Co RuntimeType.smithyRuntimeApiClient(runtimeConfig) .resolve("client::identity::SharedIdentityResolver"), "S3_EXPRESS_SCHEME_ID" to - RuntimeType.forInlineDependency( - InlineAwsDependency.forRustFile("s3_express"), - ).resolve("auth::SCHEME_ID"), + s3ExpressModule(runtimeConfig).resolve("auth::SCHEME_ID"), ) override fun section(section: ServiceConfig) = @@ -182,3 +182,26 @@ class S3ExpressIdentityProviderConfig(codegenContext: ClientCodegenContext) : Co } } } + +private fun s3ExpressModule(runtimeConfig: RuntimeConfig) = + RuntimeType.forInlineDependency( + InlineAwsDependency.forRustFile( + "s3_express", + additionalDependency = s3ExpressDependencies(runtimeConfig), + ), + ) + +private fun s3ExpressDependencies(runtimeConfig: RuntimeConfig) = + arrayOf( + AwsCargoDependency.awsCredentialTypes(runtimeConfig), + AwsCargoDependency.awsRuntime(runtimeConfig), + AwsCargoDependency.awsSigv4(runtimeConfig), + CargoDependency.FastRand, + CargoDependency.Hex, + CargoDependency.Hmac, + CargoDependency.Lru, + CargoDependency.Sha2, + CargoDependency.smithyAsync(runtimeConfig), + CargoDependency.smithyRuntimeApiClient(runtimeConfig), + CargoDependency.smithyTypes(runtimeConfig), + ) diff --git a/aws/sdk/integration-tests/s3/tests/data/express/mixed-auths.json b/aws/sdk/integration-tests/s3/tests/data/express/mixed-auths.json new file mode 100644 index 0000000000..631cb3bf01 --- /dev/null +++ b/aws/sdk/integration-tests/s3/tests/data/express/mixed-auths.json @@ -0,0 +1,585 @@ +{ + "events": [ + { + "connection_id": 0, + "action": { + "Request": { + "request": { + "uri": "https://s3express-test-bucket--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com/?session", + "headers": { + "authorization": [ + "AWS4-HMAC-SHA256 Credential=ANOTREAL/20090213/us-west-2/s3express/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-create-session-mode;x-amz-date;x-amz-user-agent, Signature=4d44fb95628114b17a0676e2da758dee74f8a0337a3f0183e5b91e4fea6d9303" + ], + "x-amz-create-session-mode": [ + "ReadWrite" + ], + "amz-sdk-request": [ + "attempt=1; max=3" + ], + "user-agent": [ + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-content-sha256": [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ], + "x-amz-user-agent": [ + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-date": [ + "20090213T233130Z" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 0, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 0, + "action": { + "Response": { + "response": { + "Ok": { + "status": 200, + "headers": { + "content-type": [ + "application/xml" + ], + "server": [ + "AmazonS3" + ], + "content-length": [ + "1035" + ], + "date": [ + "Fri, 13 Feb 2009 23:31:30 GMT" + ], + "x-amz-request-id": [ + "0033eada6b00018d62cbbce90509fbf343a84844" + ], + "x-amz-id-2": [ + "ucekwm0" + ] + } + } + } + } + } + }, + { + "connection_id": 0, + "action": { + "Data": { + "data": { + "Utf8": "\nAgAAAC4zxhfvZg/nsnQ+E5m+e2qfGfH8yDMk/595OyIEAAAAAAAAAILVdpnHAQAAAAAAAASwftXYM0Sz7TWEtmiOj3JZDHuvfme1Vb8RNXfiSKsRZoHc6F86O9pg4lgaffF4slyOwcUDv2PYnd9TSU+u40nqMCym+Joog46Q3YOTJXk0K18j9dWcNQaDrN5mZpfPtIFTtFM/Gm2PpGvFCzssffMFWMwWt5gHzHtlMHocG7uX16JsttVRiAe14iDiHcFy+ka6RcX8vgXoFnM8tVhE2jby/zYQ0GdbnzVqJoZ/DjFXoEEQ/Rp16Fq0x5dHAPB0AIPH49fYP0rHLn3I8KcTxu8NUNmWjt5HNCXCWrKIFowAtGQNFsuyUDOCDxbMsoKYgYdIRDclqgoSKdCy6E0TUUr+L+KgytQ+Br20imMRo5rFvxa6sXgynILnOGOISBOzNreKJ0JdfVLWv1F9SnauEVfBYfnn8k7aH2n/52EnD8e3gJ0AacMX4oEadZQrVfNyqhcnctQ1a+wFo0Orw8YOM4TGB3nUFwLfS7sUBOv5BXbHeKzl6pvMcFV+caquOE0jdzNPkVVhmS8OCKbicIul8KHmMYElmPy5+riXya+5cv/KymqzxZ4FuMGkEeYXHjntF2LmWVOkF8pGBIpcrt9BlLLxyTxlye9PLqL36OqPDlwPN6koQgVrhr7R0QEuFbzsH4Bvz4btNZIQUUAEYD2XZOSJYC274XBMVU2024-02-01T03:57:15Z" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 0, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } + }, + { + "connection_id": 1, + "action": { + "Request": { + "request": { + "uri": "https://s3express-test-bucket--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com/?list-type=2", + "headers": { + "user-agent": [ + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-s3session-token": [ + "AgAAAC4zxhfvZg/nsnQ+E5m+e2qfGfH8yDMk/595OyIEAAAAAAAAAILVdpnHAQAAAAAAAASwftXYM0Sz7TWEtmiOj3JZDHuvfme1Vb8RNXfiSKsRZoHc6F86O9pg4lgaffF4slyOwcUDv2PYnd9TSU+u40nqMCym+Joog46Q3YOTJXk0K18j9dWcNQaDrN5mZpfPtIFTtFM/Gm2PpGvFCzssffMFWMwWt5gHzHtlMHocG7uX16JsttVRiAe14iDiHcFy+ka6RcX8vgXoFnM8tVhE2jby/zYQ0GdbnzVqJoZ/DjFXoEEQ/Rp16Fq0x5dHAPB0AIPH49fYP0rHLn3I8KcTxu8NUNmWjt5HNCXCWrKIFowAtGQNFsuyUDOCDxbMsoKYgYdIRDclqgoSKdCy6E0TUUr+L+KgytQ+Br20imMRo5rFvxa6sXgynILnOGOISBOzNreKJ0JdfVLWv1F9SnauEVfBYfnn8k7aH2n/52EnD8e3gJ0AacMX4oEadZQrVfNyqhcnctQ1a+wFo0Orw8YOM4TGB3nUFwLfS7sUBOv5BXbHeKzl6pvMcFV+caquOE0jdzNPkVVhmS8OCKbicIul8KHmMYElmPy5+riXya+5cv/KymqzxZ4FuMGkEeYXHjntF2LmWVOkF8pGBIpcrt9BlLLxyTxlye9P" + ], + "authorization": [ + "AWS4-HMAC-SHA256 Credential=NZIQUUAEYD2XZOSJYC274XBMVU/20090213/us-west-2/s3express/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-s3session-token;x-amz-user-agent, Signature=63ec8429a44ae20698b21a810030589d2369a13c352d32970931de7b2cede687" + ], + "amz-sdk-request": [ + "attempt=1; max=3" + ], + "x-amz-date": [ + "20090213T233130Z" + ], + "x-amz-user-agent": [ + "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-content-sha256": [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 1, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 1, + "action": { + "Response": { + "response": { + "Ok": { + "status": 200, + "headers": { + "server": [ + "AmazonS3" + ], + "x-amz-bucket-region": [ + "us-west-2" + ], + "x-amz-request-id": [ + "0033eada6b00018d62cbbd9c0509eacc646e551d" + ], + "date": [ + "Fri, 13 Feb 2009 23:31:30 GMT" + ], + "content-length": [ + "520" + ], + "content-type": [ + "application/xml" + ], + "x-amz-id-2": [ + "VLW4GcfH" + ] + } + } + } + } + } + }, + { + "connection_id": 1, + "action": { + "Data": { + "data": { + "Utf8": "\ns3express-test-bucket--usw2-az1--x-s311000falseCRC32"b357dc928b454965a8dd11716a37dab8"hello-world.txt2024-01-29T18:32:24.000Z14EXPRESS_ONEZONE" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 1, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } + }, + { + "connection_id": 2, + "action": { + "Request": { + "request": { + "uri": "https://regular-test-bucket.s3.us-west-2.amazonaws.com/?list-type=2", + "headers": { + "x-amz-content-sha256": [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ], + "x-amz-user-agent": [ + "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0" + ], + "authorization": [ + "AWS4-HMAC-SHA256 Credential=ANOTREAL/20090213/us-west-2/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-user-agent, Signature=28c4fed28f2d3ee6780b5ce36a052becdb4257e910a991467eb947aad3d7ea81" + ], + "amz-sdk-request": [ + "attempt=1; max=3" + ], + "x-amz-date": [ + "20090213T233130Z" + ], + "user-agent": [ + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 2, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 2, + "action": { + "Response": { + "response": { + "Ok": { + "status": 200, + "headers": { + "x-amz-request-id": [ + "SCS4NK66GX0KHW8Q" + ], + "content-type": [ + "application/xml" + ], + "server": [ + "AmazonS3" + ], + "x-amz-bucket-region": [ + "us-west-2" + ], + "transfer-encoding": [ + "chunked" + ], + "x-amz-id-2": [ + "1TqTeHEvPfLuR0LGMqatChbIYSJm6p0VsCbdOG0HG5q3BsVhYg5RMIOzAGYSF0xBVn+SLpmTkU6m1ARguYLRnA==" + ], + "date": [ + "Fri, 13 Feb 2009 23:31:30 GMT" + ] + } + } + } + } + } + }, + { + "connection_id": 2, + "action": { + "Data": { + "data": { + "Utf8": "\nregular-test-bucket11000falseferris.png2024-02-01T03:48:28.000Z"1316cc7c39e43c50c160f0aa8168db41"58413STANDARD" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 2, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } + }, + { + "connection_id": 3, + "action": { + "Request": { + "request": { + "uri": "https://s3express-test-bucket-2--usw2-az3--x-s3.s3express-usw2-az3.us-west-2.amazonaws.com/?session", + "headers": { + "amz-sdk-request": [ + "attempt=1; max=3" + ], + "user-agent": [ + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-user-agent": [ + "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-date": [ + "20090213T233130Z" + ], + "authorization": [ + "AWS4-HMAC-SHA256 Credential=ANOTREAL/20090213/us-west-2/s3express/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-create-session-mode;x-amz-date;x-amz-user-agent, Signature=4147d1c9c1c54efea9729be3816ec31e25496b7b1a6f116c479e8391444daa7c" + ], + "x-amz-content-sha256": [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ], + "x-amz-create-session-mode": [ + "ReadWrite" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 3, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 3, + "action": { + "Response": { + "response": { + "Ok": { + "status": 200, + "headers": { + "server": [ + "AmazonS3" + ], + "content-type": [ + "application/xml" + ], + "x-amz-request-id": [ + "0033eada6b00018d62cbc0e3050ad61d5828bce1" + ], + "content-length": [ + "1043" + ], + "date": [ + "Fri, 13 Feb 2009 23:31:30 GMT" + ], + "x-amz-id-2": [ + "bL6uF0sLOL" + ] + } + } + } + } + } + }, + { + "connection_id": 3, + "action": { + "Data": { + "data": { + "Utf8": "\nAgAAANCuNFCuOMGL3GiOJQLuVjgOSnFrPq+7P4foPPYEAAAAAAAAAA6gXF7NAQAAAAAAAL6C3/74KCW+AXc/l3wx7EZdxgJtYloTtkuUXCdptWfMT+fTaAkgNGIg2f7Iw+reIMDGN+CfwZGXbnL+rbL6rDf7WFJ5EVb+nIAdeJepa8ZWAYxY1eBq2PLmXcAd/QX8dKfJijmcFk4eDi40J1TPoiL7uao6pxUkj8lK9P9MUTl+IYtvLMcEGGa5/k+Xx+Fclrj+HNA/NoEDfOub9yQ4ei/rfX7efkVm/YWcexLwSmiZ32KaoQRxa2C1DIHozypgQmA+M2K1KTDzRd9Ks2YSTlqsmKzaHnqvxrbqpQec2Z6Tb7t9z1WimidZw9hh10v57kSdnNWGfAWtaOcsopHs0QqVIxTc2XiO32pojj6NtPgoGb3jSmbbDg9Q3905CsPt4KFhaLM3oIHBbQzVNAqewWuBm0Z3olpmHOBF3CBQ7HB2QQ4g8lYouUOyQQqJTlF8ls12VCI8rprnlqcS2iqn/neWwcFbc4/cdRvu0AeOmktCnq8izc6eXiaIBso6Tv7KO4vVfCjVeRG7UkzCyTd3SsnEULENOxWTToo8BpUyxtO6ULaoI7GSQz64jHUMAguZ82zO3gX2TA2qF0ALG0JnKaA+pnri5SyjObMeV9XHzs6gDjtqiCOafa7fd/Najg/Tz2531bSDoGsQX4OGYD3AZC32QIUZXYZQULJ6J7EPNU2024-02-01T03:57:16Z" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 3, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } + }, + { + "connection_id": 4, + "action": { + "Request": { + "request": { + "uri": "https://s3express-test-bucket-2--usw2-az3--x-s3.s3express-usw2-az3.us-west-2.amazonaws.com/?list-type=2", + "headers": { + "authorization": [ + "AWS4-HMAC-SHA256 Credential=YD3AZC32QIUZXYZQULJ6J7EPNU/20090213/us-west-2/s3express/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-s3session-token;x-amz-user-agent, Signature=7e6b04135e77aa126142365335cbeb14f98c031d6abc3ce9c281d528034c9874" + ], + "x-amz-content-sha256": [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ], + "x-amz-user-agent": [ + "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0" + ], + "user-agent": [ + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-date": [ + "20090213T233130Z" + ], + "amz-sdk-request": [ + "attempt=1; max=3" + ], + "x-amz-s3session-token": [ + "AgAAANCuNFCuOMGL3GiOJQLuVjgOSnFrPq+7P4foPPYEAAAAAAAAAA6gXF7NAQAAAAAAAL6C3/74KCW+AXc/l3wx7EZdxgJtYloTtkuUXCdptWfMT+fTaAkgNGIg2f7Iw+reIMDGN+CfwZGXbnL+rbL6rDf7WFJ5EVb+nIAdeJepa8ZWAYxY1eBq2PLmXcAd/QX8dKfJijmcFk4eDi40J1TPoiL7uao6pxUkj8lK9P9MUTl+IYtvLMcEGGa5/k+Xx+Fclrj+HNA/NoEDfOub9yQ4ei/rfX7efkVm/YWcexLwSmiZ32KaoQRxa2C1DIHozypgQmA+M2K1KTDzRd9Ks2YSTlqsmKzaHnqvxrbqpQec2Z6Tb7t9z1WimidZw9hh10v57kSdnNWGfAWtaOcsopHs0QqVIxTc2XiO32pojj6NtPgoGb3jSmbbDg9Q3905CsPt4KFhaLM3oIHBbQzVNAqewWuBm0Z3olpmHOBF3CBQ7HB2QQ4g8lYouUOyQQqJTlF8ls12VCI8rprnlqcS2iqn/neWwcFbc4/cdRvu0AeOmktCnq8izc6eXiaIBso6Tv7KO4vVfCjVeRG7UkzCyTd3SsnEULENOxWTToo8BpUyxtO6ULaoI7GSQz64jHUMAguZ82zO3gX2TA2qF0ALG0JnKaA+pnri5SyjObMeV9XH" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 4, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 4, + "action": { + "Response": { + "response": { + "Ok": { + "status": 200, + "headers": { + "x-amz-request-id": [ + "0033eada6b00018d62cbc175050a70195c6f02fc" + ], + "content-length": [ + "517" + ], + "x-amz-id-2": [ + "PrnjEZSu97xW" + ], + "server": [ + "AmazonS3" + ], + "x-amz-bucket-region": [ + "us-west-2" + ], + "content-type": [ + "application/xml" + ], + "date": [ + "Fri, 13 Feb 2009 23:31:30 GMT" + ] + } + } + } + } + } + }, + { + "connection_id": 4, + "action": { + "Data": { + "data": { + "Utf8": "\ns3express-test-bucket-2--usw2-az3--x-s311000falseCRC32"278b621d9c444360b3614a78cf1a64d1"foobar.txt2024-02-01T03:32:07.000Z10EXPRESS_ONEZONE" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 4, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } + }, + { + "connection_id": 5, + "action": { + "Request": { + "request": { + "uri": "https://s3express-test-bucket--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com/?list-type=2", + "headers": { + "x-amz-user-agent": [ + "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-date": [ + "20090213T233130Z" + ], + "x-amz-content-sha256": [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ], + "user-agent": [ + "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0" + ], + "x-amz-s3session-token": [ + "AgAAAC4zxhfvZg/nsnQ+E5m+e2qfGfH8yDMk/595OyIEAAAAAAAAAILVdpnHAQAAAAAAAASwftXYM0Sz7TWEtmiOj3JZDHuvfme1Vb8RNXfiSKsRZoHc6F86O9pg4lgaffF4slyOwcUDv2PYnd9TSU+u40nqMCym+Joog46Q3YOTJXk0K18j9dWcNQaDrN5mZpfPtIFTtFM/Gm2PpGvFCzssffMFWMwWt5gHzHtlMHocG7uX16JsttVRiAe14iDiHcFy+ka6RcX8vgXoFnM8tVhE2jby/zYQ0GdbnzVqJoZ/DjFXoEEQ/Rp16Fq0x5dHAPB0AIPH49fYP0rHLn3I8KcTxu8NUNmWjt5HNCXCWrKIFowAtGQNFsuyUDOCDxbMsoKYgYdIRDclqgoSKdCy6E0TUUr+L+KgytQ+Br20imMRo5rFvxa6sXgynILnOGOISBOzNreKJ0JdfVLWv1F9SnauEVfBYfnn8k7aH2n/52EnD8e3gJ0AacMX4oEadZQrVfNyqhcnctQ1a+wFo0Orw8YOM4TGB3nUFwLfS7sUBOv5BXbHeKzl6pvMcFV+caquOE0jdzNPkVVhmS8OCKbicIul8KHmMYElmPy5+riXya+5cv/KymqzxZ4FuMGkEeYXHjntF2LmWVOkF8pGBIpcrt9BlLLxyTxlye9P" + ], + "amz-sdk-request": [ + "attempt=1; max=3" + ], + "authorization": [ + "AWS4-HMAC-SHA256 Credential=NZIQUUAEYD2XZOSJYC274XBMVU/20090213/us-west-2/s3express/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-s3session-token;x-amz-user-agent, Signature=63ec8429a44ae20698b21a810030589d2369a13c352d32970931de7b2cede687" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 5, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 5, + "action": { + "Response": { + "response": { + "Ok": { + "status": 200, + "headers": { + "x-amz-id-2": [ + "LX0peO7j" + ], + "x-amz-request-id": [ + "0033eada6b00018d62cbc1cd0509f0c8b6f330e7" + ], + "server": [ + "AmazonS3" + ], + "x-amz-bucket-region": [ + "us-west-2" + ], + "date": [ + "Fri, 13 Feb 2009 23:31:30 GMT" + ], + "content-type": [ + "application/xml" + ], + "content-length": [ + "520" + ] + } + } + } + } + } + }, + { + "connection_id": 5, + "action": { + "Data": { + "data": { + "Utf8": "\ns3express-test-bucket--usw2-az1--x-s311000falseCRC32"b357dc928b454965a8dd11716a37dab8"hello-world.txt2024-01-29T18:32:24.000Z14EXPRESS_ONEZONE" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 5, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } + } + ], + "docs": "traffic recording of listing objects in both an S3 Express One Zone bucket and a regular S3 bucket", + "version": "V0" +} diff --git a/aws/sdk/integration-tests/s3/tests/express.rs b/aws/sdk/integration-tests/s3/tests/express.rs index cc0d99aa58..b361397332 100644 --- a/aws/sdk/integration-tests/s3/tests/express.rs +++ b/aws/sdk/integration-tests/s3/tests/express.rs @@ -45,6 +45,64 @@ async fn list_objects_v2() { .unwrap(); } +#[tokio::test] +async fn mixed_auths() { + let _logs = capture_test_logs(); + + let http_client = ReplayingClient::from_file("tests/data/express/mixed-auths.json").unwrap(); + let config = aws_config::from_env() + .http_client(http_client.clone()) + .no_credentials() + .region("us-west-2") + .load() + .await; + let config = Config::from(&config) + .to_builder() + .with_test_defaults() + .build(); + let client = aws_sdk_s3::Client::from_conf(config); + + // A call to an S3 Express bucket where we should see two request/response pairs, + // one for the `create_session` API and the other for `list_objects_v2` in S3 Express bucket. + let result = client + .list_objects_v2() + .bucket("s3express-test-bucket--usw2-az1--x-s3") + .send() + .await; + dbg!(result).expect("success"); + + // A call to a regular bucket, and request headers should not contain `x-amz-s3session-token`. + let result = client + .list_objects_v2() + .bucket("regular-test-bucket") + .send() + .await; + dbg!(result).expect("success"); + + // A call to another S3 Express bucket where we should again see two request/response pairs, + // one for the `create_session` API and the other for `list_objects_v2` in S3 Express bucket. + let result = client + .list_objects_v2() + .bucket("s3express-test-bucket-2--usw2-az3--x-s3") + .send() + .await; + dbg!(result).expect("success"); + + // This call should be an identity cache hit for the first S3 Express bucket, + // thus no HTTP request should be sent to the `create_session` API. + let result = client + .list_objects_v2() + .bucket("s3express-test-bucket--usw2-az1--x-s3") + .send() + .await; + dbg!(result).expect("success"); + + http_client + .validate_body_and_headers(Some(&["x-amz-s3session-token"]), "application/xml") + .await + .unwrap(); +} + fn create_session_request() -> http::Request { http::Request::builder() .uri("https://s3express-test-bucket--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com/?session") diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt index 23ce4428af..378cbed860 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt @@ -248,16 +248,19 @@ data class CargoDependency( val BytesUtils: CargoDependency = CargoDependency("bytes-utils", CratesIo("0.1.0")) val FastRand: CargoDependency = CargoDependency("fastrand", CratesIo("2.0.0")) val Hex: CargoDependency = CargoDependency("hex", CratesIo("0.4.3")) + val Hmac: CargoDependency = CargoDependency("hmac", CratesIo("0.12")) val Http: CargoDependency = CargoDependency("http", CratesIo("0.2.9")) val HttpBody: CargoDependency = CargoDependency("http-body", CratesIo("0.4.4")) val Hyper: CargoDependency = CargoDependency("hyper", CratesIo("0.14.26")) val HyperWithStream: CargoDependency = Hyper.withFeature("stream") val LazyStatic: CargoDependency = CargoDependency("lazy_static", CratesIo("1.4.0")) + val Lru: CargoDependency = CargoDependency("lru", CratesIo("0.12.2")) val Md5: CargoDependency = CargoDependency("md-5", CratesIo("0.10.0"), rustName = "md5") val PercentEncoding: CargoDependency = CargoDependency("percent-encoding", CratesIo("2.0.0")) val Regex: CargoDependency = CargoDependency("regex", CratesIo("1.5.5")) val RegexLite: CargoDependency = CargoDependency("regex-lite", CratesIo("0.1.5")) val Ring: CargoDependency = CargoDependency("ring", CratesIo("0.17.5")) + val Sha2: CargoDependency = CargoDependency("sha2", CratesIo("0.10")) val TokioStream: CargoDependency = CargoDependency("tokio-stream", CratesIo("0.1.7")) val Tower: CargoDependency = CargoDependency("tower", CratesIo("0.4")) val Tracing: CargoDependency = CargoDependency("tracing", CratesIo("0.1"))