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!("provide_credentials", provider = %"default_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_blank);
282 make_test!(imds_token_fail);
283
284 make_test!(imds_no_iam_role);
285 make_test!(imds_default_chain_error);
286 make_test!(imds_default_chain_success, builder: |config| {
287 config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
288 });
289 make_test!(imds_assume_role);
290 make_test!(imds_config_with_no_creds, builder: |config| {
291 config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
292 });
293 make_test!(imds_disabled);
294 make_test!(imds_default_chain_retries, builder: |config| {
295 config.with_time_source(StaticTimeSource::new(UNIX_EPOCH))
296 });
297 make_test!(ecs_assume_role);
298 make_test!(ecs_credentials);
299 make_test!(ecs_credentials_invalid_profile);
300
301 make_test!(eks_pod_identity_credentials);
302 #[cfg(not(windows))]
304 make_test!(eks_pod_identity_no_token_file);
305
306 #[cfg(not(feature = "sso"))]
307 make_test!(sso_assume_role #[should_panic(expected = "This behavior requires following cargo feature(s) enabled: sso")]);
308
309 #[cfg(feature = "sso")]
310 make_test!(sso_assume_role);
311
312 #[cfg(not(any(feature = "sso", windows)))]
314 make_test!(sso_no_token_file #[should_panic(expected = "This behavior requires following cargo feature(s) enabled: sso")]);
315 #[cfg(all(feature = "sso", not(windows)))]
317 make_test!(sso_no_token_file);
318
319 #[cfg(feature = "sso")]
320 make_test!(e2e_fips_and_dual_stack_sso);
321
322 #[tokio::test]
323 async fn profile_name_override() {
324 let provider_config = TestEnvironment::<crate::test_case::Credentials, ()>::from_dir(
329 "./test-data/default-credential-provider-chain/profile_static_keys",
330 StaticTestProvider::new(|_| unreachable!()),
331 )
332 .await
333 .unwrap()
334 .provider_config()
335 .clone();
336
337 let creds = DefaultCredentialsChain::builder()
338 .profile_name("secondary")
339 .configure(provider_config)
340 .build()
341 .await
342 .provide_credentials()
343 .await
344 .expect("creds should load");
345
346 assert_eq!(creds.access_key_id(), "correct_key_secondary");
347 }
348
349 #[tokio::test]
350 async fn no_providers_configured_err() {
351 use crate::provider_config::ProviderConfig;
352 use aws_credential_types::provider::error::CredentialsError;
353 use aws_smithy_async::rt::sleep::TokioSleep;
354 use aws_smithy_http_client::test_util::NeverTcpConnector;
355
356 tokio::time::pause();
357 let conf = ProviderConfig::no_configuration()
358 .with_http_client(NeverTcpConnector::new().into_client())
359 .with_time_source(StaticTimeSource::new(UNIX_EPOCH))
360 .with_sleep_impl(TokioSleep::new());
361 let provider = DefaultCredentialsChain::builder()
362 .configure(conf)
363 .build()
364 .await;
365 let creds = provider
366 .provide_credentials()
367 .await
368 .expect_err("no providers enabled");
369 assert!(
370 matches!(creds, CredentialsError::CredentialsNotLoaded { .. }),
371 "should be NotLoaded: {:?}",
372 creds
373 )
374 }
375}