Skip to content

Commit

Permalink
Update CredentialsProcess to support no-expiry credentials (#3335)
Browse files Browse the repository at this point in the history
## 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._
  • Loading branch information
rcoh authored Dec 20, 2023
1 parent a27be2b commit ee5ae95
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 34 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
74 changes: 40 additions & 34 deletions aws/rust-runtime/aws-config/src/credential_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
))),
}
))
})
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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,
})?,
)
.map_err(|_| {
InvalidJsonCredentials::Other(
"credential expiration time cannot be represented by a DateTime".into(),
)
})
}

Expand Down Expand Up @@ -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"));
Expand Down

0 comments on commit ee5ae95

Please sign in to comment.