Skip to content

Support selectable auth schemes #4203

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
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
37 changes: 37 additions & 0 deletions .changelog/1752160009.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
applies_to:
- aws-sdk-rust
- client
authors:
- ysaito1001
references:
- smithy-rs#4203
breaking: false
new_feature: true
bug_fix: false
---
Add support for configuring auth schemes manually using an auth scheme preference list.
The preference list allows customers to reprioritize the order of auth schemes originally
determined by the auth scheme resolver.
Customers can configure the auth scheme preference at the following locations, listed in order of precedence:
1. Service Client Configuration
```rust
use aws_runtime::auth::sigv4;
use aws_smithy_runtime_api::client::auth::AuthSchemeId;
use aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID;

let config = aws_sdk_s3::Config::builder()
.auth_scheme_preference([AuthSchemeId::from("scheme1"), sigv4::SCHEME_ID, HTTP_BEARER_AUTH_SCHEME_ID])
// ...
.build();
```
2. Environment Variable
```
AWS_AUTH_SCHEME_PREFERENCE=scheme1, sigv4, httpBearerAuth
```
3. Configuration File
```
auth_scheme_preference=scheme1, sigv4, httpBearerAuth
```
With this configuration, the auth scheme resolver will prefer to select them in the specified order,
if they are supported.
8 changes: 4 additions & 4 deletions aws/rust-runtime/aws-config/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion aws/rust-runtime/aws-config/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aws-config"
version = "1.8.2"
version = "1.8.3"
authors = [
"AWS Rust SDK Team <aws-sdk-rust@amazon.com>",
"Russell Cohen <rcoh@amazon.com>",
Expand Down
11 changes: 1 addition & 10 deletions aws/rust-runtime/aws-config/external-types.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
# require manual version bumping every time an automated version bump
# to the exposed SDK crates happens.
allowed_external_types = [
"aws_credential_types::cache::CredentialsCache",
"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_runtime::env_config::error::EnvConfigFileLoadError",
"aws_runtime::env_config::file::Builder",
Expand All @@ -17,30 +15,23 @@ allowed_external_types = [
"aws_runtime::env_config::section::EnvConfigSections",
"aws_runtime::env_config::section::Profile",
"aws_smithy_async::rt::sleep::AsyncSleep",
"aws_smithy_async::rt::sleep::SharedAsyncSleep",
"aws_smithy_async::time::SharedTimeSource",
"aws_smithy_async::time::TimeSource",
"aws_smithy_runtime::client::identity::cache::IdentityCache",
"aws_smithy_runtime::client::identity::cache::lazy::LazyCacheBuilder",
"aws_smithy_runtime_api::client::auth::AuthSchemePreference",
"aws_smithy_runtime_api::box_error::BoxError",
"aws_smithy_runtime_api::client::behavior_version::BehaviorVersion",
"aws_smithy_runtime_api::client::dns::ResolveDns",
"aws_smithy_runtime_api::client::dns::SharedDnsResolver",
"aws_smithy_runtime_api::client::http::HttpClient",
"aws_smithy_runtime_api::client::http::SharedHttpClient",
"aws_smithy_runtime_api::client::identity::ResolveCachedIdentity",
"aws_smithy_runtime_api::client::identity::ResolveIdentity",
"aws_smithy_runtime_api::client::orchestrator::HttpResponse",
"aws_smithy_runtime_api::client::result::SdkError",
"aws_smithy_runtime_api::client::retries::classifiers::ClassifyRetry",
"aws_smithy_runtime_api::client::retries::classifiers::SharedRetryClassifier",
"aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig",
"aws_smithy_types::body::SdkBody",
"aws_smithy_types::checksum_config::RequestChecksumCalculation",
"aws_smithy_types::checksum_config::ResponseChecksumValidation",
"aws_smithy_types::retry",
"aws_smithy_types::retry::*",
"aws_smithy_types::timeout",
"aws_smithy_types::timeout::OperationTimeoutConfig",
"aws_smithy_types::timeout::TimeoutConfig",
"aws_smithy_types::timeout::TimeoutConfigBuilder",
Expand Down
3 changes: 3 additions & 0 deletions aws/rust-runtime/aws-config/src/default_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,6 @@ pub mod checksums;

/// Default provider chain for account-based endpoint mode
pub mod account_id_endpoint_mode;

/// Default provider chain for auth scheme preference list
pub mod auth_scheme_preference;
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use crate::provider_config::ProviderConfig;
use aws_runtime::env_config::EnvConfigValue;
use aws_smithy_runtime_api::client::auth::{AuthSchemeId, AuthSchemePreference};
use aws_smithy_types::error::display::DisplayErrorContext;
use std::borrow::Cow;
use std::fmt;

mod env {
pub(super) const AUTH_SCHEME_PREFERENCE: &str = "AWS_AUTH_SCHEME_PREFERENCE";
}

mod profile_key {
pub(super) const AUTH_SCHEME_PREFERENCE: &str = "auth_scheme_preference";
}

/// Load the value for the auth scheme preference
///
/// This checks the following sources:
/// 1. The environment variable `AWS_AUTH_SCHEME_PREFERENCE=scheme1,scheme2,scheme3`
/// 2. The profile key `auth_scheme_preference=scheme1,scheme2,scheme3`
///
/// A scheme name can be either a fully qualified name or a shorthand with the namespace prefix trimmed.
/// For example, valid scheme names include "aws.auth#sigv4", "smithy.api#httpBasicAuth", "sigv4", and "httpBasicAuth".
/// Whitespace (spaces or tabs), including leading, trailing, and between names, is ignored.
///
/// Returns `None` if a parsed string component is empty when creating an `AuthSchemeId`.
pub(crate) async fn auth_scheme_preference_provider(
provider_config: &ProviderConfig,
) -> Option<AuthSchemePreference> {
let env = provider_config.env();
let profiles = provider_config.profile().await;

EnvConfigValue::new()
.env(env::AUTH_SCHEME_PREFERENCE)
.profile(profile_key::AUTH_SCHEME_PREFERENCE)
.validate(&env, profiles, parse_auth_scheme_names)
.map_err(|err| tracing::warn!(err = %DisplayErrorContext(&err), "invalid value for `AuthSchemePreference`"))
.unwrap_or(None)
}

fn parse_auth_scheme_names(csv: &str) -> Result<AuthSchemePreference, InvalidAuthSchemeNamesCsv> {
csv.split(',')
.map(|s| {
let trimmed = s.trim().replace([' ', '\t'], "");
if trimmed.is_empty() {
return Err(InvalidAuthSchemeNamesCsv {
value: format!("Empty name found in `{csv}`."),
});
}
let scheme_name = trimmed.split('#').next_back().unwrap_or(&trimmed);
Ok(AuthSchemeId::from(Cow::Owned(scheme_name.to_owned())))
})
.collect::<Result<Vec<_>, _>>()
.map(AuthSchemePreference::from)
}

#[derive(Debug)]
pub(crate) struct InvalidAuthSchemeNamesCsv {
value: String,
}

impl fmt::Display for InvalidAuthSchemeNamesCsv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Not a valid comma-separated auth scheme names: {}",
self.value
)
}
}

impl std::error::Error for InvalidAuthSchemeNamesCsv {}

#[cfg(test)]
mod test {
use super::env;
use crate::{
default_provider::auth_scheme_preference::auth_scheme_preference_provider,
provider_config::ProviderConfig,
};
use aws_types::os_shim_internal::Env;
use tracing_test::traced_test;

#[tokio::test]
#[traced_test]
async fn log_error_on_invalid_value() {
let conf = ProviderConfig::empty().with_env(Env::from_slice(&[(
env::AUTH_SCHEME_PREFERENCE,
"scheme1, , \tscheme2",
)]));
assert_eq!(None, auth_scheme_preference_provider(&conf).await);
assert!(logs_contain(
"Not a valid comma-separated auth scheme names: Empty name found"
));
}

#[cfg(feature = "sso")] // for aws-smithy-runtime-api/http-auth
mod http_auth_tests {
use super::env;
#[allow(deprecated)]
use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
use crate::{
default_provider::auth_scheme_preference::auth_scheme_preference_provider,
provider_config::ProviderConfig,
};
use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
use aws_types::os_shim_internal::{Env, Fs};

#[tokio::test]
async fn environment_priority() {
let conf = ProviderConfig::empty()
.with_env(Env::from_slice(&[(
env::AUTH_SCHEME_PREFERENCE,
"aws.auth#sigv4, smithy.api#httpBasicAuth, smithy.api#httpDigestAuth, smithy.api#httpBearerAuth, smithy.api#httpApiKeyAuth",
)]))
.with_profile_config(
Some(
#[allow(deprecated)]
ProfileFiles::builder()
.with_file(
#[allow(deprecated)]
ProfileFileKind::Config,
"conf",
)
.build(),
),
None,
)
.with_fs(Fs::from_slice(&[(
"conf",
"[default]\nauth_scheme_preference = scheme1, scheme2 , \tscheme3 \t",
)]));
assert_eq!(
AuthSchemePreference::from([
aws_runtime::auth::sigv4::SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
]),
auth_scheme_preference_provider(&conf).await.unwrap()
);
}

#[tokio::test]
async fn load_from_profile() {
let conf = ProviderConfig::empty()
.with_profile_config(
Some(
#[allow(deprecated)]
ProfileFiles::builder()
.with_file(
#[allow(deprecated)]
ProfileFileKind::Config,
"conf",
)
.build(),
),
None,
)
.with_fs(Fs::from_slice(&[(
"conf",
"[default]\nauth_scheme_preference = sigv4, httpBasicAuth, httpDigestAuth, \thttpBearerAuth \t, httpApiKeyAuth ",
)]));
assert_eq!(
AuthSchemePreference::from([
aws_runtime::auth::sigv4::SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
]),
auth_scheme_preference_provider(&conf).await.unwrap()
);
}
}
}
39 changes: 36 additions & 3 deletions aws/rust-runtime/aws-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ mod loader {
use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
use aws_smithy_async::time::{SharedTimeSource, TimeSource};
use aws_smithy_runtime::client::identity::IdentityCache;
use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
use aws_smithy_runtime_api::client::http::HttpClient;
use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache};
Expand All @@ -242,9 +243,10 @@ mod loader {
use aws_types::SdkConfig;

use crate::default_provider::{
account_id_endpoint_mode, app_name, checksums, credentials, disable_request_compression,
endpoint_url, ignore_configured_endpoint_urls as ignore_ep, region,
request_min_compression_size_bytes, retry_config, timeout_config, use_dual_stack, use_fips,
account_id_endpoint_mode, app_name, auth_scheme_preference, checksums, credentials,
disable_request_compression, endpoint_url, ignore_configured_endpoint_urls as ignore_ep,
region, request_min_compression_size_bytes, retry_config, timeout_config, use_dual_stack,
use_fips,
};
use crate::meta::region::ProvideRegion;
#[allow(deprecated)]
Expand All @@ -271,6 +273,7 @@ mod loader {
#[derive(Default, Debug)]
pub struct ConfigLoader {
app_name: Option<AppName>,
auth_scheme_preference: Option<AuthSchemePreference>,
identity_cache: Option<SharedIdentityCache>,
credentials_provider: TriStateOption<SharedCredentialsProvider>,
token_provider: Option<SharedTokenProvider>,
Expand Down Expand Up @@ -403,6 +406,28 @@ mod loader {
self
}

#[doc = docs_for!(auth_scheme_preference)]
///
/// # Examples
/// ```no_run
/// # use aws_smithy_runtime_api::client::auth::AuthSchemeId;
/// # async fn create_config() {
/// let config = aws_config::from_env()
/// // Favors a custom auth scheme over the SigV4 auth scheme.
/// // Note: This will not result in an error, even if the custom scheme is missing from the resolved auth schemes.
/// .auth_scheme_preference([AuthSchemeId::from("custom"), aws_runtime::auth::sigv4::SCHEME_ID])
/// .load()
/// .await;
/// # }
/// ```
pub fn auth_scheme_preference(
mut self,
auth_scheme_preference: impl Into<AuthSchemePreference>,
) -> Self {
self.auth_scheme_preference = Some(auth_scheme_preference.into());
self
}

/// Override the identity cache used to build [`SdkConfig`].
///
/// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used
Expand Down Expand Up @@ -962,6 +987,13 @@ mod loader {
account_id_endpoint_mode::account_id_endpoint_mode_provider(&conf).await
};

let auth_scheme_preference =
if let Some(auth_scheme_preference) = self.auth_scheme_preference {
Some(auth_scheme_preference)
} else {
auth_scheme_preference::auth_scheme_preference_provider(&conf).await
};

builder.set_request_checksum_calculation(request_checksum_calculation);
builder.set_response_checksum_validation(response_checksum_validation);
builder.set_identity_cache(identity_cache);
Expand All @@ -974,6 +1006,7 @@ mod loader {
builder.set_request_min_compression_size_bytes(request_min_compression_size_bytes);
builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
builder.set_account_id_endpoint_mode(account_id_endpoint_mode);
builder.set_auth_scheme_preference(auth_scheme_preference);
builder.build()
}
}
Expand Down
Loading
Loading