Skip to content

Commit

Permalink
feat: add support for external_id in AWS assume role (vectordotdev#…
Browse files Browse the repository at this point in the history
…17743)

aws assume role did not have support for specifying external id which is
quintessential for security concerned consumers.

P.S. [contributing
guidelines](https://github.com/vectordotdev/vector/blob/44be37843c0599abb64073fe737ce146e30b3aa5/CONTRIBUTING.md)
is empty, help me if I'm missing anything.

Closes: vectordotdev#17739

---------

Co-authored-by: Spencer Gilbert <spencer.gilbert@datadoghq.com>
  • Loading branch information
ankitLu and spencergilbert committed Jul 21, 2023
1 parent 983a92a commit 689a79e
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 1 deletion.
71 changes: 70 additions & 1 deletion src/aws/auth.rs
Expand Up @@ -79,6 +79,12 @@ pub enum AwsAuthentication {
#[configurable(metadata(docs::examples = "arn:aws:iam::123456789098:role/my_role"))]
assume_role: Option<String>,

/// The optional unique external ID in conjunction with role to assume.
///
/// [external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
#[configurable(metadata(docs::examples = "randomEXAMPLEidString"))]
external_id: Option<String>,

/// The [AWS region][aws_region] to send STS requests to.
///
/// If not set, this will default to the configured region
Expand Down Expand Up @@ -115,6 +121,12 @@ pub enum AwsAuthentication {
#[configurable(metadata(docs::examples = "arn:aws:iam::123456789098:role/my_role"))]
assume_role: String,

/// The optional unique external ID in conjunction with role to assume.
///
/// [external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
#[configurable(metadata(docs::examples = "randomEXAMPLEidString"))]
external_id: Option<String>,

/// Timeout for assuming the role, in seconds.
///
/// Relevant when the default credentials chain or `assume_role` is used.
Expand Down Expand Up @@ -199,6 +211,7 @@ impl AwsAuthentication {
access_key_id,
secret_access_key,
assume_role,
external_id,
region,
} => {
let provider = SharedCredentialsProvider::new(Credentials::from_keys(
Expand All @@ -208,8 +221,10 @@ impl AwsAuthentication {
));
if let Some(assume_role) = assume_role {
let auth_region = region.clone().map(Region::new).unwrap_or(service_region);
let auth_external_id = external_id.clone().unwrap();
let provider = AssumeRoleProviderBuilder::new(assume_role)
.region(auth_region)
.external_id(auth_external_id)
.build(provider);
return Ok(SharedCredentialsProvider::new(provider));
}
Expand All @@ -232,15 +247,17 @@ impl AwsAuthentication {
}
AwsAuthentication::Role {
assume_role,
external_id,
imds,
region,
..
} => {
let auth_region = region.clone().map(Region::new).unwrap_or(service_region);
let auth_external_id = external_id.clone().unwrap();
let provider = AssumeRoleProviderBuilder::new(assume_role)
.region(auth_region.clone())
.external_id(auth_external_id)
.build(default_credentials_provider(auth_region, *imds).await?);

Ok(SharedCredentialsProvider::new(provider))
}
AwsAuthentication::Default { imds, region, .. } => Ok(SharedCredentialsProvider::new(
Expand All @@ -259,6 +276,7 @@ impl AwsAuthentication {
access_key_id: "dummy".to_string().into(),
secret_access_key: "dummy".to_string().into(),
assume_role: None,
external_id: None,
region: None,
}
}
Expand Down Expand Up @@ -295,6 +313,7 @@ mod tests {
#[derive(Serialize, Deserialize, Clone, Debug)]
struct ComponentConfig {
assume_role: Option<String>,
external_id: Option<String>,
#[serde(default)]
auth: AwsAuthentication,
}
Expand Down Expand Up @@ -396,6 +415,20 @@ mod tests {
assert!(matches!(config.auth, AwsAuthentication::Role { .. }));
}

#[test]
fn parsing_external_id_with_assume_role() {
let config = toml::from_str::<ComponentConfig>(
r#"
auth.assume_role = "root"
auth.external_id = "id"
auth.load_timeout_secs = 10
"#,
)
.unwrap();

assert!(matches!(config.auth, AwsAuthentication::Role { .. }));
}

#[test]
fn parsing_assume_role_with_imds_client() {
let config = toml::from_str::<ComponentConfig>(
Expand All @@ -411,11 +444,13 @@ mod tests {
match config.auth {
AwsAuthentication::Role {
assume_role,
external_id,
load_timeout_secs,
imds,
region,
} => {
assert_eq!(&assume_role, "root");
assert_eq!(external_id, None);
assert_eq!(load_timeout_secs, None);
assert!(matches!(
imds,
Expand Down Expand Up @@ -446,11 +481,13 @@ mod tests {
match config.auth {
AwsAuthentication::Role {
assume_role,
external_id,
load_timeout_secs,
imds,
region,
} => {
assert_eq!(&assume_role, "auth.root");
assert_eq!(external_id, None);
assert_eq!(load_timeout_secs, Some(10));
assert!(matches!(imds, ImdsAuthentication { .. }));
assert_eq!(region.unwrap(), "us-west-2");
Expand Down Expand Up @@ -501,6 +538,38 @@ mod tests {
}
}

#[test]
fn parsing_static_with_assume_role_and_external_id() {
let config = toml::from_str::<ComponentConfig>(
r#"
auth.access_key_id = "key"
auth.secret_access_key = "other"
auth.assume_role = "root"
auth.external_id = "id"
"#,
)
.unwrap();

match config.auth {
AwsAuthentication::AccessKey {
access_key_id,
secret_access_key,
assume_role,
external_id,
..
} => {
assert_eq!(&access_key_id, &SensitiveString::from("key".to_string()));
assert_eq!(
&secret_access_key,
&SensitiveString::from("other".to_string())
);
assert_eq!(&assume_role, &Some("root".to_string()));
assert_eq!(&external_id, &Some("id".to_string()));
}
_ => panic!(),
}
}

#[test]
fn parsing_file() {
let config = toml::from_str::<ComponentConfig>(
Expand Down
Expand Up @@ -50,6 +50,15 @@ base: components: sinks: aws_cloudwatch_logs: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down
Expand Up @@ -50,6 +50,15 @@ base: components: sinks: aws_cloudwatch_metrics: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down
Expand Up @@ -50,6 +50,15 @@ base: components: sinks: aws_kinesis_firehose: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down
Expand Up @@ -50,6 +50,15 @@ base: components: sinks: aws_kinesis_streams: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down
9 changes: 9 additions & 0 deletions website/cue/reference/components/sinks/base/aws_s3.cue
Expand Up @@ -125,6 +125,15 @@ base: components: sinks: aws_s3: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down
9 changes: 9 additions & 0 deletions website/cue/reference/components/sinks/base/aws_sqs.cue
Expand Up @@ -50,6 +50,15 @@ base: components: sinks: aws_sqs: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down
10 changes: 10 additions & 0 deletions website/cue/reference/components/sinks/base/elasticsearch.cue
Expand Up @@ -76,6 +76,16 @@ base: components: sinks: elasticsearch: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
relevant_when: "strategy = \"aws\""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
relevant_when: "strategy = \"aws\""
Expand Down
Expand Up @@ -53,6 +53,16 @@ base: components: sinks: prometheus_remote_write: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
relevant_when: "strategy = \"aws\""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
relevant_when: "strategy = \"aws\""
Expand Down
9 changes: 9 additions & 0 deletions website/cue/reference/components/sources/base/aws_s3.cue
Expand Up @@ -45,6 +45,15 @@ base: components: sources: aws_s3: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down
9 changes: 9 additions & 0 deletions website/cue/reference/components/sources/base/aws_sqs.cue
Expand Up @@ -45,6 +45,15 @@ base: components: sources: aws_sqs: configuration: {
required: true
type: string: examples: ["/my/aws/credentials"]
}
external_id: {
description: """
The optional unique external ID in conjunction with role to assume.
[external_id]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html
"""
required: false
type: string: examples: ["randomEXAMPLEidString"]
}
imds: {
description: "Configuration for authenticating with AWS through IMDS."
required: false
Expand Down

0 comments on commit 689a79e

Please sign in to comment.