From 689a79e20e0130fd2070be28173fa3ef565b27ac Mon Sep 17 00:00:00 2001 From: Ankit Luthra Date: Fri, 21 Jul 2023 12:58:03 -0400 Subject: [PATCH] feat: add support for `external_id` in AWS assume role (#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: https://github.com/vectordotdev/vector/issues/17739 --------- Co-authored-by: Spencer Gilbert --- src/aws/auth.rs | 71 ++++++++++++++++++- .../sinks/base/aws_cloudwatch_logs.cue | 9 +++ .../sinks/base/aws_cloudwatch_metrics.cue | 9 +++ .../sinks/base/aws_kinesis_firehose.cue | 9 +++ .../sinks/base/aws_kinesis_streams.cue | 9 +++ .../components/sinks/base/aws_s3.cue | 9 +++ .../components/sinks/base/aws_sqs.cue | 9 +++ .../components/sinks/base/elasticsearch.cue | 10 +++ .../sinks/base/prometheus_remote_write.cue | 10 +++ .../components/sources/base/aws_s3.cue | 9 +++ .../components/sources/base/aws_sqs.cue | 9 +++ 11 files changed, 162 insertions(+), 1 deletion(-) diff --git a/src/aws/auth.rs b/src/aws/auth.rs index f95256fcf69a1..665f382c33d73 100644 --- a/src/aws/auth.rs +++ b/src/aws/auth.rs @@ -79,6 +79,12 @@ pub enum AwsAuthentication { #[configurable(metadata(docs::examples = "arn:aws:iam::123456789098:role/my_role"))] assume_role: Option, + /// 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, + /// The [AWS region][aws_region] to send STS requests to. /// /// If not set, this will default to the configured region @@ -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, + /// Timeout for assuming the role, in seconds. /// /// Relevant when the default credentials chain or `assume_role` is used. @@ -199,6 +211,7 @@ impl AwsAuthentication { access_key_id, secret_access_key, assume_role, + external_id, region, } => { let provider = SharedCredentialsProvider::new(Credentials::from_keys( @@ -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)); } @@ -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( @@ -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, } } @@ -295,6 +313,7 @@ mod tests { #[derive(Serialize, Deserialize, Clone, Debug)] struct ComponentConfig { assume_role: Option, + external_id: Option, #[serde(default)] auth: AwsAuthentication, } @@ -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::( + 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::( @@ -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, @@ -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"); @@ -501,6 +538,38 @@ mod tests { } } + #[test] + fn parsing_static_with_assume_role_and_external_id() { + let config = toml::from_str::( + 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::( diff --git a/website/cue/reference/components/sinks/base/aws_cloudwatch_logs.cue b/website/cue/reference/components/sinks/base/aws_cloudwatch_logs.cue index e5ea223052de1..4cbe09f99f3b0 100644 --- a/website/cue/reference/components/sinks/base/aws_cloudwatch_logs.cue +++ b/website/cue/reference/components/sinks/base/aws_cloudwatch_logs.cue @@ -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 diff --git a/website/cue/reference/components/sinks/base/aws_cloudwatch_metrics.cue b/website/cue/reference/components/sinks/base/aws_cloudwatch_metrics.cue index 36eede120e998..1f54161e12839 100644 --- a/website/cue/reference/components/sinks/base/aws_cloudwatch_metrics.cue +++ b/website/cue/reference/components/sinks/base/aws_cloudwatch_metrics.cue @@ -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 diff --git a/website/cue/reference/components/sinks/base/aws_kinesis_firehose.cue b/website/cue/reference/components/sinks/base/aws_kinesis_firehose.cue index 19a70860692e5..a43ed56875662 100644 --- a/website/cue/reference/components/sinks/base/aws_kinesis_firehose.cue +++ b/website/cue/reference/components/sinks/base/aws_kinesis_firehose.cue @@ -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 diff --git a/website/cue/reference/components/sinks/base/aws_kinesis_streams.cue b/website/cue/reference/components/sinks/base/aws_kinesis_streams.cue index 40164b9d0e292..92ed82abd7849 100644 --- a/website/cue/reference/components/sinks/base/aws_kinesis_streams.cue +++ b/website/cue/reference/components/sinks/base/aws_kinesis_streams.cue @@ -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 diff --git a/website/cue/reference/components/sinks/base/aws_s3.cue b/website/cue/reference/components/sinks/base/aws_s3.cue index 11265897c80fc..2f31fdbd5b3a6 100644 --- a/website/cue/reference/components/sinks/base/aws_s3.cue +++ b/website/cue/reference/components/sinks/base/aws_s3.cue @@ -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 diff --git a/website/cue/reference/components/sinks/base/aws_sqs.cue b/website/cue/reference/components/sinks/base/aws_sqs.cue index 403beb674ba3e..07bdf7373a655 100644 --- a/website/cue/reference/components/sinks/base/aws_sqs.cue +++ b/website/cue/reference/components/sinks/base/aws_sqs.cue @@ -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 diff --git a/website/cue/reference/components/sinks/base/elasticsearch.cue b/website/cue/reference/components/sinks/base/elasticsearch.cue index 5c76cb9537ba7..31ec23da07b00 100644 --- a/website/cue/reference/components/sinks/base/elasticsearch.cue +++ b/website/cue/reference/components/sinks/base/elasticsearch.cue @@ -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\"" diff --git a/website/cue/reference/components/sinks/base/prometheus_remote_write.cue b/website/cue/reference/components/sinks/base/prometheus_remote_write.cue index 4089af9b9af7a..1141e9e39dce4 100644 --- a/website/cue/reference/components/sinks/base/prometheus_remote_write.cue +++ b/website/cue/reference/components/sinks/base/prometheus_remote_write.cue @@ -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\"" diff --git a/website/cue/reference/components/sources/base/aws_s3.cue b/website/cue/reference/components/sources/base/aws_s3.cue index 4950291add6a7..62fa9888d87b3 100644 --- a/website/cue/reference/components/sources/base/aws_s3.cue +++ b/website/cue/reference/components/sources/base/aws_s3.cue @@ -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 diff --git a/website/cue/reference/components/sources/base/aws_sqs.cue b/website/cue/reference/components/sources/base/aws_sqs.cue index 30a7e3fd6381a..35cdf11ac23a4 100644 --- a/website/cue/reference/components/sources/base/aws_sqs.cue +++ b/website/cue/reference/components/sources/base/aws_sqs.cue @@ -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