fix: region provider now respects chained profiles (#1183)

* fix: region provider now respects chained profiles
update: profile chain creation for load_credentials

* update: CHANGELOG.next.toml

* Update aws/rust-runtime/aws-config/src/profile/region.rs

Co-authored-by: John DiSanti <jdisanti@amazon.com>

Co-authored-by: John DiSanti <jdisanti@amazon.com>
This commit is contained in:
Zelda Hessler 2022-02-11 16:39:22 -06:00 committed by GitHub
parent 6685d472d9
commit bd6b65bd6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 11 deletions

View File

@ -11,6 +11,12 @@
# meta = { "breaking" = false, "tada" = false, "bug" = false }
# author = "rcoh"
[[aws-sdk-rust]]
message = "The `ProfileFileRegionProvider` will now respect regions set in chained profiles"
references = ["aws-sdk-rust#443"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
author = "Velfi"
[[aws-sdk-rust]]
message = "Several modules defined in the `aws_config` crate that used to be declared within another module's file have been moved to their own files. The moved modules are `sts`, `connector`, and `default_providers`. They still have the exact same import paths."
references = ["smithy-rs#1144"]

View File

@ -166,13 +166,13 @@ impl ProfileFileCredentialsProvider {
}
async fn load_credentials(&self) -> credentials::Result {
let profile = build_provider_chain(
let inner_provider = build_provider_chain(
&self.provider_config,
&self.factory,
self.profile_override.as_deref(),
)
.await;
let inner_provider = profile.map_err(|err| match err {
.await
.map_err(|err| match err {
ProfileFileError::NoProfilesDefined
| ProfileFileError::ProfileDidNotContainCredentials { .. } => {
CredentialsError::not_loaded(err)

View File

@ -10,6 +10,8 @@ use crate::provider_config::ProviderConfig;
use aws_types::os_shim_internal::{Env, Fs};
use aws_types::region::Region;
use super::ProfileSet;
/// Load a region from a profile file
///
/// This provider will attempt to load AWS shared configuration, then read the `region` property
@ -88,18 +90,61 @@ impl ProfileFileRegionProvider {
}
async fn region(&self) -> Option<Region> {
let profile = super::parser::load(&self.fs, &self.env)
let profile_set = super::parser::load(&self.fs, &self.env)
.await
.map_err(|err| tracing::warn!(err = %err, "failed to parse profile"))
.ok()?;
let selected_profile = self
.profile_override
.as_deref()
.unwrap_or_else(|| profile.selected_profile());
let selected_profile = profile.get_profile(selected_profile)?;
selected_profile
resolve_profile_chain_for_region(&profile_set, self.profile_override.as_deref())
}
}
fn resolve_profile_chain_for_region(
profile_set: &'_ ProfileSet,
profile_override: Option<&str>,
) -> Option<Region> {
if profile_set.is_empty() {
return None;
}
let mut selected_profile = profile_override.unwrap_or_else(|| profile_set.selected_profile());
let mut visited_profiles = vec![];
loop {
let profile = profile_set.get_profile(selected_profile)?;
// Check to see if we're in a loop and return if that's true.
// Else, add the profile we're currently checking to our list of visited profiles.
if visited_profiles.contains(&selected_profile) {
return None;
} else {
visited_profiles.push(selected_profile);
}
// Attempt to get region and source_profile for current profile
let selected_profile_region = profile
.get("region")
.map(|region| Region::new(region.to_owned()))
.map(|region| Region::new(region.to_owned()));
let source_profile = profile.get("source_profile");
// Check to see what we got
match (selected_profile_region, source_profile) {
// Profile had a region specified, return it :D
(Some(region), _) => {
return Some(region);
}
// No region specified, source_profile is self-referential so we return to avoid infinite loop
(None, Some(source_profile)) if source_profile == selected_profile => {
return None;
}
// No region specified, no source_profile specified so we return empty-handed
(None, None) => {
return None;
}
// No region specified, check source profile for a region in next loop iteration
(None, Some(source_profile)) => {
selected_profile = source_profile;
}
}
}
}
@ -179,4 +224,37 @@ mod test {
Some(Region::from_static("us-east-1"))
);
}
#[tokio::test]
async fn load_region_from_source_profile() {
let config = r#"
[profile credentials]
aws_access_key_id = test-access-key-id
aws_secret_access_key = test-secret-access-key
aws_session_token = test-session-token
region = us-east-1
[profile needs-source]
source_profile = credentials
role_arn = arn:aws:iam::123456789012:role/test
"#
.trim();
let fs = Fs::from_slice(&[("test_config", config)]);
let env = Env::from_slice(&[("AWS_CONFIG_FILE", "test_config")]);
let provider_config = ProviderConfig::empty()
.with_fs(fs)
.with_env(env)
.with_http_connector(no_traffic_connector());
assert_eq!(
Some(Region::new("us-east-1")),
ProfileFileRegionProvider::builder()
.profile_name("needs-source")
.configure(&provider_config)
.build()
.region()
.await
);
}
}