Unverified Commit bd6b65bd authored by Zelda Hessler's avatar Zelda Hessler Committed by GitHub
Browse files

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: default avatarJohn DiSanti <jdisanti@amazon.com>

Co-authored-by: default avatarJohn DiSanti <jdisanti@amazon.com>
parent 6685d472
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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"]
+3 −3
Original line number Diff line number Diff line
@@ -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)
+86 −8
Original line number Diff line number Diff line
@@ -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
        );
    }
}