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