Skip to main content

aws_config/
provider_config.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Configuration Options for Credential Providers
7
8use crate::env_service_config::EnvServiceConfig;
9use crate::profile;
10#[allow(deprecated)]
11use crate::profile::profile_file::ProfileFiles;
12use crate::profile::{ProfileFileLoadError, ProfileSet};
13use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
14use aws_smithy_async::time::{SharedTimeSource, TimeSource};
15use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
16use aws_smithy_runtime_api::client::http::HttpClient;
17use aws_smithy_runtime_api::shared::IntoShared;
18use aws_smithy_types::error::display::DisplayErrorContext;
19use aws_smithy_types::retry::RetryConfig;
20use aws_types::os_shim_internal::{Env, Fs};
21use aws_types::region::Region;
22use aws_types::sdk_config::SharedHttpClient;
23use aws_types::SdkConfig;
24use std::borrow::Cow;
25use std::fmt::{Debug, Formatter};
26use std::sync::Arc;
27use tokio::sync::OnceCell;
28
29/// Configuration options for Credential Providers
30///
31/// Most credential providers builders offer a `configure` method which applies general provider configuration
32/// options.
33///
34/// To use a region from the default region provider chain use [`ProviderConfig::with_default_region`].
35/// Otherwise, use [`ProviderConfig::without_region`]. Note that some credentials providers require a region
36/// to be explicitly set.
37#[derive(Clone)]
38pub struct ProviderConfig {
39    env: Env,
40    fs: Fs,
41    time_source: SharedTimeSource,
42    http_client: Option<SharedHttpClient>,
43    retry_config: Option<RetryConfig>,
44    sleep_impl: Option<SharedAsyncSleep>,
45    region: Option<Region>,
46    use_fips: Option<bool>,
47    use_dual_stack: Option<bool>,
48    behavior_version: Option<BehaviorVersion>,
49    /// An AWS profile created from `ProfileFiles` and a `profile_name`
50    parsed_profile: Arc<OnceCell<Result<ProfileSet, ProfileFileLoadError>>>,
51    /// A list of [std::path::Path]s to profile files
52    #[allow(deprecated)]
53    profile_files: ProfileFiles,
54    /// An override to use when constructing a `ProfileSet`
55    profile_name_override: Option<Cow<'static, str>>,
56}
57
58impl Debug for ProviderConfig {
59    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
60        f.debug_struct("ProviderConfig")
61            .field("env", &self.env)
62            .field("fs", &self.fs)
63            .field("time_source", &self.time_source)
64            .field("http_client", &self.http_client)
65            .field("retry_config", &self.retry_config)
66            .field("sleep_impl", &self.sleep_impl)
67            .field("region", &self.region)
68            .field("use_fips", &self.use_fips)
69            .field("use_dual_stack", &self.use_dual_stack)
70            .field("profile_name_override", &self.profile_name_override)
71            .finish()
72    }
73}
74
75impl Default for ProviderConfig {
76    fn default() -> Self {
77        Self {
78            env: Env::default(),
79            fs: Fs::default(),
80            time_source: SharedTimeSource::default(),
81            http_client: None,
82            retry_config: None,
83            sleep_impl: default_async_sleep(),
84            region: None,
85            use_fips: None,
86            use_dual_stack: None,
87            behavior_version: None,
88            parsed_profile: Default::default(),
89            #[allow(deprecated)]
90            profile_files: ProfileFiles::default(),
91            profile_name_override: None,
92        }
93    }
94}
95
96#[cfg(test)]
97impl ProviderConfig {
98    /// ProviderConfig with all configuration removed
99    ///
100    /// Unlike [`ProviderConfig::empty`] where `env` and `fs` will use their non-mocked implementations,
101    /// this method will use an empty mock environment and an empty mock file system.
102    pub fn no_configuration() -> Self {
103        use aws_smithy_async::time::StaticTimeSource;
104        use std::collections::HashMap;
105        use std::time::UNIX_EPOCH;
106        let fs = Fs::from_raw_map(HashMap::new());
107        let env = Env::from_slice(&[]);
108        Self {
109            parsed_profile: Default::default(),
110            #[allow(deprecated)]
111            profile_files: ProfileFiles::default(),
112            env,
113            fs,
114            time_source: SharedTimeSource::new(StaticTimeSource::new(UNIX_EPOCH)),
115            http_client: None,
116            retry_config: None,
117            sleep_impl: None,
118            region: None,
119            use_fips: None,
120            use_dual_stack: None,
121            behavior_version: None,
122            profile_name_override: None,
123        }
124    }
125}
126
127impl ProviderConfig {
128    /// Create a default provider config with the region unset.
129    ///
130    /// Using this option means that you may need to set a region manually.
131    ///
132    /// This constructor will use a default value for the HTTPS connector and Sleep implementation
133    /// when they are enabled as crate features which is usually the correct option. To construct
134    /// a `ProviderConfig` without these fields set, use [`ProviderConfig::empty`].
135    ///
136    ///
137    /// # Examples
138    /// ```no_run
139    /// # #[cfg(feature = "default-https-client")]
140    /// # fn example() {
141    /// use aws_config::provider_config::ProviderConfig;
142    /// use aws_sdk_sts::config::Region;
143    /// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
144    /// let conf = ProviderConfig::without_region().with_region(Some(Region::new("us-east-1")));
145    ///
146    /// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
147    /// # }
148    /// ```
149    pub fn without_region() -> Self {
150        Self::default()
151    }
152
153    /// Constructs a ProviderConfig with no fields set
154    pub fn empty() -> Self {
155        ProviderConfig {
156            env: Env::default(),
157            fs: Fs::default(),
158            time_source: SharedTimeSource::default(),
159            http_client: None,
160            retry_config: None,
161            sleep_impl: None,
162            region: None,
163            use_fips: None,
164            use_dual_stack: None,
165            behavior_version: None,
166            parsed_profile: Default::default(),
167            #[allow(deprecated)]
168            profile_files: ProfileFiles::default(),
169            profile_name_override: None,
170        }
171    }
172
173    /// Initializer for ConfigBag to avoid possibly setting incorrect defaults.
174    pub(crate) fn init(
175        time_source: SharedTimeSource,
176        sleep_impl: Option<SharedAsyncSleep>,
177    ) -> Self {
178        Self {
179            parsed_profile: Default::default(),
180            #[allow(deprecated)]
181            profile_files: ProfileFiles::default(),
182            env: Env::default(),
183            fs: Fs::default(),
184            time_source,
185            http_client: None,
186            retry_config: None,
187            sleep_impl,
188            region: None,
189            use_fips: None,
190            use_dual_stack: None,
191            behavior_version: None,
192            profile_name_override: None,
193        }
194    }
195
196    /// Create a default provider config with the region region automatically loaded from the default chain.
197    ///
198    /// # Examples
199    /// ```no_run
200    /// # async fn test() {
201    /// use aws_config::provider_config::ProviderConfig;
202    /// use aws_sdk_sts::config::Region;
203    /// use aws_config::web_identity_token::WebIdentityTokenCredentialsProvider;
204    /// let conf = ProviderConfig::with_default_region().await;
205    /// let credential_provider = WebIdentityTokenCredentialsProvider::builder().configure(&conf).build();
206    /// }
207    /// ```
208    pub async fn with_default_region() -> Self {
209        Self::without_region().load_default_region().await
210    }
211
212    /// Attempt to get a representation of `SdkConfig` from this `ProviderConfig`.
213    ///
214    ///
215    /// **WARN**: Some options (e.g. `service_config`) can only be set if the profile has been
216    /// parsed already (e.g. by calling [`ProviderConfig::profile()`]). This is an
217    /// imperfect mapping and should be used sparingly.
218    pub(crate) fn client_config(&self) -> SdkConfig {
219        let profiles = self.parsed_profile.get().and_then(|v| v.as_ref().ok());
220        let service_config = EnvServiceConfig {
221            env: self.env(),
222            env_config_sections: profiles.cloned().unwrap_or_default(),
223            ignore_configured_endpoint_urls: false,
224        };
225
226        let mut builder = SdkConfig::builder()
227            .retry_config(
228                self.retry_config
229                    .as_ref()
230                    .map_or(RetryConfig::standard(), |config| config.clone()),
231            )
232            .region(self.region())
233            .time_source(self.time_source())
234            .use_fips(self.use_fips().unwrap_or_default())
235            .use_dual_stack(self.use_dual_stack().unwrap_or_default())
236            .service_config(service_config)
237            .behavior_version(crate::BehaviorVersion::latest());
238        builder.set_http_client(self.http_client.clone());
239        builder.set_sleep_impl(self.sleep_impl.clone());
240        builder.build()
241    }
242
243    // When all crate features are disabled, these accessors are unused
244
245    #[allow(dead_code)]
246    pub(crate) fn env(&self) -> Env {
247        self.env.clone()
248    }
249
250    #[allow(dead_code)]
251    pub(crate) fn fs(&self) -> Fs {
252        self.fs.clone()
253    }
254
255    #[allow(dead_code)]
256    pub(crate) fn time_source(&self) -> SharedTimeSource {
257        self.time_source.clone()
258    }
259
260    #[allow(dead_code)]
261    pub(crate) fn http_client(&self) -> Option<SharedHttpClient> {
262        self.http_client.clone()
263    }
264
265    #[allow(dead_code)]
266    pub(crate) fn retry_config(&self) -> Option<RetryConfig> {
267        self.retry_config.clone()
268    }
269
270    #[allow(dead_code)]
271    pub(crate) fn sleep_impl(&self) -> Option<SharedAsyncSleep> {
272        self.sleep_impl.clone()
273    }
274
275    #[allow(dead_code)]
276    pub(crate) fn region(&self) -> Option<Region> {
277        self.region.clone()
278    }
279
280    #[allow(dead_code)]
281    pub(crate) fn use_fips(&self) -> Option<bool> {
282        self.use_fips
283    }
284
285    #[allow(dead_code)]
286    pub(crate) fn use_dual_stack(&self) -> Option<bool> {
287        self.use_dual_stack
288    }
289
290    pub(crate) async fn try_profile(&self) -> Result<&ProfileSet, &ProfileFileLoadError> {
291        let parsed_profile = self
292            .parsed_profile
293            .get_or_init(|| async {
294                let profile = profile::load(
295                    &self.fs,
296                    &self.env,
297                    &self.profile_files,
298                    self.profile_name_override.clone(),
299                )
300                .await;
301                if let Err(err) = profile.as_ref() {
302                    tracing::warn!(err = %DisplayErrorContext(&err), "failed to parse profile")
303                }
304                profile
305            })
306            .await;
307        parsed_profile.as_ref()
308    }
309
310    pub(crate) async fn profile(&self) -> Option<&ProfileSet> {
311        self.try_profile().await.ok()
312    }
313
314    /// Override the region for the configuration
315    pub fn with_region(mut self, region: Option<Region>) -> Self {
316        self.region = region;
317        self
318    }
319
320    /// Override the `use_fips` setting.
321    ///
322    /// When set to `Some(true)`, credential providers configured with this
323    /// `ProviderConfig` (e.g., [`DefaultCredentialsChain`], `AssumeRoleProvider`,
324    /// `WebIdentityTokenCredentialsProvider`) will use FIPS-compliant endpoints.
325    ///
326    /// This is the `ProviderConfig` equivalent of
327    /// [`ConfigLoader::use_fips`](crate::ConfigLoader::use_fips). It is needed
328    /// when constructing a `ProviderConfig` directly (e.g., via
329    /// [`ProviderConfig::empty()`]) rather than going through the `ConfigLoader`.
330    ///
331    /// [`DefaultCredentialsChain`]: crate::default_provider::credentials::DefaultCredentialsChain
332    pub fn with_use_fips(mut self, use_fips: Option<bool>) -> Self {
333        self.use_fips = use_fips;
334        self
335    }
336
337    /// Override the `use_dual_stack` setting.
338    ///
339    /// When set to `Some(true)`, credential providers configured with this
340    /// `ProviderConfig` will use dual-stack endpoints.
341    ///
342    /// This is the `ProviderConfig` equivalent of
343    /// [`ConfigLoader::use_dual_stack`](crate::ConfigLoader::use_dual_stack).
344    pub fn with_use_dual_stack(mut self, use_dual_stack: Option<bool>) -> Self {
345        self.use_dual_stack = use_dual_stack;
346        self
347    }
348
349    pub(crate) fn behavior_version(&self) -> Option<BehaviorVersion> {
350        self.behavior_version
351    }
352
353    /// Sets the behavior version for this provider config.
354    pub fn with_behavior_version(mut self, behavior_version: Option<BehaviorVersion>) -> Self {
355        self.behavior_version = behavior_version;
356        self
357    }
358
359    pub(crate) fn with_profile_name(self, profile_name: String) -> Self {
360        let profile_files = self.profile_files.clone();
361        self.with_profile_config(Some(profile_files), Some(profile_name))
362    }
363
364    /// Override the profile file paths (`~/.aws/config` by default) and name (`default` by default)
365    #[allow(deprecated)]
366    pub(crate) fn with_profile_config(
367        self,
368        profile_files: Option<ProfileFiles>,
369        profile_name_override: Option<String>,
370    ) -> Self {
371        // if there is no override, then don't clear out `parsed_profile`.
372        if profile_files.is_none() && profile_name_override.is_none() {
373            return self;
374        }
375        ProviderConfig {
376            // clear out the profile since we need to reparse it
377            parsed_profile: Default::default(),
378            profile_files: profile_files.unwrap_or(self.profile_files),
379            profile_name_override: profile_name_override
380                .map(Cow::Owned)
381                .or(self.profile_name_override),
382            ..self
383        }
384    }
385
386    /// Use the [default region chain](crate::default_provider::region) to set the
387    /// region for this configuration
388    ///
389    /// Note: the `env` and `fs` already set on this provider will be used when loading the default region.
390    pub async fn load_default_region(self) -> Self {
391        use crate::default_provider::region::DefaultRegionChain;
392        let provider_chain = DefaultRegionChain::builder().configure(&self).build();
393        self.with_region(provider_chain.region().await)
394    }
395
396    pub(crate) fn with_fs(self, fs: Fs) -> Self {
397        ProviderConfig {
398            parsed_profile: Default::default(),
399            fs,
400            ..self
401        }
402    }
403
404    pub(crate) fn with_env(self, env: Env) -> Self {
405        ProviderConfig {
406            parsed_profile: Default::default(),
407            env,
408            ..self
409        }
410    }
411
412    /// Override the time source for this configuration
413    pub fn with_time_source(self, time_source: impl TimeSource + 'static) -> Self {
414        ProviderConfig {
415            time_source: time_source.into_shared(),
416            ..self
417        }
418    }
419
420    /// Override the HTTP client for this configuration
421    pub fn with_http_client(self, http_client: impl HttpClient + 'static) -> Self {
422        ProviderConfig {
423            http_client: Some(http_client.into_shared()),
424            ..self
425        }
426    }
427
428    /// Override the sleep implementation for this configuration
429    pub fn with_sleep_impl(self, sleep_impl: impl AsyncSleep + 'static) -> Self {
430        ProviderConfig {
431            sleep_impl: Some(sleep_impl.into_shared()),
432            ..self
433        }
434    }
435
436    /// Override the retry config for this configuration
437    pub fn with_retry_config(self, retry_config: RetryConfig) -> Self {
438        ProviderConfig {
439            retry_config: Some(retry_config),
440            ..self
441        }
442    }
443}