Skip to content

Commit ebfd02f

Browse files
Friede80alamb
andauthored
AWS S3: Support STS endpoint, WebIdentity, RoleArn, RoleSession configuration (#480)
* Allow setting STS endpoint via env var * Properly use AmazonS3Builder::credentials_from_env for AssumeRoleWithWebIdentity auth flow --------- Co-authored-by: Andrew Lamb <[email protected]>
1 parent f1dd075 commit ebfd02f

File tree

1 file changed

+128
-6
lines changed

1 file changed

+128
-6
lines changed

src/aws/builder.rs

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ pub struct AmazonS3Builder {
156156
container_credentials_full_uri: Option<String>,
157157
/// Container authorization token file, see <https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html>
158158
container_authorization_token_file: Option<String>,
159+
/// Web identity token file path for AssumeRoleWithWebIdentity
160+
web_identity_token_file: Option<String>,
161+
/// Role ARN to assume when using web identity token
162+
role_arn: Option<String>,
163+
/// Session name for web identity role assumption
164+
role_session_name: Option<String>,
165+
/// Custom STS endpoint for web identity token exchange
166+
sts_endpoint: Option<String>,
159167
/// Client options
160168
client_options: ClientOptions,
161169
/// Credentials
@@ -319,6 +327,34 @@ pub enum AmazonS3ConfigKey {
319327
/// <https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html>
320328
ContainerAuthorizationTokenFile,
321329

330+
/// Web identity token file path for AssumeRoleWithWebIdentity
331+
///
332+
/// Supported keys:
333+
/// - `aws_web_identity_token_file`
334+
/// - `web_identity_token_file`
335+
WebIdentityTokenFile,
336+
337+
/// Role ARN to assume when using web identity token
338+
///
339+
/// Supported keys:
340+
/// - `aws_role_arn`
341+
/// - `role_arn`
342+
RoleArn,
343+
344+
/// Session name for web identity role assumption
345+
///
346+
/// Supported keys:
347+
/// - `aws_role_session_name`
348+
/// - `role_session_name`
349+
RoleSessionName,
350+
351+
/// Custom STS endpoint for web identity token exchange
352+
///
353+
/// Supported keys:
354+
/// - `aws_endpoint_url_sts`
355+
/// - `endpoint_url_sts`
356+
StsEndpoint,
357+
322358
/// Configure how to provide `copy_if_not_exists`
323359
///
324360
/// See [`S3CopyIfNotExists`]
@@ -381,6 +417,10 @@ impl AsRef<str> for AmazonS3ConfigKey {
381417
Self::ContainerCredentialsRelativeUri => "aws_container_credentials_relative_uri",
382418
Self::ContainerCredentialsFullUri => "aws_container_credentials_full_uri",
383419
Self::ContainerAuthorizationTokenFile => "aws_container_authorization_token_file",
420+
Self::WebIdentityTokenFile => "aws_web_identity_token_file",
421+
Self::RoleArn => "aws_role_arn",
422+
Self::RoleSessionName => "aws_role_session_name",
423+
Self::StsEndpoint => "aws_endpoint_url_sts",
384424
Self::SkipSignature => "aws_skip_signature",
385425
Self::CopyIfNotExists => "aws_copy_if_not_exists",
386426
Self::ConditionalPut => "aws_conditional_put",
@@ -415,6 +455,12 @@ impl FromStr for AmazonS3ConfigKey {
415455
"aws_container_credentials_relative_uri" => Ok(Self::ContainerCredentialsRelativeUri),
416456
"aws_container_credentials_full_uri" => Ok(Self::ContainerCredentialsFullUri),
417457
"aws_container_authorization_token_file" => Ok(Self::ContainerAuthorizationTokenFile),
458+
"aws_web_identity_token_file" | "web_identity_token_file" => {
459+
Ok(Self::WebIdentityTokenFile)
460+
}
461+
"aws_role_arn" | "role_arn" => Ok(Self::RoleArn),
462+
"aws_role_session_name" | "role_session_name" => Ok(Self::RoleSessionName),
463+
"aws_endpoint_url_sts" | "endpoint_url_sts" => Ok(Self::StsEndpoint),
418464
"aws_skip_signature" | "skip_signature" => Ok(Self::SkipSignature),
419465
"aws_copy_if_not_exists" | "copy_if_not_exists" => Ok(Self::CopyIfNotExists),
420466
"aws_conditional_put" | "conditional_put" => Ok(Self::ConditionalPut),
@@ -458,6 +504,10 @@ impl AmazonS3Builder {
458504
/// * `AWS_DEFAULT_REGION` -> region
459505
/// * `AWS_ENDPOINT` -> endpoint
460506
/// * `AWS_SESSION_TOKEN` -> token
507+
/// * `AWS_WEB_IDENTITY_TOKEN_FILE` -> path to file containing web identity token for AssumeRoleWithWebIdentity
508+
/// * `AWS_ROLE_ARN` -> ARN of the role to assume when using web identity token
509+
/// * `AWS_ROLE_SESSION_NAME` -> optional session name for web identity role assumption (defaults to "WebIdentitySession")
510+
/// * `AWS_ENDPOINT_URL_STS` -> optional custom STS endpoint for web identity token exchange (defaults to "https://sts.{region}.amazonaws.com")
461511
/// * `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` -> <https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html>
462512
/// * `AWS_CONTAINER_CREDENTIALS_FULL_URI` -> <https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html>
463513
/// * `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` -> <https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html>
@@ -543,6 +593,18 @@ impl AmazonS3Builder {
543593
AmazonS3ConfigKey::ContainerAuthorizationTokenFile => {
544594
self.container_authorization_token_file = Some(value.into());
545595
}
596+
AmazonS3ConfigKey::WebIdentityTokenFile => {
597+
self.web_identity_token_file = Some(value.into());
598+
}
599+
AmazonS3ConfigKey::RoleArn => {
600+
self.role_arn = Some(value.into());
601+
}
602+
AmazonS3ConfigKey::RoleSessionName => {
603+
self.role_session_name = Some(value.into());
604+
}
605+
AmazonS3ConfigKey::StsEndpoint => {
606+
self.sts_endpoint = Some(value.into());
607+
}
546608
AmazonS3ConfigKey::Client(key) => {
547609
self.client_options = self.client_options.with_config(key, value)
548610
}
@@ -612,6 +674,10 @@ impl AmazonS3Builder {
612674
AmazonS3ConfigKey::ContainerAuthorizationTokenFile => {
613675
self.container_authorization_token_file.clone()
614676
}
677+
AmazonS3ConfigKey::WebIdentityTokenFile => self.web_identity_token_file.clone(),
678+
AmazonS3ConfigKey::RoleArn => self.role_arn.clone(),
679+
AmazonS3ConfigKey::RoleSessionName => self.role_session_name.clone(),
680+
AmazonS3ConfigKey::StsEndpoint => self.sts_endpoint.clone(),
615681
AmazonS3ConfigKey::SkipSignature => Some(self.skip_signature.to_string()),
616682
AmazonS3ConfigKey::CopyIfNotExists => {
617683
self.copy_if_not_exists.as_ref().map(ToString::to_string)
@@ -959,21 +1025,25 @@ impl AmazonS3Builder {
9591025
std::env::var("AWS_WEB_IDENTITY_TOKEN_FILE"),
9601026
std::env::var("AWS_ROLE_ARN"),
9611027
) {
962-
// TODO: Replace with `AmazonS3Builder::credentials_from_env`
9631028
debug!("Using WebIdentity credential provider");
9641029

965-
let session_name = std::env::var("AWS_ROLE_SESSION_NAME")
966-
.unwrap_or_else(|_| "WebIdentitySession".to_string());
1030+
let session_name = self
1031+
.role_session_name
1032+
.clone()
1033+
.unwrap_or_else(|| "WebIdentitySession".to_string());
9671034

968-
let endpoint = format!("https://sts.{region}.amazonaws.com");
1035+
let endpoint = self
1036+
.sts_endpoint
1037+
.clone()
1038+
.unwrap_or_else(|| format!("https://sts.{region}.amazonaws.com"));
9691039

9701040
// Disallow non-HTTPs requests
9711041
let options = self.client_options.clone().with_allow_http(false);
9721042

9731043
let token = WebIdentityProvider {
974-
token_path,
1044+
token_path: token_path.clone(),
9751045
session_name,
976-
role_arn,
1046+
role_arn: role_arn.clone(),
9771047
endpoint,
9781048
};
9791049

@@ -1611,4 +1681,56 @@ mod tests {
16111681
"expected EKS provider but got: {debug_str}"
16121682
);
16131683
}
1684+
1685+
#[test]
1686+
fn test_builder_web_identity_with_config() {
1687+
let builder = AmazonS3Builder::new()
1688+
.with_bucket_name("some-bucket")
1689+
.with_config(
1690+
AmazonS3ConfigKey::WebIdentityTokenFile,
1691+
"/tmp/fake-token-file",
1692+
)
1693+
.with_config(
1694+
AmazonS3ConfigKey::RoleArn,
1695+
"arn:aws:iam::123456789012:role/test-role",
1696+
)
1697+
.with_config(AmazonS3ConfigKey::RoleSessionName, "TestSession")
1698+
.with_config(
1699+
AmazonS3ConfigKey::StsEndpoint,
1700+
"https://sts.us-west-2.amazonaws.com",
1701+
);
1702+
1703+
assert_eq!(
1704+
builder
1705+
.get_config_value(&AmazonS3ConfigKey::WebIdentityTokenFile)
1706+
.unwrap(),
1707+
"/tmp/fake-token-file"
1708+
);
1709+
assert_eq!(
1710+
builder
1711+
.get_config_value(&AmazonS3ConfigKey::RoleArn)
1712+
.unwrap(),
1713+
"arn:aws:iam::123456789012:role/test-role"
1714+
);
1715+
assert_eq!(
1716+
builder
1717+
.get_config_value(&AmazonS3ConfigKey::RoleSessionName)
1718+
.unwrap(),
1719+
"TestSession"
1720+
);
1721+
assert_eq!(
1722+
builder
1723+
.get_config_value(&AmazonS3ConfigKey::StsEndpoint)
1724+
.unwrap(),
1725+
"https://sts.us-west-2.amazonaws.com"
1726+
);
1727+
1728+
let s3 = builder.build().expect("should build successfully");
1729+
let creds = &s3.client.config.credentials;
1730+
let debug_str = format!("{creds:?}");
1731+
assert!(
1732+
debug_str.contains("TokenCredentialProvider"),
1733+
"expected TokenCredentialProvider but got: {debug_str}"
1734+
);
1735+
}
16141736
}

0 commit comments

Comments
 (0)