From 3ad70ec20bae248fa06eb3d37e7f4a95eb688012 Mon Sep 17 00:00:00 2001 From: Mike Heffner Date: Sun, 28 Jun 2026 22:53:52 -0400 Subject: [PATCH 1/2] Fix Secrets Manager ARN lookup failing when suffix is absent in user-supplied ARN AWS Secrets Manager always returns the full ARN with its random 6-char suffix (e.g. myapp/api_key-AbCdEf) in BatchGetSecretValue responses, even when the caller specified the ARN without that suffix. The returned ARN failed to match back against arns_by_base, causing a spurious "Returned secret ARN was not found" error. Add a strip_sm_arn_suffix fallback so that if the direct ARN lookup misses, we try again after stripping the trailing -XXXXXX suffix. --- src/env.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/env.rs b/src/env.rs index 4224d45..b77d112 100644 --- a/src/env.rs +++ b/src/env.rs @@ -85,6 +85,30 @@ impl EnvArnParser { } } +// Secrets Manager always returns the full ARN with its 6-char random suffix (e.g. +// "myapp/api_key-AbCdEf") even when the caller specified the ARN without one. +// Strip it so the returned ARN can be matched back to the user-supplied ARN. +fn strip_sm_arn_suffix(arn_str: &str) -> Option { + let last_colon = arn_str.rfind(':')?; + let resource_id = &arn_str[last_colon + 1..]; + if resource_id.len() < 7 { + return None; + } + let suffix_start = resource_id.len() - 7; + let potential_suffix = &resource_id[suffix_start..]; + if potential_suffix.starts_with('-') + && potential_suffix[1..].chars().all(|c| c.is_ascii_alphanumeric()) + { + Some(format!( + "{}{}", + &arn_str[..last_colon + 1], + &resource_id[..suffix_start] + )) + } else { + None + } +} + pub async fn resolve_secrets( aws_creds: AwsCreds, secure_arns: &mut HashMap, @@ -143,7 +167,12 @@ pub async fn resolve_secrets( Ok(res) => { for (arn, secret) in res { let aws_arn = arn.parse::()?; - match arns_by_base.get(&aws_arn) { + let lookup = arns_by_base.get(&aws_arn).or_else(|| { + strip_sm_arn_suffix(&arn) + .and_then(|s| s.parse::().ok()) + .and_then(|a| arns_by_base.get(&a)) + }); + match lookup { None => { return Err(format!( "Returned secret ARN was not found: {}", @@ -231,10 +260,28 @@ pub async fn resolve_secrets( mod tests { use rotel::aws_api::creds::AwsCreds; - use crate::env::{EnvArnParser, resolve_secrets}; + use crate::env::{EnvArnParser, resolve_secrets, strip_sm_arn_suffix}; use crate::test_util::{init_crypto, parse_test_arns}; use std::collections::HashMap; + #[test] + fn test_strip_sm_arn_suffix() { + let full = "arn:aws:secretsmanager:eu-central-1:123456789012:secret:myapp/api_key-AbCdEf"; + let without = "arn:aws:secretsmanager:eu-central-1:123456789012:secret:myapp/api_key"; + assert_eq!(strip_sm_arn_suffix(full), Some(without.to_string())); + + // Already without suffix — no change + assert_eq!(strip_sm_arn_suffix(without), None); + + // Suffix-looking but only 5 chars after dash — not stripped + let five = "arn:aws:secretsmanager:us-east-1:123456789012:secret:name-AbCdE"; + assert_eq!(strip_sm_arn_suffix(five), None); + + // Simple name (no suffix) + let simple = "arn:aws:secretsmanager:us-east-1:123456789012:secret:short"; + assert_eq!(strip_sm_arn_suffix(simple), None); + } + #[test] fn test_extract_and_update_arns_from_env() { unsafe { std::env::set_var("ROTEL_DONT_EXPAND", "${SOMETHING}") } From 0f35dae4762f8f8fbab97d647e8942eba8d50694 Mon Sep 17 00:00:00 2001 From: Mike Heffner Date: Sun, 28 Jun 2026 22:57:05 -0400 Subject: [PATCH 2/2] fmt --- src/env.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/env.rs b/src/env.rs index b77d112..7803e0e 100644 --- a/src/env.rs +++ b/src/env.rs @@ -97,7 +97,9 @@ fn strip_sm_arn_suffix(arn_str: &str) -> Option { let suffix_start = resource_id.len() - 7; let potential_suffix = &resource_id[suffix_start..]; if potential_suffix.starts_with('-') - && potential_suffix[1..].chars().all(|c| c.is_ascii_alphanumeric()) + && potential_suffix[1..] + .chars() + .all(|c| c.is_ascii_alphanumeric()) { Some(format!( "{}{}",