aws_config/default_provider/
credentials.rs1use std::borrow::Cow;
7
8use aws_credential_types::provider::{self, future, ProvideCredentials};
9use aws_credential_types::Credentials;
10use tracing::Instrument;
11
12use crate::environment::credentials::EnvironmentVariableCredentialsProvider;
13use crate::meta::credentials::CredentialsProviderChain;
14use crate::meta::region::ProvideRegion;
15use crate::provider_config::ProviderConfig;
16
17#[cfg(any(feature = "default-https-client", feature = "rustls"))]
18pub async fn default_provider() -> impl ProvideCredentials {
22    DefaultCredentialsChain::builder().build().await
23}
24
25#[derive(Debug)]
62pub struct DefaultCredentialsChain {
63    provider_chain: CredentialsProviderChain,
64}
65
66impl DefaultCredentialsChain {
67    pub fn builder() -> Builder {
69        Builder::default()
70    }
71
72    async fn credentials(&self) -> provider::Result {
73        self.provider_chain
74            .provide_credentials()
75            .instrument(tracing::debug_span!("default_credentials_chain"))
76            .await
77    }
78}
79
80impl ProvideCredentials for DefaultCredentialsChain {
81    fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a>
82    where
83        Self: 'a,
84    {
85        future::ProvideCredentials::new(self.credentials())
86    }
87
88    fn fallback_on_interrupt(&self) -> Option<Credentials> {
89        self.provider_chain.fallback_on_interrupt()
90    }
91}
92
93#[derive(Debug, Default)]
95pub struct Builder {
96    profile_file_builder: crate::profile::credentials::Builder,
97    web_identity_builder: crate::web_identity_token::Builder,
98    imds_builder: crate::imds::credentials::Builder,
99    ecs_builder: crate::ecs::Builder,
100    region_override: Option<Box<dyn ProvideRegion>>,
101    region_chain: crate::default_provider::region::Builder,
102    conf: Option<ProviderConfig>,
103}
104
105impl Builder {
106    pub fn region(mut self, region: impl ProvideRegion + 'static) -> Self {
110        self.set_region(Some(region));
111        self
112    }
113
114    pub fn set_region(&mut self, region: Option<impl ProvideRegion + 'static>) -> &mut Self {
118        self.region_override = region.map(|provider| Box::new(provider) as _);
119        self
120    }
121
122    pub fn with_custom_credential_source(
136        mut self,
137        name: impl Into<Cow<'static, str>>,
138        provider: impl ProvideCredentials + 'static,
139    ) -> Self {
140        self.profile_file_builder = self
141            .profile_file_builder
142            .with_custom_provider(name, provider);
143        self
144    }
145
146    pub fn profile_name(mut self, name: &str) -> Self {
150        self.profile_file_builder = self.profile_file_builder.profile_name(name);
151        self.region_chain = self.region_chain.profile_name(name);
152        self
153    }
154
155    pub fn imds_client(mut self, client: crate::imds::Client) -> Self {
159        self.imds_builder = self.imds_builder.imds_client(client);
160        self
161    }
162
163    pub fn configure(mut self, config: ProviderConfig) -> Self {
165        self.region_chain = self.region_chain.configure(&config);
166        self.conf = Some(config);
167        self
168    }
169
170    pub async fn build(self) -> DefaultCredentialsChain {
176        let region = match self.region_override {
177            Some(provider) => provider.region().await,
178            None => self.region_chain.build().region().await,
179        };
180
181        let conf = self.conf.unwrap_or_default().with_region(region);
182
183        let env_provider = EnvironmentVariableCredentialsProvider::new_with_env(conf.env());
184        let profile_provider = self.profile_file_builder.configure(&conf).build();
185        let web_identity_token_provider = self.web_identity_builder.configure(&conf).build();
186        let imds_provider = self.imds_builder.configure(&conf).build();
187        let ecs_provider = self.ecs_builder.configure(&conf).build();
188
189        let provider_chain = CredentialsProviderChain::first_try("Environment", env_provider)
190            .or_else("Profile", profile_provider)
191            .or_else("WebIdentityToken", web_identity_token_provider)
192            .or_else("EcsContainer", ecs_provider)
193            .or_else("Ec2InstanceMetadata", imds_provider);
194
195        DefaultCredentialsChain { provider_chain }
196    }
197}
198
199#[cfg(test)]
200mod test {
201    use crate::default_provider::credentials::DefaultCredentialsChain;
202    use crate::test_case::{StaticTestProvider, TestEnvironment};
203    use aws_credential_types::provider::ProvideCredentials;
204    use aws_smithy_async::time::StaticTimeSource;
205    use std::time::UNIX_EPOCH;
206
207    macro_rules! make_test {
227        ($name:ident $(#[$m:meta])*) => {
228            make_test!($name, execute, $(#[$m])*);
229        };
230        (update: $name:ident) => {
231            make_test!($name, execute_and_update);
232        };
233        (live: $name:ident) => {
234            make_test!($name, execute_from_live_traffic);
235        };
236        ($name:ident, $func:ident, $(#[$m:meta])*) => {
237            make_test!($name, $func, std::convert::identity $(, #[$m])*);
238        };
239        ($name:ident, builder: $provider_config_builder:expr) => {
240            make_test!($name, execute, $provider_config_builder);
241        };
242        ($name:ident, $func:ident, $provider_config_builder:expr $(, #[$m:meta])*) => {
243            $(#[$m])*
244            #[tokio::test]
245            async fn $name() {
246                let _ = crate::test_case::TestEnvironment::from_dir(
247                    concat!(
248                        "./test-data/default-credential-provider-chain/",
249                        stringify!($name)
250                    ),
251                    crate::test_case::test_credentials_provider(|config| {
252                        async move {
253                            crate::default_provider::credentials::Builder::default()
254                                .configure(config)
255                                .build()
256                                .await
257                                .provide_credentials()
258                                .await
259                        }
260                    }),
261                )
262                .await
263                .unwrap()
264                .map_provider_config($provider_config_builder)
265                .$func()
266                .await;
267            }
268        };
269    }
270
271    make_test!(prefer_environment);
272    make_test!(profile_static_keys);
273    make_test!(profile_static_keys_case_insensitive);
274    make_test!(web_identity_token_env);
275    make_test!(web_identity_source_profile_no_env);
276    make_test!(web_identity_token_invalid_jwt);
277    make_test!(web_identity_token_source_profile);
278    make_test!(web_identity_token_profile);
279    make_test!(profile_name);
280    make_test!(profile_overrides_web_identity);
281    make_test!(environment_variables);
282    make_test!(environment_variables_blank);
283    make_test!(imds_token_fail);
284
285    make_test!(imds_no_iam_role);
286    make_test!(imds_default_chain_error);
287    make_test!(imds_default_chain_success, builder: |config| {
288        config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
289    });
290    make_test!(imds_assume_role);
291    make_test!(imds_config_with_no_creds, builder: |config| {
292        config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
293    });
294    make_test!(imds_disabled);
295    make_test!(imds_default_chain_retries, builder: |config| {
296        config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
297    });
298    make_test!(ecs_assume_role);
299    make_test!(ecs_credentials);
300    make_test!(ecs_credentials_invalid_profile);
301
302    make_test!(eks_pod_identity_credentials);
303    #[cfg(not(windows))]
305    make_test!(eks_pod_identity_no_token_file);
306
307    #[cfg(not(feature = "sso"))]
308    make_test!(sso_assume_role #[should_panic(expected = "This behavior requires following cargo feature(s) enabled: sso")]);
309
310    #[cfg(feature = "sso")]
311    make_test!(sso_assume_role);
312
313    #[cfg(not(any(feature = "sso", windows)))]
315    make_test!(sso_no_token_file #[should_panic(expected = "This behavior requires following cargo feature(s) enabled: sso")]);
316    #[cfg(all(feature = "sso", not(windows)))]
318    make_test!(sso_no_token_file);
319
320    #[cfg(feature = "sso")]
321    make_test!(sso_server_error, builder: |config| {
322        config.with_retry_config(crate::retry::RetryConfig::disabled())
324    });
325
326    #[cfg(feature = "sso")]
327    make_test!(e2e_fips_and_dual_stack_sso);
328
329    #[tokio::test]
330    async fn profile_name_override() {
331        let provider_config = TestEnvironment::<crate::test_case::Credentials, ()>::from_dir(
336            "./test-data/default-credential-provider-chain/profile_static_keys",
337            StaticTestProvider::new(|_| unreachable!()),
338        )
339        .await
340        .unwrap()
341        .provider_config()
342        .clone();
343
344        let creds = DefaultCredentialsChain::builder()
345            .profile_name("secondary")
346            .configure(provider_config)
347            .build()
348            .await
349            .provide_credentials()
350            .await
351            .expect("creds should load");
352
353        assert_eq!(creds.access_key_id(), "correct_key_secondary");
354    }
355
356    #[tokio::test]
357    async fn no_providers_configured_err() {
358        use crate::provider_config::ProviderConfig;
359        use aws_credential_types::provider::error::CredentialsError;
360        use aws_smithy_async::rt::sleep::TokioSleep;
361        use aws_smithy_http_client::test_util::NeverTcpConnector;
362
363        tokio::time::pause();
364        let conf = ProviderConfig::no_configuration()
365            .with_http_client(NeverTcpConnector::new().into_client())
366            .with_time_source(StaticTimeSource::new(UNIX_EPOCH))
367            .with_sleep_impl(TokioSleep::new());
368        let provider = DefaultCredentialsChain::builder()
369            .configure(conf)
370            .build()
371            .await;
372        let creds = provider
373            .provide_credentials()
374            .await
375            .expect_err("no providers enabled");
376        assert!(
377            matches!(creds, CredentialsError::CredentialsNotLoaded { .. }),
378            "should be NotLoaded: {:?}",
379            creds
380        )
381    }
382}