Unverified Commit 7b451c57 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Add support for the Smithy Auth trait (#652)



* Add support for the Smithy Auth trait

Some services have explicitly disabled authentication. This adds two things:
1. Customization to remove auth schemes for 2 STS operations
2. Add codegen support for the OptionalAuth and Auth trait to code generation.

CredentialsStage now will pass through unset credentials and the signer will check the signing trait.

A future enhancement may remove the signing middleware entirely.

* Update changelog

* Remove unused STS import

* remove unused variable

* Update CHANGELOG.md

Co-authored-by: default avatarJohn DiSanti <jdisanti@amazon.com>

Co-authored-by: default avatarJohn DiSanti <jdisanti@amazon.com>
parent f7ba94c1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@ vNext (Month Day, Year)
- Add initial implementation of a default provider chain. (#650)
- Update smithy-client to simplify creating HTTP/HTTPS connectors (#650)
- Remove Bintray/JCenter source from gradle build. (#651)
- Add support for the smithy auth trait. This enables authorizations that explicitly disable authorization to work when no credentials have been provided. (#652)
- :bug: Fix STS Assume Role with WebIdentity & Assume role with SAML to support clients with no credentials provided (#652)

v0.20 (August 10th, 2021)
--------------------------
+60 −20
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
 * SPDX-License-Identifier: Apache-2.0.
 */

use crate::provider::CredentialsProvider;
use crate::provider::{CredentialsError, CredentialsProvider};
use smithy_http::middleware::AsyncMapRequest;
use smithy_http::operation::Request;
use std::future::Future;
@@ -24,6 +24,30 @@ impl CredentialsStage {
    pub fn new() -> Self {
        CredentialsStage
    }

    async fn load_creds(mut request: Request) -> Result<Request, CredentialsStageError> {
        let provider = request.properties().get::<CredentialsProvider>().cloned();
        let provider = match provider {
            Some(provider) => provider,
            None => {
                tracing::info!("no credentials provider for request");
                return Ok(request);
            }
        };
        match provider.provide_credentials().await {
            Ok(creds) => {
                request.properties_mut().insert(creds);
            }
            // ignore the case where there is no provider wired up
            Err(CredentialsError::CredentialsNotLoaded) => {
                tracing::info!("provider returned CredentialsNotLoaded, ignoring")
            }
            // if we get another error class, there is probably something actually wrong that the user will
            // want to know about
            Err(other) => return Err(CredentialsStageError::CredentialsLoadingError(other)),
        }
        Ok(request)
    }
}

mod error {
@@ -70,29 +94,15 @@ impl AsyncMapRequest for CredentialsStage {
    type Error = CredentialsStageError;
    type Future = Pin<Box<dyn Future<Output = Result<Request, Self::Error>> + Send + 'static>>;

    fn apply(&self, mut request: Request) -> BoxFuture<Result<Request, Self::Error>> {
        Box::pin(async move {
            let provider = {
                let properties = request.properties();
                let credential_provider = properties
                    .get::<CredentialsProvider>()
                    .ok_or(CredentialsStageError::MissingCredentialsProvider)?;
                // we need to enable releasing the config lock so that we don't hold the config
                // lock across an await point
                credential_provider.clone()
            };
            let cred_future = { provider.provide_credentials() };
            let credentials = cred_future.await?;
            request.properties_mut().insert(credentials);
            Ok(request)
        })
    fn apply(&self, request: Request) -> BoxFuture<Result<Request, Self::Error>> {
        Box::pin(Self::load_creds(request))
    }
}

#[cfg(test)]
mod tests {
    use super::CredentialsStage;
    use crate::provider::set_provider;
    use crate::provider::{async_provide_credentials_fn, set_provider, CredentialsError};
    use crate::Credentials;
    use smithy_http::body::SdkBody;
    use smithy_http::middleware::AsyncMapRequest;
@@ -100,12 +110,42 @@ mod tests {
    use std::sync::Arc;

    #[tokio::test]
    async fn async_map_request_apply_requires_credential_provider() {
    async fn no_cred_provider_is_ok() {
        let req = operation::Request::new(http::Request::new(SdkBody::from("some body")));
        CredentialsStage::new()
            .apply(req)
            .await
            .expect_err("should fail if there's no credential provider in the bag");
            .expect("no credential provider should not populate credentials");
    }

    #[tokio::test]
    async fn provider_failure_is_failure() {
        let mut req = operation::Request::new(http::Request::new(SdkBody::from("some body")));
        set_provider(
            &mut req.properties_mut(),
            Arc::new(async_provide_credentials_fn(|| async {
                Err(CredentialsError::Unhandled("whoops".into()))
            })),
        );
        CredentialsStage::new()
            .apply(req)
            .await
            .expect_err("no credential provider should not populate credentials");
    }

    #[tokio::test]
    async fn credentials_not_loaded_is_ok() {
        let mut req = operation::Request::new(http::Request::new(SdkBody::from("some body")));
        set_provider(
            &mut req.properties_mut(),
            Arc::new(async_provide_credentials_fn(|| async {
                Err(CredentialsError::CredentialsNotLoaded)
            })),
        );
        CredentialsStage::new()
            .apply(req)
            .await
            .expect("credentials not loaded is OK");
    }

    #[tokio::test]
+15 −2
Original line number Diff line number Diff line
@@ -3,7 +3,9 @@
 * SPDX-License-Identifier: Apache-2.0.
 */

use crate::signer::{OperationSigningConfig, RequestConfig, SigV4Signer, SigningError};
use crate::signer::{
    OperationSigningConfig, RequestConfig, SigV4Signer, SigningError, SigningRequirements,
};
use aws_auth::Credentials;
use aws_sigv4::http_request::SignableBody;
use aws_types::region::SigningRegion;
@@ -106,7 +108,18 @@ impl MapRequest for SigV4SigningStage {

    fn apply(&self, req: Request) -> Result<Request, Self::Error> {
        req.augment(|mut req, config| {
            let (operation_config, request_config, creds) = signing_config(config)?;
            let operation_config = config
                .get::<OperationSigningConfig>()
                .ok_or(SigningStageError::MissingSigningConfig)?;
            let (operation_config, request_config, creds) =
                match &operation_config.signing_requirements {
                    SigningRequirements::Disabled => return Ok(req),
                    SigningRequirements::Optional => match signing_config(config) {
                        Ok(parts) => parts,
                        Err(_) => return Ok(req),
                    },
                    SigningRequirements::Required => signing_config(config)?,
                };

            let signature = self
                .signer
+17 −0
Original line number Diff line number Diff line
@@ -40,10 +40,12 @@ pub enum HttpSignatureType {
/// Although these fields MAY be customized on a per request basis, they are generally static
/// for a given operation
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct OperationSigningConfig {
    pub algorithm: SigningAlgorithm,
    pub signature_type: HttpSignatureType,
    pub signing_options: SigningOptions,
    pub signing_requirements: SigningRequirements,
}

impl OperationSigningConfig {
@@ -58,8 +60,23 @@ impl OperationSigningConfig {
                double_uri_encode: true,
                content_sha256_header: false,
            },
            signing_requirements: SigningRequirements::Required,
        }
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum SigningRequirements {
    /// A signature MAY be added if credentials are defined
    Optional,

    /// A signature MUST be added.
    ///
    /// If no credentials are provided, this will return an error without dispatching the operation.
    Required,

    /// A signature MUST NOT be added.
    Disabled,
}

#[derive(Clone, Eq, PartialEq)]
+2 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ package software.amazon.smithy.rustsdk

import software.amazon.smithy.rust.codegen.smithy.customize.CombinedCodegenDecorator
import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayDecorator
import software.amazon.smithy.rustsdk.customize.auth.DisabledAuthDecorator
import software.amazon.smithy.rustsdk.customize.ec2.Ec2Decorator
import software.amazon.smithy.rustsdk.customize.s3.S3Decorator

@@ -23,6 +24,7 @@ val DECORATORS = listOf(
    CrateLicenseDecorator(),

    // Service specific decorators
    DisabledAuthDecorator(),
    ApiGatewayDecorator(),
    S3Decorator(),
    Ec2Decorator()
Loading