aws_config/profile/credentials/
exec.rs
1use super::repr::{self, BaseProvider};
7#[cfg(feature = "credentials-process")]
8use crate::credential_process::CredentialProcessProvider;
9use crate::profile::credentials::ProfileFileError;
10use crate::provider_config::ProviderConfig;
11use crate::sts;
12use crate::web_identity_token::{StaticConfiguration, WebIdentityTokenCredentialsProvider};
13use aws_credential_types::provider::{
14 self, error::CredentialsError, ProvideCredentials, SharedCredentialsProvider,
15};
16use aws_sdk_sts::config::Credentials;
17use aws_sdk_sts::Client as StsClient;
18use aws_smithy_async::time::SharedTimeSource;
19use aws_types::SdkConfig;
20use std::fmt::Debug;
21use std::sync::Arc;
22
23#[derive(Debug)]
24pub(super) struct AssumeRoleProvider {
25 role_arn: String,
26 external_id: Option<String>,
27 session_name: Option<String>,
28 time_source: SharedTimeSource,
29}
30
31impl AssumeRoleProvider {
32 pub(super) async fn credentials(
33 &self,
34 input_credentials: Credentials,
35 sdk_config: &SdkConfig,
36 ) -> provider::Result {
37 let config = sdk_config
38 .to_builder()
39 .credentials_provider(SharedCredentialsProvider::new(input_credentials))
40 .build();
41 let client = StsClient::new(&config);
42 let session_name = &self.session_name.as_ref().cloned().unwrap_or_else(|| {
43 sts::util::default_session_name("assume-role-from-profile", self.time_source.now())
44 });
45 let assume_role_creds = client
46 .assume_role()
47 .role_arn(&self.role_arn)
48 .set_external_id(self.external_id.clone())
49 .role_session_name(session_name)
50 .send()
51 .await
52 .map_err(CredentialsError::provider_error)?
53 .credentials;
54 sts::util::into_credentials(assume_role_creds, "AssumeRoleProvider")
55 }
56}
57
58#[derive(Debug)]
59pub(super) struct ProviderChain {
60 base: Arc<dyn ProvideCredentials>,
61 chain: Vec<AssumeRoleProvider>,
62}
63
64impl ProviderChain {
65 pub(crate) fn base(&self) -> &dyn ProvideCredentials {
66 self.base.as_ref()
67 }
68
69 pub(crate) fn chain(&self) -> &[AssumeRoleProvider] {
70 self.chain.as_slice()
71 }
72}
73
74impl ProviderChain {
75 pub(super) fn from_repr(
76 provider_config: &ProviderConfig,
77 repr: repr::ProfileChain<'_>,
78 factory: &named::NamedProviderFactory,
79 ) -> Result<Self, ProfileFileError> {
80 let base = match repr.base() {
81 BaseProvider::NamedSource(name) => {
82 factory
83 .provider(name)
84 .ok_or(ProfileFileError::UnknownProvider {
85 name: name.to_string(),
86 })?
87 }
88 BaseProvider::AccessKey(key) => Arc::new(key.clone()),
89 BaseProvider::CredentialProcess(_credential_process) => {
90 #[cfg(feature = "credentials-process")]
91 {
92 Arc::new(CredentialProcessProvider::from_command(_credential_process))
93 }
94 #[cfg(not(feature = "credentials-process"))]
95 {
96 Err(ProfileFileError::FeatureNotEnabled {
97 feature: "credentials-process".into(),
98 message: Some(
99 "In order to spawn a subprocess, the `credentials-process` feature must be enabled."
100 .into(),
101 ),
102 })?
103 }
104 }
105 BaseProvider::WebIdentityTokenRole {
106 role_arn,
107 web_identity_token_file,
108 session_name,
109 } => {
110 let provider = WebIdentityTokenCredentialsProvider::builder()
111 .static_configuration(StaticConfiguration {
112 web_identity_token_file: web_identity_token_file.into(),
113 role_arn: role_arn.to_string(),
114 session_name: session_name.map(|sess| sess.to_string()).unwrap_or_else(
115 || {
116 sts::util::default_session_name(
117 "web-identity-token-profile",
118 provider_config.time_source().now(),
119 )
120 },
121 ),
122 })
123 .configure(provider_config)
124 .build();
125 Arc::new(provider)
126 }
127 #[allow(unused_variables)]
128 BaseProvider::Sso {
129 sso_account_id,
130 sso_region,
131 sso_role_name,
132 sso_start_url,
133 sso_session_name,
134 } => {
135 #[cfg(feature = "sso")]
136 {
137 use crate::sso::{credentials::SsoProviderConfig, SsoCredentialsProvider};
138 use aws_types::region::Region;
139
140 let (Some(sso_account_id), Some(sso_role_name)) =
141 (sso_account_id, sso_role_name)
142 else {
143 return Err(ProfileFileError::TokenProviderConfig {});
144 };
145 let sso_config = SsoProviderConfig {
146 account_id: sso_account_id.to_string(),
147 role_name: sso_role_name.to_string(),
148 start_url: sso_start_url.to_string(),
149 region: Region::new(sso_region.to_string()),
150 session_name: sso_session_name.map(|s| s.to_string()),
151 };
152 Arc::new(SsoCredentialsProvider::new(provider_config, sso_config))
153 }
154 #[cfg(not(feature = "sso"))]
155 {
156 Err(ProfileFileError::FeatureNotEnabled {
157 feature: "sso".into(),
158 message: None,
159 })?
160 }
161 }
162 };
163 tracing::debug!(base = ?repr.base(), "first credentials will be loaded from {:?}", repr.base());
164 let chain = repr
165 .chain()
166 .iter()
167 .map(|role_arn| {
168 tracing::debug!(role_arn = ?role_arn, "which will be used to assume a role");
169 AssumeRoleProvider {
170 role_arn: role_arn.role_arn.into(),
171 external_id: role_arn.external_id.map(Into::into),
172 session_name: role_arn.session_name.map(Into::into),
173 time_source: provider_config.time_source(),
174 }
175 })
176 .collect();
177 Ok(ProviderChain { base, chain })
178 }
179}
180
181pub(super) mod named {
182 use std::collections::HashMap;
183 use std::sync::Arc;
184
185 use aws_credential_types::provider::ProvideCredentials;
186 use std::borrow::Cow;
187
188 #[derive(Debug)]
189 pub(crate) struct NamedProviderFactory {
190 providers: HashMap<Cow<'static, str>, Arc<dyn ProvideCredentials>>,
191 }
192
193 fn lower_cow(mut input: Cow<'_, str>) -> Cow<'_, str> {
194 if !input.chars().all(|c| c.is_ascii_lowercase()) {
195 input.to_mut().make_ascii_lowercase();
196 }
197 input
198 }
199
200 impl NamedProviderFactory {
201 pub(crate) fn new(
202 providers: HashMap<Cow<'static, str>, Arc<dyn ProvideCredentials>>,
203 ) -> Self {
204 let providers = providers
205 .into_iter()
206 .map(|(k, v)| (lower_cow(k), v))
207 .collect();
208 Self { providers }
209 }
210
211 pub(crate) fn provider(&self, name: &str) -> Option<Arc<dyn ProvideCredentials>> {
212 self.providers.get(&lower_cow(Cow::Borrowed(name))).cloned()
213 }
214 }
215}
216
217#[cfg(test)]
218mod test {
219 use crate::profile::credentials::exec::named::NamedProviderFactory;
220 use crate::profile::credentials::exec::ProviderChain;
221 use crate::profile::credentials::repr::{BaseProvider, ProfileChain};
222 use crate::provider_config::ProviderConfig;
223 use crate::test_case::no_traffic_client;
224
225 use aws_credential_types::Credentials;
226 use std::collections::HashMap;
227 use std::sync::Arc;
228
229 #[test]
230 fn providers_case_insensitive() {
231 let mut base = HashMap::new();
232 base.insert(
233 "Environment".into(),
234 Arc::new(Credentials::for_tests()) as _,
235 );
236 let provider = NamedProviderFactory::new(base);
237 assert!(provider.provider("environment").is_some());
238 assert!(provider.provider("envIROnment").is_some());
239 assert!(provider.provider(" envIROnment").is_none());
240 assert!(provider.provider("Environment").is_some());
241 }
242
243 #[test]
244 fn error_on_unknown_provider() {
245 let factory = NamedProviderFactory::new(HashMap::new());
246 let chain = ProviderChain::from_repr(
247 &ProviderConfig::empty().with_http_client(no_traffic_client()),
248 ProfileChain {
249 base: BaseProvider::NamedSource("floozle"),
250 chain: vec![],
251 },
252 &factory,
253 );
254 let err = chain.expect_err("no source by that name");
255 assert!(
256 format!("{}", err).contains(
257 "profile referenced `floozle` provider but that provider is not supported"
258 ),
259 "`{}` did not match expected error",
260 err
261 );
262 }
263}