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