Skip to content

Commit dd4342f

Browse files
authored
Merge branch 'main' into fix/ecs-updateservice-fields
2 parents f332765 + db18c50 commit dd4342f

3 files changed

Lines changed: 90 additions & 2 deletions

File tree

crates/fakecloud-cloudformation/src/resource_provisioner/ses.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ impl ResourceProvisioner {
314314
mail_from_behavior_on_mx_failure: mail_from_behavior,
315315
mail_from_domain_status: "Success".to_string(),
316316
configuration_set_name,
317+
bounce_topic: None,
318+
complaint_topic: None,
319+
delivery_topic: None,
317320
};
318321
let mut accounts = self.ses_state.write();
319322
let state = accounts.get_or_create(&self.account_id);

crates/fakecloud-secretsmanager/src/service.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,7 +1570,19 @@ impl SecretsManagerService {
15701570
"CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
15711571
});
15721572
if let Some(ref s) = version.secret_string {
1573-
entry["SecretString"] = json!(s);
1573+
// Decrypt the same way GetSecretValue does;
1574+
// pushing the raw stored value leaked
1575+
// ciphertext when a KMS hook + KmsKeyId were
1576+
// configured (bug-audit 2026-06-20, 1.10).
1577+
let plaintext = self
1578+
.maybe_decrypt_secret_string(
1579+
&req.account_id,
1580+
&secret.arn,
1581+
secret.kms_key_id.as_deref(),
1582+
Some(s.as_str()),
1583+
)
1584+
.unwrap_or_else(|| s.clone());
1585+
entry["SecretString"] = json!(plaintext);
15741586
}
15751587
if let Some(ref b) = version.secret_binary {
15761588
entry["SecretBinary"] = json!(base64_encode(b));
@@ -1647,7 +1659,17 @@ impl SecretsManagerService {
16471659
"CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
16481660
});
16491661
if let Some(ref s) = version.secret_string {
1650-
entry["SecretString"] = json!(s);
1662+
// Decrypt like GetSecretValue; the raw stored value
1663+
// is ciphertext under a configured KMS hook (1.10).
1664+
let plaintext = self
1665+
.maybe_decrypt_secret_string(
1666+
&req.account_id,
1667+
&secret.arn,
1668+
secret.kms_key_id.as_deref(),
1669+
Some(s.as_str()),
1670+
)
1671+
.unwrap_or_else(|| s.clone());
1672+
entry["SecretString"] = json!(plaintext);
16511673
}
16521674
if let Some(ref b) = version.secret_binary {
16531675
entry["SecretBinary"] = json!(base64_encode(b));

crates/fakecloud-secretsmanager/src/service_tests.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2295,3 +2295,66 @@ async fn snapshot_hook_fires_with_store() {
22952295
.expect("hook present when a store is set");
22962296
hook().await;
22972297
}
2298+
2299+
/// A KMS hook that "encrypts" by prefixing `ENC:` and "decrypts" by stripping
2300+
/// it, so a test can prove a read path returns plaintext, not the stored
2301+
/// ciphertext.
2302+
struct PrefixKmsHook;
2303+
impl fakecloud_core::delivery::KmsHook for PrefixKmsHook {
2304+
fn encrypt(
2305+
&self,
2306+
_account_id: &str,
2307+
_region: &str,
2308+
_key_id: &str,
2309+
plaintext: &[u8],
2310+
_service_principal: &str,
2311+
_ctx: std::collections::HashMap<String, String>,
2312+
) -> Result<String, String> {
2313+
Ok(format!("ENC:{}", String::from_utf8_lossy(plaintext)))
2314+
}
2315+
fn decrypt(
2316+
&self,
2317+
_account_id: &str,
2318+
ciphertext_b64: &str,
2319+
_service_principal: &str,
2320+
_ctx: std::collections::HashMap<String, String>,
2321+
) -> Result<Vec<u8>, String> {
2322+
Ok(ciphertext_b64
2323+
.strip_prefix("ENC:")
2324+
.unwrap_or(ciphertext_b64)
2325+
.as_bytes()
2326+
.to_vec())
2327+
}
2328+
}
2329+
2330+
#[tokio::test]
2331+
async fn batch_get_secret_value_returns_plaintext_not_ciphertext() {
2332+
// GetSecretValue decrypts a KMS-backed secret, but BatchGetSecretValue
2333+
// pushed the raw stored ciphertext -- so a batch fetch returned an
2334+
// unusable encrypted blob (bug-audit 2026-06-20, 1.10).
2335+
let state = make_state();
2336+
let svc = SecretsManagerService::new(state).with_kms_hook(std::sync::Arc::new(PrefixKmsHook));
2337+
2338+
let req = make_request(
2339+
"CreateSecret",
2340+
r#"{"Name":"enc-secret","SecretString":"topsecret","KmsKeyId":"alias/test"}"#,
2341+
);
2342+
svc.handle(req).await.unwrap();
2343+
2344+
// Sanity: single-get decrypts.
2345+
let req = make_request("GetSecretValue", r#"{"SecretId":"enc-secret"}"#);
2346+
let single: Value =
2347+
serde_json::from_slice(svc.handle(req).await.unwrap().body.expect_bytes()).unwrap();
2348+
assert_eq!(single["SecretString"], "topsecret");
2349+
2350+
// The fix: batch must decrypt too.
2351+
let body = serde_json::json!({ "SecretIdList": ["enc-secret"] });
2352+
let req = make_request("BatchGetSecretValue", &body.to_string());
2353+
let batch: Value =
2354+
serde_json::from_slice(svc.handle(req).await.unwrap().body.expect_bytes()).unwrap();
2355+
let v = &batch["SecretValues"].as_array().unwrap()[0];
2356+
assert_eq!(
2357+
v["SecretString"], "topsecret",
2358+
"BatchGetSecretValue must return plaintext, not ciphertext"
2359+
);
2360+
}

0 commit comments

Comments
 (0)