diff --git a/bottlecap/src/secrets/decrypt.rs b/bottlecap/src/secrets/decrypt.rs index 47467c7ee..b014c3b16 100644 --- a/bottlecap/src/secrets/decrypt.rs +++ b/bottlecap/src/secrets/decrypt.rs @@ -202,8 +202,20 @@ async fn decrypt_aws_sm( ); let v = request(json_body, headers?, client).await?; + extract_secret_string(&v) +} + +// When a Secrets Manager secret is a JSON object, this key is used to extract the API key. +// Falls back to the raw secret string if the key is absent or the value is not valid JSON. +const JSON_SECRET_DD_API_KEY: &str = "dd_api_key"; +fn extract_secret_string(v: &Value) -> Result> { if let Some(secret_string) = v["SecretString"].as_str() { + if let Ok(parsed) = serde_json::from_str::(secret_string) + && let Some(extracted) = parsed[JSON_SECRET_DD_API_KEY].as_str() + { + return Ok(extracted.to_string()); + } Ok(secret_string.to_string()) } else { Err(Error::new(std::io::ErrorKind::InvalidData, v.to_string()).into()) @@ -419,6 +431,40 @@ mod tests { use super::*; use chrono::{NaiveDateTime, TimeZone}; + fn make_sm_response(secret_string: &str) -> Value { + serde_json::json!({ "SecretString": secret_string }) + } + + #[test] + fn test_json_secret_extraction() { + let v = make_sm_response(r#"{"dd_api_key":"abc123"}"#); + let result = extract_secret_string(&v).expect("should extract dd_api_key"); + assert_eq!(result, "abc123"); + } + + #[test] + fn test_json_secret_missing_key_falls_back_to_raw() { + let raw = r#"{"other_key":"abc123"}"#; + let v = make_sm_response(raw); + let result = extract_secret_string(&v).expect("should fall back to raw JSON string"); + assert_eq!(result, raw); + } + + #[test] + fn test_plain_secret_unaffected() { + let v = make_sm_response("abc123"); + let result = extract_secret_string(&v).expect("should return raw value"); + assert_eq!(result, "abc123"); + } + + #[test] + fn test_malformed_json_secret_falls_back_to_raw() { + let raw = r#"{"dd_api_key":"abc123""#; // missing closing brace + let v = make_sm_response(raw); + let result = extract_secret_string(&v).expect("should fall back to raw string"); + assert_eq!(result, raw); + } + #[test] fn key_cleanup() { let key = clean_api_key(Some(" 32alxcxf\n".to_string()));