Skip to content

Commit

Permalink
Add support for SSO bearer token authentication to the SDK (#3453)
Browse files Browse the repository at this point in the history
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:
- #3381
- #3442
- #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._
  • Loading branch information
jdisanti authored Mar 6, 2024
1 parent 1f4d2b9 commit 58a14ca
Show file tree
Hide file tree
Showing 56 changed files with 2,304 additions and 643 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
7 changes: 4 additions & 3 deletions aws/rust-runtime/aws-config/external-types.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions aws/rust-runtime/aws-config/src/default_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
13 changes: 7 additions & 6 deletions aws/rust-runtime/aws-config/src/default_provider/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand All @@ -256,7 +257,7 @@ mod test {
.provide_credentials()
.await
}
})
}),
)
.await
.unwrap()
Expand Down
129 changes: 129 additions & 0 deletions aws/rust-runtime/aws-config/src/default_provider/token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* 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 }
}
}
56 changes: 54 additions & 2 deletions aws/rust-runtime/aws-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ mod loader {
use crate::meta::region::ProvideRegion;
use crate::profile::profile_file::ProfileFiles;
use crate::provider_config::ProviderConfig;
use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider};
use aws_credential_types::provider::{
token::{ProvideToken, SharedTokenProvider},
ProvideCredentials, SharedCredentialsProvider,
};
use aws_credential_types::Credentials;
use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
use aws_smithy_async::time::{SharedTimeSource, TimeSource};
Expand Down Expand Up @@ -253,6 +256,7 @@ mod loader {
app_name: Option<AppName>,
identity_cache: Option<SharedIdentityCache>,
credentials_provider: CredentialsProviderOption,
token_provider: Option<SharedTokenProvider>,
endpoint_url: Option<String>,
region: Option<Box<dyn ProvideRegion>>,
retry_config: Option<RetryConfig>,
Expand Down Expand Up @@ -490,7 +494,36 @@ mod loader {

/// Set test credentials for use when signing requests
pub fn test_credentials(self) -> Self {
self.credentials_provider(Credentials::for_tests())
#[allow(unused_mut)]
let mut ret = self.credentials_provider(Credentials::for_tests());
#[cfg(all(feature = "sso", feature = "test-util"))]
{
use aws_smithy_runtime_api::client::identity::http::Token;
ret = ret.token_provider(Token::for_tests());
}
ret
}

/// Override the access token provider used to build [`SdkConfig`].
///
/// # Examples
///
/// Override the token provider but load the default value for region:
/// ```no_run
/// # use aws_credential_types::Token;
/// # fn create_my_token_provider() -> Token {
/// # Token::new("example", None)
/// # }
/// # async fn create_config() {
/// let config = aws_config::from_env()
/// .token_provider(create_my_token_provider())
/// .load()
/// .await;
/// # }
/// ```
pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
self.token_provider = Some(SharedTokenProvider::new(token_provider));
self
}

/// Override the name of the app used to build [`SdkConfig`].
Expand Down Expand Up @@ -754,6 +787,24 @@ mod loader {
CredentialsProviderOption::ExplicitlyUnset => None,
};

let token_provider = match self.token_provider {
Some(provider) => Some(provider),
None => {
#[cfg(feature = "sso")]
{
let mut builder =
crate::default_provider::token::DefaultTokenChain::builder()
.configure(conf.clone());
builder.set_region(region.clone());
Some(SharedTokenProvider::new(builder.build().await))
}
#[cfg(not(feature = "sso"))]
{
None
}
}
};

let mut builder = SdkConfig::builder()
.region(region)
.retry_config(retry_config)
Expand All @@ -765,6 +816,7 @@ mod loader {
builder.set_app_name(app_name);
builder.set_identity_cache(self.identity_cache);
builder.set_credentials_provider(credentials_provider);
builder.set_token_provider(token_provider);
builder.set_sleep_impl(sleep_impl);
builder.set_endpoint_url(self.endpoint_url);
builder.set_use_fips(use_fips);
Expand Down
1 change: 1 addition & 0 deletions aws/rust-runtime/aws-config/src/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
pub mod credentials;
pub mod region;
pub mod token;
Loading

0 comments on commit 58a14ca

Please sign in to comment.