Unverified Commit ee5ae957 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Update CredentialsProcess to support no-expiry credentials (#3335)

## Motivation and Context
- aws-sdk-rust#1021

## Description
Fix bug in CredentialsProcess provider where expiry was erroneously
required

## Testing
- unit test

## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the
smithy-rs codegen or runtime crates
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS
SDK, generated SDK code, or SDK runtime crates

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
parent a27be2ba
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -34,3 +34,9 @@ message = "Serialize 0/false in query parameters, and ignore actual default valu
references = ["smithy-rs#3252", "smithy-rs#3312"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
author = "milesziemer"

[[aws-sdk-rust]]
message = "Fix bug in `CredentialsProcess` provider where `expiry` was incorrectly treated as a required field."
references = ["smithy-rs#3335", "aws-sdk-rust#1021"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
author = "rcoh"
+40 −34
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@

//! Credentials Provider for external process

use crate::json_credentials::{json_parse_loop, InvalidJsonCredentials, RefreshableCredentials};
use crate::json_credentials::{json_parse_loop, InvalidJsonCredentials};
use crate::sensitive_command::CommandWithSensitiveArgs;
use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials};
use aws_credential_types::Credentials;
@@ -120,25 +120,12 @@ impl CredentialProcessProvider {
            ))
        })?;

        match parse_credential_process_json_credentials(output) {
            Ok(RefreshableCredentials {
                access_key_id,
                secret_access_key,
                session_token,
                expiration,
                ..
            }) => Ok(Credentials::new(
                access_key_id,
                secret_access_key,
                Some(session_token.to_string()),
                expiration.into(),
                "CredentialProcess",
            )),
            Err(invalid) => Err(CredentialsError::provider_error(format!(
        parse_credential_process_json_credentials(output).map_err(|invalid| {
            CredentialsError::provider_error(format!(
                "Error retrieving credentials from external process, could not parse response: {}",
                invalid
            ))),
        }
            ))
        })
    }
}

@@ -149,7 +136,7 @@ impl CredentialProcessProvider {
/// Keys are case insensitive.
pub(crate) fn parse_credential_process_json_credentials(
    credentials_response: &str,
) -> Result<RefreshableCredentials<'_>, InvalidJsonCredentials> {
) -> Result<Credentials, InvalidJsonCredentials> {
    let mut version = None;
    let mut access_key_id = None;
    let mut secret_access_key = None;
@@ -206,25 +193,32 @@ pub(crate) fn parse_credential_process_json_credentials(
    let access_key_id = access_key_id.ok_or(InvalidJsonCredentials::MissingField("AccessKeyId"))?;
    let secret_access_key =
        secret_access_key.ok_or(InvalidJsonCredentials::MissingField("SecretAccessKey"))?;
    let session_token = session_token.ok_or(InvalidJsonCredentials::MissingField("Token"))?;
    let expiration = expiration.ok_or(InvalidJsonCredentials::MissingField("Expiration"))?;
    let expiration =
        SystemTime::try_from(OffsetDateTime::parse(&expiration, &Rfc3339).map_err(|err| {
    let expiration = expiration.map(parse_expiration).transpose()?;
    if expiration.is_none() {
        tracing::debug!("no expiration provided for credentials provider credentials. these credentials will never be refreshed.")
    }
    Ok(Credentials::new(
        access_key_id,
        secret_access_key,
        session_token.map(|tok| tok.to_string()),
        expiration,
        "CredentialProcess",
    ))
}

fn parse_expiration(expiration: impl AsRef<str>) -> Result<SystemTime, InvalidJsonCredentials> {
    SystemTime::try_from(
        OffsetDateTime::parse(expiration.as_ref(), &Rfc3339).map_err(|err| {
            InvalidJsonCredentials::InvalidField {
                field: "Expiration",
                err: err.into(),
            }
        })?)
        })?,
    )
    .map_err(|_| {
        InvalidJsonCredentials::Other(
            "credential expiration time cannot be represented by a DateTime".into(),
        )
        })?;
    Ok(RefreshableCredentials {
        access_key_id,
        secret_access_key,
        session_token,
        expiration,
    })
}

@@ -258,6 +252,18 @@ mod test {
        );
    }

    #[tokio::test]
    async fn test_credential_process_no_expiry() {
        let provider = CredentialProcessProvider::new(String::from(
            r#"echo '{ "Version": 1, "AccessKeyId": "ASIARTESTID", "SecretAccessKey": "TESTSECRETKEY" }'"#,
        ));
        let creds = provider.provide_credentials().await.expect("valid creds");
        assert_eq!(creds.access_key_id(), "ASIARTESTID");
        assert_eq!(creds.secret_access_key(), "TESTSECRETKEY");
        assert_eq!(creds.session_token(), None);
        assert_eq!(creds.expiry(), None);
    }

    #[tokio::test]
    async fn credentials_process_timeouts() {
        let provider = CredentialProcessProvider::new(String::from("sleep 1000"));