From 4167edc467355008ce6cb554c61500980f2feaad Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Tue, 11 Mar 2025 13:35:13 -0400 Subject: [PATCH 1/4] feat: support x-region secrets --- bottlecap/src/secrets/decrypt.rs | 75 +++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/bottlecap/src/secrets/decrypt.rs b/bottlecap/src/secrets/decrypt.rs index eb96612f7..1b7a65116 100644 --- a/bottlecap/src/secrets/decrypt.rs +++ b/bottlecap/src/secrets/decrypt.rs @@ -53,7 +53,18 @@ pub async fn resolve_secrets(config: Arc, aws_config: &mut AwsConfig) -> } let decrypted_key = if config.kms_api_key.is_empty() { - decrypt_aws_sm(&client, config.api_key_secret_arn.clone(), aws_config).await + let secret_region = config + .kms_api_key + .split(":") + .nth(3) + .unwrap_or(&aws_config.region); + decrypt_aws_sm( + &client, + config.api_key_secret_arn.clone(), + aws_config, + secret_region.to_string(), + ) + .await } else { decrypt_aws_kms(&client, config.kms_api_key.clone(), aws_config).await }; @@ -107,6 +118,7 @@ async fn decrypt_aws_kms( let headers = build_get_secret_signed_headers( aws_config, + aws_config.region.clone(), RequestArgs { service: "kms".to_string(), body: json_body, @@ -128,6 +140,7 @@ async fn decrypt_aws_kms( let headers = build_get_secret_signed_headers( aws_config, + aws_config.region.clone(), RequestArgs { service: "kms".to_string(), body: json_body, @@ -151,11 +164,13 @@ async fn decrypt_aws_sm( client: &Client, secret_arn: String, aws_config: &AwsConfig, + secret_region: String, ) -> Result> { let json_body = &serde_json::json!({ "SecretId": secret_arn}); let headers = build_get_secret_signed_headers( aws_config, + secret_region, RequestArgs { service: "secretsmanager".to_string(), body: json_body, @@ -211,6 +226,7 @@ async fn request( fn build_get_secret_signed_headers( aws_config: &AwsConfig, + region: String, header_values: RequestArgs, ) -> Result> { let amz_date = header_values.time.format("%Y%m%dT%H%M%SZ").to_string(); @@ -222,7 +238,7 @@ fn build_get_secret_signed_headers( "amazonaws.com" }; - let host = format!("{}.{}.{}", header_values.service, aws_config.region, domain); + let host = format!("{}.{}.{}", header_values.service, region, domain); let canonical_uri = "/"; let canonical_querystring = ""; @@ -338,6 +354,7 @@ mod tests { function_name: "arn:some-function".to_string(), sandbox_init_time: Instant::now(), }, + "us-east-1".to_string(), RequestArgs { service: "secretsmanager".to_string(), body: &serde_json::json!({ "SecretId": "arn:aws:secretsmanager:region:account-id:secret:secret-name"}), @@ -373,4 +390,58 @@ mod tests { assert_eq!(headers.get(k).expect("cannot get header"), v); } } + + #[test] + #[allow(clippy::unwrap_used)] + fn test_cross_region_secret() { + let time = Utc.from_utc_datetime( + &NaiveDateTime::parse_from_str("2024-05-30 09:10:11", "%Y-%m-%d %H:%M:%S").unwrap(), + ); + let headers = build_get_secret_signed_headers( + &AwsConfig{ + region: "us-east-1".to_string(), + aws_access_key_id: "AKIDEXAMPLE".to_string(), + aws_secret_access_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".to_string(), + aws_session_token: "AQoDYXdzEJr...".to_string(), + aws_container_authorization_token: String::new(), + aws_container_credentials_full_uri: String::new(), + function_name: "arn:some-function".to_string(), + sandbox_init_time: Instant::now(), + }, + "us-west-2".to_string(), + RequestArgs { + service: "secretsmanager".to_string(), + body: &serde_json::json!({ "SecretId": "arn:aws:secretsmanager:us-west-2:account-id:secret:secret-name"}), + time, + x_amz_target: "secretsmanager.GetSecretValue".to_string(), + }, + ).unwrap(); + + let mut expected_headers = HeaderMap::new(); + expected_headers.insert("authorization", HeaderValue::from_str("AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20240530/us-east-1/secretsmanager/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=233e62035a3d0db0eed5b163ccb77e65fbab95df22e19aafdc2d9d0c1b1cc7a5").unwrap()); + expected_headers.insert( + "host", + HeaderValue::from_str("secretsmanager.us-west-2.amazonaws.com").unwrap(), + ); + expected_headers.insert( + "content-type", + HeaderValue::from_str("application/x-amz-json-1.1").unwrap(), + ); + expected_headers.insert( + "x-amz-date", + HeaderValue::from_str("20240530T091011Z").unwrap(), + ); + expected_headers.insert( + "x-amz-target", + HeaderValue::from_str("secretsmanager.GetSecretValue").unwrap(), + ); + expected_headers.insert( + "x-amz-security-token", + HeaderValue::from_str("AQoDYXdzEJr...").unwrap(), + ); + + for (k, v) in &expected_headers { + assert_eq!(headers.get(k).expect("cannot get header"), v); + } + } } From 5cd57e6fbdf13a5203df84dd30f0839a87b42833 Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Tue, 11 Mar 2025 14:32:03 -0400 Subject: [PATCH 2/4] feat: support x-region secrets manager secrets --- bottlecap/src/secrets/decrypt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bottlecap/src/secrets/decrypt.rs b/bottlecap/src/secrets/decrypt.rs index 1b7a65116..e2f10bda2 100644 --- a/bottlecap/src/secrets/decrypt.rs +++ b/bottlecap/src/secrets/decrypt.rs @@ -54,8 +54,8 @@ pub async fn resolve_secrets(config: Arc, aws_config: &mut AwsConfig) -> let decrypted_key = if config.kms_api_key.is_empty() { let secret_region = config - .kms_api_key - .split(":") + .api_key_secret_arn + .split(':') .nth(3) .unwrap_or(&aws_config.region); decrypt_aws_sm( @@ -256,7 +256,7 @@ fn build_get_secret_signed_headers( let algorithm = "AWS4-HMAC-SHA256"; let credential_scope = format!( "{}/{}/{}/aws4_request", - date_stamp, aws_config.region, header_values.service + date_stamp, region, header_values.service ); let string_to_sign = format!( "{}\n{}\n{}\n{}", @@ -269,7 +269,7 @@ fn build_get_secret_signed_headers( let signing_key = get_aws4_signature_key( &aws_config.aws_secret_access_key, &date_stamp, - aws_config.region.as_str(), + region.as_str(), header_values.service.as_str(), )?; From d1765575d78d9b4176c661a9c7dbd151775d5175 Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Tue, 11 Mar 2025 14:45:11 -0400 Subject: [PATCH 3/4] fix: Push config into secrets_manager method as this only applies to secrets manager --- bottlecap/src/secrets/decrypt.rs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/bottlecap/src/secrets/decrypt.rs b/bottlecap/src/secrets/decrypt.rs index e2f10bda2..a640642dc 100644 --- a/bottlecap/src/secrets/decrypt.rs +++ b/bottlecap/src/secrets/decrypt.rs @@ -53,18 +53,7 @@ pub async fn resolve_secrets(config: Arc, aws_config: &mut AwsConfig) -> } let decrypted_key = if config.kms_api_key.is_empty() { - let secret_region = config - .api_key_secret_arn - .split(':') - .nth(3) - .unwrap_or(&aws_config.region); - decrypt_aws_sm( - &client, - config.api_key_secret_arn.clone(), - aws_config, - secret_region.to_string(), - ) - .await + decrypt_aws_sm(&client, config.api_key_secret_arn.clone(), aws_config).await } else { decrypt_aws_kms(&client, config.kms_api_key.clone(), aws_config).await }; @@ -164,10 +153,13 @@ async fn decrypt_aws_sm( client: &Client, secret_arn: String, aws_config: &AwsConfig, - secret_region: String, ) -> Result> { let json_body = &serde_json::json!({ "SecretId": secret_arn}); - + let secret_region = secret_arn + .split(':') + .nth(3) + .unwrap_or(&aws_config.region) + .to_string(); let headers = build_get_secret_signed_headers( aws_config, secret_region, @@ -418,7 +410,7 @@ mod tests { ).unwrap(); let mut expected_headers = HeaderMap::new(); - expected_headers.insert("authorization", HeaderValue::from_str("AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20240530/us-east-1/secretsmanager/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=233e62035a3d0db0eed5b163ccb77e65fbab95df22e19aafdc2d9d0c1b1cc7a5").unwrap()); + expected_headers.insert("authorization", HeaderValue::from_str("AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20240530/us-west-2/secretsmanager/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=bd77112dd24d7a35566b29a39861f7421660c49b065964212f966db509e90813").unwrap()); expected_headers.insert( "host", HeaderValue::from_str("secretsmanager.us-west-2.amazonaws.com").unwrap(), From 66f3b7e1e77d069599f0f029194e93f0a841c5aa Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Tue, 11 Mar 2025 15:03:28 -0400 Subject: [PATCH 4/4] add comment --- bottlecap/src/secrets/decrypt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bottlecap/src/secrets/decrypt.rs b/bottlecap/src/secrets/decrypt.rs index a640642dc..de0376e09 100644 --- a/bottlecap/src/secrets/decrypt.rs +++ b/bottlecap/src/secrets/decrypt.rs @@ -155,6 +155,7 @@ async fn decrypt_aws_sm( aws_config: &AwsConfig, ) -> Result> { let json_body = &serde_json::json!({ "SecretId": secret_arn}); + // Supports cross-region secrets let secret_region = secret_arn .split(':') .nth(3)