aws_config/lib.rs
1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/* Automatically managed default lints */
7#![cfg_attr(docsrs, feature(doc_cfg))]
8/* End of automatically managed default lints */
9#![allow(clippy::derive_partial_eq_without_eq)]
10#![warn(
11 missing_debug_implementations,
12 missing_docs,
13 rust_2018_idioms,
14 rustdoc::missing_crate_level_docs,
15 unreachable_pub
16)]
17// Allow disallowed methods in tests
18#![cfg_attr(test, allow(clippy::disallowed_methods))]
19
20//! `aws-config` provides implementations of region and credential resolution.
21//!
22//! These implementations can be used either via the default chain implementation
23//! [`from_env`]/[`ConfigLoader`] or ad-hoc individual credential and region providers.
24//!
25//! [`ConfigLoader`] can combine different configuration sources into an AWS shared-config:
26//! [`SdkConfig`]. `SdkConfig` can be used configure an AWS service client.
27//!
28//! # Examples
29//!
30//! Load default SDK configuration:
31//! ```no_run
32//! use aws_config::BehaviorVersion;
33//! mod aws_sdk_dynamodb {
34//! # pub struct Client;
35//! # impl Client {
36//! # pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
37//! # }
38//! # }
39//! # async fn docs() {
40//! let config = aws_config::load_defaults(BehaviorVersion::v2023_11_09()).await;
41//! let client = aws_sdk_dynamodb::Client::new(&config);
42//! # }
43//! ```
44//!
45//! Load SDK configuration with a region override:
46//! ```no_run
47//! # mod aws_sdk_dynamodb {
48//! # pub struct Client;
49//! # impl Client {
50//! # pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
51//! # }
52//! # }
53//! # async fn docs() {
54//! # use aws_config::meta::region::RegionProviderChain;
55//! let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
56//! // Note: requires the `behavior-version-latest` feature enabled
57//! let config = aws_config::from_env().region(region_provider).load().await;
58//! let client = aws_sdk_dynamodb::Client::new(&config);
59//! # }
60//! ```
61//!
62//! Override configuration after construction of `SdkConfig`:
63//!
64//! ```no_run
65//! # use aws_credential_types::provider::ProvideCredentials;
66//! # use aws_types::SdkConfig;
67//! # mod aws_sdk_dynamodb {
68//! # pub mod config {
69//! # pub struct Builder;
70//! # impl Builder {
71//! # pub fn credentials_provider(
72//! # self,
73//! # credentials_provider: impl aws_credential_types::provider::ProvideCredentials + 'static) -> Self { self }
74//! # pub fn build(self) -> Builder { self }
75//! # }
76//! # impl From<&aws_types::SdkConfig> for Builder {
77//! # fn from(_: &aws_types::SdkConfig) -> Self {
78//! # todo!()
79//! # }
80//! # }
81//! # }
82//! # pub struct Client;
83//! # impl Client {
84//! # pub fn from_conf(conf: config::Builder) -> Self { Client }
85//! # pub fn new(config: &aws_types::SdkConfig) -> Self { Client }
86//! # }
87//! # }
88//! # async fn docs() {
89//! # use aws_config::meta::region::RegionProviderChain;
90//! # fn custom_provider(base: &SdkConfig) -> impl ProvideCredentials {
91//! # base.credentials_provider().unwrap().clone()
92//! # }
93//! let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
94//! let custom_credentials_provider = custom_provider(&sdk_config);
95//! let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config)
96//! .credentials_provider(custom_credentials_provider)
97//! .build();
98//! let client = aws_sdk_dynamodb::Client::from_conf(dynamo_config);
99//! # }
100//! ```
101
102pub use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
103// Re-export types from aws-types
104pub use aws_types::{
105 app_name::{AppName, InvalidAppName},
106 region::Region,
107 sdk_ua_metadata::{FrameworkMetadata, InvalidFrameworkMetadata},
108 SdkConfig,
109};
110/// Load default sources for all configuration with override support
111pub use loader::ConfigLoader;
112
113/// Types for configuring identity caching.
114pub mod identity {
115 pub use aws_smithy_runtime::client::identity::IdentityCache;
116 pub use aws_smithy_runtime::client::identity::LazyCacheBuilder;
117}
118
119#[allow(dead_code)]
120const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
121
122mod http_credential_provider;
123mod json_credentials;
124#[cfg(test)]
125mod test_case;
126
127pub mod credential_process;
128pub mod default_provider;
129pub mod ecs;
130mod env_service_config;
131pub mod environment;
132pub mod imds;
133#[cfg(feature = "credentials-login")]
134pub mod login;
135pub mod meta;
136pub mod profile;
137pub mod provider_config;
138pub mod retry;
139mod sensitive_command;
140#[cfg(feature = "sso")]
141pub mod sso;
142pub mod stalled_stream_protection;
143pub mod sts;
144pub mod timeout;
145pub mod web_identity_token;
146
147/// Create a config loader with the _latest_ defaults.
148///
149/// This loader will always set [`BehaviorVersion::latest`].
150///
151/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
152///
153/// # Examples
154/// ```no_run
155/// # async fn create_config() {
156/// let config = aws_config::from_env().region("us-east-1").load().await;
157/// # }
158/// ```
159#[cfg(feature = "behavior-version-latest")]
160pub fn from_env() -> ConfigLoader {
161 ConfigLoader::default().behavior_version(BehaviorVersion::latest())
162}
163
164/// Load default configuration with the _latest_ defaults.
165///
166/// Convenience wrapper equivalent to `aws_config::load_defaults(BehaviorVersion::latest()).await`
167///
168/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
169#[cfg(feature = "behavior-version-latest")]
170pub async fn load_from_env() -> SdkConfig {
171 from_env().load().await
172}
173
174/// Create a config loader with the _latest_ defaults.
175#[cfg(not(feature = "behavior-version-latest"))]
176#[deprecated(
177 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."
178)]
179pub fn from_env() -> ConfigLoader {
180 ConfigLoader::default().behavior_version(BehaviorVersion::latest())
181}
182
183/// Load default configuration with the _latest_ defaults.
184#[cfg(not(feature = "behavior-version-latest"))]
185#[deprecated(
186 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."
187)]
188pub async fn load_from_env() -> SdkConfig {
189 load_defaults(BehaviorVersion::latest()).await
190}
191
192/// Create a config loader with the defaults for the given behavior version.
193///
194/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
195///
196/// # Examples
197/// ```no_run
198/// # async fn create_config() {
199/// use aws_config::BehaviorVersion;
200/// let config = aws_config::defaults(BehaviorVersion::v2023_11_09())
201/// .region("us-east-1")
202/// .load()
203/// .await;
204/// # }
205/// ```
206pub fn defaults(version: BehaviorVersion) -> ConfigLoader {
207 ConfigLoader::default().behavior_version(version)
208}
209
210/// Load default configuration with the given behavior version.
211///
212/// Convenience wrapper equivalent to `aws_config::defaults(behavior_version).load().await`
213///
214/// For more information about default configuration, refer to the AWS SDKs and Tools [shared configuration documentation](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html).
215pub async fn load_defaults(version: BehaviorVersion) -> SdkConfig {
216 defaults(version).load().await
217}
218
219mod loader {
220 use crate::env_service_config::EnvServiceConfig;
221 use aws_credential_types::provider::{
222 token::{ProvideToken, SharedTokenProvider},
223 ProvideCredentials, SharedCredentialsProvider,
224 };
225 use aws_credential_types::Credentials;
226 use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
227 use aws_smithy_async::time::{SharedTimeSource, TimeSource};
228 use aws_smithy_runtime::client::identity::IdentityCache;
229 use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
230 use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
231 use aws_smithy_runtime_api::client::http::HttpClient;
232 use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache};
233 use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
234 use aws_smithy_runtime_api::shared::IntoShared;
235 use aws_smithy_schema::protocol::{ClientProtocol, SharedClientProtocol};
236 use aws_smithy_types::checksum_config::{
237 RequestChecksumCalculation, ResponseChecksumValidation,
238 };
239 use aws_smithy_types::retry::RetryConfig;
240 use aws_smithy_types::timeout::TimeoutConfig;
241 use aws_types::app_name::AppName;
242 use aws_types::docs_for;
243 use aws_types::endpoint_config::AccountIdEndpointMode;
244 use aws_types::origin::Origin;
245 use aws_types::os_shim_internal::{Env, Fs};
246 use aws_types::region::SigningRegionSet;
247 use aws_types::sdk_config::SharedHttpClient;
248 use aws_types::sdk_ua_metadata::FrameworkMetadata;
249 use aws_types::SdkConfig;
250
251 use crate::default_provider::{
252 account_id_endpoint_mode, app_name, auth_scheme_preference, checksums, credentials,
253 disable_request_compression, endpoint_url, ignore_configured_endpoint_urls as ignore_ep,
254 region, request_min_compression_size_bytes, retry_config, sigv4a_signing_region_set,
255 timeout_config, use_dual_stack, use_fips,
256 };
257 use crate::meta::region::ProvideRegion;
258 #[allow(deprecated)]
259 use crate::profile::profile_file::ProfileFiles;
260 use crate::provider_config::ProviderConfig;
261
262 #[derive(Default, Debug)]
263 enum TriStateOption<T> {
264 /// No option was set by the user. We can set up the default.
265 #[default]
266 NotSet,
267 /// The option was explicitly unset. Do not set up a default.
268 ExplicitlyUnset,
269 /// Use the given user provided option.
270 Set(T),
271 }
272
273 /// Load a cross-service [`SdkConfig`] from the environment
274 ///
275 /// This builder supports overriding individual components of the generated config. Overriding a component
276 /// will skip the standard resolution chain from **for that component**. For example,
277 /// if you override the region provider, _even if that provider returns None_, the default region provider
278 /// chain will not be used.
279 #[derive(Default, Debug)]
280 pub struct ConfigLoader {
281 app_name: Option<AppName>,
282 framework_metadata: Vec<FrameworkMetadata>,
283 auth_scheme_preference: Option<AuthSchemePreference>,
284 sigv4a_signing_region_set: Option<SigningRegionSet>,
285 identity_cache: Option<SharedIdentityCache>,
286 credentials_provider: TriStateOption<SharedCredentialsProvider>,
287 token_provider: Option<SharedTokenProvider>,
288 account_id_endpoint_mode: Option<AccountIdEndpointMode>,
289 endpoint_url: Option<String>,
290 region: Option<Box<dyn ProvideRegion>>,
291 retry_config: Option<RetryConfig>,
292 sleep: Option<SharedAsyncSleep>,
293 timeout_config: Option<TimeoutConfig>,
294 provider_config: Option<ProviderConfig>,
295 http_client: Option<SharedHttpClient>,
296 profile_name_override: Option<String>,
297 #[allow(deprecated)]
298 profile_files_override: Option<ProfileFiles>,
299 use_fips: Option<bool>,
300 use_dual_stack: Option<bool>,
301 time_source: Option<SharedTimeSource>,
302 disable_request_compression: Option<bool>,
303 request_min_compression_size_bytes: Option<u32>,
304 stalled_stream_protection_config: Option<StalledStreamProtectionConfig>,
305 env: Option<Env>,
306 fs: Option<Fs>,
307 behavior_version: Option<BehaviorVersion>,
308 request_checksum_calculation: Option<RequestChecksumCalculation>,
309 response_checksum_validation: Option<ResponseChecksumValidation>,
310 protocol: Option<SharedClientProtocol>,
311 }
312
313 impl ConfigLoader {
314 /// Sets the [`BehaviorVersion`] used to build [`SdkConfig`].
315 pub fn behavior_version(mut self, behavior_version: BehaviorVersion) -> Self {
316 self.behavior_version = Some(behavior_version);
317 self
318 }
319
320 /// Override the region used to build [`SdkConfig`].
321 ///
322 /// # Examples
323 /// ```no_run
324 /// # async fn create_config() {
325 /// use aws_types::region::Region;
326 /// let config = aws_config::from_env()
327 /// .region(Region::new("us-east-1"))
328 /// .load().await;
329 /// # }
330 /// ```
331 pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
332 self.region = Some(Box::new(region));
333 self
334 }
335
336 /// Override the retry_config used to build [`SdkConfig`].
337 ///
338 /// # Examples
339 /// ```no_run
340 /// # async fn create_config() {
341 /// use aws_config::retry::RetryConfig;
342 ///
343 /// let config = aws_config::from_env()
344 /// .retry_config(RetryConfig::standard().with_max_attempts(2))
345 /// .load()
346 /// .await;
347 /// # }
348 /// ```
349 pub fn retry_config(mut self, retry_config: RetryConfig) -> Self {
350 self.retry_config = Some(retry_config);
351 self
352 }
353
354 /// Override the timeout config used to build [`SdkConfig`].
355 ///
356 /// This will be merged with timeouts coming from the timeout information provider, which
357 /// currently includes a default `CONNECT` timeout of `3.1s`.
358 ///
359 /// If you want to disable timeouts, use [`TimeoutConfig::disabled`]. If you want to disable
360 /// a specific timeout, use `TimeoutConfig::set_<type>(None)`.
361 ///
362 /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials
363 /// provider chain are configured separately.
364 ///
365 /// # Examples
366 /// ```no_run
367 /// # use std::time::Duration;
368 /// # async fn create_config() {
369 /// use aws_config::timeout::TimeoutConfig;
370 ///
371 /// let config = aws_config::from_env()
372 /// .timeout_config(
373 /// TimeoutConfig::builder()
374 /// .operation_timeout(Duration::from_secs(5))
375 /// .build()
376 /// )
377 /// .load()
378 /// .await;
379 /// # }
380 /// ```
381 pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self {
382 self.timeout_config = Some(timeout_config);
383 self
384 }
385
386 /// Override the sleep implementation for this [`ConfigLoader`].
387 ///
388 /// The sleep implementation is used to create timeout futures.
389 /// You generally won't need to change this unless you're using an async runtime other
390 /// than Tokio.
391 pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self {
392 // it's possible that we could wrapping an `Arc in an `Arc` and that's OK
393 self.sleep = Some(sleep.into_shared());
394 self
395 }
396
397 /// Set the time source used for tasks like signing requests.
398 ///
399 /// You generally won't need to change this unless you're compiling for a target
400 /// that can't provide a default, such as WASM, or unless you're writing a test against
401 /// the client that needs a fixed time.
402 pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
403 self.time_source = Some(time_source.into_shared());
404 self
405 }
406
407 /// Override the [`HttpClient`] for this [`ConfigLoader`].
408 ///
409 /// The HTTP client will be used for both AWS services and credentials providers.
410 ///
411 /// If you wish to use a separate HTTP client for credentials providers when creating clients,
412 /// then override the HTTP client set with this function on the client-specific `Config`s.
413 pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self {
414 self.http_client = Some(http_client.into_shared());
415 self
416 }
417
418 /// Sets the client protocol to use for serialization and deserialization.
419 ///
420 /// This overrides the default protocol determined by the service model.
421 ///
422 /// # Transport
423 ///
424 /// This setter is HTTP-specific. `self.protocol` is typed
425 /// `Option<SharedClientProtocol>` which elides to the HTTP specialization,
426 /// and only `SharedClientProtocol<http::Request, http::Response>` has a
427 /// `Storable` impl. The `impl ClientProtocol + 'static` bound here
428 /// elides to `impl ClientProtocol<http::Request, http::Response>` to
429 /// match. A non-HTTP transport would add its own setter paired with its
430 /// own `Storable` newtype rather than generalizing this one — the
431 /// underlying `ClientProtocol<Req, Res>` trait is already
432 /// transport-generic.
433 pub fn protocol(mut self, protocol: impl ClientProtocol + 'static) -> Self {
434 self.protocol = Some(SharedClientProtocol::new(protocol));
435 self
436 }
437
438 #[doc = docs_for!(auth_scheme_preference)]
439 ///
440 /// # Examples
441 /// ```no_run
442 /// # use aws_smithy_runtime_api::client::auth::AuthSchemeId;
443 /// # async fn create_config() {
444 /// let config = aws_config::from_env()
445 /// // Favors a custom auth scheme over the SigV4 auth scheme.
446 /// // Note: This will not result in an error, even if the custom scheme is missing from the resolved auth schemes.
447 /// .auth_scheme_preference([AuthSchemeId::from("custom"), aws_runtime::auth::sigv4::SCHEME_ID])
448 /// .load()
449 /// .await;
450 /// # }
451 /// ```
452 pub fn auth_scheme_preference(
453 mut self,
454 auth_scheme_preference: impl Into<AuthSchemePreference>,
455 ) -> Self {
456 self.auth_scheme_preference = Some(auth_scheme_preference.into());
457 self
458 }
459
460 #[doc = docs_for!(sigv4a_signing_region_set)]
461 pub fn sigv4a_signing_region_set(
462 mut self,
463 sigv4a_signing_region_set: impl Into<SigningRegionSet>,
464 ) -> Self {
465 self.sigv4a_signing_region_set = Some(sigv4a_signing_region_set.into());
466 self
467 }
468
469 /// Override the identity cache used to build [`SdkConfig`].
470 ///
471 /// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used
472 /// that will load credentials upon first request, cache them, and then reload them during
473 /// another request when they are close to expiring.
474 ///
475 /// # Examples
476 ///
477 /// Change a setting on the default lazy caching implementation:
478 /// ```no_run
479 /// use aws_config::identity::IdentityCache;
480 /// use std::time::Duration;
481 ///
482 /// # async fn create_config() {
483 /// let config = aws_config::from_env()
484 /// .identity_cache(
485 /// IdentityCache::lazy()
486 /// // Change the load timeout to 10 seconds.
487 /// // Note: there are other timeouts that could trigger if the load timeout is too long.
488 /// .load_timeout(Duration::from_secs(10))
489 /// .build()
490 /// )
491 /// .load()
492 /// .await;
493 /// # }
494 /// ```
495 pub fn identity_cache(
496 mut self,
497 identity_cache: impl ResolveCachedIdentity + 'static,
498 ) -> Self {
499 self.identity_cache = Some(identity_cache.into_shared());
500 self
501 }
502
503 /// Override the credentials provider used to build [`SdkConfig`].
504 ///
505 /// # Examples
506 ///
507 /// Override the credentials provider but load the default value for region:
508 /// ```no_run
509 /// # use aws_credential_types::Credentials;
510 /// # fn create_my_credential_provider() -> Credentials {
511 /// # Credentials::new("example", "example", None, None, "example")
512 /// # }
513 /// # async fn create_config() {
514 /// let config = aws_config::from_env()
515 /// .credentials_provider(create_my_credential_provider())
516 /// .load()
517 /// .await;
518 /// # }
519 /// ```
520 pub fn credentials_provider(
521 mut self,
522 credentials_provider: impl ProvideCredentials + 'static,
523 ) -> Self {
524 self.credentials_provider =
525 TriStateOption::Set(SharedCredentialsProvider::new(credentials_provider));
526 self
527 }
528
529 /// Don't use credentials to sign requests.
530 ///
531 /// Turning off signing with credentials is necessary in some cases, such as using
532 /// anonymous auth for S3, calling operations in STS that don't require a signature,
533 /// or using token-based auth.
534 ///
535 /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you
536 /// want. If credentials are disabled, requests cannot be signed. For these use cases, use
537 /// [`test_credentials`](Self::test_credentials).
538 ///
539 /// # Examples
540 ///
541 /// Turn off credentials in order to call a service without signing:
542 /// ```no_run
543 /// # async fn create_config() {
544 /// let config = aws_config::from_env()
545 /// .no_credentials()
546 /// .load()
547 /// .await;
548 /// # }
549 /// ```
550 pub fn no_credentials(mut self) -> Self {
551 self.credentials_provider = TriStateOption::ExplicitlyUnset;
552 self
553 }
554
555 /// Set test credentials for use when signing requests
556 pub fn test_credentials(self) -> Self {
557 #[allow(unused_mut)]
558 let mut ret = self.credentials_provider(Credentials::for_tests());
559 #[cfg(feature = "sso")]
560 {
561 use aws_smithy_runtime_api::client::identity::http::Token;
562 ret = ret.token_provider(Token::for_tests());
563 }
564 ret
565 }
566
567 /// Ignore any environment variables on the host during config resolution
568 ///
569 /// This allows for testing in a reproducible environment that ensures any
570 /// environment variables from the host do not influence environment variable
571 /// resolution.
572 pub fn empty_test_environment(mut self) -> Self {
573 self.env = Some(Env::from_slice(&[]));
574 self
575 }
576
577 /// Override the access token provider used to build [`SdkConfig`].
578 ///
579 /// # Examples
580 ///
581 /// Override the token provider but load the default value for region:
582 /// ```no_run
583 /// # use aws_credential_types::Token;
584 /// # fn create_my_token_provider() -> Token {
585 /// # Token::new("example", None)
586 /// # }
587 /// # async fn create_config() {
588 /// let config = aws_config::from_env()
589 /// .token_provider(create_my_token_provider())
590 /// .load()
591 /// .await;
592 /// # }
593 /// ```
594 pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
595 self.token_provider = Some(SharedTokenProvider::new(token_provider));
596 self
597 }
598
599 /// Override the name of the app used to build [`SdkConfig`].
600 ///
601 /// This _optional_ name is used to identify the application in the user agent header that
602 /// gets sent along with requests.
603 ///
604 /// The app name is selected from an ordered list of sources:
605 /// 1. This override.
606 /// 2. The value of the `AWS_SDK_UA_APP_ID` environment variable.
607 /// 3. Profile files from the key `sdk_ua_app_id`
608 ///
609 /// If none of those sources are set the value is `None` and it is not added to the user agent header.
610 ///
611 /// # Examples
612 /// ```no_run
613 /// # async fn create_config() {
614 /// use aws_config::AppName;
615 /// let config = aws_config::from_env()
616 /// .app_name(AppName::new("my-app-name").expect("valid app name"))
617 /// .load().await;
618 /// # }
619 /// ```
620 pub fn app_name(mut self, app_name: AppName) -> Self {
621 self.app_name = Some(app_name);
622 self
623 }
624
625 /// Appends framework metadata to the user agent.
626 ///
627 /// This _optional_ metadata identifies a software framework or third-party library that is
628 /// being used with the SDK. It is rendered into the user agent (as `lib/{name}/{version}`)
629 /// so that libraries built on top of the AWS SDK can self-identify in the requests they
630 /// make. Each call appends another entry rather than replacing previous ones.
631 ///
632 /// Unlike the app name, framework metadata has no environment variable or profile source;
633 /// it can only be set programmatically.
634 ///
635 /// Entries are de-duplicated on `(name, version)`, rendered in first-seen order, and the
636 /// total number of unique entries included in the user agent is capped (currently at 10);
637 /// additional entries beyond the cap are dropped with a warning.
638 ///
639 /// # Examples
640 /// ```no_run
641 /// # async fn create_config() {
642 /// use aws_config::FrameworkMetadata;
643 /// let config = aws_config::from_env()
644 /// .framework_metadata(FrameworkMetadata::new("some-framework", Some("1.0")).expect("valid framework metadata"))
645 /// .load().await;
646 /// # }
647 /// ```
648 pub fn framework_metadata(mut self, framework_metadata: FrameworkMetadata) -> Self {
649 self.framework_metadata.push(framework_metadata);
650 self
651 }
652
653 /// Provides the ability to programmatically override the profile files that get loaded by the SDK.
654 ///
655 /// The [`Default`] for `ProfileFiles` includes the default SDK config and credential files located in
656 /// `~/.aws/config` and `~/.aws/credentials` respectively.
657 ///
658 /// Any number of config and credential files may be added to the `ProfileFiles` file set, with the
659 /// only requirement being that there is at least one of each. Profile file locations will produce an
660 /// error if they don't exist, but the default config/credentials files paths are exempt from this validation.
661 ///
662 /// # Example: Using a custom profile file path
663 ///
664 /// ```no_run
665 /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
666 /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind};
667 ///
668 /// # async fn example() {
669 /// let profile_files = ProfileFiles::builder()
670 /// .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file")
671 /// .build();
672 /// let sdk_config = aws_config::from_env()
673 /// .profile_files(profile_files)
674 /// .load()
675 /// .await;
676 /// # }
677 #[allow(deprecated)]
678 pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
679 self.profile_files_override = Some(profile_files);
680 self
681 }
682
683 /// Override the profile name used by configuration providers
684 ///
685 /// Profile name is selected from an ordered list of sources:
686 /// 1. This override.
687 /// 2. The value of the `AWS_PROFILE` environment variable.
688 /// 3. `default`
689 ///
690 /// Each AWS profile has a name. For example, in the file below, the profiles are named
691 /// `dev`, `prod` and `staging`:
692 /// ```ini
693 /// [dev]
694 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
695 ///
696 /// [staging]
697 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
698 ///
699 /// [prod]
700 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
701 /// ```
702 ///
703 /// See [Named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
704 /// for more information about naming profiles.
705 ///
706 /// # Example: Using a custom profile name
707 ///
708 /// ```no_run
709 /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
710 ///
711 /// # async fn example() {
712 /// let sdk_config = aws_config::from_env()
713 /// .profile_name("prod")
714 /// .load()
715 /// .await;
716 /// # }
717 pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
718 self.profile_name_override = Some(profile_name.into());
719 self
720 }
721
722 #[doc = docs_for!(account_id_endpoint_mode)]
723 pub fn account_id_endpoint_mode(
724 mut self,
725 account_id_endpoint_mode: AccountIdEndpointMode,
726 ) -> Self {
727 self.account_id_endpoint_mode = Some(account_id_endpoint_mode);
728 self
729 }
730
731 /// Override the endpoint URL used for **all** AWS services.
732 ///
733 /// This method will override the endpoint URL used for **all** AWS services. This primarily
734 /// exists to set a static endpoint for tools like `LocalStack`. When sending requests to
735 /// production AWS services, this method should only be used for service-specific behavior.
736 ///
737 /// When this method is used, the [`Region`](aws_types::region::Region) is only used for signing;
738 /// It is **not** used to route the request.
739 ///
740 /// # Examples
741 ///
742 /// Use a static endpoint for all services
743 /// ```no_run
744 /// # async fn create_config() {
745 /// let sdk_config = aws_config::from_env()
746 /// .endpoint_url("http://localhost:1234")
747 /// .load()
748 /// .await;
749 /// # }
750 pub fn endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
751 self.endpoint_url = Some(endpoint_url.into());
752 self
753 }
754
755 #[doc = docs_for!(use_fips)]
756 pub fn use_fips(mut self, use_fips: bool) -> Self {
757 self.use_fips = Some(use_fips);
758 self
759 }
760
761 #[doc = docs_for!(use_dual_stack)]
762 pub fn use_dual_stack(mut self, use_dual_stack: bool) -> Self {
763 self.use_dual_stack = Some(use_dual_stack);
764 self
765 }
766
767 #[doc = docs_for!(disable_request_compression)]
768 pub fn disable_request_compression(mut self, disable_request_compression: bool) -> Self {
769 self.disable_request_compression = Some(disable_request_compression);
770 self
771 }
772
773 #[doc = docs_for!(request_min_compression_size_bytes)]
774 pub fn request_min_compression_size_bytes(mut self, size: u32) -> Self {
775 self.request_min_compression_size_bytes = Some(size);
776 self
777 }
778
779 /// Override the [`StalledStreamProtectionConfig`] used to build [`SdkConfig`].
780 ///
781 /// This configures stalled stream protection. When enabled, download streams
782 /// that stop (stream no data) for longer than a configured grace period will return an error.
783 ///
784 /// By default, streams that transmit less than one byte per-second for five seconds will
785 /// be cancelled.
786 ///
787 /// _Note_: When an override is provided, the default implementation is replaced.
788 ///
789 /// # Examples
790 /// ```no_run
791 /// # async fn create_config() {
792 /// use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
793 /// use std::time::Duration;
794 /// let config = aws_config::from_env()
795 /// .stalled_stream_protection(
796 /// StalledStreamProtectionConfig::enabled()
797 /// .grace_period(Duration::from_secs(1))
798 /// .build()
799 /// )
800 /// .load()
801 /// .await;
802 /// # }
803 /// ```
804 pub fn stalled_stream_protection(
805 mut self,
806 stalled_stream_protection_config: StalledStreamProtectionConfig,
807 ) -> Self {
808 self.stalled_stream_protection_config = Some(stalled_stream_protection_config);
809 self
810 }
811
812 /// Set the checksum calculation strategy to use when making requests.
813 /// # Examples
814 /// ```
815 /// use aws_types::SdkConfig;
816 /// use aws_smithy_types::checksum_config::RequestChecksumCalculation;
817 /// let config = SdkConfig::builder().request_checksum_calculation(RequestChecksumCalculation::WhenSupported).build();
818 /// ```
819 pub fn request_checksum_calculation(
820 mut self,
821 request_checksum_calculation: RequestChecksumCalculation,
822 ) -> Self {
823 self.request_checksum_calculation = Some(request_checksum_calculation);
824 self
825 }
826
827 /// Set the checksum calculation strategy to use for responses.
828 /// # Examples
829 /// ```
830 /// use aws_types::SdkConfig;
831 /// use aws_smithy_types::checksum_config::ResponseChecksumValidation;
832 /// let config = SdkConfig::builder().response_checksum_validation(ResponseChecksumValidation::WhenSupported).build();
833 /// ```
834 pub fn response_checksum_validation(
835 mut self,
836 response_checksum_validation: ResponseChecksumValidation,
837 ) -> Self {
838 self.response_checksum_validation = Some(response_checksum_validation);
839 self
840 }
841
842 /// Load the default configuration chain
843 ///
844 /// If fields have been overridden during builder construction, the override values will be used.
845 ///
846 /// Otherwise, the default values for each field will be provided.
847 ///
848 /// NOTE: When an override is provided, the default implementation is **not** used as a fallback.
849 /// This means that if you provide a region provider that does not return a region, no region will
850 /// be set in the resulting [`SdkConfig`].
851 pub async fn load(self) -> SdkConfig {
852 let time_source = self.time_source.unwrap_or_default();
853
854 let sleep_impl = if self.sleep.is_some() {
855 self.sleep
856 } else {
857 if default_async_sleep().is_none() {
858 tracing::warn!(
859 "An implementation of AsyncSleep was requested by calling default_async_sleep \
860 but no default was set.
861 This happened when ConfigLoader::load was called during Config construction. \
862 You can fix this by setting a sleep_impl on the ConfigLoader before calling \
863 load or by enabling the rt-tokio feature"
864 );
865 }
866 default_async_sleep()
867 };
868
869 let conf = self
870 .provider_config
871 .unwrap_or_else(|| {
872 let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone())
873 .with_fs(self.fs.unwrap_or_default())
874 .with_env(self.env.unwrap_or_default());
875 if let Some(http_client) = self.http_client.clone() {
876 config = config.with_http_client(http_client);
877 }
878 config
879 })
880 .with_behavior_version(self.behavior_version)
881 .with_profile_config(self.profile_files_override, self.profile_name_override);
882
883 let use_fips = if let Some(use_fips) = self.use_fips {
884 Some(use_fips)
885 } else {
886 use_fips::use_fips_provider(&conf).await
887 };
888
889 let use_dual_stack = if let Some(use_dual_stack) = self.use_dual_stack {
890 Some(use_dual_stack)
891 } else {
892 use_dual_stack::use_dual_stack_provider(&conf).await
893 };
894
895 let conf = conf
896 .with_use_fips(use_fips)
897 .with_use_dual_stack(use_dual_stack);
898
899 let region = if let Some(provider) = self.region {
900 provider.region().await
901 } else {
902 region::Builder::default()
903 .configure(&conf)
904 .build()
905 .region()
906 .await
907 };
908 let conf = conf.with_region(region.clone());
909
910 let app_name = if self.app_name.is_some() {
911 self.app_name
912 } else {
913 app_name::default_provider()
914 .configure(&conf)
915 .app_name()
916 .await
917 };
918
919 let disable_request_compression = if self.disable_request_compression.is_some() {
920 self.disable_request_compression
921 } else {
922 disable_request_compression::disable_request_compression_provider(&conf).await
923 };
924
925 let request_min_compression_size_bytes =
926 if self.request_min_compression_size_bytes.is_some() {
927 self.request_min_compression_size_bytes
928 } else {
929 request_min_compression_size_bytes::request_min_compression_size_bytes_provider(
930 &conf,
931 )
932 .await
933 };
934
935 let base_config = timeout_config::default_provider()
936 .configure(&conf)
937 .timeout_config()
938 .await;
939 let mut timeout_config = self
940 .timeout_config
941 .unwrap_or_else(|| TimeoutConfig::builder().build());
942 timeout_config.take_defaults_from(&base_config);
943
944 let (retry_config, retry_config_explicitly_set) = match self.retry_config {
945 Some(rc) => (rc, true),
946 None => (
947 retry_config::default_provider()
948 .configure(&conf)
949 .retry_config()
950 .await,
951 false,
952 ),
953 };
954 let conf = conf
955 .with_retry_config(retry_config.clone())
956 .with_timeout_config(timeout_config.clone());
957
958 let credentials_provider = match self.credentials_provider {
959 TriStateOption::Set(provider) => Some(provider),
960 TriStateOption::NotSet => {
961 let mut builder =
962 credentials::DefaultCredentialsChain::builder().configure(conf.clone());
963 builder.set_region(region.clone());
964 Some(SharedCredentialsProvider::new(builder.build().await))
965 }
966 TriStateOption::ExplicitlyUnset => None,
967 };
968
969 let profiles = conf.profile().await;
970 let ignore_configured_endpoint_urls = if self.endpoint_url.is_some() {
971 // If an endpoint URL is set programmatically, the ignore flag is irrelevant
972 // because programmatic config always takes precedence.
973 false
974 } else {
975 ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
976 .await
977 .unwrap_or_default()
978 };
979 let service_config = EnvServiceConfig {
980 env: conf.env(),
981 env_config_sections: profiles.cloned().unwrap_or_default(),
982 ignore_configured_endpoint_urls,
983 };
984 let mut builder = SdkConfig::builder()
985 .region(region.clone())
986 .timeout_config(timeout_config)
987 .time_source(time_source)
988 .service_config(service_config);
989
990 if retry_config_explicitly_set {
991 builder.insert_origin("retry_config", Origin::shared_config());
992 }
993 builder = builder.retry_config(retry_config);
994
995 // If an endpoint URL is set programmatically, then our work is done.
996 let endpoint_url = if self.endpoint_url.is_some() {
997 builder.insert_origin("endpoint_url", Origin::shared_config());
998 self.endpoint_url
999 } else if ignore_configured_endpoint_urls {
1000 // If yes, log a trace and return `None`.
1001 tracing::trace!(
1002 "`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
1003 NOTE: Endpoint URLs set programmatically WILL still be respected"
1004 );
1005 None
1006 } else {
1007 // Otherwise, attempt to resolve one.
1008 let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
1009 builder.insert_origin("endpoint_url", origin);
1010 v
1011 };
1012
1013 let token_provider = match self.token_provider {
1014 Some(provider) => {
1015 builder.insert_origin("token_provider", Origin::shared_config());
1016 Some(provider)
1017 }
1018 None => {
1019 #[cfg(feature = "sso")]
1020 {
1021 let mut builder =
1022 crate::default_provider::token::DefaultTokenChain::builder()
1023 .configure(conf.clone());
1024 builder.set_region(region);
1025 Some(SharedTokenProvider::new(builder.build().await))
1026 }
1027 #[cfg(not(feature = "sso"))]
1028 {
1029 None
1030 }
1031 // Not setting `Origin` in this arm, and that's good for now as long as we know
1032 // it's not programmatically set in the shared config.
1033 // We can consider adding `Origin::Default` if needed.
1034 }
1035 };
1036
1037 builder.set_endpoint_url(endpoint_url);
1038 builder.set_behavior_version(self.behavior_version);
1039 builder.set_http_client(self.http_client);
1040 builder.set_protocol(self.protocol);
1041 builder.set_app_name(app_name);
1042 builder.set_framework_metadata(self.framework_metadata);
1043
1044 let identity_cache = match self.identity_cache {
1045 None => match self.behavior_version {
1046 #[allow(deprecated)]
1047 Some(bv) if bv.is_at_least(BehaviorVersion::v2024_03_28()) => {
1048 Some(IdentityCache::lazy().build())
1049 }
1050 _ => None,
1051 },
1052 Some(user_cache) => Some(user_cache),
1053 };
1054
1055 let request_checksum_calculation =
1056 if let Some(request_checksum_calculation) = self.request_checksum_calculation {
1057 Some(request_checksum_calculation)
1058 } else {
1059 checksums::request_checksum_calculation_provider(&conf).await
1060 };
1061
1062 let response_checksum_validation =
1063 if let Some(response_checksum_validation) = self.response_checksum_validation {
1064 Some(response_checksum_validation)
1065 } else {
1066 checksums::response_checksum_validation_provider(&conf).await
1067 };
1068
1069 let account_id_endpoint_mode =
1070 if let Some(acccount_id_endpoint_mode) = self.account_id_endpoint_mode {
1071 Some(acccount_id_endpoint_mode)
1072 } else {
1073 account_id_endpoint_mode::account_id_endpoint_mode_provider(&conf).await
1074 };
1075
1076 let auth_scheme_preference =
1077 if let Some(auth_scheme_preference) = self.auth_scheme_preference {
1078 builder.insert_origin("auth_scheme_preference", Origin::shared_config());
1079 Some(auth_scheme_preference)
1080 } else {
1081 auth_scheme_preference::auth_scheme_preference_provider(&conf).await
1082 // Not setting `Origin` in this arm, and that's good for now as long as we know
1083 // it's not programmatically set in the shared config.
1084 };
1085
1086 let sigv4a_signing_region_set =
1087 if let Some(sigv4a_signing_region_set) = self.sigv4a_signing_region_set {
1088 Some(sigv4a_signing_region_set)
1089 } else {
1090 sigv4a_signing_region_set::sigv4a_signing_region_set_provider(&conf).await
1091 };
1092
1093 builder.set_request_checksum_calculation(request_checksum_calculation);
1094 builder.set_response_checksum_validation(response_checksum_validation);
1095 builder.set_identity_cache(identity_cache);
1096 builder.set_credentials_provider(credentials_provider);
1097 builder.set_token_provider(token_provider);
1098 builder.set_sleep_impl(sleep_impl);
1099 builder.set_use_fips(use_fips);
1100 builder.set_use_dual_stack(use_dual_stack);
1101 builder.set_disable_request_compression(disable_request_compression);
1102 builder.set_request_min_compression_size_bytes(request_min_compression_size_bytes);
1103 builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
1104 builder.set_account_id_endpoint_mode(account_id_endpoint_mode);
1105 builder.set_auth_scheme_preference(auth_scheme_preference);
1106 builder.set_sigv4a_signing_region_set(sigv4a_signing_region_set);
1107 builder.build()
1108 }
1109 }
1110
1111 #[cfg(any(test, feature = "test-util"))]
1112 impl ConfigLoader {
1113 /// Override the environment variables used during config resolution.
1114 ///
1115 /// This is intended for testing only.
1116 pub fn env(mut self, env: Env) -> Self {
1117 self.env = Some(env);
1118 self
1119 }
1120
1121 /// Override the filesystem used during config resolution.
1122 ///
1123 /// This is intended for testing only.
1124 pub fn fs(mut self, fs: Fs) -> Self {
1125 self.fs = Some(fs);
1126 self
1127 }
1128 }
1129
1130 #[cfg(test)]
1131 mod test {
1132 #[allow(deprecated)]
1133 use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
1134 use crate::test_case::{no_traffic_client, InstantSleep};
1135 use crate::BehaviorVersion;
1136 use crate::{defaults, ConfigLoader};
1137 use aws_credential_types::provider::ProvideCredentials;
1138 use aws_smithy_async::rt::sleep::TokioSleep;
1139 use aws_smithy_async::test_util::tick_advance_sleep::tick_advance_time_and_sleep;
1140 use aws_smithy_http_client::test_util::{infallible_client_fn, NeverClient};
1141 use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
1142 use aws_smithy_runtime_api::client::identity::{
1143 ResolveCachedIdentity, SharedIdentityResolver,
1144 };
1145 use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
1146 use aws_types::app_name::AppName;
1147 use aws_types::origin::Origin;
1148 use aws_types::os_shim_internal::{Env, Fs};
1149 use aws_types::sdk_config::{RequestChecksumCalculation, ResponseChecksumValidation};
1150 use std::sync::atomic::{AtomicUsize, Ordering};
1151 use std::sync::Arc;
1152 use std::time::Duration;
1153
1154 #[tokio::test]
1155 async fn provider_config_used() {
1156 let (_guard, logs_rx) = capture_test_logs();
1157 let env = Env::from_slice(&[
1158 ("AWS_MAX_ATTEMPTS", "10"),
1159 ("AWS_REGION", "us-west-4"),
1160 ("AWS_ACCESS_KEY_ID", "akid"),
1161 ("AWS_SECRET_ACCESS_KEY", "secret"),
1162 ]);
1163 let fs =
1164 Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
1165 let loader = defaults(BehaviorVersion::latest())
1166 .sleep_impl(TokioSleep::new())
1167 .env(env)
1168 .fs(fs)
1169 .http_client(NeverClient::new())
1170 .profile_name("custom")
1171 .profile_files(
1172 #[allow(deprecated)]
1173 ProfileFiles::builder()
1174 .with_file(
1175 #[allow(deprecated)]
1176 ProfileFileKind::Config,
1177 "test_config",
1178 )
1179 .build(),
1180 )
1181 .load()
1182 .await;
1183 assert_eq!(10, loader.retry_config().unwrap().max_attempts());
1184 assert_eq!("us-west-4", loader.region().unwrap().as_ref());
1185 assert_eq!(
1186 "akid",
1187 loader
1188 .credentials_provider()
1189 .unwrap()
1190 .provide_credentials()
1191 .await
1192 .unwrap()
1193 .access_key_id(),
1194 );
1195 assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
1196
1197 let num_config_loader_logs = logs_rx.contents()
1198 .lines()
1199 // The logger uses fancy formatting, so we have to account for that.
1200 .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}"))
1201 .count();
1202
1203 match num_config_loader_logs {
1204 0 => panic!("no config file logs found!"),
1205 1 => (),
1206 more => panic!("the config file was parsed more than once! (parsed {more})",),
1207 };
1208 }
1209
1210 fn base_conf() -> ConfigLoader {
1211 defaults(BehaviorVersion::latest())
1212 .sleep_impl(InstantSleep)
1213 .http_client(no_traffic_client())
1214 }
1215
1216 #[tokio::test]
1217 async fn test_origin_programmatic() {
1218 let _ = tracing_subscriber::fmt::try_init();
1219 let loader = base_conf()
1220 .test_credentials()
1221 .profile_name("custom")
1222 .profile_files(
1223 #[allow(deprecated)]
1224 ProfileFiles::builder()
1225 .with_contents(
1226 #[allow(deprecated)]
1227 ProfileFileKind::Config,
1228 "[profile custom]\nendpoint_url = http://localhost:8989",
1229 )
1230 .build(),
1231 )
1232 .endpoint_url("http://localhost:1111")
1233 .load()
1234 .await;
1235 assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
1236 }
1237
1238 #[tokio::test]
1239 async fn test_origin_env() {
1240 let _ = tracing_subscriber::fmt::try_init();
1241 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
1242 let loader = base_conf()
1243 .test_credentials()
1244 .env(env)
1245 .profile_name("custom")
1246 .profile_files(
1247 #[allow(deprecated)]
1248 ProfileFiles::builder()
1249 .with_contents(
1250 #[allow(deprecated)]
1251 ProfileFileKind::Config,
1252 "[profile custom]\nendpoint_url = http://localhost:8989",
1253 )
1254 .build(),
1255 )
1256 .load()
1257 .await;
1258 assert_eq!(
1259 Origin::shared_environment_variable(),
1260 loader.get_origin("endpoint_url")
1261 );
1262 }
1263
1264 #[tokio::test]
1265 async fn test_origin_fs() {
1266 let _ = tracing_subscriber::fmt::try_init();
1267 let loader = base_conf()
1268 .test_credentials()
1269 .profile_name("custom")
1270 .profile_files(
1271 #[allow(deprecated)]
1272 ProfileFiles::builder()
1273 .with_contents(
1274 #[allow(deprecated)]
1275 ProfileFileKind::Config,
1276 "[profile custom]\nendpoint_url = http://localhost:8989",
1277 )
1278 .build(),
1279 )
1280 .load()
1281 .await;
1282 assert_eq!(
1283 Origin::shared_profile_file(),
1284 loader.get_origin("endpoint_url")
1285 );
1286 }
1287
1288 #[tokio::test]
1289 async fn load_use_fips() {
1290 let conf = base_conf().use_fips(true).load().await;
1291 assert_eq!(Some(true), conf.use_fips());
1292 }
1293
1294 #[tokio::test]
1295 async fn load_dual_stack() {
1296 let conf = base_conf().use_dual_stack(false).load().await;
1297 assert_eq!(Some(false), conf.use_dual_stack());
1298
1299 let conf = base_conf().load().await;
1300 assert_eq!(None, conf.use_dual_stack());
1301 }
1302
1303 #[tokio::test]
1304 async fn load_disable_request_compression() {
1305 let conf = base_conf().disable_request_compression(true).load().await;
1306 assert_eq!(Some(true), conf.disable_request_compression());
1307
1308 let conf = base_conf().load().await;
1309 assert_eq!(None, conf.disable_request_compression());
1310 }
1311
1312 #[tokio::test]
1313 async fn load_request_min_compression_size_bytes() {
1314 let conf = base_conf()
1315 .request_min_compression_size_bytes(99)
1316 .load()
1317 .await;
1318 assert_eq!(Some(99), conf.request_min_compression_size_bytes());
1319
1320 let conf = base_conf().load().await;
1321 assert_eq!(None, conf.request_min_compression_size_bytes());
1322 }
1323
1324 #[tokio::test]
1325 async fn app_name() {
1326 let app_name = AppName::new("my-app-name").unwrap();
1327 let conf = base_conf().app_name(app_name.clone()).load().await;
1328 assert_eq!(Some(&app_name), conf.app_name());
1329 }
1330
1331 #[tokio::test]
1332 async fn framework_metadata() {
1333 use aws_types::sdk_ua_metadata::FrameworkMetadata;
1334
1335 let one = FrameworkMetadata::new("framework-one", Some("1.0")).unwrap();
1336 let two = FrameworkMetadata::new("framework-two", Some("2.0")).unwrap();
1337 let conf = base_conf()
1338 .framework_metadata(one.clone())
1339 .framework_metadata(two.clone())
1340 .load()
1341 .await;
1342 assert_eq!(&[one, two], conf.framework_metadata());
1343 }
1344
1345 #[tokio::test]
1346 async fn request_checksum_calculation() {
1347 let conf = base_conf()
1348 .request_checksum_calculation(RequestChecksumCalculation::WhenRequired)
1349 .load()
1350 .await;
1351 assert_eq!(
1352 Some(RequestChecksumCalculation::WhenRequired),
1353 conf.request_checksum_calculation()
1354 );
1355 }
1356
1357 #[tokio::test]
1358 async fn response_checksum_validation() {
1359 let conf = base_conf()
1360 .response_checksum_validation(ResponseChecksumValidation::WhenRequired)
1361 .load()
1362 .await;
1363 assert_eq!(
1364 Some(ResponseChecksumValidation::WhenRequired),
1365 conf.response_checksum_validation()
1366 );
1367 }
1368
1369 #[cfg(feature = "default-https-client")]
1370 #[tokio::test]
1371 async fn disable_default_credentials() {
1372 let config = defaults(BehaviorVersion::latest())
1373 .no_credentials()
1374 .load()
1375 .await;
1376 assert!(config.credentials_provider().is_none());
1377 }
1378
1379 #[cfg(feature = "default-https-client")]
1380 #[tokio::test]
1381 async fn identity_cache_defaulted() {
1382 let config = defaults(BehaviorVersion::latest()).load().await;
1383
1384 assert!(config.identity_cache().is_some());
1385 }
1386
1387 #[cfg(feature = "default-https-client")]
1388 #[allow(deprecated)]
1389 #[tokio::test]
1390 async fn identity_cache_old_behavior_version() {
1391 // Previously, `load()` did not need an explicit HTTP client because
1392 // internal providers (e.g. IMDS) built their Operation without a
1393 // BehaviorVersion, so default_plugins defaulted to latest() and the
1394 // `default-https-client` code path provided one automatically.
1395 //
1396 // Now that BehaviorVersion is threaded through to Operation::builder(),
1397 // the old BV here (v2023_11_09) causes default_plugins to use the
1398 // legacy hyper 0.14 code path, which returns None without
1399 // `legacy-rustls-ring`. NeverClient satisfies the debug_assertions
1400 // check in Operation::build() without additional TLS dependencies.
1401 let config = defaults(BehaviorVersion::v2023_11_09())
1402 .http_client(NeverClient::new())
1403 .sleep_impl(InstantSleep)
1404 .load()
1405 .await;
1406
1407 assert!(config.identity_cache().is_none());
1408 }
1409
1410 #[tokio::test]
1411 async fn connector_is_shared() {
1412 let num_requests = Arc::new(AtomicUsize::new(0));
1413 let movable = num_requests.clone();
1414 let http_client = infallible_client_fn(move |_req| {
1415 movable.fetch_add(1, Ordering::Relaxed);
1416 http::Response::new("ok!")
1417 });
1418 let config = defaults(BehaviorVersion::latest())
1419 .fs(Fs::from_slice(&[]))
1420 .env(Env::from_slice(&[]))
1421 .http_client(http_client.clone())
1422 .load()
1423 .await;
1424 config
1425 .credentials_provider()
1426 .unwrap()
1427 .provide_credentials()
1428 .await
1429 .expect_err("did not expect credentials to be loaded—no traffic is allowed");
1430 let num_requests = num_requests.load(Ordering::Relaxed);
1431 assert!(num_requests > 0, "{}", num_requests);
1432 }
1433
1434 #[tokio::test]
1435 async fn endpoint_urls_may_be_ignored_from_env() {
1436 let fs = Fs::from_slice(&[(
1437 "test_config",
1438 "[profile custom]\nendpoint_url = http://profile",
1439 )]);
1440 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1441
1442 let conf = base_conf().use_dual_stack(false).load().await;
1443 assert_eq!(Some(false), conf.use_dual_stack());
1444
1445 let conf = base_conf().load().await;
1446 assert_eq!(None, conf.use_dual_stack());
1447
1448 // Check that we get nothing back because the env said we should ignore endpoints
1449 let config = base_conf()
1450 .fs(fs.clone())
1451 .env(env)
1452 .profile_name("custom")
1453 .profile_files(
1454 #[allow(deprecated)]
1455 ProfileFiles::builder()
1456 .with_file(
1457 #[allow(deprecated)]
1458 ProfileFileKind::Config,
1459 "test_config",
1460 )
1461 .build(),
1462 )
1463 .load()
1464 .await;
1465 assert_eq!(None, config.endpoint_url());
1466
1467 // Check that without the env, we DO get something back
1468 let config = base_conf()
1469 .fs(fs)
1470 .profile_name("custom")
1471 .profile_files(
1472 #[allow(deprecated)]
1473 ProfileFiles::builder()
1474 .with_file(
1475 #[allow(deprecated)]
1476 ProfileFileKind::Config,
1477 "test_config",
1478 )
1479 .build(),
1480 )
1481 .load()
1482 .await;
1483 assert_eq!(Some("http://profile"), config.endpoint_url());
1484 }
1485
1486 #[tokio::test]
1487 async fn endpoint_urls_may_be_ignored_from_profile() {
1488 let fs = Fs::from_slice(&[(
1489 "test_config",
1490 "[profile custom]\nignore_configured_endpoint_urls = true",
1491 )]);
1492 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
1493
1494 // Check that we get nothing back because the profile said we should ignore endpoints
1495 let config = base_conf()
1496 .fs(fs)
1497 .env(env.clone())
1498 .profile_name("custom")
1499 .profile_files(
1500 #[allow(deprecated)]
1501 ProfileFiles::builder()
1502 .with_file(
1503 #[allow(deprecated)]
1504 ProfileFileKind::Config,
1505 "test_config",
1506 )
1507 .build(),
1508 )
1509 .load()
1510 .await;
1511 assert_eq!(None, config.endpoint_url());
1512
1513 // Check that without the profile, we DO get something back
1514 let config = base_conf().env(env).load().await;
1515 assert_eq!(Some("http://environment"), config.endpoint_url());
1516 }
1517
1518 #[tokio::test]
1519 async fn programmatic_endpoint_urls_may_not_be_ignored() {
1520 let fs = Fs::from_slice(&[(
1521 "test_config",
1522 "[profile custom]\nignore_configured_endpoint_urls = true",
1523 )]);
1524 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1525
1526 // Check that we get something back because we explicitly set the loader's endpoint URL
1527 let config = base_conf()
1528 .fs(fs)
1529 .env(env)
1530 .endpoint_url("http://localhost")
1531 .profile_name("custom")
1532 .profile_files(
1533 #[allow(deprecated)]
1534 ProfileFiles::builder()
1535 .with_file(
1536 #[allow(deprecated)]
1537 ProfileFileKind::Config,
1538 "test_config",
1539 )
1540 .build(),
1541 )
1542 .load()
1543 .await;
1544 assert_eq!(Some("http://localhost"), config.endpoint_url());
1545 }
1546
1547 #[tokio::test]
1548 async fn retry_config_propagated_to_inner_sts_client() {
1549 let request_count = Arc::new(AtomicUsize::new(0));
1550 let counter = request_count.clone();
1551
1552 // Return STS Throttling error for every request
1553 let http_client = infallible_client_fn(move |_req| {
1554 counter.fetch_add(1, Ordering::Relaxed);
1555 http::Response::builder()
1556 .status(400)
1557 .body(
1558 r#"<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
1559 <Error>
1560 <Type>Sender</Type>
1561 <Code>Throttling</Code>
1562 <Message>Rate exceeded</Message>
1563 </Error>
1564 <RequestId>test-request-id</RequestId>
1565 </ErrorResponse>"#,
1566 )
1567 .unwrap()
1568 });
1569
1570 // Set up web identity token env vars + AWS_MAX_ATTEMPTS=5
1571 let env = Env::from_slice(&[
1572 ("AWS_WEB_IDENTITY_TOKEN_FILE", "/token.jwt"),
1573 ("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/test-role"),
1574 ("AWS_ROLE_SESSION_NAME", "test-session"),
1575 ("AWS_REGION", "us-east-1"),
1576 ("AWS_MAX_ATTEMPTS", "5"),
1577 ]);
1578 let fs = Fs::from_slice(&[("/token.jwt", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test")]);
1579
1580 let config = defaults(BehaviorVersion::latest())
1581 .sleep_impl(InstantSleep)
1582 .http_client(http_client)
1583 .env(env)
1584 .fs(fs)
1585 .load()
1586 .await;
1587
1588 // Attempt to load credentials — will fail because all responses are throttled
1589 let _ = config
1590 .credentials_provider()
1591 .unwrap()
1592 .provide_credentials()
1593 .await;
1594
1595 // The inner STS client should have made 5 attempts (not the default 3),
1596 // proving that AWS_MAX_ATTEMPTS propagated to the inner STS client.
1597 assert_eq!(5, request_count.load(Ordering::Relaxed));
1598 }
1599
1600 #[tokio::test]
1601 async fn pessimistic_load_timeout_allows_retries_to_complete() {
1602 let (time_source, sleep_impl) = tick_advance_time_and_sleep();
1603
1604 let request_count = Arc::new(AtomicUsize::new(0));
1605 let counter = request_count.clone();
1606
1607 // Return STS Throttling error for first 4 attempts, succeed on 5th
1608 let http_client = infallible_client_fn(move |_req| {
1609 let count = counter.fetch_add(1, Ordering::Relaxed) + 1;
1610 if count < 5 {
1611 http::Response::builder()
1612 .status(400)
1613 .body(
1614 r#"<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
1615 <Error>
1616 <Type>Sender</Type>
1617 <Code>Throttling</Code>
1618 <Message>Rate exceeded</Message>
1619 </Error>
1620 <RequestId>test-request-id</RequestId>
1621 </ErrorResponse>"#,
1622 )
1623 .unwrap()
1624 } else {
1625 http::Response::builder()
1626 .status(200)
1627 .body(
1628 r#"<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
1629 <AssumeRoleWithWebIdentityResult>
1630 <Credentials>
1631 <AccessKeyId>ASIATESTACCESSKEYID</AccessKeyId>
1632 <SecretAccessKey>TESTSECRETKEY</SecretAccessKey>
1633 <SessionToken>TESTSESSIONTOKEN</SessionToken>
1634 <Expiration>2099-01-01T00:00:00Z</Expiration>
1635 </Credentials>
1636 </AssumeRoleWithWebIdentityResult>
1637 </AssumeRoleWithWebIdentityResponse>"#,
1638 )
1639 .unwrap()
1640 }
1641 });
1642
1643 let env = Env::from_slice(&[
1644 ("AWS_WEB_IDENTITY_TOKEN_FILE", "/token.jwt"),
1645 ("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/test-role"),
1646 ("AWS_ROLE_SESSION_NAME", "test-session"),
1647 ("AWS_REGION", "us-east-1"),
1648 ("AWS_MAX_ATTEMPTS", "5"),
1649 ]);
1650 let fs = Fs::from_slice(&[("/token.jwt", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test")]);
1651
1652 let config = defaults(BehaviorVersion::latest())
1653 .sleep_impl(sleep_impl.clone())
1654 .time_source(time_source.clone())
1655 .http_client(http_client)
1656 .env(env)
1657 .fs(fs)
1658 .load()
1659 .await;
1660
1661 // Exercise the LazyCache path by calling resolve_cached_identity directly,
1662 // which is what the orchestrator does during an operation.
1663 let identity_cache = config
1664 .identity_cache()
1665 .expect("identity cache should be set");
1666 let credentials_provider = config.credentials_provider().unwrap();
1667 let identity_resolver = SharedIdentityResolver::new(credentials_provider.clone());
1668
1669 let runtime_components = RuntimeComponentsBuilder::for_tests()
1670 .with_time_source(Some(time_source.clone()))
1671 .with_sleep_impl(Some(sleep_impl.clone()))
1672 .build()
1673 .unwrap();
1674
1675 let mut config_bag = aws_smithy_types::config_bag::ConfigBag::base();
1676 config_bag
1677 .interceptor_state()
1678 .store_put(aws_smithy_types::retry::RetryConfig::standard().with_max_attempts(5));
1679
1680 // Spawn identity resolution through the cache (includes timeout)
1681 let task = tokio::spawn(async move {
1682 identity_cache
1683 .resolve_cached_identity(identity_resolver, &runtime_components, &config_bag)
1684 .await
1685 });
1686 tokio::task::yield_now().await;
1687
1688 // Advance time enough for all retry backoffs to complete.
1689 // 5 attempts with 1s exponential backoff: 1+2+4+8 = 15s total backoff.
1690 // This exceeds the old 5s load_timeout but fits within the pessimistic
1691 // timeout (~46s for 5 attempts with 3.1s connect_timeout).
1692 time_source.tick(Duration::from_secs(60)).await;
1693
1694 let identity = task
1695 .await
1696 .unwrap()
1697 .expect("identity should resolve — pessimistic timeout gives retries room");
1698
1699 assert!(identity.expiration().is_some());
1700 assert_eq!(5, request_count.load(Ordering::Relaxed));
1701 }
1702 }
1703}