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

Endpoints 2.0 Integration pre-work #2063

Merged
merged 7 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 17 additions & 5 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ author = "david-perez"
[[smithy-rs]]
message = "Fix bug that can cause panics in paginators"
references = ["smithy-rs#1903", "smithy-rs#1902"]
meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client"}
meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client" }
author = "rcoh"

[[smithy-rs]]
Expand All @@ -65,13 +65,13 @@ Operation metadata is now added to the property bag before sending requests allo
differently depending on the operation being sent.
"""
references = ["smithy-rs#1919"]
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client"}
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" }
author = "Velfi"

[[smithy-rs]]
message = "Upgrade Smithy to v1.26"
references = ["smithy-rs#1929"]
meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "all"}
meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "all" }
author = "Velfi"

[[smithy-rs]]
Expand Down Expand Up @@ -392,7 +392,7 @@ match some_enum {
This is forward-compatible because the execution will hit the second last match arm regardless of whether the enum defines `SomeEnum::NewVariant` or not.
"""
references = ["smithy-rs#1945"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"}
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "ysaito1001"

[[aws-sdk-rust]]
Expand Down Expand Up @@ -635,5 +635,17 @@ author = "jdisanti"
[[smithy-rs]]
message = "`SdkBody` callbacks have been removed. If you were using these, please [file an issue](https://github.com/awslabs/smithy-rs/issues/new) so that we can better understand your use-case and provide the support you need."
references = ["smithy-rs#2065"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client"}
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"

[[aws-sdk-rust]]
message = "`AwsEndpointStage`, a middleware which set endpoints and auth has been split into `AwsAuthStage` and `SmithyEndpointStage`. Related types have also been renamed."
references = ["smithy-rs#2063"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "rcoh"

[[smithy-rs]]
message = "Added SmithyEndpointStage which can be used to set an endpoint for smithy-native clients"
references = ["smithy-rs#2063"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
author = "rcoh"
130 changes: 46 additions & 84 deletions aws/rust-runtime/aws-endpoint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,15 @@ pub use partition::PartitionResolver;
use std::collections::HashMap;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting to seem like everything in aws-endpoint should just be in aws-http.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this crate can go away soon

use aws_smithy_http::endpoint::error::ResolveEndpointError;
use aws_smithy_http::endpoint::{apply_endpoint, EndpointPrefix, ResolveEndpoint};
use aws_smithy_http::endpoint::ResolveEndpoint;
use aws_smithy_http::middleware::MapRequest;
use aws_smithy_http::operation::Request;
use aws_smithy_types::endpoint::Endpoint as SmithyEndpoint;
use aws_smithy_types::Document;
use aws_types::region::{Region, SigningRegion};
use aws_types::SigningService;
use http::header::HeaderName;
use http::{HeaderValue, Uri};
use std::error::Error;
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;

pub use aws_types::endpoint::{AwsEndpoint, BoxError, CredentialScope, ResolveAwsEndpoint};
Expand Down Expand Up @@ -87,93 +84,62 @@ impl ResolveEndpoint<Params> for EndpointShim {
}
}

/// Middleware Stage to Add an Endpoint to a Request
/// Middleware Stage to add authentication information from a Smithy endpoint into the property bag
///
/// AwsEndpointStage implements [`MapRequest`](aws_smithy_http::middleware::MapRequest). It will:
/// 1. Load an endpoint provider from the property bag.
/// 2. Load an endpoint given the [`Region`](aws_types::region::Region) in the property bag.
/// 3. Apply the endpoint to the URI in the request
/// 4. Set the `SigningRegion` and `SigningService` in the property bag to drive downstream
/// AwsAuthStage implements [`MapRequest`](MapRequest). It will:
/// 1. Load an endpoint from the property bag
/// 2. Set the `SigningRegion` and `SigningService` in the property bag to drive downstream
/// signing middleware.
#[derive(Clone, Debug)]
pub struct AwsEndpointStage;
pub struct AwsAuthStage;

#[derive(Debug)]
enum AwsEndpointStageErrorKind {
enum AwsAuthStageErrorKind {
NoEndpointResolver,
EndpointResolutionError(BoxError),
}

#[derive(Debug)]
pub struct AwsEndpointStageError {
kind: AwsEndpointStageErrorKind,
pub struct AwsAuthStageError {
kind: AwsAuthStageErrorKind,
}

impl fmt::Display for AwsEndpointStageError {
impl fmt::Display for AwsAuthStageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use AwsEndpointStageErrorKind::*;
use AwsAuthStageErrorKind::*;
match &self.kind {
NoEndpointResolver => write!(f, "endpoint resolution failed: no endpoint resolver"),
NoEndpointResolver => write!(f, "endpoint resolution failed: no endpoint present"),
EndpointResolutionError(_) => write!(f, "endpoint resolution failed"),
}
}
}

impl Error for AwsEndpointStageError {
impl Error for AwsAuthStageError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use AwsEndpointStageErrorKind::*;
use AwsAuthStageErrorKind::*;
match &self.kind {
EndpointResolutionError(source) => Some(source.as_ref() as _),
NoEndpointResolver => None,
}
}
}

impl From<AwsEndpointStageErrorKind> for AwsEndpointStageError {
fn from(kind: AwsEndpointStageErrorKind) -> Self {
impl From<AwsAuthStageErrorKind> for AwsAuthStageError {
fn from(kind: AwsAuthStageErrorKind) -> Self {
Self { kind }
}
}

impl MapRequest for AwsEndpointStage {
type Error = AwsEndpointStageError;
impl MapRequest for AwsAuthStage {
type Error = AwsAuthStageError;

fn apply(&self, request: Request) -> Result<Request, Self::Error> {
request.augment(|mut http_req, props| {
let endpoint_result = props
.get_mut::<aws_smithy_http::endpoint::Result>()
.ok_or(AwsEndpointStageErrorKind::NoEndpointResolver)?;
let endpoint = match endpoint_result {
// downgrade the mut ref to a shared ref
Ok(_endpoint) => props.get::<aws_smithy_http::endpoint::Result>()
.expect("unreachable (prevalidated that the endpoint is in the bag)")
.as_ref()
.expect("unreachable (prevalidated that this is OK)"),
Err(e) => {
// We need to own the error to return it, so take it and leave a stub error in
// its place
return Err(AwsEndpointStageErrorKind::EndpointResolutionError(std::mem::replace(
e,
ResolveEndpointError::message("the original error was directly returned")
).into()).into());
}
};
let (uri, signing_scope_override, signing_service_override) = smithy_to_aws(endpoint)
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err))?;
tracing::debug!(endpoint = ?endpoint, base_region = ?signing_scope_override, "resolved endpoint");
apply_endpoint(http_req.uri_mut(), &uri, props.get::<EndpointPrefix>())
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err.into()))?;
for (header_name, header_values) in endpoint.headers() {
http_req.headers_mut().remove(header_name);
for value in header_values {
http_req.headers_mut().insert(
HeaderName::from_str(header_name)
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err.into()))?,
HeaderValue::from_str(value)
.map_err(|err| AwsEndpointStageErrorKind::EndpointResolutionError(err.into()))?,
);
}
}
request.augment(|http_req, props| {
let endpoint = props
.get::<aws_smithy_types::endpoint::Endpoint>()
.ok_or(AwsAuthStageErrorKind::NoEndpointResolver)?;
let (signing_scope_override, signing_service_override) = smithy_to_aws(endpoint)
.map_err(|err| AwsAuthStageErrorKind::EndpointResolutionError(err))?;

if let Some(signing_scope) = signing_scope_override {
props.insert(signing_scope);
Expand All @@ -186,17 +152,14 @@ impl MapRequest for AwsEndpointStage {
}
}

type EndpointMetadata = (Uri, Option<SigningRegion>, Option<SigningService>);
type EndpointMetadata = (Option<SigningRegion>, Option<SigningService>);

fn smithy_to_aws(value: &SmithyEndpoint) -> Result<EndpointMetadata, Box<dyn Error + Send + Sync>> {
let uri: Uri = value.url().parse()?;
// look for v4 as an auth scheme
let auth_schemes = match value
.properties()
.get("authSchemes")
.ok_or("no auth schemes in metadata")?
{
Document::Array(schemes) => schemes,
let auth_schemes = match value.properties().get("authSchemes") {
Some(Document::Array(schemes)) => schemes,
// no auth schemes:
None => return Ok((None, None)),
_other => return Err("expected an array for authSchemes".into()),
};
let v4 = auth_schemes
Expand All @@ -210,7 +173,7 @@ fn smithy_to_aws(value: &SmithyEndpoint) -> Result<EndpointMetadata, Box<dyn Err
_ => None,
})
.next()
.ok_or("could not find v4 as an acceptable auth scheme")?;
.ok_or("could not find v4 as an acceptable auth scheme (the SDK does not support Bearer Auth at this time)")?;

let signing_scope = match v4.get("signingRegion") {
Some(Document::String(s)) => Some(SigningRegion::from(Region::new(s.clone()))),
Expand All @@ -222,15 +185,14 @@ fn smithy_to_aws(value: &SmithyEndpoint) -> Result<EndpointMetadata, Box<dyn Err
None => None,
_ => return Err("unexpected type".into()),
};
Ok((uri, signing_scope, signing_service))
Ok((signing_scope, signing_service))
}

#[cfg(test)]
mod test {
use std::sync::Arc;

use http::header::HOST;
use http::Uri;

use aws_smithy_http::body::SdkBody;
use aws_smithy_http::endpoint::ResolveEndpoint;
Expand All @@ -241,7 +203,7 @@ mod test {
use aws_types::SigningService;

use crate::partition::endpoint::{Metadata, Protocol, SignatureVersion};
use crate::{AwsEndpointStage, EndpointShim, Params};
use crate::{AwsAuthStage, EndpointShim, Params};

#[test]
fn default_endpoint_updates_request() {
Expand All @@ -260,25 +222,21 @@ mod test {
props.insert(SigningService::from_static("kinesis"));
props.insert(
EndpointShim::from_arc(provider)
.resolve_endpoint(&Params::new(Some(region.clone()))),
.resolve_endpoint(&Params::new(Some(region.clone())))
.unwrap(),
);
};
let req = AwsEndpointStage.apply(req).expect("should succeed");
let req = AwsAuthStage.apply(req).expect("should succeed");
assert_eq!(req.properties().get(), Some(&SigningRegion::from(region)));
assert_eq!(
req.properties().get(),
Some(&SigningService::from_static("kinesis"))
);

let (req, conf) = req.into_parts();
assert_eq!(
req.uri(),
&Uri::from_static("https://kinesis.us-east-1.amazonaws.com")
);
assert!(req.headers().get(HOST).is_none());
assert!(req.http().headers().get(HOST).is_none());
assert!(
conf.acquire()
.get::<aws_smithy_http::endpoint::Result>()
req.properties()
.get::<aws_smithy_types::endpoint::Endpoint>()
.is_some(),
"Endpoint middleware MUST leave the result in the bag"
);
Expand All @@ -303,10 +261,12 @@ mod test {
props.insert(region.clone());
props.insert(SigningService::from_static("qldb"));
props.insert(
EndpointShim::from_arc(provider).resolve_endpoint(&Params::new(Some(region))),
EndpointShim::from_arc(provider)
.resolve_endpoint(&Params::new(Some(region)))
.unwrap(),
);
};
let req = AwsEndpointStage.apply(req).expect("should succeed");
let req = AwsAuthStage.apply(req).expect("should succeed");
assert_eq!(
req.properties().get(),
Some(&SigningRegion::from(Region::new("us-east-override")))
Expand All @@ -333,10 +293,12 @@ mod test {
props.insert(region.clone());
props.insert(SigningService::from_static("qldb"));
props.insert(
EndpointShim::from_arc(provider).resolve_endpoint(&Params::new(Some(region))),
EndpointShim::from_arc(provider)
.resolve_endpoint(&Params::new(Some(region)))
.unwrap(),
);
};
let req = AwsEndpointStage.apply(req).expect("should succeed");
let req = AwsAuthStage.apply(req).expect("should succeed");
assert_eq!(
req.properties().get(),
Some(&SigningRegion::from(Region::new("us-east-1")))
Expand Down
40 changes: 24 additions & 16 deletions aws/rust-runtime/aws-inlineable/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,36 @@

//! Base Middleware Stack

use aws_endpoint::AwsEndpointStage;
use aws_endpoint::AwsAuthStage;
use aws_http::auth::CredentialsStage;
use aws_http::recursion_detection::RecursionDetectionStage;
use aws_http::user_agent::UserAgentStage;
use aws_sig_auth::middleware::SigV4SigningStage;
use aws_sig_auth::signer::SigV4Signer;
use aws_smithy_http::endpoint::middleware::SmithyEndpointStage;
use aws_smithy_http_tower::map_request::{AsyncMapRequestLayer, MapRequestLayer};
use std::fmt::Debug;
use tower::layer::util::{Identity, Stack};
use tower::ServiceBuilder;

type DefaultMiddlewareStack = Stack<
/// Macro to generate the tower stack type. Arguments should be in reverse order
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool

macro_rules! stack_type {
($first: ty, $($rest:ty),+) => {
tower::layer::util::Stack<$first, stack_type!($($rest),+)>
};
($only: ty) => {
tower::layer::util::Stack<$only, tower::layer::util::Identity>
}
}

// Note: the layers here appear in reverse order
type DefaultMiddlewareStack = stack_type!(
MapRequestLayer<RecursionDetectionStage>,
Stack<
MapRequestLayer<SigV4SigningStage>,
Stack<
AsyncMapRequestLayer<CredentialsStage>,
Stack<
MapRequestLayer<UserAgentStage>,
Stack<MapRequestLayer<AwsEndpointStage>, Identity>,
>,
>,
>,
>;
MapRequestLayer<SigV4SigningStage>,
AsyncMapRequestLayer<CredentialsStage>,
MapRequestLayer<UserAgentStage>,
MapRequestLayer<AwsAuthStage>,
MapRequestLayer<SmithyEndpointStage>
);

/// AWS Middleware Stack
///
Expand All @@ -54,7 +60,8 @@ impl DefaultMiddleware {
fn base() -> ServiceBuilder<DefaultMiddlewareStack> {
let credential_provider = AsyncMapRequestLayer::for_mapper(CredentialsStage::new());
let signer = MapRequestLayer::for_mapper(SigV4SigningStage::new(SigV4Signer::new()));
let endpoint_resolver = MapRequestLayer::for_mapper(AwsEndpointStage);
let endpoint_stage = MapRequestLayer::for_mapper(SmithyEndpointStage::new());
let auth_stage = MapRequestLayer::for_mapper(AwsAuthStage);
let user_agent = MapRequestLayer::for_mapper(UserAgentStage::new());
let recursion_detection = MapRequestLayer::for_mapper(RecursionDetectionStage::new());
// These layers can be considered as occurring in order, that is:
Expand All @@ -64,7 +71,8 @@ fn base() -> ServiceBuilder<DefaultMiddlewareStack> {
// 4. Sign with credentials
// (5. Dispatch over the wire)
ServiceBuilder::new()
.layer(endpoint_resolver)
.layer(endpoint_stage)
.layer(auth_stage)
.layer(user_agent)
.layer(credential_provider)
.layer(signer)
Expand Down
Loading