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

fix: don't transform a ProfileDidNotContainCredentials error if base_provider...

fix: don't transform a ProfileDidNotContainCredentials error if base_provider is running for first provider in chain (#873)

* fix: don't transform a ProfileDidNotContainCredentials if base_provider is running for first provider in chain
add: more comments to aws_config::profile::credentials::repr functions

* add: Test ensuring provider chain hits IMDS even if some config data exists

* update: test to expect success response

* add: clarifying comment about chain vs base providers
parent 1ee7d65a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -524,6 +524,7 @@ pub mod credentials {
        make_test!(imds_default_chain_error);
        make_test!(imds_default_chain_success);
        make_test!(imds_assume_role);
        make_test!(imds_config_with_no_creds);
        make_test!(imds_disabled);
        make_test!(imds_default_chain_retries);

+35 −21
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ pub fn resolve_chain<'a>(
    let mut visited_profiles = vec![];
    let mut chain = vec![];
    let base = loop {
        // Get the next profile in the chain
        let profile = profile_set.get_profile(source_profile_name).ok_or(
            ProfileFileError::MissingProfile {
                profile: source_profile_name.into(),
@@ -124,6 +125,8 @@ pub fn resolve_chain<'a>(
                .into(),
            },
        )?;
        // If the profile we just got is one we've already seen, we're in a loop and
        // need to break out with a CredentialLoop error
        if visited_profiles.contains(&source_profile_name) {
            return Err(ProfileFileError::CredentialLoop {
                profiles: visited_profiles
@@ -133,6 +136,7 @@ pub fn resolve_chain<'a>(
                next: source_profile_name.to_string(),
            });
        }
        // otherwise, store the name of the profile in case we see it again later
        visited_profiles.push(source_profile_name);
        // After the first item in the chain, we will prioritize static credentials if they exist
        if visited_profiles.len() > 1 {
@@ -141,22 +145,32 @@ pub fn resolve_chain<'a>(
                break BaseProvider::AccessKey(static_credentials);
            }
        }
        let next_profile = match chain_provider(profile) {
            // this provider wasn't a chain provider, reload it as a base provider
            None => {

        let next_profile = {
            // The existence of a `role_arn` is the only signal that multiple profiles will be chained.
            // We check for one here and then process the profile accordingly as either a "chain provider"
            // or a "base provider"
            if let Some(role_provider) = role_arn_from_profile(profile) {
                let next = chain_provider(profile)?;
                chain.push(role_provider);
                next
            } else {
                break base_provider(profile).map_err(|err| {
                    // It's possible for base_provider to return a `ProfileFileError::ProfileDidNotContainCredentials`
                    // if we're still looking at the first provider we want to surface it. However,
                    // if we're looking at any provider after the first we want to instead return a `ProfileFileError::InvalidCredentialSource`
                    if visited_profiles.len() == 1 {
                        err
                    } else {
                        ProfileFileError::InvalidCredentialSource {
                            profile: profile.name().into(),
                            message: format!("could not load source profile: {}", err).into(),
                        }
                })?;
                    }
            Some(result) => {
                let (chain_profile, next) = result?;
                chain.push(chain_profile);
                next
                })?;
            }
        };

        match next_profile {
            NextProfile::SelfReference => {
                // self referential profile, don't go through the loop because it will error
@@ -205,13 +219,12 @@ enum NextProfile<'a> {
    Named(&'a str),
}

fn chain_provider(profile: &Profile) -> Option<Result<(RoleArn, NextProfile), ProfileFileError>> {
    let role_provider = role_arn_from_profile(profile)?;
fn chain_provider(profile: &Profile) -> Result<NextProfile, ProfileFileError> {
    let (source_profile, credential_source) = (
        profile.get(role::SOURCE_PROFILE),
        profile.get(role::CREDENTIAL_SOURCE),
    );
    let profile = match (source_profile, credential_source) {
    match (source_profile, credential_source) {
        (Some(_), Some(_)) => Err(ProfileFileError::InvalidCredentialSource {
            profile: profile.name().to_string(),
            message: "profile contained both source_profile and credential_source. \
@@ -225,14 +238,12 @@ fn chain_provider(profile: &Profile) -> Option<Result<(RoleArn, NextProfile), Pr
                    .into(),
        }),
        (Some(source_profile), None) if source_profile == profile.name() => {
            Ok((role_provider, NextProfile::SelfReference))
            Ok(NextProfile::SelfReference)
        }

        (Some(source_profile), None) => Ok((role_provider, NextProfile::Named(source_profile))),
        (Some(source_profile), None) => Ok(NextProfile::Named(source_profile)),
        // we want to loop back into this profile and pick up the credential source
        (None, Some(_credential_source)) => Ok((role_provider, NextProfile::SelfReference)),
    };
    Some(profile)
        (None, Some(_credential_source)) => Ok(NextProfile::SelfReference),
    }
}

fn role_arn_from_profile(profile: &Profile) -> Option<RoleArn> {
@@ -285,11 +296,13 @@ fn static_creds_from_profile(profile: &Profile) -> Result<Credentials, ProfileFi
    let access_key = profile.get(AWS_ACCESS_KEY_ID);
    let secret_key = profile.get(AWS_SECRET_ACCESS_KEY);
    let session_token = profile.get(AWS_SESSION_TOKEN);
    // If all three fields are missing return a `ProfileFileError::ProfileDidNotContainCredentials`
    if let (None, None, None) = (access_key, secret_key, session_token) {
        return Err(ProfileFileError::ProfileDidNotContainCredentials {
            profile: profile.name().to_string(),
        });
    }
    // Otherwise, check to make sure the access and secret keys are defined
    let access_key = access_key.ok_or_else(|| ProfileFileError::InvalidCredentialSource {
        profile: profile.name().to_string(),
        message: "profile missing aws_access_key_id".into(),
@@ -298,6 +311,7 @@ fn static_creds_from_profile(profile: &Profile) -> Result<Credentials, ProfileFi
        profile: profile.name().to_string(),
        message: "profile missing aws_secret_access_key".into(),
    })?;
    // There might not be an active session token so we don't error out if it's missing
    Ok(Credentials::new(
        access_key,
        secret_key,
@@ -335,7 +349,7 @@ mod tests {
        match (expected, actual) {
            (TestOutput::Error(s), Err(e)) => assert!(
                format!("{}", e).contains(&s),
                "expected {} to contain `{}`",
                "expected\n{}\nto contain\n{}\n",
                e,
                s
            ),
+4 −0
Original line number Diff line number Diff line
{
  "HOME": "/home",
  "AWS_REGION": "us-east-1"
}
+2 −0
Original line number Diff line number Diff line
[default]
max_attempts = 1
+270 −0
Original line number Diff line number Diff line
{
    "events": [
        {
            "connection_id": 0,
            "action": {
                "Request": {
                    "request": {
                        "uri": "http://169.254.169.254/latest/api/token",
                        "headers": {
                            "x-amz-user-agent": [
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
                            ],
                            "user-agent": [
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
                            ],
                            "x-aws-ec2-metadata-token-ttl-seconds": [
                                "21600"
                            ]
                        },
                        "method": "PUT"
                    }
                }
            }
        },
        {
            "connection_id": 0,
            "action": {
                "Eof": {
                    "ok": true,
                    "direction": "Request"
                }
            }
        },
        {
            "connection_id": 0,
            "action": {
                "Response": {
                    "response": {
                        "Ok": {
                            "status": 200,
                            "version": "HTTP/1.1",
                            "headers": {
                                "x-aws-ec2-metadata-token-ttl-seconds": [
                                    "21600"
                                ],
                                "content-type": [
                                    "text/plain"
                                ],
                                "connection": [
                                    "close"
                                ],
                                "server": [
                                    "EC2ws"
                                ],
                                "content-length": [
                                    "56"
                                ],
                                "date": [
                                    "Mon, 20 Sep 2021 21:43:31 GMT"
                                ]
                            }
                        }
                    }
                }
            }
        },
        {
            "connection_id": 0,
            "action": {
                "Data": {
                    "data": {
                        "Utf8": "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw=="
                    },
                    "direction": "Response"
                }
            }
        },
        {
            "connection_id": 0,
            "action": {
                "Eof": {
                    "ok": true,
                    "direction": "Response"
                }
            }
        },
        {
            "connection_id": 1,
            "action": {
                "Request": {
                    "request": {
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
                        "headers": {
                            "x-amz-user-agent": [
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
                            ],
                            "x-aws-ec2-metadata-token": [
                                "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw=="
                            ],
                            "user-agent": [
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
                            ]
                        },
                        "method": "GET"
                    }
                }
            }
        },
        {
            "connection_id": 1,
            "action": {
                "Eof": {
                    "ok": true,
                    "direction": "Request"
                }
            }
        },
        {
            "connection_id": 1,
            "action": {
                "Response": {
                    "response": {
                        "Ok": {
                            "status": 200,
                            "version": "HTTP/1.1",
                            "headers": {
                                "server": [
                                    "EC2ws"
                                ],
                                "content-length": [
                                    "21"
                                ],
                                "x-aws-ec2-metadata-token-ttl-seconds": [
                                    "21600"
                                ],
                                "connection": [
                                    "close"
                                ],
                                "last-modified": [
                                    "Mon, 20 Sep 2021 21:41:54 GMT"
                                ],
                                "content-type": [
                                    "text/plain"
                                ],
                                "date": [
                                    "Mon, 20 Sep 2021 21:43:31 GMT"
                                ],
                                "accept-ranges": [
                                    "none"
                                ]
                            }
                        }
                    }
                }
            }
        },
        {
            "connection_id": 1,
            "action": {
                "Data": {
                    "data": {
                        "Utf8": "imds-assume-role-test"
                    },
                    "direction": "Response"
                }
            }
        },
        {
            "connection_id": 1,
            "action": {
                "Eof": {
                    "ok": true,
                    "direction": "Response"
                }
            }
        },
        {
            "connection_id": 2,
            "action": {
                "Request": {
                    "request": {
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test",
                        "headers": {
                            "x-aws-ec2-metadata-token": [
                                "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw=="
                            ],
                            "user-agent": [
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
                            ],
                            "x-amz-user-agent": [
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
                            ]
                        },
                        "method": "GET"
                    }
                }
            }
        },
        {
            "connection_id": 2,
            "action": {
                "Eof": {
                    "ok": true,
                    "direction": "Request"
                }
            }
        },
        {
            "connection_id": 2,
            "action": {
                "Response": {
                    "response": {
                        "Ok": {
                            "status": 200,
                            "version": "HTTP/1.1",
                            "headers": {
                                "content-type": [
                                    "text/plain"
                                ],
                                "server": [
                                    "EC2ws"
                                ],
                                "content-length": [
                                    "1322"
                                ],
                                "accept-ranges": [
                                    "none"
                                ],
                                "date": [
                                    "Mon, 20 Sep 2021 21:43:31 GMT"
                                ],
                                "connection": [
                                    "close"
                                ],
                                "last-modified": [
                                    "Mon, 20 Sep 2021 21:41:54 GMT"
                                ],
                                "x-aws-ec2-metadata-token-ttl-seconds": [
                                    "21600"
                                ]
                            }
                        }
                    }
                }
            }
        },
        {
            "connection_id": 2,
            "action": {
                "Data": {
                    "data": {
                        "Utf8": "{\n  \"Code\" : \"Success\",\n  \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n  \"Type\" : \"AWS-HMAC\",\n  \"AccessKeyId\" : \"ASIARTEST\",\n  \"SecretAccessKey\" : \"testsecret\",\n  \"Token\" : \"testtoken\",\n  \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"
                    },
                    "direction": "Response"
                }
            }
        },
        {
            "connection_id": 2,
            "action": {
                "Eof": {
                    "ok": true,
                    "direction": "Response"
                }
            }
        }
    ],
    "docs": "profile provider should hit IMDS if a profile contains config data but no credentials.",
    "version": "V0"
}
Loading