Unverified Commit 58a14caa authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Add support for SSO bearer token authentication to the SDK (#3453)

This PR adds support for SSO bearer token authentication to the AWS SDK,
specifically for Code Catalyst, which requires authentication via SSO
with a Builder ID using a bearer token rather than SigV4.

This functionality was developed in a feature branch, and this PR merely
merges that branch to main. The changes consist of the following
previous PRs:
- https://github.com/smithy-lang/smithy-rs/pull/3381
- https://github.com/smithy-lang/smithy-rs/pull/3442
- https://github.com/smithy-lang/smithy-rs/pull/3443

All these changes have been reviewed in the previous PRs, but it would
be good to review this again as a whole to verify it all looks good.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
parent 1f4d2b9e
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -40,3 +40,9 @@ message = "Add support for Lambda's `InvokeWithResponseStreaming` and Bedrock Ag
references = ["aws-sdk-rust#1075", "aws-sdk-rust#1080", "smithy-rs#3451"]
meta = { "breaking" = false, "bug" = false, "tada" = true }
author = "jdisanti"

[[aws-sdk-rust]]
message = "Added support for SSO bearer token authentication. The aws-sdk-codecatalyst crate can now send requests without erroring."
references = ["aws-sdk-rust#703", "smithy-rs#3453"]
meta = { "breaking" = false, "bug" = false, "tada" = true }
author = "jdisanti"
+4 −3
Original line number Diff line number Diff line
@@ -4,9 +4,10 @@
# to the exposed SDK crates happens.
allowed_external_types = [
   "aws_credential_types::cache::CredentialsCache",
   "aws_credential_types::provider::ProvideCredentials",
   "aws_credential_types::provider::Result",
   "aws_credential_types::provider::SharedCredentialsProvider",
   "aws_credential_types::provider::credentials::ProvideCredentials",
   "aws_credential_types::provider::credentials::Result",
   "aws_credential_types::provider::credentials::SharedCredentialsProvider",
   "aws_credential_types::provider::token::ProvideToken",
   "aws_smithy_async::rt::sleep::AsyncSleep",
   "aws_smithy_async::rt::sleep::SharedAsyncSleep",
   "aws_smithy_async::time::SharedTimeSource",
+4 −0
Original line number Diff line number Diff line
@@ -47,3 +47,7 @@ pub mod use_fips;

/// Default dual-stack provider chain
pub mod use_dual_stack;

/// Default access token provider chain
#[cfg(feature = "sso")]
pub mod token;
+7 −6
Original line number Diff line number Diff line
@@ -198,10 +198,8 @@ impl Builder {

#[cfg(test)]
mod test {
    use crate::test_case::TestEnvironment;
    use crate::{
        default_provider::credentials::DefaultCredentialsChain, test_case::StaticTestProvider,
    };
    use crate::default_provider::credentials::DefaultCredentialsChain;
    use crate::test_case::{StaticTestProvider, TestEnvironment};
    use aws_credential_types::provider::ProvideCredentials;
    use aws_smithy_async::time::StaticTimeSource;
    use std::time::UNIX_EPOCH;
@@ -246,7 +244,10 @@ mod test {
            #[tokio::test]
            async fn $name() {
                let _ = crate::test_case::TestEnvironment::from_dir(
                    concat!("./test-data/default-credential-provider-chain/", stringify!($name)),
                    concat!(
                        "./test-data/default-credential-provider-chain/",
                        stringify!($name)
                    ),
                    crate::test_case::test_credentials_provider(|config| {
                        async move {
                            crate::default_provider::credentials::Builder::default()
@@ -256,7 +257,7 @@ mod test {
                                .provide_credentials()
                                .await
                        }
                    })
                    }),
                )
                .await
                .unwrap()
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

use crate::{
    meta::{region::ProvideRegion, token::TokenProviderChain},
    provider_config::ProviderConfig,
};
use aws_credential_types::provider::{future, token::ProvideToken};

/// Default access token provider chain
///
/// The region from the default region provider will be used
pub async fn default_provider() -> impl ProvideToken {
    DefaultTokenChain::builder().build().await
}

/// Default access token provider chain
///
/// Currently, the default chain only examines the shared config
/// (`~/.aws/config`) file and the SSO token cache to resolve an
/// access token.
///
/// The AWS CLI can be used to retrieve the initial access token into
/// the SSO token cache. Once it's there, the SDK can refresh automatically
/// long as the it remains refreshable (it will eventually expire).
///
/// # Examples
/// Create a default chain with a custom region:
/// ```no_run
/// use aws_types::region::Region;
/// use aws_config::default_provider::token::DefaultTokenChain;
/// let token_provider = DefaultTokenChain::builder()
///     .region(Region::new("us-west-1"))
///     .build();
/// ```
///
/// Create a default chain with no overrides:
/// ```no_run
/// use aws_config::default_provider::token::DefaultTokenChain;
/// let token_provider = DefaultTokenChain::builder().build();
/// ```
///
/// Create a default chain that uses a different profile:
/// ```no_run
/// use aws_config::default_provider::token::DefaultTokenChain;
/// let token_provider = DefaultTokenChain::builder()
///     .profile_name("otherprofile")
///     .build();
/// ```
#[derive(Debug)]
pub struct DefaultTokenChain {
    provider_chain: TokenProviderChain,
}

impl DefaultTokenChain {
    /// Builder for `DefaultTokenChain`.
    pub fn builder() -> Builder {
        Builder::default()
    }
}

impl ProvideToken for DefaultTokenChain {
    fn provide_token<'a>(&'a self) -> future::ProvideToken<'a>
    where
        Self: 'a,
    {
        self.provider_chain.provide_token()
    }
}

/// Builder for [`DefaultTokenChain`].
#[derive(Debug, Default)]
pub struct Builder {
    profile_file_builder: crate::profile::token::Builder,
    region_override: Option<Box<dyn ProvideRegion>>,
    region_chain: crate::default_provider::region::Builder,
    conf: Option<ProviderConfig>,
}

impl Builder {
    /// Sets the region used when making requests to AWS services
    ///
    /// When unset, the default region resolver chain will be used.
    pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
        self.set_region(Some(region));
        self
    }

    /// Sets the region used when making requests to AWS services
    ///
    /// When unset, the default region resolver chain will be used.
    pub fn set_region(&mut self, region: Option<impl ProvideRegion + 'static>) -> &mut Self {
        self.region_override = region.map(|provider| Box::new(provider) as _);
        self
    }

    /// Override the profile name used by this provider
    ///
    /// When unset, the value of the `AWS_PROFILE` environment variable will be used.
    pub fn profile_name(mut self, name: &str) -> Self {
        self.profile_file_builder = self.profile_file_builder.profile_name(name);
        self.region_chain = self.region_chain.profile_name(name);
        self
    }

    /// Override the configuration used for this provider
    pub(crate) fn configure(mut self, config: ProviderConfig) -> Self {
        self.region_chain = self.region_chain.configure(&config);
        self.conf = Some(config);
        self
    }

    /// Creates a [`DefaultTokenChain`].
    pub async fn build(self) -> DefaultTokenChain {
        let region = match self.region_override {
            Some(provider) => provider.region().await,
            None => self.region_chain.build().region().await,
        };
        let conf = self.conf.unwrap_or_default().with_region(region);

        let provider_chain = TokenProviderChain::first_try(
            "Profile",
            self.profile_file_builder.configure(&conf).build(),
        );
        DefaultTokenChain { provider_chain }
    }
}
Loading