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 all 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
139 changes: 128 additions & 11 deletions aws/rust-runtime/aws-inlineable/src/s3_express.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

/// Supporting code for S3 Express auth
pub(crate) mod auth {
use aws_runtime::auth::sigv4::SigV4Signer;
use aws_sigv4::http_request::{SignatureLocation, SigningSettings};
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::auth::{
AuthScheme, AuthSchemeEndpointConfig, AuthSchemeId, Sign,
Expand Down Expand Up @@ -56,15 +58,37 @@ pub(crate) mod auth {
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,
request: &mut HttpRequest,
identity: &Identity,
auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
runtime_components: &RuntimeComponents,
config_bag: &ConfigBag,
) -> Result<(), BoxError> {
todo!()
let operation_config =
SigV4Signer::extract_operation_config(auth_scheme_endpoint_config, config_bag)?;
let mut settings = SigV4Signer::signing_settings(&operation_config);
override_session_token_name(&mut settings)?;

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

fn override_session_token_name(settings: &mut SigningSettings) -> Result<(), BoxError> {
let session_token_name_override = match settings.signature_location {
SignatureLocation::Headers => Some("x-amz-s3session-token"),
SignatureLocation::QueryParams => Some("X-Amz-S3session-Token"),
_ => { return Err(BoxError::from("`SignatureLocation` adds a new variant, which needs to be handled in a separate match arm")) },
};
settings.session_token_name_override = session_token_name_override;
Ok(())
}
}

/// Supporting code for S3 Express identity cache
Expand All @@ -80,11 +104,21 @@ pub(crate) mod identity_cache {

/// 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::{
IdentityCacheLocation, IdentityFuture, ResolveIdentity,
Identity, IdentityCacheLocation, IdentityFuture, ResolveCachedIdentity, ResolveIdentity,
};
use aws_smithy_runtime_api::client::interceptors::SharedInterceptor;
use aws_smithy_runtime_api::client::runtime_components::{
GetIdentityResolver, RuntimeComponents,
};
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_types::config_bag::ConfigBag;

#[derive(Debug)]
Expand All @@ -95,10 +129,93 @@ pub(crate) mod identity_provider {
#[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) -> Result<&'a str, BoxError> {
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()
.ok_or("A bucket was not set in endpoint params".into())
}

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::from_config_bag(config_bag);

// inherits all runtime components from a current S3 operation but clears out
// out interceptors configured for that operation
let mut rc_builder = runtime_components.to_builder();
rc_builder.set_interceptors(std::iter::empty::<SharedInterceptor>());
config_builder.runtime_components = rc_builder;
jdisanti marked this conversation as resolved.
Show resolved Hide resolved

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 {
Expand All @@ -112,10 +229,10 @@ pub(crate) mod identity_provider {
impl ResolveIdentity for DefaultS3ExpressIdentityProvider {
fn resolve_identity<'a>(
&'a self,
_runtime_components: &'a RuntimeComponents,
_config_bag: &'a ConfigBag,
runtime_components: &'a RuntimeComponents,
config_bag: &'a ConfigBag,
) -> IdentityFuture<'a> {
todo!()
IdentityFuture::new(async move { self.identity(runtime_components, config_bag).await })
}

fn cache_location(&self) -> IdentityCacheLocation {
Expand Down
64 changes: 47 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,27 @@ 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`.
///
/// TODO(S3Express): Make this method more user friendly, possibly returning a builder
/// instead of taking these input parameters. The builder will have a `sign` method that
/// does what this method body currently does.
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 +219,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
27 changes: 21 additions & 6 deletions aws/rust-runtime/aws-sigv4/src/http_request/canonical_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::http_request::settings::UriPathNormalizationMode;
use crate::http_request::sign::SignableRequest;
use crate::http_request::uri_path_normalization::normalize_uri_path;
use crate::http_request::url_escape::percent_encode_path;
use crate::http_request::PercentEncodingMode;
use crate::http_request::{PayloadChecksumKind, SignableBody, SignatureLocation, SigningParams};
use crate::http_request::{PercentEncodingMode, SigningSettings};
use crate::sign::v4::sha256_hex_string;
use crate::SignatureVersion;
use aws_smithy_http::query_writer::QueryWriter;
Expand Down Expand Up @@ -218,7 +218,7 @@ impl<'a> CanonicalRequest<'a> {
let creq = CanonicalRequest {
method: req.method(),
path,
params: Self::params(req.uri(), &values),
params: Self::params(req.uri(), &values, params.settings()),
headers: canonical_headers,
values,
};
Expand Down Expand Up @@ -250,6 +250,11 @@ impl<'a> CanonicalRequest<'a> {

Self::insert_host_header(&mut canonical_headers, req.uri());

let token_header_name = params
.settings()
.session_token_name_override
.unwrap_or(header::X_AMZ_SECURITY_TOKEN);

if params.settings().signature_location == SignatureLocation::Headers {
let creds = params
.credentials()
Expand All @@ -259,7 +264,7 @@ impl<'a> CanonicalRequest<'a> {
if let Some(security_token) = creds.session_token() {
let mut sec_header = HeaderValue::from_str(security_token)?;
sec_header.set_sensitive(true);
canonical_headers.insert(header::X_AMZ_SECURITY_TOKEN, sec_header);
canonical_headers.insert(token_header_name, sec_header);
}

if params.settings().payload_checksum_kind == PayloadChecksumKind::XAmzSha256 {
Expand All @@ -283,7 +288,7 @@ impl<'a> CanonicalRequest<'a> {
}

if params.settings().session_token_mode == SessionTokenMode::Exclude
&& name == HeaderName::from_static(header::X_AMZ_SECURITY_TOKEN)
&& name == HeaderName::from_static(token_header_name)
{
continue;
}
Expand Down Expand Up @@ -320,7 +325,11 @@ impl<'a> CanonicalRequest<'a> {
}
}

fn params(uri: &Uri, values: &SignatureValues<'_>) -> Option<String> {
fn params(
uri: &Uri,
values: &SignatureValues<'_>,
settings: &SigningSettings,
) -> Option<String> {
let mut params: Vec<(Cow<'_, str>, Cow<'_, str>)> =
form_urlencoded::parse(uri.query().unwrap_or_default().as_bytes()).collect();
fn add_param<'a>(params: &mut Vec<(Cow<'a, str>, Cow<'a, str>)>, k: &'a str, v: &'a str) {
Expand All @@ -345,7 +354,13 @@ impl<'a> CanonicalRequest<'a> {
);

if let Some(security_token) = values.security_token {
add_param(&mut params, param::X_AMZ_SECURITY_TOKEN, security_token);
add_param(
&mut params,
settings
.session_token_name_override
.unwrap_or(param::X_AMZ_SECURITY_TOKEN),
security_token,
);
}
}
// Sort by param name, and then by param value
Expand Down
5 changes: 5 additions & 0 deletions aws/rust-runtime/aws-sigv4/src/http_request/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub struct SigningSettings {
/// canonical request. Other services require only it to be added after
/// calculating the signature.
pub session_token_mode: SessionTokenMode,

/// Some services require an alternative session token header or query param instead of
/// `x-amz-security-token` or `X-Amz-Security-Token`.
pub session_token_name_override: Option<&'static str>,
}

/// HTTP payload checksum type
Expand Down Expand Up @@ -133,6 +137,7 @@ impl Default for SigningSettings {
excluded_headers,
uri_path_normalization_mode: UriPathNormalizationMode::Enabled,
session_token_mode: SessionTokenMode::Include,
session_token_name_override: None,
}
}
}
Expand Down
Loading
Loading