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_auto_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 SdkConfig,
108};
109/// Load default sources for all configuration with override support
110pub use loader::ConfigLoader;
111
112/// Types for configuring identity caching.
113pub mod identity {
114 pub use aws_smithy_runtime::client::identity::IdentityCache;
115 pub use aws_smithy_runtime::client::identity::LazyCacheBuilder;
116}
117
118#[allow(dead_code)]
119const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
120
121mod http_credential_provider;
122mod json_credentials;
123#[cfg(test)]
124mod test_case;
125
126pub mod credential_process;
127pub mod default_provider;
128pub mod ecs;
129mod env_service_config;
130pub mod environment;
131pub mod imds;
132pub mod meta;
133pub mod profile;
134pub mod provider_config;
135pub mod retry;
136mod sensitive_command;
137#[cfg(feature = "sso")]
138pub mod sso;
139pub mod stalled_stream_protection;
140pub mod sts;
141pub mod timeout;
142pub mod web_identity_token;
143
144/// Create a config loader with the _latest_ defaults.
145///
146/// This loader will always set [`BehaviorVersion::latest`].
147///
148/// # Examples
149/// ```no_run
150/// # async fn create_config() {
151/// let config = aws_config::from_env().region("us-east-1").load().await;
152/// # }
153/// ```
154#[cfg(feature = "behavior-version-latest")]
155pub fn from_env() -> ConfigLoader {
156 ConfigLoader::default().behavior_version(BehaviorVersion::latest())
157}
158
159/// Load default configuration with the _latest_ defaults.
160///
161/// Convenience wrapper equivalent to `aws_config::load_defaults(BehaviorVersion::latest()).await`
162#[cfg(feature = "behavior-version-latest")]
163pub async fn load_from_env() -> SdkConfig {
164 from_env().load().await
165}
166
167/// Create a config loader with the _latest_ defaults.
168#[cfg(not(feature = "behavior-version-latest"))]
169#[deprecated(
170 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."
171)]
172pub fn from_env() -> ConfigLoader {
173 ConfigLoader::default().behavior_version(BehaviorVersion::latest())
174}
175
176/// Load default configuration with the _latest_ defaults.
177#[cfg(not(feature = "behavior-version-latest"))]
178#[deprecated(
179 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."
180)]
181pub async fn load_from_env() -> SdkConfig {
182 load_defaults(BehaviorVersion::latest()).await
183}
184
185/// Create a config loader with the defaults for the given behavior version.
186///
187/// # Examples
188/// ```no_run
189/// # async fn create_config() {
190/// use aws_config::BehaviorVersion;
191/// let config = aws_config::defaults(BehaviorVersion::v2023_11_09())
192/// .region("us-east-1")
193/// .load()
194/// .await;
195/// # }
196/// ```
197pub fn defaults(version: BehaviorVersion) -> ConfigLoader {
198 ConfigLoader::default().behavior_version(version)
199}
200
201/// Load default configuration with the given behavior version.
202///
203/// Convenience wrapper equivalent to `aws_config::defaults(behavior_version).load().await`
204pub async fn load_defaults(version: BehaviorVersion) -> SdkConfig {
205 defaults(version).load().await
206}
207
208mod loader {
209 use crate::env_service_config::EnvServiceConfig;
210 use aws_credential_types::provider::{
211 token::{ProvideToken, SharedTokenProvider},
212 ProvideCredentials, SharedCredentialsProvider,
213 };
214 use aws_credential_types::Credentials;
215 use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
216 use aws_smithy_async::time::{SharedTimeSource, TimeSource};
217 use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
218 use aws_smithy_runtime_api::client::http::HttpClient;
219 use aws_smithy_runtime_api::client::identity::{ResolveCachedIdentity, SharedIdentityCache};
220 use aws_smithy_runtime_api::client::stalled_stream_protection::StalledStreamProtectionConfig;
221 use aws_smithy_runtime_api::shared::IntoShared;
222 use aws_smithy_types::retry::RetryConfig;
223 use aws_smithy_types::timeout::TimeoutConfig;
224 use aws_types::app_name::AppName;
225 use aws_types::docs_for;
226 use aws_types::origin::Origin;
227 use aws_types::os_shim_internal::{Env, Fs};
228 use aws_types::sdk_config::SharedHttpClient;
229 use aws_types::SdkConfig;
230
231 use crate::default_provider::{
232 app_name, credentials, endpoint_url, ignore_configured_endpoint_urls as ignore_ep, region,
233 retry_config, timeout_config, use_dual_stack, use_fips,
234 };
235 use crate::meta::region::ProvideRegion;
236 #[allow(deprecated)]
237 use crate::profile::profile_file::ProfileFiles;
238 use crate::provider_config::ProviderConfig;
239
240 #[derive(Default, Debug)]
241 enum CredentialsProviderOption {
242 /// No provider was set by the user. We can set up the default credentials provider chain.
243 #[default]
244 NotSet,
245 /// The credentials provider was explicitly unset. Do not set up a default chain.
246 ExplicitlyUnset,
247 /// Use the given credentials provider.
248 Set(SharedCredentialsProvider),
249 }
250
251 /// Load a cross-service [`SdkConfig`] from the environment
252 ///
253 /// This builder supports overriding individual components of the generated config. Overriding a component
254 /// will skip the standard resolution chain from **for that component**. For example,
255 /// if you override the region provider, _even if that provider returns None_, the default region provider
256 /// chain will not be used.
257 #[derive(Default, Debug)]
258 pub struct ConfigLoader {
259 app_name: Option<AppName>,
260 identity_cache: Option<SharedIdentityCache>,
261 credentials_provider: CredentialsProviderOption,
262 token_provider: Option<SharedTokenProvider>,
263 endpoint_url: Option<String>,
264 region: Option<Box<dyn ProvideRegion>>,
265 retry_config: Option<RetryConfig>,
266 sleep: Option<SharedAsyncSleep>,
267 timeout_config: Option<TimeoutConfig>,
268 provider_config: Option<ProviderConfig>,
269 http_client: Option<SharedHttpClient>,
270 profile_name_override: Option<String>,
271 #[allow(deprecated)]
272 profile_files_override: Option<ProfileFiles>,
273 use_fips: Option<bool>,
274 use_dual_stack: Option<bool>,
275 time_source: Option<SharedTimeSource>,
276 stalled_stream_protection_config: Option<StalledStreamProtectionConfig>,
277 env: Option<Env>,
278 fs: Option<Fs>,
279 behavior_version: Option<BehaviorVersion>,
280 }
281
282 impl ConfigLoader {
283 /// Sets the [`BehaviorVersion`] used to build [`SdkConfig`].
284 pub fn behavior_version(mut self, behavior_version: BehaviorVersion) -> Self {
285 self.behavior_version = Some(behavior_version);
286 self
287 }
288
289 /// Override the region used to build [`SdkConfig`].
290 ///
291 /// # Examples
292 /// ```no_run
293 /// # async fn create_config() {
294 /// use aws_types::region::Region;
295 /// let config = aws_config::from_env()
296 /// .region(Region::new("us-east-1"))
297 /// .load().await;
298 /// # }
299 /// ```
300 pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
301 self.region = Some(Box::new(region));
302 self
303 }
304
305 /// Override the retry_config used to build [`SdkConfig`].
306 ///
307 /// # Examples
308 /// ```no_run
309 /// # async fn create_config() {
310 /// use aws_config::retry::RetryConfig;
311 ///
312 /// let config = aws_config::from_env()
313 /// .retry_config(RetryConfig::standard().with_max_attempts(2))
314 /// .load()
315 /// .await;
316 /// # }
317 /// ```
318 pub fn retry_config(mut self, retry_config: RetryConfig) -> Self {
319 self.retry_config = Some(retry_config);
320 self
321 }
322
323 /// Override the timeout config used to build [`SdkConfig`].
324 ///
325 /// This will be merged with timeouts coming from the timeout information provider, which
326 /// currently includes a default `CONNECT` timeout of `3.1s`.
327 ///
328 /// If you want to disable timeouts, use [`TimeoutConfig::disabled`]. If you want to disable
329 /// a specific timeout, use `TimeoutConfig::set_<type>(None)`.
330 ///
331 /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials
332 /// provider chain are configured separately.
333 ///
334 /// # Examples
335 /// ```no_run
336 /// # use std::time::Duration;
337 /// # async fn create_config() {
338 /// use aws_config::timeout::TimeoutConfig;
339 ///
340 /// let config = aws_config::from_env()
341 /// .timeout_config(
342 /// TimeoutConfig::builder()
343 /// .operation_timeout(Duration::from_secs(5))
344 /// .build()
345 /// )
346 /// .load()
347 /// .await;
348 /// # }
349 /// ```
350 pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self {
351 self.timeout_config = Some(timeout_config);
352 self
353 }
354
355 /// Override the sleep implementation for this [`ConfigLoader`].
356 ///
357 /// The sleep implementation is used to create timeout futures.
358 /// You generally won't need to change this unless you're using an async runtime other
359 /// than Tokio.
360 pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self {
361 // it's possible that we could wrapping an `Arc in an `Arc` and that's OK
362 self.sleep = Some(sleep.into_shared());
363 self
364 }
365
366 /// Set the time source used for tasks like signing requests.
367 ///
368 /// You generally won't need to change this unless you're compiling for a target
369 /// that can't provide a default, such as WASM, or unless you're writing a test against
370 /// the client that needs a fixed time.
371 pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
372 self.time_source = Some(time_source.into_shared());
373 self
374 }
375
376 /// Override the [`HttpClient`] for this [`ConfigLoader`].
377 ///
378 /// The HTTP client will be used for both AWS services and credentials providers.
379 ///
380 /// If you wish to use a separate HTTP client for credentials providers when creating clients,
381 /// then override the HTTP client set with this function on the client-specific `Config`s.
382 ///
383 /// ## Examples
384 ///
385 /// ```no_run
386 /// # use aws_smithy_async::rt::sleep::SharedAsyncSleep;
387 /// #[cfg(feature = "client-hyper")]
388 /// # async fn create_config() {
389 /// use std::time::Duration;
390 /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder;
391 ///
392 /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new()
393 /// .with_webpki_roots()
394 /// // NOTE: setting `https_only()` will not allow this connector to work with IMDS.
395 /// .https_only()
396 /// .enable_http1()
397 /// .enable_http2()
398 /// .build();
399 ///
400 /// let hyper_client = HyperClientBuilder::new().build(tls_connector);
401 /// let sdk_config = aws_config::from_env()
402 /// .http_client(hyper_client)
403 /// .load()
404 /// .await;
405 /// # }
406 /// ```
407 pub fn http_client(mut self, http_client: impl HttpClient + 'static) -> Self {
408 self.http_client = Some(http_client.into_shared());
409 self
410 }
411
412 /// Override the identity cache used to build [`SdkConfig`].
413 ///
414 /// The identity cache caches AWS credentials and SSO tokens. By default, a lazy cache is used
415 /// that will load credentials upon first request, cache them, and then reload them during
416 /// another request when they are close to expiring.
417 ///
418 /// # Examples
419 ///
420 /// Change a setting on the default lazy caching implementation:
421 /// ```no_run
422 /// use aws_config::identity::IdentityCache;
423 /// use std::time::Duration;
424 ///
425 /// # async fn create_config() {
426 /// let config = aws_config::from_env()
427 /// .identity_cache(
428 /// IdentityCache::lazy()
429 /// // Change the load timeout to 10 seconds.
430 /// // Note: there are other timeouts that could trigger if the load timeout is too long.
431 /// .load_timeout(Duration::from_secs(10))
432 /// .build()
433 /// )
434 /// .load()
435 /// .await;
436 /// # }
437 /// ```
438 pub fn identity_cache(
439 mut self,
440 identity_cache: impl ResolveCachedIdentity + 'static,
441 ) -> Self {
442 self.identity_cache = Some(identity_cache.into_shared());
443 self
444 }
445
446 /// Override the credentials provider used to build [`SdkConfig`].
447 ///
448 /// # Examples
449 ///
450 /// Override the credentials provider but load the default value for region:
451 /// ```no_run
452 /// # use aws_credential_types::Credentials;
453 /// # fn create_my_credential_provider() -> Credentials {
454 /// # Credentials::new("example", "example", None, None, "example")
455 /// # }
456 /// # async fn create_config() {
457 /// let config = aws_config::from_env()
458 /// .credentials_provider(create_my_credential_provider())
459 /// .load()
460 /// .await;
461 /// # }
462 /// ```
463 pub fn credentials_provider(
464 mut self,
465 credentials_provider: impl ProvideCredentials + 'static,
466 ) -> Self {
467 self.credentials_provider = CredentialsProviderOption::Set(
468 SharedCredentialsProvider::new(credentials_provider),
469 );
470 self
471 }
472
473 /// Don't use credentials to sign requests.
474 ///
475 /// Turning off signing with credentials is necessary in some cases, such as using
476 /// anonymous auth for S3, calling operations in STS that don't require a signature,
477 /// or using token-based auth.
478 ///
479 /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you
480 /// want. If credentials are disabled, requests cannot be signed. For these use cases, use
481 /// [`test_credentials`](Self::test_credentials).
482 ///
483 /// # Examples
484 ///
485 /// Turn off credentials in order to call a service without signing:
486 /// ```no_run
487 /// # async fn create_config() {
488 /// let config = aws_config::from_env()
489 /// .no_credentials()
490 /// .load()
491 /// .await;
492 /// # }
493 /// ```
494 pub fn no_credentials(mut self) -> Self {
495 self.credentials_provider = CredentialsProviderOption::ExplicitlyUnset;
496 self
497 }
498
499 /// Set test credentials for use when signing requests
500 pub fn test_credentials(self) -> Self {
501 #[allow(unused_mut)]
502 let mut ret = self.credentials_provider(Credentials::for_tests());
503 #[cfg(all(feature = "sso", feature = "test-util"))]
504 {
505 use aws_smithy_runtime_api::client::identity::http::Token;
506 ret = ret.token_provider(Token::for_tests());
507 }
508 ret
509 }
510
511 /// Override the access token provider used to build [`SdkConfig`].
512 ///
513 /// # Examples
514 ///
515 /// Override the token provider but load the default value for region:
516 /// ```no_run
517 /// # use aws_credential_types::Token;
518 /// # fn create_my_token_provider() -> Token {
519 /// # Token::new("example", None)
520 /// # }
521 /// # async fn create_config() {
522 /// let config = aws_config::from_env()
523 /// .token_provider(create_my_token_provider())
524 /// .load()
525 /// .await;
526 /// # }
527 /// ```
528 pub fn token_provider(mut self, token_provider: impl ProvideToken + 'static) -> Self {
529 self.token_provider = Some(SharedTokenProvider::new(token_provider));
530 self
531 }
532
533 /// Override the name of the app used to build [`SdkConfig`].
534 ///
535 /// This _optional_ name is used to identify the application in the user agent that
536 /// gets sent along with requests.
537 ///
538 /// # Examples
539 /// ```no_run
540 /// # async fn create_config() {
541 /// use aws_config::AppName;
542 /// let config = aws_config::from_env()
543 /// .app_name(AppName::new("my-app-name").expect("valid app name"))
544 /// .load().await;
545 /// # }
546 /// ```
547 pub fn app_name(mut self, app_name: AppName) -> Self {
548 self.app_name = Some(app_name);
549 self
550 }
551
552 /// Provides the ability to programmatically override the profile files that get loaded by the SDK.
553 ///
554 /// The [`Default`] for `ProfileFiles` includes the default SDK config and credential files located in
555 /// `~/.aws/config` and `~/.aws/credentials` respectively.
556 ///
557 /// Any number of config and credential files may be added to the `ProfileFiles` file set, with the
558 /// only requirement being that there is at least one of each. Profile file locations will produce an
559 /// error if they don't exist, but the default config/credentials files paths are exempt from this validation.
560 ///
561 /// # Example: Using a custom profile file path
562 ///
563 /// ```no_run
564 /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
565 /// use aws_config::profile::profile_file::{ProfileFiles, ProfileFileKind};
566 ///
567 /// # async fn example() {
568 /// let profile_files = ProfileFiles::builder()
569 /// .with_file(ProfileFileKind::Credentials, "some/path/to/credentials-file")
570 /// .build();
571 /// let sdk_config = aws_config::from_env()
572 /// .profile_files(profile_files)
573 /// .load()
574 /// .await;
575 /// # }
576 #[allow(deprecated)]
577 pub fn profile_files(mut self, profile_files: ProfileFiles) -> Self {
578 self.profile_files_override = Some(profile_files);
579 self
580 }
581
582 /// Override the profile name used by configuration providers
583 ///
584 /// Profile name is selected from an ordered list of sources:
585 /// 1. This override.
586 /// 2. The value of the `AWS_PROFILE` environment variable.
587 /// 3. `default`
588 ///
589 /// Each AWS profile has a name. For example, in the file below, the profiles are named
590 /// `dev`, `prod` and `staging`:
591 /// ```ini
592 /// [dev]
593 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
594 ///
595 /// [staging]
596 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
597 ///
598 /// [prod]
599 /// ec2_metadata_service_endpoint = http://my-custom-endpoint:444
600 /// ```
601 ///
602 /// See [Named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
603 /// for more information about naming profiles.
604 ///
605 /// # Example: Using a custom profile name
606 ///
607 /// ```no_run
608 /// use aws_config::profile::{ProfileFileCredentialsProvider, ProfileFileRegionProvider};
609 ///
610 /// # async fn example() {
611 /// let sdk_config = aws_config::from_env()
612 /// .profile_name("prod")
613 /// .load()
614 /// .await;
615 /// # }
616 pub fn profile_name(mut self, profile_name: impl Into<String>) -> Self {
617 self.profile_name_override = Some(profile_name.into());
618 self
619 }
620
621 /// Override the endpoint URL used for **all** AWS services.
622 ///
623 /// This method will override the endpoint URL used for **all** AWS services. This primarily
624 /// exists to set a static endpoint for tools like `LocalStack`. When sending requests to
625 /// production AWS services, this method should only be used for service-specific behavior.
626 ///
627 /// When this method is used, the [`Region`](aws_types::region::Region) is only used for signing;
628 /// It is **not** used to route the request.
629 ///
630 /// # Examples
631 ///
632 /// Use a static endpoint for all services
633 /// ```no_run
634 /// # async fn create_config() {
635 /// let sdk_config = aws_config::from_env()
636 /// .endpoint_url("http://localhost:1234")
637 /// .load()
638 /// .await;
639 /// # }
640 pub fn endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
641 self.endpoint_url = Some(endpoint_url.into());
642 self
643 }
644
645 #[doc = docs_for!(use_fips)]
646 pub fn use_fips(mut self, use_fips: bool) -> Self {
647 self.use_fips = Some(use_fips);
648 self
649 }
650
651 #[doc = docs_for!(use_dual_stack)]
652 pub fn use_dual_stack(mut self, use_dual_stack: bool) -> Self {
653 self.use_dual_stack = Some(use_dual_stack);
654 self
655 }
656
657 /// Override the [`StalledStreamProtectionConfig`] used to build [`SdkConfig`].
658 ///
659 /// This configures stalled stream protection. When enabled, download streams
660 /// that stop (stream no data) for longer than a configured grace period will return an error.
661 ///
662 /// By default, streams that transmit less than one byte per-second for five seconds will
663 /// be cancelled.
664 ///
665 /// _Note_: When an override is provided, the default implementation is replaced.
666 ///
667 /// # Examples
668 /// ```no_run
669 /// # async fn create_config() {
670 /// use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
671 /// use std::time::Duration;
672 /// let config = aws_config::from_env()
673 /// .stalled_stream_protection(
674 /// StalledStreamProtectionConfig::enabled()
675 /// .grace_period(Duration::from_secs(1))
676 /// .build()
677 /// )
678 /// .load()
679 /// .await;
680 /// # }
681 /// ```
682 pub fn stalled_stream_protection(
683 mut self,
684 stalled_stream_protection_config: StalledStreamProtectionConfig,
685 ) -> Self {
686 self.stalled_stream_protection_config = Some(stalled_stream_protection_config);
687 self
688 }
689
690 /// Load the default configuration chain
691 ///
692 /// If fields have been overridden during builder construction, the override values will be used.
693 ///
694 /// Otherwise, the default values for each field will be provided.
695 ///
696 /// NOTE: When an override is provided, the default implementation is **not** used as a fallback.
697 /// This means that if you provide a region provider that does not return a region, no region will
698 /// be set in the resulting [`SdkConfig`].
699 pub async fn load(self) -> SdkConfig {
700 let time_source = self.time_source.unwrap_or_default();
701
702 let sleep_impl = if self.sleep.is_some() {
703 self.sleep
704 } else {
705 if default_async_sleep().is_none() {
706 tracing::warn!(
707 "An implementation of AsyncSleep was requested by calling default_async_sleep \
708 but no default was set.
709 This happened when ConfigLoader::load was called during Config construction. \
710 You can fix this by setting a sleep_impl on the ConfigLoader before calling \
711 load or by enabling the rt-tokio feature"
712 );
713 }
714 default_async_sleep()
715 };
716
717 let conf = self
718 .provider_config
719 .unwrap_or_else(|| {
720 let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone())
721 .with_fs(self.fs.unwrap_or_default())
722 .with_env(self.env.unwrap_or_default());
723 if let Some(http_client) = self.http_client.clone() {
724 config = config.with_http_client(http_client);
725 }
726 config
727 })
728 .with_profile_config(self.profile_files_override, self.profile_name_override);
729
730 let use_fips = if let Some(use_fips) = self.use_fips {
731 Some(use_fips)
732 } else {
733 use_fips::use_fips_provider(&conf).await
734 };
735
736 let use_dual_stack = if let Some(use_dual_stack) = self.use_dual_stack {
737 Some(use_dual_stack)
738 } else {
739 use_dual_stack::use_dual_stack_provider(&conf).await
740 };
741
742 let conf = conf
743 .with_use_fips(use_fips)
744 .with_use_dual_stack(use_dual_stack);
745
746 let region = if let Some(provider) = self.region {
747 provider.region().await
748 } else {
749 region::Builder::default()
750 .configure(&conf)
751 .build()
752 .region()
753 .await
754 };
755
756 let retry_config = if let Some(retry_config) = self.retry_config {
757 retry_config
758 } else {
759 retry_config::default_provider()
760 .configure(&conf)
761 .retry_config()
762 .await
763 };
764
765 let app_name = if self.app_name.is_some() {
766 self.app_name
767 } else {
768 app_name::default_provider()
769 .configure(&conf)
770 .app_name()
771 .await
772 };
773
774 let base_config = timeout_config::default_provider()
775 .configure(&conf)
776 .timeout_config()
777 .await;
778 let mut timeout_config = self
779 .timeout_config
780 .unwrap_or_else(|| TimeoutConfig::builder().build());
781 timeout_config.take_defaults_from(&base_config);
782
783 let credentials_provider = match self.credentials_provider {
784 CredentialsProviderOption::Set(provider) => Some(provider),
785 CredentialsProviderOption::NotSet => {
786 let mut builder =
787 credentials::DefaultCredentialsChain::builder().configure(conf.clone());
788 builder.set_region(region.clone());
789 Some(SharedCredentialsProvider::new(builder.build().await))
790 }
791 CredentialsProviderOption::ExplicitlyUnset => None,
792 };
793
794 let token_provider = match self.token_provider {
795 Some(provider) => Some(provider),
796 None => {
797 #[cfg(feature = "sso")]
798 {
799 let mut builder =
800 crate::default_provider::token::DefaultTokenChain::builder()
801 .configure(conf.clone());
802 builder.set_region(region.clone());
803 Some(SharedTokenProvider::new(builder.build().await))
804 }
805 #[cfg(not(feature = "sso"))]
806 {
807 None
808 }
809 }
810 };
811
812 let profiles = conf.profile().await;
813 let service_config = EnvServiceConfig {
814 env: conf.env(),
815 env_config_sections: profiles.cloned().unwrap_or_default(),
816 };
817 let mut builder = SdkConfig::builder()
818 .region(region)
819 .retry_config(retry_config)
820 .timeout_config(timeout_config)
821 .time_source(time_source)
822 .service_config(service_config);
823
824 // If an endpoint URL is set programmatically, then our work is done.
825 let endpoint_url = if self.endpoint_url.is_some() {
826 builder.insert_origin("endpoint_url", Origin::shared_config());
827 self.endpoint_url
828 } else {
829 // Otherwise, check to see if we should ignore EP URLs set in the environment.
830 let ignore_configured_endpoint_urls =
831 ignore_ep::ignore_configured_endpoint_urls_provider(&conf)
832 .await
833 .unwrap_or_default();
834
835 if ignore_configured_endpoint_urls {
836 // If yes, log a trace and return `None`.
837 tracing::trace!(
838 "`ignore_configured_endpoint_urls` is set, any endpoint URLs configured in the environment will be ignored. \
839 NOTE: Endpoint URLs set programmatically WILL still be respected"
840 );
841 None
842 } else {
843 // Otherwise, attempt to resolve one.
844 let (v, origin) = endpoint_url::endpoint_url_provider_with_origin(&conf).await;
845 builder.insert_origin("endpoint_url", origin);
846 v
847 }
848 };
849 builder.set_endpoint_url(endpoint_url);
850
851 builder.set_behavior_version(self.behavior_version);
852 builder.set_http_client(self.http_client);
853 builder.set_app_name(app_name);
854 builder.set_identity_cache(self.identity_cache);
855 builder.set_credentials_provider(credentials_provider);
856 builder.set_token_provider(token_provider);
857 builder.set_sleep_impl(sleep_impl);
858 builder.set_use_fips(use_fips);
859 builder.set_use_dual_stack(use_dual_stack);
860 builder.set_stalled_stream_protection(self.stalled_stream_protection_config);
861 builder.build()
862 }
863 }
864
865 #[cfg(test)]
866 impl ConfigLoader {
867 pub(crate) fn env(mut self, env: Env) -> Self {
868 self.env = Some(env);
869 self
870 }
871
872 pub(crate) fn fs(mut self, fs: Fs) -> Self {
873 self.fs = Some(fs);
874 self
875 }
876 }
877
878 #[cfg(test)]
879 mod test {
880 #[allow(deprecated)]
881 use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
882 use crate::test_case::{no_traffic_client, InstantSleep};
883 use crate::BehaviorVersion;
884 use crate::{defaults, ConfigLoader};
885 use aws_credential_types::provider::ProvideCredentials;
886 use aws_smithy_async::rt::sleep::TokioSleep;
887 use aws_smithy_runtime::client::http::test_util::{infallible_client_fn, NeverClient};
888 use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
889 use aws_types::app_name::AppName;
890 use aws_types::origin::Origin;
891 use aws_types::os_shim_internal::{Env, Fs};
892 use std::sync::atomic::{AtomicUsize, Ordering};
893 use std::sync::Arc;
894
895 #[tokio::test]
896 async fn provider_config_used() {
897 let (_guard, logs_rx) = capture_test_logs();
898 let env = Env::from_slice(&[
899 ("AWS_MAX_ATTEMPTS", "10"),
900 ("AWS_REGION", "us-west-4"),
901 ("AWS_ACCESS_KEY_ID", "akid"),
902 ("AWS_SECRET_ACCESS_KEY", "secret"),
903 ]);
904 let fs =
905 Fs::from_slice(&[("test_config", "[profile custom]\nsdk-ua-app-id = correct")]);
906 let loader = defaults(BehaviorVersion::latest())
907 .sleep_impl(TokioSleep::new())
908 .env(env)
909 .fs(fs)
910 .http_client(NeverClient::new())
911 .profile_name("custom")
912 .profile_files(
913 #[allow(deprecated)]
914 ProfileFiles::builder()
915 .with_file(
916 #[allow(deprecated)]
917 ProfileFileKind::Config,
918 "test_config",
919 )
920 .build(),
921 )
922 .load()
923 .await;
924 assert_eq!(10, loader.retry_config().unwrap().max_attempts());
925 assert_eq!("us-west-4", loader.region().unwrap().as_ref());
926 assert_eq!(
927 "akid",
928 loader
929 .credentials_provider()
930 .unwrap()
931 .provide_credentials()
932 .await
933 .unwrap()
934 .access_key_id(),
935 );
936 assert_eq!(Some(&AppName::new("correct").unwrap()), loader.app_name());
937
938 let num_config_loader_logs = logs_rx.contents()
939 .lines()
940 // The logger uses fancy formatting, so we have to account for that.
941 .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}"))
942 .count();
943
944 match num_config_loader_logs {
945 0 => panic!("no config file logs found!"),
946 1 => (),
947 more => panic!("the config file was parsed more than once! (parsed {more})",),
948 };
949 }
950
951 fn base_conf() -> ConfigLoader {
952 defaults(BehaviorVersion::latest())
953 .sleep_impl(InstantSleep)
954 .http_client(no_traffic_client())
955 }
956
957 #[tokio::test]
958 async fn test_origin_programmatic() {
959 let _ = tracing_subscriber::fmt::try_init();
960 let loader = base_conf()
961 .test_credentials()
962 .profile_name("custom")
963 .profile_files(
964 #[allow(deprecated)]
965 ProfileFiles::builder()
966 .with_contents(
967 #[allow(deprecated)]
968 ProfileFileKind::Config,
969 "[profile custom]\nendpoint_url = http://localhost:8989",
970 )
971 .build(),
972 )
973 .endpoint_url("http://localhost:1111")
974 .load()
975 .await;
976 assert_eq!(Origin::shared_config(), loader.get_origin("endpoint_url"));
977 }
978
979 #[tokio::test]
980 async fn test_origin_env() {
981 let _ = tracing_subscriber::fmt::try_init();
982 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://localhost:7878")]);
983 let loader = base_conf()
984 .test_credentials()
985 .env(env)
986 .profile_name("custom")
987 .profile_files(
988 #[allow(deprecated)]
989 ProfileFiles::builder()
990 .with_contents(
991 #[allow(deprecated)]
992 ProfileFileKind::Config,
993 "[profile custom]\nendpoint_url = http://localhost:8989",
994 )
995 .build(),
996 )
997 .load()
998 .await;
999 assert_eq!(
1000 Origin::shared_environment_variable(),
1001 loader.get_origin("endpoint_url")
1002 );
1003 }
1004
1005 #[tokio::test]
1006 async fn test_origin_fs() {
1007 let _ = tracing_subscriber::fmt::try_init();
1008 let loader = base_conf()
1009 .test_credentials()
1010 .profile_name("custom")
1011 .profile_files(
1012 #[allow(deprecated)]
1013 ProfileFiles::builder()
1014 .with_contents(
1015 #[allow(deprecated)]
1016 ProfileFileKind::Config,
1017 "[profile custom]\nendpoint_url = http://localhost:8989",
1018 )
1019 .build(),
1020 )
1021 .load()
1022 .await;
1023 assert_eq!(
1024 Origin::shared_profile_file(),
1025 loader.get_origin("endpoint_url")
1026 );
1027 }
1028
1029 #[tokio::test]
1030 async fn load_fips() {
1031 let conf = base_conf().use_fips(true).load().await;
1032 assert_eq!(Some(true), conf.use_fips());
1033 }
1034
1035 #[tokio::test]
1036 async fn load_dual_stack() {
1037 let conf = base_conf().use_dual_stack(false).load().await;
1038 assert_eq!(Some(false), conf.use_dual_stack());
1039
1040 let conf = base_conf().load().await;
1041 assert_eq!(None, conf.use_dual_stack());
1042 }
1043
1044 #[tokio::test]
1045 async fn app_name() {
1046 let app_name = AppName::new("my-app-name").unwrap();
1047 let conf = base_conf().app_name(app_name.clone()).load().await;
1048 assert_eq!(Some(&app_name), conf.app_name());
1049 }
1050
1051 #[cfg(feature = "rustls")]
1052 #[tokio::test]
1053 async fn disable_default_credentials() {
1054 let config = defaults(BehaviorVersion::latest())
1055 .no_credentials()
1056 .load()
1057 .await;
1058 assert!(config.identity_cache().is_none());
1059 assert!(config.credentials_provider().is_none());
1060 }
1061
1062 #[tokio::test]
1063 async fn connector_is_shared() {
1064 let num_requests = Arc::new(AtomicUsize::new(0));
1065 let movable = num_requests.clone();
1066 let http_client = infallible_client_fn(move |_req| {
1067 movable.fetch_add(1, Ordering::Relaxed);
1068 http::Response::new("ok!")
1069 });
1070 let config = defaults(BehaviorVersion::latest())
1071 .fs(Fs::from_slice(&[]))
1072 .env(Env::from_slice(&[]))
1073 .http_client(http_client.clone())
1074 .load()
1075 .await;
1076 config
1077 .credentials_provider()
1078 .unwrap()
1079 .provide_credentials()
1080 .await
1081 .expect_err("did not expect credentials to be loaded—no traffic is allowed");
1082 let num_requests = num_requests.load(Ordering::Relaxed);
1083 assert!(num_requests > 0, "{}", num_requests);
1084 }
1085
1086 #[tokio::test]
1087 async fn endpoint_urls_may_be_ignored_from_env() {
1088 let fs = Fs::from_slice(&[(
1089 "test_config",
1090 "[profile custom]\nendpoint_url = http://profile",
1091 )]);
1092 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1093
1094 let conf = base_conf().use_dual_stack(false).load().await;
1095 assert_eq!(Some(false), conf.use_dual_stack());
1096
1097 let conf = base_conf().load().await;
1098 assert_eq!(None, conf.use_dual_stack());
1099
1100 // Check that we get nothing back because the env said we should ignore endpoints
1101 let config = base_conf()
1102 .fs(fs.clone())
1103 .env(env)
1104 .profile_name("custom")
1105 .profile_files(
1106 #[allow(deprecated)]
1107 ProfileFiles::builder()
1108 .with_file(
1109 #[allow(deprecated)]
1110 ProfileFileKind::Config,
1111 "test_config",
1112 )
1113 .build(),
1114 )
1115 .load()
1116 .await;
1117 assert_eq!(None, config.endpoint_url());
1118
1119 // Check that without the env, we DO get something back
1120 let config = base_conf()
1121 .fs(fs)
1122 .profile_name("custom")
1123 .profile_files(
1124 #[allow(deprecated)]
1125 ProfileFiles::builder()
1126 .with_file(
1127 #[allow(deprecated)]
1128 ProfileFileKind::Config,
1129 "test_config",
1130 )
1131 .build(),
1132 )
1133 .load()
1134 .await;
1135 assert_eq!(Some("http://profile"), config.endpoint_url());
1136 }
1137
1138 #[tokio::test]
1139 async fn endpoint_urls_may_be_ignored_from_profile() {
1140 let fs = Fs::from_slice(&[(
1141 "test_config",
1142 "[profile custom]\nignore_configured_endpoint_urls = true",
1143 )]);
1144 let env = Env::from_slice(&[("AWS_ENDPOINT_URL", "http://environment")]);
1145
1146 // Check that we get nothing back because the profile said we should ignore endpoints
1147 let config = base_conf()
1148 .fs(fs)
1149 .env(env.clone())
1150 .profile_name("custom")
1151 .profile_files(
1152 #[allow(deprecated)]
1153 ProfileFiles::builder()
1154 .with_file(
1155 #[allow(deprecated)]
1156 ProfileFileKind::Config,
1157 "test_config",
1158 )
1159 .build(),
1160 )
1161 .load()
1162 .await;
1163 assert_eq!(None, config.endpoint_url());
1164
1165 // Check that without the profile, we DO get something back
1166 let config = base_conf().env(env).load().await;
1167 assert_eq!(Some("http://environment"), config.endpoint_url());
1168 }
1169
1170 #[tokio::test]
1171 async fn programmatic_endpoint_urls_may_not_be_ignored() {
1172 let fs = Fs::from_slice(&[(
1173 "test_config",
1174 "[profile custom]\nignore_configured_endpoint_urls = true",
1175 )]);
1176 let env = Env::from_slice(&[("AWS_IGNORE_CONFIGURED_ENDPOINT_URLS", "true")]);
1177
1178 // Check that we get something back because we explicitly set the loader's endpoint URL
1179 let config = base_conf()
1180 .fs(fs)
1181 .env(env)
1182 .endpoint_url("http://localhost")
1183 .profile_name("custom")
1184 .profile_files(
1185 #[allow(deprecated)]
1186 ProfileFiles::builder()
1187 .with_file(
1188 #[allow(deprecated)]
1189 ProfileFileKind::Config,
1190 "test_config",
1191 )
1192 .build(),
1193 )
1194 .load()
1195 .await;
1196 assert_eq!(Some("http://localhost"), config.endpoint_url());
1197 }
1198 }
1199}