Skip to content

Commit ccc3474

Browse files
authored
Fix S3 presigning in the orchestrator (#2738)
## Motivation and Context This PR fixes S3 presigning in the client orchestrator implementation. A Polly presigning fix will come in a separate PR. This PR also: - Renames `ClientProtocolGenerator` to `OperationGenerator` to better represent what its generating. - Moves `OperationCustomization` into `codegen-client` since it is client specific and unused by the server codegen. - Deletes the `ProtocolGenerator` base class in `codegen-core` since it wasn't adding any value. - Adds the concept of stop points to the orchestrator so that orchestration can be halted before transmitting a request. - Adds `DisableInvocationIdInterceptor`, `DisableRequestInfoInterceptor`, `DisableUserAgentInterceptor`, and `HeaderSerializationSettings` config bag configs to facilitate presigning requirements. - Fixes compilation of all S3 and Polly tests when in orchestrator mode. ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent 500aef3 commit ccc3474

File tree

76 files changed

+1445
-822
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1445
-822
lines changed

aws/rust-runtime/aws-inlineable/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ pub mod no_credentials;
2828
/// Support types required for adding presigning to an operation in a generated service.
2929
pub mod presigning;
3030

31+
/// Presigning tower service
32+
pub mod presigning_service;
33+
34+
/// Presigning interceptors
35+
pub mod presigning_interceptors;
36+
3137
/// Special logic for extracting request IDs from S3's responses.
3238
pub mod s3_request_id;
3339

@@ -49,6 +55,10 @@ pub mod http_body_checksum;
4955
#[allow(dead_code)]
5056
pub mod endpoint_discovery;
5157

58+
// This module is symlinked in from the smithy-rs rust-runtime inlineables so that
59+
// the `presigning_interceptors` module can refer to it.
60+
mod serialization_settings;
61+
5262
// just so docs work
5363
#[allow(dead_code)]
5464
/// allow docs to work

aws/rust-runtime/aws-inlineable/src/presigning.rs

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -215,60 +215,3 @@ impl From<PresignedRequest> for http::request::Builder {
215215
builder
216216
}
217217
}
218-
219-
/// Tower middleware service for creating presigned requests
220-
#[allow(dead_code)]
221-
pub(crate) mod service {
222-
use super::PresignedRequest;
223-
use aws_smithy_http::operation;
224-
use http::header::USER_AGENT;
225-
use std::future::{ready, Ready};
226-
use std::marker::PhantomData;
227-
use std::task::{Context, Poll};
228-
229-
/// Tower [`Service`](tower::Service) for generated a [`PresignedRequest`] from the AWS middleware.
230-
#[derive(Default, Debug)]
231-
#[non_exhaustive]
232-
pub(crate) struct PresignedRequestService<E> {
233-
_phantom: PhantomData<E>,
234-
}
235-
236-
// Required because of the derive Clone on MapRequestService.
237-
// Manually implemented to avoid requiring errors to implement Clone.
238-
impl<E> Clone for PresignedRequestService<E> {
239-
fn clone(&self) -> Self {
240-
Self {
241-
_phantom: Default::default(),
242-
}
243-
}
244-
}
245-
246-
impl<E> PresignedRequestService<E> {
247-
/// Creates a new `PresignedRequestService`
248-
pub(crate) fn new() -> Self {
249-
Self {
250-
_phantom: Default::default(),
251-
}
252-
}
253-
}
254-
255-
impl<E> tower::Service<operation::Request> for PresignedRequestService<E> {
256-
type Response = PresignedRequest;
257-
type Error = E;
258-
type Future = Ready<Result<PresignedRequest, E>>;
259-
260-
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
261-
Poll::Ready(Ok(()))
262-
}
263-
264-
fn call(&mut self, req: operation::Request) -> Self::Future {
265-
let (mut req, _) = req.into_parts();
266-
267-
// Remove user agent headers since the request will not be executed by the AWS Rust SDK.
268-
req.headers_mut().remove(USER_AGENT);
269-
req.headers_mut().remove("X-Amz-User-Agent");
270-
271-
ready(Ok(PresignedRequest::new(req.map(|_| ()))))
272-
}
273-
}
274-
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#![allow(dead_code)]
7+
8+
use crate::presigning::PresigningConfig;
9+
use crate::serialization_settings::HeaderSerializationSettings;
10+
use aws_runtime::auth::sigv4::{HttpSignatureType, SigV4OperationSigningConfig};
11+
use aws_runtime::invocation_id::DisableInvocationIdInterceptor;
12+
use aws_runtime::request_info::DisableRequestInfoInterceptor;
13+
use aws_runtime::user_agent::DisableUserAgentInterceptor;
14+
use aws_smithy_async::time::{SharedTimeSource, StaticTimeSource};
15+
use aws_smithy_runtime_api::client::interceptors::{
16+
BeforeSerializationInterceptorContextMut, BeforeTransmitInterceptorContextMut, BoxError,
17+
Interceptor, InterceptorRegistrar, SharedInterceptor,
18+
};
19+
use aws_smithy_runtime_api::client::orchestrator::ConfigBagAccessors;
20+
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
21+
use aws_smithy_types::config_bag::ConfigBag;
22+
23+
/// Interceptor that tells the SigV4 signer to add the signature to query params,
24+
/// and sets the request expiration time from the presigning config.
25+
#[derive(Debug)]
26+
pub(crate) struct SigV4PresigningInterceptor {
27+
config: PresigningConfig,
28+
}
29+
30+
impl SigV4PresigningInterceptor {
31+
pub(crate) fn new(config: PresigningConfig) -> Self {
32+
Self { config }
33+
}
34+
}
35+
36+
impl Interceptor for SigV4PresigningInterceptor {
37+
fn modify_before_serialization(
38+
&self,
39+
_context: &mut BeforeSerializationInterceptorContextMut<'_>,
40+
cfg: &mut ConfigBag,
41+
) -> Result<(), BoxError> {
42+
cfg.put::<HeaderSerializationSettings>(
43+
HeaderSerializationSettings::new()
44+
.omit_default_content_length()
45+
.omit_default_content_type(),
46+
);
47+
cfg.set_request_time(SharedTimeSource::new(StaticTimeSource::new(
48+
self.config.start_time(),
49+
)));
50+
Ok(())
51+
}
52+
53+
fn modify_before_signing(
54+
&self,
55+
_context: &mut BeforeTransmitInterceptorContextMut<'_>,
56+
cfg: &mut ConfigBag,
57+
) -> Result<(), BoxError> {
58+
if let Some(mut config) = cfg.get::<SigV4OperationSigningConfig>().cloned() {
59+
config.signing_options.expires_in = Some(self.config.expires());
60+
config.signing_options.signature_type = HttpSignatureType::HttpRequestQueryParams;
61+
config.signing_options.payload_override =
62+
Some(aws_sigv4::http_request::SignableBody::UnsignedPayload);
63+
cfg.put::<SigV4OperationSigningConfig>(config);
64+
Ok(())
65+
} else {
66+
Err(
67+
"SigV4 presigning requires the SigV4OperationSigningConfig to be in the config bag. \
68+
This is a bug. Please file an issue.".into(),
69+
)
70+
}
71+
}
72+
}
73+
74+
/// Runtime plugin that registers the SigV4PresigningInterceptor.
75+
#[derive(Debug)]
76+
pub(crate) struct SigV4PresigningRuntimePlugin {
77+
interceptor: SharedInterceptor,
78+
}
79+
80+
impl SigV4PresigningRuntimePlugin {
81+
pub(crate) fn new(config: PresigningConfig) -> Self {
82+
Self {
83+
interceptor: SharedInterceptor::new(SigV4PresigningInterceptor::new(config)),
84+
}
85+
}
86+
}
87+
88+
impl RuntimePlugin for SigV4PresigningRuntimePlugin {
89+
fn configure(
90+
&self,
91+
cfg: &mut ConfigBag,
92+
interceptors: &mut InterceptorRegistrar,
93+
) -> Result<(), BoxError> {
94+
// Disable some SDK interceptors that shouldn't run for presigning
95+
cfg.put(DisableInvocationIdInterceptor::new("presigning"));
96+
cfg.put(DisableRequestInfoInterceptor::new("presigning"));
97+
cfg.put(DisableUserAgentInterceptor::new("presigning"));
98+
99+
// Register the presigning interceptor
100+
interceptors.register(self.interceptor.clone());
101+
Ok(())
102+
}
103+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#![allow(dead_code)]
7+
8+
//! Tower middleware service for creating presigned requests
9+
10+
use crate::presigning::PresignedRequest;
11+
use aws_smithy_http::operation;
12+
use http::header::USER_AGENT;
13+
use std::future::{ready, Ready};
14+
use std::marker::PhantomData;
15+
use std::task::{Context, Poll};
16+
17+
/// Tower [`Service`](tower::Service) for generated a [`PresignedRequest`] from the AWS middleware.
18+
#[derive(Default, Debug)]
19+
#[non_exhaustive]
20+
pub(crate) struct PresignedRequestService<E> {
21+
_phantom: PhantomData<E>,
22+
}
23+
24+
// Required because of the derive Clone on MapRequestService.
25+
// Manually implemented to avoid requiring errors to implement Clone.
26+
impl<E> Clone for PresignedRequestService<E> {
27+
fn clone(&self) -> Self {
28+
Self {
29+
_phantom: Default::default(),
30+
}
31+
}
32+
}
33+
34+
impl<E> PresignedRequestService<E> {
35+
/// Creates a new `PresignedRequestService`
36+
pub(crate) fn new() -> Self {
37+
Self {
38+
_phantom: Default::default(),
39+
}
40+
}
41+
}
42+
43+
impl<E> tower::Service<operation::Request> for PresignedRequestService<E> {
44+
type Response = PresignedRequest;
45+
type Error = E;
46+
type Future = Ready<Result<PresignedRequest, E>>;
47+
48+
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
49+
Poll::Ready(Ok(()))
50+
}
51+
52+
fn call(&mut self, req: operation::Request) -> Self::Future {
53+
let (mut req, _) = req.into_parts();
54+
55+
// Remove user agent headers since the request will not be executed by the AWS Rust SDK.
56+
req.headers_mut().remove(USER_AGENT);
57+
req.headers_mut().remove("X-Amz-User-Agent");
58+
59+
ready(Ok(PresignedRequest::new(req.map(|_| ()))))
60+
}
61+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../rust-runtime/inlineable/src/serialization_settings.rs

aws/rust-runtime/aws-runtime/src/invocation_id.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ pub use test_util::{NoInvocationIdGenerator, PredefinedInvocationIdGenerator};
1818
#[allow(clippy::declare_interior_mutable_const)] // we will never mutate this
1919
const AMZ_SDK_INVOCATION_ID: HeaderName = HeaderName::from_static("amz-sdk-invocation-id");
2020

21+
/// Config marker that disables the invocation ID interceptor.
22+
#[doc(hidden)]
23+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24+
pub struct DisableInvocationIdInterceptor {
25+
why: &'static str,
26+
}
27+
28+
impl DisableInvocationIdInterceptor {
29+
/// Creates a new `DisableInvocationIdInterceptor`.
30+
///
31+
/// Takes a human readable string for the `Debug` impl to state why it is being disabled.
32+
/// This is to assist with debugging issues with requests.
33+
pub fn new(why: &'static str) -> Self {
34+
Self { why }
35+
}
36+
}
37+
2138
/// A generator for returning new invocation IDs on demand.
2239
pub trait InvocationIdGenerator: Debug + Send + Sync {
2340
/// Call this function to receive a new [`InvocationId`] or an error explaining why one couldn't

aws/rust-runtime/aws-runtime/src/request_info.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@ use std::time::{Duration, SystemTime};
2020
#[allow(clippy::declare_interior_mutable_const)] // we will never mutate this
2121
const AMZ_SDK_REQUEST: HeaderName = HeaderName::from_static("amz-sdk-request");
2222

23+
/// Config marker that disables the invocation ID interceptor.
24+
#[doc(hidden)]
25+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26+
pub struct DisableRequestInfoInterceptor {
27+
why: &'static str,
28+
}
29+
30+
impl DisableRequestInfoInterceptor {
31+
/// Creates a new `DisableRequestInfoInterceptor`.
32+
///
33+
/// Takes a human readable string for the `Debug` impl to state why it is being disabled.
34+
/// This is to assist with debugging issues with requests.
35+
pub fn new(why: &'static str) -> Self {
36+
Self { why }
37+
}
38+
}
39+
2340
/// Generates and attaches a request header that communicates request-related metadata.
2441
/// Examples include:
2542
///

aws/rust-runtime/aws-runtime/src/user_agent.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ impl From<InvalidHeaderValue> for UserAgentInterceptorError {
4949
}
5050
}
5151

52+
/// Config marker that disables the user agent interceptor.
53+
#[doc(hidden)]
54+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
55+
pub struct DisableUserAgentInterceptor {
56+
why: &'static str,
57+
}
58+
59+
impl DisableUserAgentInterceptor {
60+
/// Creates a new `DisableUserAgentInterceptor`.
61+
///
62+
/// Takes a human readable string for the `Debug` impl to state why it is being disabled.
63+
/// This is to assist with debugging issues with requests.
64+
pub fn new(why: &'static str) -> Self {
65+
Self { why }
66+
}
67+
}
68+
5269
/// Generates and attaches the AWS SDK's user agent to a HTTP request
5370
#[non_exhaustive]
5471
#[derive(Debug, Default)]

aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class AwsFluentClientDecorator : ClientCodegenDecorator {
6464
reexportSmithyClientBuilder = false,
6565
generics = generics,
6666
customizations = listOf(
67-
AwsPresignedFluentBuilderMethod(runtimeConfig),
67+
AwsPresignedFluentBuilderMethod(codegenContext),
6868
AwsFluentClientDocs(codegenContext),
6969
),
7070
retryClassifier = AwsRuntimeType.awsHttp(runtimeConfig).resolve("retry::AwsResponseRetryClassifier"),

0 commit comments

Comments
 (0)