aws_config/profile/credentials/
exec.rsuse super::repr::{self, BaseProvider};
#[cfg(feature = "credentials-process")]
use crate::credential_process::CredentialProcessProvider;
use crate::profile::credentials::ProfileFileError;
use crate::provider_config::ProviderConfig;
use crate::sts;
use crate::web_identity_token::{StaticConfiguration, WebIdentityTokenCredentialsProvider};
use aws_credential_types::provider::{
self, error::CredentialsError, ProvideCredentials, SharedCredentialsProvider,
};
use aws_sdk_sts::config::Credentials;
use aws_sdk_sts::Client as StsClient;
use aws_smithy_async::time::SharedTimeSource;
use aws_types::SdkConfig;
use std::fmt::Debug;
use std::sync::Arc;
#[derive(Debug)]
pub(super) struct AssumeRoleProvider {
role_arn: String,
external_id: Option<String>,
session_name: Option<String>,
time_source: SharedTimeSource,
}
impl AssumeRoleProvider {
pub(super) async fn credentials(
&self,
input_credentials: Credentials,
sdk_config: &SdkConfig,
) -> provider::Result {
let config = sdk_config
.to_builder()
.credentials_provider(SharedCredentialsProvider::new(input_credentials))
.build();
let client = StsClient::new(&config);
let session_name = &self.session_name.as_ref().cloned().unwrap_or_else(|| {
sts::util::default_session_name("assume-role-from-profile", self.time_source.now())
});
let assume_role_creds = client
.assume_role()
.role_arn(&self.role_arn)
.set_external_id(self.external_id.clone())
.role_session_name(session_name)
.send()
.await
.map_err(CredentialsError::provider_error)?
.credentials;
sts::util::into_credentials(assume_role_creds, "AssumeRoleProvider")
}
}
#[derive(Debug)]
pub(super) struct ProviderChain {
base: Arc<dyn ProvideCredentials>,
chain: Vec<AssumeRoleProvider>,
}
impl ProviderChain {
pub(crate) fn base(&self) -> &dyn ProvideCredentials {
self.base.as_ref()
}
pub(crate) fn chain(&self) -> &[AssumeRoleProvider] {
self.chain.as_slice()
}
}
impl ProviderChain {
pub(super) fn from_repr(
provider_config: &ProviderConfig,
repr: repr::ProfileChain<'_>,
factory: &named::NamedProviderFactory,
) -> Result<Self, ProfileFileError> {
let base = match repr.base() {
BaseProvider::NamedSource(name) => {
factory
.provider(name)
.ok_or(ProfileFileError::UnknownProvider {
name: name.to_string(),
})?
}
BaseProvider::AccessKey(key) => Arc::new(key.clone()),
BaseProvider::CredentialProcess(_credential_process) => {
#[cfg(feature = "credentials-process")]
{
Arc::new(CredentialProcessProvider::from_command(_credential_process))
}
#[cfg(not(feature = "credentials-process"))]
{
Err(ProfileFileError::FeatureNotEnabled {
feature: "credentials-process".into(),
message: Some(
"In order to spawn a subprocess, the `credentials-process` feature must be enabled."
.into(),
),
})?
}
}
BaseProvider::WebIdentityTokenRole {
role_arn,
web_identity_token_file,
session_name,
} => {
let provider = WebIdentityTokenCredentialsProvider::builder()
.static_configuration(StaticConfiguration {
web_identity_token_file: web_identity_token_file.into(),
role_arn: role_arn.to_string(),
session_name: session_name.map(|sess| sess.to_string()).unwrap_or_else(
|| {
sts::util::default_session_name(
"web-identity-token-profile",
provider_config.time_source().now(),
)
},
),
})
.configure(provider_config)
.build();
Arc::new(provider)
}
#[allow(unused_variables)]
BaseProvider::Sso {
sso_account_id,
sso_region,
sso_role_name,
sso_start_url,
sso_session_name,
} => {
#[cfg(feature = "sso")]
{
use crate::sso::{credentials::SsoProviderConfig, SsoCredentialsProvider};
use aws_types::region::Region;
let (Some(sso_account_id), Some(sso_role_name)) =
(sso_account_id, sso_role_name)
else {
return Err(ProfileFileError::TokenProviderConfig {});
};
let sso_config = SsoProviderConfig {
account_id: sso_account_id.to_string(),
role_name: sso_role_name.to_string(),
start_url: sso_start_url.to_string(),
region: Region::new(sso_region.to_string()),
session_name: sso_session_name.map(|s| s.to_string()),
};
Arc::new(SsoCredentialsProvider::new(provider_config, sso_config))
}
#[cfg(not(feature = "sso"))]
{
Err(ProfileFileError::FeatureNotEnabled {
feature: "sso".into(),
message: None,
})?
}
}
};
tracing::debug!(base = ?repr.base(), "first credentials will be loaded from {:?}", repr.base());
let chain = repr
.chain()
.iter()
.map(|role_arn| {
tracing::debug!(role_arn = ?role_arn, "which will be used to assume a role");
AssumeRoleProvider {
role_arn: role_arn.role_arn.into(),
external_id: role_arn.external_id.map(Into::into),
session_name: role_arn.session_name.map(Into::into),
time_source: provider_config.time_source(),
}
})
.collect();
Ok(ProviderChain { base, chain })
}
}
pub(super) mod named {
use std::collections::HashMap;
use std::sync::Arc;
use aws_credential_types::provider::ProvideCredentials;
use std::borrow::Cow;
#[derive(Debug)]
pub(crate) struct NamedProviderFactory {
providers: HashMap<Cow<'static, str>, Arc<dyn ProvideCredentials>>,
}
fn lower_cow(mut input: Cow<'_, str>) -> Cow<'_, str> {
if !input.chars().all(|c| c.is_ascii_lowercase()) {
input.to_mut().make_ascii_lowercase();
}
input
}
impl NamedProviderFactory {
pub(crate) fn new(
providers: HashMap<Cow<'static, str>, Arc<dyn ProvideCredentials>>,
) -> Self {
let providers = providers
.into_iter()
.map(|(k, v)| (lower_cow(k), v))
.collect();
Self { providers }
}
pub(crate) fn provider(&self, name: &str) -> Option<Arc<dyn ProvideCredentials>> {
self.providers.get(&lower_cow(Cow::Borrowed(name))).cloned()
}
}
}
#[cfg(test)]
mod test {
use crate::profile::credentials::exec::named::NamedProviderFactory;
use crate::profile::credentials::exec::ProviderChain;
use crate::profile::credentials::repr::{BaseProvider, ProfileChain};
use crate::provider_config::ProviderConfig;
use crate::test_case::no_traffic_client;
use aws_credential_types::Credentials;
use std::collections::HashMap;
use std::sync::Arc;
#[test]
fn providers_case_insensitive() {
let mut base = HashMap::new();
base.insert(
"Environment".into(),
Arc::new(Credentials::for_tests()) as _,
);
let provider = NamedProviderFactory::new(base);
assert!(provider.provider("environment").is_some());
assert!(provider.provider("envIROnment").is_some());
assert!(provider.provider(" envIROnment").is_none());
assert!(provider.provider("Environment").is_some());
}
#[test]
fn error_on_unknown_provider() {
let factory = NamedProviderFactory::new(HashMap::new());
let chain = ProviderChain::from_repr(
&ProviderConfig::empty().with_http_client(no_traffic_client()),
ProfileChain {
base: BaseProvider::NamedSource("floozle"),
chain: vec![],
},
&factory,
);
let err = chain.expect_err("no source by that name");
assert!(
format!("{}", err).contains(
"profile referenced `floozle` provider but that provider is not supported"
),
"`{}` did not match expected error",
err
);
}
}