Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow list-objects-v2 to run against an S3 Express bucket #3388

Merged
merged 35 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
dbcfe73
Add placeholder types for S3 Express
ysaito1001 Jan 25, 2024
714aaad
Enable control flow to be altered for S3 Express
ysaito1001 Jan 25, 2024
9086db2
Fix lint-related failures in CI
ysaito1001 Jan 25, 2024
1f07f92
Temporarily disable S3 Express endpoint tests
ysaito1001 Jan 25, 2024
13c3c5a
Update aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk…
ysaito1001 Jan 29, 2024
38ce80f
Make s3_express inlineable modules `pub(crate)`
ysaito1001 Jan 29, 2024
ef1e1be
Remove unnecessary `set_shared_identity_resolver`
ysaito1001 Jan 29, 2024
9e4542b
Allow list-objects-v2 to run against S3 Express bucket
ysaito1001 Jan 29, 2024
67223e3
Add recording test for S3 Express list-objects-v2
ysaito1001 Jan 29, 2024
b30abb2
Exclude `s3_express` from aws-inlineable's module tree
ysaito1001 Jan 30, 2024
2fcd2f7
Enable S3 Express endpoint tests
ysaito1001 Jan 30, 2024
21ad106
Update aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk…
ysaito1001 Feb 7, 2024
9acaf29
Support presigning for S3 Express
ysaito1001 Feb 8, 2024
ec08ae9
Add integration test for S3 Express `get_object` presigning
ysaito1001 Feb 8, 2024
c2a2708
Make `cache_location` a method on `ResolveIdentity`
ysaito1001 Feb 8, 2024
15e89c6
Merge branch 'ysaito/s3express' into s3express-add-placeholders
ysaito1001 Feb 8, 2024
c4df9c7
Error instead of panic on missing bucket name
ysaito1001 Feb 8, 2024
0e405e1
Error instead of panic in `add_token_to_request`
ysaito1001 Feb 9, 2024
e47157f
Update comment for `invalidXmlRootAllowList`
ysaito1001 Feb 9, 2024
5a4e3b2
Implement `from_runtime_components` instead of `From` trait
ysaito1001 Feb 9, 2024
51518f9
Allow `config::Builder` to be created from `ConfigBag`
ysaito1001 Feb 9, 2024
573ef36
Add `TODO` to make `sign_http_request` user-friendly
ysaito1001 Feb 9, 2024
457f6b8
Add a reference to docs for `IdentityCacheLocation`
ysaito1001 Feb 9, 2024
f569231
Use rustTemplate instead of rustBlockTemplate for readability
ysaito1001 Feb 10, 2024
c3d754f
Exclude `s3_express` from `aws-inlineable`'s module tree
ysaito1001 Feb 10, 2024
5b27e4d
Merge branch 'main' into s3express-add-placeholders
ysaito1001 Feb 10, 2024
99a9112
Merge branch 's3express-add-placeholders' into s3express-allow-list-o…
ysaito1001 Feb 10, 2024
3233dbe
Add placeholder types for S3 Express and enable control flow to be re…
ysaito1001 Feb 10, 2024
63dcb93
Merge branch 'ysaito/s3express' into s3express-allow-list-objects-v2-…
ysaito1001 Feb 10, 2024
daea66b
Merge branch 'ysaito/s3express' into s3express-allow-list-objects-v2-…
ysaito1001 Feb 10, 2024
6c795b9
Merge branch 'ysaito/s3express' into s3express-allow-list-objects-v2-…
ysaito1001 Feb 10, 2024
d2354bd
Anonymize tokens in S3 Express recording test
ysaito1001 Feb 12, 2024
027c2cd
Remove `From<RuntimeComponents> for RuntimeComponentsBuilder`
ysaito1001 Feb 14, 2024
830ef02
Add a setting to replace session token name
ysaito1001 Feb 15, 2024
97cecd3
Add a tracking issue for `BuilderFromConfigBag`
ysaito1001 Feb 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 298 additions & 0 deletions aws/rust-runtime/aws-inlineable/src/s3_express.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/// Supporting code for S3 Express auth
pub(crate) mod auth {
use std::borrow::Cow;

use aws_credential_types::Credentials;
use aws_runtime::auth::sigv4::SigV4Signer;
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::auth::{
AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, Sign,
};
use aws_smithy_runtime_api::client::identity::{Identity, SharedIdentityResolver};
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
use aws_smithy_runtime_api::client::runtime_components::{
GetIdentityResolver, RuntimeComponents,
};
use aws_smithy_types::config_bag::ConfigBag;

/// Auth scheme ID for S3 Express.
pub(crate) const SCHEME_ID: AuthSchemeId = AuthSchemeId::new("sigv4-s3express");

/// S3 Express auth scheme.
#[derive(Debug, Default)]
pub(crate) struct S3ExpressAuthScheme {
signer: S3ExpressSigner,
}

impl S3ExpressAuthScheme {
/// Creates a new `S3ExpressAuthScheme`.
pub(crate) fn new() -> Self {
Default::default()
}
}

impl AuthScheme for S3ExpressAuthScheme {
fn scheme_id(&self) -> AuthSchemeId {
SCHEME_ID
}

fn identity_resolver(
&self,
identity_resolvers: &dyn GetIdentityResolver,
) -> Option<SharedIdentityResolver> {
identity_resolvers.identity_resolver(self.scheme_id())
}

fn signer(&self) -> &dyn Sign {
&self.signer
}
}

/// S3 Express signer.
#[derive(Debug, Default)]
pub(crate) struct S3ExpressSigner;

impl Sign for S3ExpressSigner {
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
runtime_components: &RuntimeComponents,
config_bag: &ConfigBag,
) -> Result<(), BoxError> {
let operation_config =
SigV4Signer::extract_operation_config(auth_scheme_endpoint_config, config_bag)?;

let mut settings = SigV4Signer::signing_settings(&operation_config);
if let Some(excluded) = settings.excluded_headers.as_mut() {
excluded.push(Cow::Borrowed("x-amz-security-token"));
}

let express_credentials = identity.data::<Credentials>().ok_or(
"wrong identity type for SigV4. Expected AWS credentials but got `{identity:?}",
)?;
let mut value = http::HeaderValue::from_str(
express_credentials
.session_token()
.expect("S3 session token should be set"),
)
.unwrap();
value.set_sensitive(true);
request.headers_mut().insert(
http::HeaderName::from_static("x-amz-s3session-token"),
value,
);

SigV4Signer.sign_http_request(
request,
identity,
settings,
&operation_config,
runtime_components,
config_bag,
)
}
}
}

/// Supporting code for S3 Express identity cache
pub(crate) mod identity_cache {
/// 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;
}

/// Supporting code for S3 Express identity provider
pub(crate) mod identity_provider {
use std::time::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_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams;
use aws_smithy_runtime_api::client::identity::{
Identity, IdentityFuture, ResolveCachedIdentity, ResolveIdentity,
};
use aws_smithy_runtime_api::client::interceptors::SharedInterceptor;
use aws_smithy_runtime_api::client::runtime_components::{
GetIdentityResolver, RuntimeComponents,
};
use aws_smithy_types::config_bag::ConfigBag;

#[derive(Debug)]
pub(crate) struct DefaultS3ExpressIdentityProvider {
_cache: S3ExpressIdentityCache,
}

#[derive(Default)]
pub(crate) struct Builder;

impl TryFrom<SessionCredentials> for Credentials {
type Error = BoxError;

fn try_from(session_creds: SessionCredentials) -> Result<Self, Self::Error> {
Ok(Credentials::new(
session_creds.access_key_id,
session_creds.secret_access_key,
Some(session_creds.session_token),
Some(SystemTime::try_from(session_creds.expiration).map_err(|_| {
CredentialsError::unhandled(
"credential expiration time cannot be represented by a SystemTime",
)
})?),
"s3express",
))
}
}

impl DefaultS3ExpressIdentityProvider {
pub(crate) fn builder() -> Builder {
Builder
}

async fn identity<'a>(
&'a self,
runtime_components: &'a RuntimeComponents,
config_bag: &'a ConfigBag,
) -> Result<Identity, BoxError> {
let bucket_name = self.bucket_name(config_bag);

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
.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()))
}

fn bucket_name<'a>(&'a self, config_bag: &'a ConfigBag) -> &'a str {
let params = config_bag
.load::<EndpointResolverParams>()
.expect("endpoint resolver params must be set");
let params = params
.get::<crate::config::endpoint::Params>()
.expect("`Params` should be wrapped in `EndpointResolverParams`");
params.bucket().expect("bucket name must be set")
ysaito1001 marked this conversation as resolved.
Show resolved Hide resolved
}

async fn express_session_credentials<'a>(
&'a self,
bucket_name: &'a str,
runtime_components: &'a RuntimeComponents,
config_bag: &'a ConfigBag,
) -> Result<SessionCredentials, BoxError> {
let mut config_builder = crate::Config::builder();
ysaito1001 marked this conversation as resolved.
Show resolved Hide resolved
config_builder.set_accelerate(
config_bag
.load::<crate::config::Accelerate>()
.map(|ty| ty.0),
);
config_builder.set_app_name(config_bag.load::<aws_types::app_name::AppName>().cloned());
config_builder.set_disable_multi_region_access_points(
config_bag
.load::<crate::config::DisableMultiRegionAccessPoints>()
.map(|ty| ty.0),
);
config_builder.set_endpoint_url(
config_bag
.load::<::aws_types::endpoint_config::EndpointUrl>()
.map(|ty| ty.0.clone()),
);
config_builder.set_force_path_style(
config_bag
.load::<crate::config::ForcePathStyle>()
.map(|ty| ty.0),
);
config_builder.set_region(config_bag.load::<::aws_types::region::Region>().cloned());
config_builder.set_retry_config(
config_bag
.load::<aws_smithy_types::retry::RetryConfig>()
.cloned(),
);
config_builder.set_retry_partition(
config_bag
.load::<::aws_smithy_runtime::client::retries::RetryPartition>()
.cloned(),
);
config_builder.set_timeout_config(
config_bag
.load::<::aws_smithy_types::timeout::TimeoutConfig>()
.cloned(),
);
config_builder.set_use_arn_region(
config_bag
.load::<crate::config::UseArnRegion>()
.map(|ty| ty.0),
);
config_builder.set_use_dual_stack(
config_bag
.load::<::aws_types::endpoint_config::UseDualStack>()
.map(|ty| ty.0),
);
config_builder.set_use_fips(
config_bag
.load::<::aws_types::endpoint_config::UseFips>()
.map(|ty| ty.0),
);

// inherits all runtime components from a current S3 operation but clears out
// out interceptors configured for that operation
let mut builder = runtime_components.to_builder();
builder.set_interceptors(std::iter::empty::<SharedInterceptor>());
config_builder.runtime_components = builder;

let client = crate::Client::from_conf(config_builder.build());
ysaito1001 marked this conversation as resolved.
Show resolved Hide resolved
let response = client
.create_session()
.bucket(bucket_name)
.session_mode(crate::types::SessionMode::ReadWrite)
.send()
.await?;

response
.credentials
.ok_or("no session credentials in response".into())
}
}

impl Builder {
pub(crate) fn build(self) -> DefaultS3ExpressIdentityProvider {
DefaultS3ExpressIdentityProvider {
_cache: S3ExpressIdentityCache,
}
}
}

impl ResolveIdentity for DefaultS3ExpressIdentityProvider {
fn resolve_identity<'a>(
&'a self,
runtime_components: &'a RuntimeComponents,
config_bag: &'a ConfigBag,
) -> IdentityFuture<'a> {
IdentityFuture::new(async move { self.identity(runtime_components, config_bag).await })
}
}
}
60 changes: 43 additions & 17 deletions aws/rust-runtime/aws-runtime/src/auth/sigv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ impl SigV4Signer {
Self
}

fn settings(operation_config: &SigV4OperationSigningConfig) -> SigningSettings {
/// Creates a [`SigningSettings`] from the given `operation_config`.
pub fn signing_settings(operation_config: &SigV4OperationSigningConfig) -> SigningSettings {
super::settings(operation_config)
}

Expand Down Expand Up @@ -117,10 +118,11 @@ impl SigV4Signer {
.expect("all required fields set"))
}

fn extract_operation_config<'a>(
/// Extracts a [`SigV4OperationSigningConfig`].
pub fn extract_operation_config<'a>(
auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'a>,
config_bag: &'a ConfigBag,
) -> Result<Cow<'a, SigV4OperationSigningConfig>, SigV4SigningError> {
) -> Result<Cow<'a, SigV4OperationSigningConfig>, BoxError> {
let operation_config = config_bag
.load::<SigV4OperationSigningConfig>()
.ok_or(SigV4SigningError::MissingOperationSigningConfig)?;
Expand All @@ -141,28 +143,23 @@ impl SigV4Signer {
}
}
}
}

impl Sign for SigV4Signer {
fn sign_http_request(
/// Signs the given `request`.
///
/// This is a helper used by [`Sign::sign_http_request`] and will be useful if calling code
/// needs to pass a configured `settings`.
pub fn sign_http_request(
ysaito1001 marked this conversation as resolved.
Show resolved Hide resolved
&self,
request: &mut HttpRequest,
identity: &Identity,
auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
settings: SigningSettings,
operation_config: &SigV4OperationSigningConfig,
runtime_components: &RuntimeComponents,
config_bag: &ConfigBag,
#[allow(unused_variables)] config_bag: &ConfigBag,
) -> Result<(), BoxError> {
let operation_config =
Self::extract_operation_config(auth_scheme_endpoint_config, config_bag)?;
let request_time = runtime_components.time_source().unwrap_or_default().now();

if identity.data::<Credentials>().is_none() {
return Err(SigV4SigningError::WrongIdentityType(identity.clone()).into());
};

let settings = Self::settings(&operation_config);
let signing_params =
Self::signing_params(settings, identity, &operation_config, request_time)?;
Self::signing_params(settings, identity, operation_config, request_time)?;

let (signing_instructions, _signature) = {
// A body that is already in memory can be signed directly. A body that is not in memory
Expand Down Expand Up @@ -218,6 +215,35 @@ impl Sign for SigV4Signer {
}
}

impl Sign for SigV4Signer {
fn sign_http_request(
&self,
request: &mut HttpRequest,
identity: &Identity,
auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
runtime_components: &RuntimeComponents,
config_bag: &ConfigBag,
) -> Result<(), BoxError> {
if identity.data::<Credentials>().is_none() {
return Err(SigV4SigningError::WrongIdentityType(identity.clone()).into());
};

let operation_config =
Self::extract_operation_config(auth_scheme_endpoint_config, config_bag)?;

let settings = Self::signing_settings(&operation_config);

self.sign_http_request(
request,
identity,
settings,
&operation_config,
runtime_components,
config_bag,
)
}
}

#[cfg(feature = "event-stream")]
mod event_stream {
use aws_sigv4::event_stream::{sign_empty_message, sign_message};
Expand Down
Loading
Loading