#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
rustdoc::missing_crate_level_docs,
unreachable_pub
)]
#![cfg_attr(test, allow(clippy::disallowed_methods))]
pub use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
pub use aws_types::{
app_name::{AppName, InvalidAppName},
region::Region,
SdkConfig,
};
pub use loader::ConfigLoader;
pub mod identity {
pub use aws_smithy_runtime::client::identity::IdentityCache;
pub use aws_smithy_runtime::client::identity::LazyCacheBuilder;
}
#[allow(dead_code)]
const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
mod http_credential_provider;
mod json_credentials;
#[cfg(test)]
mod test_case;
pub mod credential_process;
pub mod default_provider;
pub mod ecs;
mod env_service_config;
pub mod environment;
pub mod imds;
pub mod meta;
pub mod profile;
pub mod provider_config;
pub mod retry;
mod sensitive_command;
#[cfg(feature = "sso")]
pub mod sso;
pub mod stalled_stream_protection;
pub mod sts;
pub mod timeout;
pub mod web_identity_token;
#[cfg(feature = "behavior-version-latest")]
pub fn from_env() -> ConfigLoader {
ConfigLoader::default().behavior_version(BehaviorVersion::latest())
}
#[cfg(feature = "behavior-version-latest")]
pub async fn load_from_env() -> SdkConfig {
from_env().load().await
}
#[cfg(not(feature = "behavior-version-latest"))]
#[deprecated(
note = "Use the `aws_config::defaults` function. If you don't care about future default behavior changes, you can continue to use this function by enabling the `behavior-version-latest` feature. Doing so will make this deprecation notice go away."
)]
pub fn from_env() -> ConfigLoader {
ConfigLoader::default().behavior_version(BehaviorVersion::latest())
}
#[cfg(not(feature = "behavior-version-latest"))]
#[deprecated(
note = "Use the `aws_config::load_defaults` function. If you don't care about future default behavior changes, you can continue to use this function by enabling the `behavior-version-latest` feature. Doing so will make this deprecation notice go away."
)]
pub async fn load_from_env() -> SdkConfig {
load_defaults(BehaviorVersion::latest()).await
}
pub fn defaults(version: BehaviorVersion) -> ConfigLoader {
ConfigLoader::default().behavior_version(version)
}
pub async fn load_defaults(version: BehaviorVersion) -> SdkConfig {
defaults(version).load().await
}
mod loader {
use crate::env_service_config::EnvServiceConfig;
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};
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};
use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
use aws_smithy_runtime_api::shared::IntoShared;
use aws_smithy_types::retry::RetryConfig;
use aws_smithy_types::timeout::TimeoutConfig;
use aws_types::app_name::AppName;
use aws_types::docs_for;
use aws_types::origin::Origin;
use aws_types::os_shim_internal::{Env, Fs};
use aws_types::sdk_config::SharedHttpClient;
use aws_types::SdkConfig;
use crate::default_provider::{
app_name, credentials, endpoint_url, ignore_configured_endpoint_urls as ignore_ep, region,
retry_config, timeout_config, use_dual_stack, use_fips,
};
use crate::meta::region::ProvideRegion;
#[allow(deprecated)]
use crate::profile::profile_file::ProfileFiles;
use crate::provider_config::ProviderConfig;
#[derive(Default, Debug)]
enum CredentialsProviderOption {
#[default]
NotSet,
ExplicitlyUnset,
Set(SharedCredentialsProvider),
}
#[derive(Default, Debug)]
pub struct ConfigLoader {
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>,
sleep: Option<SharedAsyncSleep>,
timeout_config: Option<TimeoutConfig>,
provider_config: Option<ProviderConfig>,
http_client: Option<SharedHttpClient>,
profile_name_override: Option<String>,
#[allow(deprecated)]
profile_files_override: Option<ProfileFiles>,
use_fips: Option<bool>,
use_dual_stack: Option<bool>,
time_source: Option<SharedTimeSource>,
stalled_stream_protection_config: Option<StalledStreamProtectionConfig>,
env: Option<Env>,
fs: Option<Fs>,
behavior_version: Option<BehaviorVersion>,
}
impl ConfigLoader {
pub fn behavior_version(mut self, behavior_version: BehaviorVersion) -> Self {
self.behavior_version = Some(behavior_version);
self
}
pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
self.region = Some(Box::new(region));
self
}
pub fn retry_config(mut self, retry_config: RetryConfig) -> Self {
self.retry_config = Some(retry_config);
self
}
pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self {
self.timeout_config = Some(timeout_config);
self
}
pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self {
self.sleep = Some(sleep.into_shared());
self
}
pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
self.time_source = Some(time_source.into_shared());
self
}
pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self {
self.http_client = Some(http_client.into_shared());
self
}
pub fn identity_cache(
mut self,
identity_cache: impl ResolveCachedIdentity + 'static,
) -> Self {
self.identity_cache = Some(identity_cache.into_shared());
self
}
pub fn credentials_provider(
mut self,
credentials_provider: impl ProvideCredentials + 'static,
) -> Self {
self.credentials_provider = CredentialsProviderOption::Set(
SharedCredentialsProvider::new(credentials_provider),
);
self
}
pub fn no_credentials(mut self) -> Self {
self.credentials_provider = CredentialsProviderOption::ExplicitlyUnset;
self
}
pub fn test_credentials(self) -> Self {
#[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
}
pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
self.token_provider = Some(SharedTokenProvider::new(token_provider));
self
}
pub fn app_name(mut self, app_name: AppName) -> Self {
self.app_name = Some(app_name);
self
}
#[allow(deprecated)]
pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
self.profile_files_override = Some(profile_files);
self
}
pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
self.profile_name_override = Some(profile_name.into());
self
}
pub fn endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
self.endpoint_url = Some(endpoint_url.into());
self
}
#[doc = docs_for!(use_fips)]
pub fn use_fips(mut self, use_fips: bool) -> Self {
self.use_fips = Some(use_fips);
self
}
#[doc = docs_for!(use_dual_stack)]
pub fn use_dual_stack(mut self, use_dual_stack: bool) -> Self {
self.use_dual_stack = Some(use_dual_stack);
self
}
pub fn stalled_stream_protection(
mut self,
stalled_stream_protection_config: StalledStreamProtectionConfig,
) -> Self {
self.stalled_stream_protection_config = Some(stalled_stream_protection_config);
self
}
pub async fn load(self) -> SdkConfig {
let time_source = self.time_source.unwrap_or_default();
let sleep_impl = if self.sleep.is_some() {
self.sleep
} else {
if default_async_sleep().is_none() {
tracing::warn!(
"An implementation of AsyncSleep was requested by calling default_async_sleep \
but no default was set.
This happened when ConfigLoader::load was called during Config construction. \
You can fix this by setting a sleep_impl on the ConfigLoader before calling \
load or by enabling the rt-tokio feature"
);
}
default_async_sleep()
};
let conf = self
.provider_config
.unwrap_or_else(|| {
let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone())
.with_fs(self.fs.unwrap_or_default())
.with_env(self.env.unwrap_or_default());
if let Some(http_client) = self.http_client.clone() {
config = config.with_http_client(http_client);
}
config
})
.with_profile_config(self.profile_files_override, self.profile_name_override);
let use_fips = if let Some(use_fips) = self.use_fips {
Some(use_fips)
} else {
use_fips::use_fips_provider(&conf).await
};
let use_dual_stack = if let Some(use_dual_stack) = self.use_dual_stack {
Some(use_dual_stack)
} else {
use_dual_stack::use_dual_stack_provider(&conf).await
};
let conf = conf
.with_use_fips(use_fips)
.with_use_dual_stack(use_dual_stack);
let region = if let Some(provider) = self.region {
provider.region().await
} else {
region::Builder::default()
.configure(&conf)
.build()
.region()
.await
};
let retry_config = if let Some(retry_config) = self.retry_config {
retry_config
} else {
retry_config::default_provider()
.configure(&conf)
.retry_config()
.await
};
let app_name = if self.app_name.is_some() {
self.app_name
} else {
app_name::default_provider()
.configure(&conf)
.app_name()
.await
};
let base_config = timeout_config::default_provider()
.configure(&conf)
.timeout_config()
.await;
let mut timeout_config = self
.timeout_config
.unwrap_or_else(|| TimeoutConfig::builder().build());
timeout_config.take_defaults_from(&base_config);
let credentials_provider = match self.credentials_provider {
CredentialsProviderOption::Set(provider) => Some(provider),
CredentialsProviderOption::NotSet => {
let mut builder =
credentials::DefaultCredentialsChain::builder().configure(conf.clone());
builder.set_region(region.clone());
Some(SharedCredentialsProvider::new(builder.build().await))
}
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 profiles = conf.profile().await;
let service_config = EnvServiceConfig {
env: conf.env(),
env_config_sections: profiles.cloned().unwrap_or_default(),
};
let mut builder = SdkConfig::builder()
.region(region)
.retry_config(retry_config)
.timeout_config(timeout_config)
.time_source(time_source)
.service_config(service_config);
let endpoint_url = if self.endpoint_url.is_some() {
builder.insert_origin("endpoint_url", Origin::shared_config());
self.endpoint_url
} else {
let ignore_configured_endpoint_urls =
ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
.await
.unwrap_or_default();
if ignore_configured_endpoint_urls {
tracing::trace!(
"`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
NOTE: Endpoint URLs set programmatically WILL still be respected"
);
None
} else {
let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
builder.insert_origin("endpoint_url", origin);
v
}
};
builder.set_endpoint_url(endpoint_url);
builder.set_behavior_version(self.behavior_version);
builder.set_http_client(self.http_client);
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_use_fips(use_fips);
builder.set_use_dual_stack(use_dual_stack);
builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
builder.build()
}
}
#[cfg(test)]
impl ConfigLoader {
pub(crate) fn env(mut self, env: Env) -> Self {
self.env = Some(env);
self
}
pub(crate) fn fs(mut self, fs: Fs) -> Self {
self.fs = Some(fs);
self
}
}
#[cfg(test)]
mod test {
#[allow(deprecated)]
use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
use crate::test_case::{no_traffic_client, InstantSleep};
use crate::BehaviorVersion;
use crate::{defaults, ConfigLoader};
use aws_credential_types::provider::ProvideCredentials;
use aws_smithy_async::rt::sleep::TokioSleep;
use aws_smithy_runtime::client::http::test_util::{infallible_client_fn, NeverClient};
use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
use aws_types::app_name::AppName;
use aws_types::origin::Origin;
use aws_types::os_shim_internal::{Env, Fs};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[tokio::test]
async fn provider_config_used() {
let (_guard, logs_rx) = capture_test_logs();
let env = Env::from_slice(&[
("AWS_MAX_ATTEMPTS", "10"),
("AWS_REGION", "us-west-4"),
("AWS_ACCESS_KEY_ID", "akid"),
("AWS_SECRET_ACCESS_KEY", "secret"),
]);
let fs =
Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
let loader = defaults(BehaviorVersion::latest())
.sleep_impl(TokioSleep::new())
.env(env)
.fs(fs)
.http_client(NeverClient::new())
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_file(
#[allow(deprecated)]
ProfileFileKind::Config,
"test_config",
)
.build(),
)
.load()
.await;
assert_eq!(10, loader.retry_config().unwrap().max_attempts());
assert_eq!("us-west-4", loader.region().unwrap().as_ref());
assert_eq!(
"akid",
loader
.credentials_provider()
.unwrap()
.provide_credentials()
.await
.unwrap()
.access_key_id(),
);
assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
let num_config_loader_logs = logs_rx.contents()
.lines()
.filter(|l| l.contains("config file loaded \u{1b}[3mpath\u{1b}[0m\u{1b}[2m=\u{1b}[0mSome(\"test_config\") \u{1b}[3msize\u{1b}[0m\u{1b}[2m=\u{1b}"))
.count();
match num_config_loader_logs {
0 => panic!("no config file logs found!"),
1 => (),
more => panic!("the config file was parsed more than once! (parsed {more})",),
};
}
fn base_conf() -> ConfigLoader {
defaults(BehaviorVersion::latest())
.sleep_impl(InstantSleep)
.http_client(no_traffic_client())
}
#[tokio::test]
async fn test_origin_programmatic() {
let _ = tracing_subscriber::fmt::try_init();
let loader = base_conf()
.test_credentials()
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_contents(
#[allow(deprecated)]
ProfileFileKind::Config,
"[profile custom]\nendpoint_url = http://localhost:8989",
)
.build(),
)
.endpoint_url("http://localhost:1111")
.load()
.await;
assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
}
#[tokio::test]
async fn test_origin_env() {
let _ = tracing_subscriber::fmt::try_init();
let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
let loader = base_conf()
.test_credentials()
.env(env)
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_contents(
#[allow(deprecated)]
ProfileFileKind::Config,
"[profile custom]\nendpoint_url = http://localhost:8989",
)
.build(),
)
.load()
.await;
assert_eq!(
Origin::shared_environment_variable(),
loader.get_origin("endpoint_url")
);
}
#[tokio::test]
async fn test_origin_fs() {
let _ = tracing_subscriber::fmt::try_init();
let loader = base_conf()
.test_credentials()
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_contents(
#[allow(deprecated)]
ProfileFileKind::Config,
"[profile custom]\nendpoint_url = http://localhost:8989",
)
.build(),
)
.load()
.await;
assert_eq!(
Origin::shared_profile_file(),
loader.get_origin("endpoint_url")
);
}
#[tokio::test]
async fn load_fips() {
let conf = base_conf().use_fips(true).load().await;
assert_eq!(Some(true), conf.use_fips());
}
#[tokio::test]
async fn load_dual_stack() {
let conf = base_conf().use_dual_stack(false).load().await;
assert_eq!(Some(false), conf.use_dual_stack());
let conf = base_conf().load().await;
assert_eq!(None, conf.use_dual_stack());
}
#[tokio::test]
async fn app_name() {
let app_name = AppName::new("my-app-name").unwrap();
let conf = base_conf().app_name(app_name.clone()).load().await;
assert_eq!(Some(&app_name), conf.app_name());
}
#[cfg(feature = "rustls")]
#[tokio::test]
async fn disable_default_credentials() {
let config = defaults(BehaviorVersion::latest())
.no_credentials()
.load()
.await;
assert!(config.identity_cache().is_none());
assert!(config.credentials_provider().is_none());
}
#[tokio::test]
async fn connector_is_shared() {
let num_requests = Arc::new(AtomicUsize::new(0));
let movable = num_requests.clone();
let http_client = infallible_client_fn(move |_req| {
movable.fetch_add(1, Ordering::Relaxed);
http::Response::new("ok!")
});
let config = defaults(BehaviorVersion::latest())
.fs(Fs::from_slice(&[]))
.env(Env::from_slice(&[]))
.http_client(http_client.clone())
.load()
.await;
config
.credentials_provider()
.unwrap()
.provide_credentials()
.await
.expect_err("did not expect credentials to be loaded—no traffic is allowed");
let num_requests = num_requests.load(Ordering::Relaxed);
assert!(num_requests > 0, "{}", num_requests);
}
#[tokio::test]
async fn endpoint_urls_may_be_ignored_from_env() {
let fs = Fs::from_slice(&[(
"test_config",
"[profile custom]\nendpoint_url = http://profile",
)]);
let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
let conf = base_conf().use_dual_stack(false).load().await;
assert_eq!(Some(false), conf.use_dual_stack());
let conf = base_conf().load().await;
assert_eq!(None, conf.use_dual_stack());
let config = base_conf()
.fs(fs.clone())
.env(env)
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_file(
#[allow(deprecated)]
ProfileFileKind::Config,
"test_config",
)
.build(),
)
.load()
.await;
assert_eq!(None, config.endpoint_url());
let config = base_conf()
.fs(fs)
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_file(
#[allow(deprecated)]
ProfileFileKind::Config,
"test_config",
)
.build(),
)
.load()
.await;
assert_eq!(Some("http://profile"), config.endpoint_url());
}
#[tokio::test]
async fn endpoint_urls_may_be_ignored_from_profile() {
let fs = Fs::from_slice(&[(
"test_config",
"[profile custom]\nignore_configured_endpoint_urls = true",
)]);
let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
let config = base_conf()
.fs(fs)
.env(env.clone())
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_file(
#[allow(deprecated)]
ProfileFileKind::Config,
"test_config",
)
.build(),
)
.load()
.await;
assert_eq!(None, config.endpoint_url());
let config = base_conf().env(env).load().await;
assert_eq!(Some("http://environment"), config.endpoint_url());
}
#[tokio::test]
async fn programmatic_endpoint_urls_may_not_be_ignored() {
let fs = Fs::from_slice(&[(
"test_config",
"[profile custom]\nignore_configured_endpoint_urls = true",
)]);
let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
let config = base_conf()
.fs(fs)
.env(env)
.endpoint_url("http://localhost")
.profile_name("custom")
.profile_files(
#[allow(deprecated)]
ProfileFiles::builder()
.with_file(
#[allow(deprecated)]
ProfileFileKind::Config,
"test_config",
)
.build(),
)
.load()
.await;
assert_eq!(Some("http://localhost"), config.endpoint_url());
}
}
}