From 3419b137f879f9aae9b38f6745b095542dcbecdc Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Tue, 29 Sep 2026 02:02:38 -0300 Subject: [PATCH 1/3] fix(kms): mint default KMS key ARNs in the region's partition RDS, EFS, MSK and Timestream synthesized default or expanded KMS key ARNs with a hardcoded aws partition, so a cn-/us-gov- resource pointed at a key ARN KMS itself would never produce. They now build it with fakecloud_kms::kms_key_arn / kms_alias_arn, byte-identical in commercial regions. --- Cargo.lock | 4 +++ crates/fakecloud-efs/Cargo.toml | 1 + crates/fakecloud-efs/src/service.rs | 41 +++++++++++++++++++--- crates/fakecloud-kafka/Cargo.toml | 1 + crates/fakecloud-kafka/src/builders.rs | 2 +- crates/fakecloud-kafka/src/service.rs | 12 +++++++ crates/fakecloud-rds/Cargo.toml | 1 + crates/fakecloud-rds/src/extras/mod.rs | 1 - crates/fakecloud-rds/src/extras/parse.rs | 4 +-- crates/fakecloud-rds/src/extras/tests.rs | 26 ++++++++++++++ crates/fakecloud-timestream/Cargo.toml | 1 + crates/fakecloud-timestream/src/service.rs | 32 +++++++++++++++-- 12 files changed, 115 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff85c12f2..32193f0ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5631,6 +5631,7 @@ dependencies = [ "fakecloud-aws", "fakecloud-core", "fakecloud-ec2", + "fakecloud-kms", "fakecloud-persistence", "http 1.4.0", "parking_lot", @@ -6000,6 +6001,7 @@ dependencies = [ "chrono", "fakecloud-aws", "fakecloud-core", + "fakecloud-kms", "fakecloud-persistence", "http 1.4.0", "parking_lot", @@ -6438,6 +6440,7 @@ dependencies = [ "fakecloud-aws", "fakecloud-core", "fakecloud-k8s", + "fakecloud-kms", "fakecloud-persistence", "http 1.4.0", "k8s-openapi", @@ -7074,6 +7077,7 @@ dependencies = [ "chrono", "fakecloud-aws", "fakecloud-core", + "fakecloud-kms", "fakecloud-persistence", "http 1.4.0", "parking_lot", diff --git a/crates/fakecloud-efs/Cargo.toml b/crates/fakecloud-efs/Cargo.toml index 28ba18e52..e758dc9d5 100644 --- a/crates/fakecloud-efs/Cargo.toml +++ b/crates/fakecloud-efs/Cargo.toml @@ -10,6 +10,7 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } +fakecloud-kms = { workspace = true } fakecloud-persistence = { workspace = true } fakecloud-ec2 = { workspace = true } async-trait = { workspace = true } diff --git a/crates/fakecloud-efs/src/service.rs b/crates/fakecloud-efs/src/service.rs index 52de42184..a02143b4b 100644 --- a/crates/fakecloud-efs/src/service.rs +++ b/crates/fakecloud-efs/src/service.rs @@ -675,16 +675,15 @@ impl EfsService { .and_then(Value::as_str) .map(str::to_string) .unwrap_or_else(|| { - format!( - "arn:aws:kms:{}:{}:key/{}-{}-{}-{}-{}", - ctx.region, - ctx.account, + let key_id = format!( + "{}-{}-{}-{}-{}", &hex17()[..8], &hex17()[..4], &hex17()[..4], &hex17()[..4], &hex17()[..12.min(hex17().len())] - ) + ); + fakecloud_kms::kms_key_arn(&ctx.region, &ctx.account, &key_id) }); fs.insert("KmsKeyId".into(), json!(kms)); } @@ -1888,6 +1887,38 @@ mod tests { ); } + /// An encrypted file system's default key is the ARN KMS itself mints for + /// that key id in the caller's region, partition included. + #[test] + fn default_kms_key_uses_the_regions_partition() { + let s = svc(); + let c = Ctx { + account: "000000000000".to_string(), + region: "cn-north-1".to_string(), + }; + let fs = body_value( + &s.create_file_system(&c, &json!({ "CreationToken": "enc", "Encrypted": true })) + .unwrap(), + ); + let kms = fs["KmsKeyId"].as_str().unwrap(); + let key_id = kms + .strip_prefix("arn:aws-cn:kms:cn-north-1:000000000000:key/") + .unwrap_or_else(|| panic!("{kms}")); + assert_eq!( + kms, + fakecloud_kms::kms_key_arn("cn-north-1", "000000000000", key_id) + ); + // Commercial output keeps the `aws` partition. + let fs = body_value( + &s.create_file_system(&ctx(), &json!({ "CreationToken": "us", "Encrypted": true })) + .unwrap(), + ); + assert!(fs["KmsKeyId"] + .as_str() + .unwrap() + .starts_with("arn:aws:kms:us-east-1:000000000000:key/")); + } + // Defect #1: a nonexistent subnet must be rejected with SubnetNotFound once // EC2 state is wired to validate against (rather than synthesizing one). #[test] diff --git a/crates/fakecloud-kafka/Cargo.toml b/crates/fakecloud-kafka/Cargo.toml index b49c211c0..14a51df67 100644 --- a/crates/fakecloud-kafka/Cargo.toml +++ b/crates/fakecloud-kafka/Cargo.toml @@ -10,6 +10,7 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } +fakecloud-kms = { workspace = true } fakecloud-persistence = { workspace = true } async-trait = { workspace = true } base64 = { workspace = true } diff --git a/crates/fakecloud-kafka/src/builders.rs b/crates/fakecloud-kafka/src/builders.rs index 9fcefe183..11bc55bbe 100644 --- a/crates/fakecloud-kafka/src/builders.rs +++ b/crates/fakecloud-kafka/src/builders.rs @@ -34,7 +34,7 @@ pub(crate) fn default_bngi() -> Value { /// (real MSK always echoes a resolved key ARN, so `encryption_info` round-trips /// and `MatchResourceAttrRegionalARN(..., "kms", "key/.+")` matches). fn default_kms_key_arn(region: &str, account: &str) -> String { - format!("arn:aws:kms:{region}:{account}:key/{}", Uuid::new_v4()) + fakecloud_kms::kms_key_arn(region, account, &Uuid::new_v4().to_string()) } /// Fill an `EncryptionInfo` with the sub-objects + AWS defaults `DescribeCluster` diff --git a/crates/fakecloud-kafka/src/service.rs b/crates/fakecloud-kafka/src/service.rs index cfde04326..4869abf87 100644 --- a/crates/fakecloud-kafka/src/service.rs +++ b/crates/fakecloud-kafka/src/service.rs @@ -3543,6 +3543,18 @@ mod tests { settle(&s, &c); let d = json_of(s.describe_cluster(&c, &arn, false).unwrap()); assert_eq!(d["clusterInfo"]["clusterArn"], json!(arn)); + // The synthesized at-rest key is the ARN KMS itself mints for that key + // id in the cluster's region. + let kms = d["clusterInfo"]["encryptionInfo"]["encryptionAtRest"]["dataVolumeKMSKeyId"] + .as_str() + .unwrap(); + let key_id = kms + .strip_prefix("arn:aws-cn:kms:cn-north-1:123456789012:key/") + .unwrap_or_else(|| panic!("{kms}")); + assert_eq!( + kms, + fakecloud_kms::kms_key_arn("cn-north-1", "123456789012", key_id) + ); let nodes = json_of(s.list_nodes(&c, &arn, &[]).unwrap()); let node_arn = nodes["nodeInfoList"][0]["nodeARN"].as_str().unwrap(); assert!( diff --git a/crates/fakecloud-rds/Cargo.toml b/crates/fakecloud-rds/Cargo.toml index 36e1067d9..bd7bcfd00 100644 --- a/crates/fakecloud-rds/Cargo.toml +++ b/crates/fakecloud-rds/Cargo.toml @@ -10,6 +10,7 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } +fakecloud-kms = { workspace = true } fakecloud-k8s = { workspace = true } fakecloud-persistence = { workspace = true } k8s-openapi = { workspace = true } diff --git a/crates/fakecloud-rds/src/extras/mod.rs b/crates/fakecloud-rds/src/extras/mod.rs index 8bbb9bd72..a91bf537a 100644 --- a/crates/fakecloud-rds/src/extras/mod.rs +++ b/crates/fakecloud-rds/src/extras/mod.rs @@ -15,7 +15,6 @@ use http::StatusCode; use serde_json::{json, Value}; use std::collections::BTreeMap; -use fakecloud_aws::arn::Arn; use fakecloud_aws::xml::xml_escape; use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError}; diff --git a/crates/fakecloud-rds/src/extras/parse.rs b/crates/fakecloud-rds/src/extras/parse.rs index 2fb25c26f..4879e51bd 100644 --- a/crates/fakecloud-rds/src/extras/parse.rs +++ b/crates/fakecloud-rds/src/extras/parse.rs @@ -144,9 +144,9 @@ pub(super) fn format_kms_arn(input: &str, region: &str, account_id: &str) -> Str return input.to_string(); } if input.starts_with("alias/") { - return Arn::new("kms", region, account_id, input).to_string(); + return fakecloud_kms::kms_alias_arn(region, account_id, input); } - Arn::new("kms", region, account_id, &format!("key/{input}")).to_string() + fakecloud_kms::kms_key_arn(region, account_id, input) } /// Render a whole extras category as a Describe list response. diff --git a/crates/fakecloud-rds/src/extras/tests.rs b/crates/fakecloud-rds/src/extras/tests.rs index c863f99d2..d394d4ca0 100644 --- a/crates/fakecloud-rds/src/extras/tests.rs +++ b/crates/fakecloud-rds/src/extras/tests.rs @@ -6563,3 +6563,29 @@ fn describe_db_cluster_snapshots_filters_by_snapshot_type_and_cluster() { assert!(body.contains("auto-snap")); assert!(body.contains("manual-snap")); } + +/// A bare key id or alias echoed back as `KmsKeyId` must be the ARN KMS +/// itself mints for that key in the caller's region, partition included. +#[test] +fn kms_key_id_is_expanded_in_the_regions_partition() { + let key = super::format_kms_arn("1234abcd", "cn-north-1", "000000000000"); + assert_eq!(key, "arn:aws-cn:kms:cn-north-1:000000000000:key/1234abcd"); + assert_eq!( + key, + fakecloud_kms::kms_key_arn("cn-north-1", "000000000000", "1234abcd") + ); + let alias = super::format_kms_arn("alias/aws/rds", "cn-north-1", "000000000000"); + assert_eq!( + alias, + "arn:aws-cn:kms:cn-north-1:000000000000:alias/aws/rds" + ); + assert_eq!( + alias, + fakecloud_kms::kms_alias_arn("cn-north-1", "000000000000", "alias/aws/rds") + ); + // Commercial output is unchanged. + assert_eq!( + super::format_kms_arn("1234abcd", "us-east-1", "000000000000"), + "arn:aws:kms:us-east-1:000000000000:key/1234abcd" + ); +} diff --git a/crates/fakecloud-timestream/Cargo.toml b/crates/fakecloud-timestream/Cargo.toml index 0a1493e31..4f1d1d691 100644 --- a/crates/fakecloud-timestream/Cargo.toml +++ b/crates/fakecloud-timestream/Cargo.toml @@ -10,6 +10,7 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } +fakecloud-kms = { workspace = true } fakecloud-persistence = { workspace = true } async-trait = { workspace = true } chrono = { workspace = true } diff --git a/crates/fakecloud-timestream/src/service.rs b/crates/fakecloud-timestream/src/service.rs index d8b49fe17..00702c2aa 100644 --- a/crates/fakecloud-timestream/src/service.rs +++ b/crates/fakecloud-timestream/src/service.rs @@ -323,8 +323,10 @@ impl TimestreamService { name: name.clone(), arn: arn.clone(), kms_key_id: kms.or_else(|| { - Some(format!( - "arn:aws:kms:{region}:{account}:key/timestream-default" + Some(fakecloud_kms::kms_key_arn( + ®ion, + &account, + "timestream-default", )) }), table_count: 0, @@ -1479,6 +1481,32 @@ mod tests { assert_eq!(e.code(), "ResourceNotFoundException"); } + /// The default database key is the ARN KMS itself mints for that key id + /// in the caller's region, partition included. + #[test] + fn default_database_key_uses_the_regions_partition() { + let s = svc(); + let mut r = req("CreateDatabase", json!({ "DatabaseName": "cn" })); + r.region = "cn-north-1".to_string(); + let resp = s.dispatch("CreateDatabase", &r).unwrap(); + let out: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap(); + let kms = out["Database"]["KmsKeyId"].as_str().unwrap(); + assert_eq!( + kms, + "arn:aws-cn:kms:cn-north-1:000000000000:key/timestream-default" + ); + assert_eq!( + kms, + fakecloud_kms::kms_key_arn("cn-north-1", "000000000000", "timestream-default") + ); + // Commercial output is unchanged. + let out = call(&s, "CreateDatabase", json!({ "DatabaseName": "us" })).unwrap(); + assert_eq!( + out["Database"]["KmsKeyId"], + "arn:aws:kms:us-east-1:000000000000:key/timestream-default" + ); + } + #[test] fn table_requires_database() { let s = svc(); From 34169cb599bb71ce9c02e55bf3fb34468b943ee6 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Tue, 29 Sep 2026 02:08:25 -0300 Subject: [PATCH 2/3] fix: export partition-aware ARN builders and convert the remaining hardcoded partitions Export SNS topic_arn, EventBridge bus_arn/rule_arn, CloudWatch alarm_arn and SSM param_arn at their crate roots so the CloudFormation provisioner can build the same ARNs the services do. Athena workgroup, data catalog and capacity reservation ARNs now take the region's partition, and a Bedrock prompt router's default model ARN is built in the router's own region and partition instead of a fixed arn:aws:bedrock:us-east-1 one. --- crates/fakecloud-athena/src/service/mod.rs | 44 ++++++++++++- .../fakecloud-bedrock/src/prompt_routers.rs | 65 +++++++++++++++---- crates/fakecloud-cloudwatch/src/lib.rs | 6 +- crates/fakecloud-cloudwatch/src/state.rs | 2 +- crates/fakecloud-eventbridge/src/lib.rs | 5 +- crates/fakecloud-eventbridge/src/state.rs | 4 +- crates/fakecloud-sns/src/helpers.rs | 2 +- crates/fakecloud-sns/src/lib.rs | 2 +- crates/fakecloud-sns/src/service.rs | 1 + crates/fakecloud-ssm/src/lib.rs | 2 +- crates/fakecloud-ssm/src/service/mod.rs | 1 + .../fakecloud-ssm/src/service/parameters.rs | 2 +- 12 files changed, 109 insertions(+), 27 deletions(-) diff --git a/crates/fakecloud-athena/src/service/mod.rs b/crates/fakecloud-athena/src/service/mod.rs index c687a2342..5ba3a2f98 100644 --- a/crates/fakecloud-athena/src/service/mod.rs +++ b/crates/fakecloud-athena/src/service/mod.rs @@ -664,7 +664,7 @@ fn workgroup_arn(account_id: &str, region: &str, name: &str) -> String { } else { region }; - Arn::new("athena", region, account_id, &format!("workgroup/{name}")).to_string() + Arn::regional("athena", region, account_id, &format!("workgroup/{name}")).to_string() } fn datacatalog_arn(account_id: &str, region: &str, name: &str) -> String { @@ -673,7 +673,7 @@ fn datacatalog_arn(account_id: &str, region: &str, name: &str) -> String { } else { region }; - Arn::new("athena", region, account_id, &format!("datacatalog/{name}")).to_string() + Arn::regional("athena", region, account_id, &format!("datacatalog/{name}")).to_string() } fn capacity_reservation_arn(account_id: &str, region: &str, name: &str) -> String { @@ -682,7 +682,7 @@ fn capacity_reservation_arn(account_id: &str, region: &str, name: &str) -> Strin } else { region }; - Arn::new( + Arn::regional( "athena", region, account_id, @@ -2004,6 +2004,44 @@ mod tests { assert_eq!(deleted["DataCatalog"]["Type"], "GLUE"); assert_eq!(deleted["DataCatalog"]["Status"], "DELETE_COMPLETE"); } + + /// Athena ARNs take the partition of the region they are minted in, so a + /// resource created in a China region is tagged and looked up under its + /// `aws-cn` ARN. + #[test] + fn arns_use_the_regions_partition() { + assert_eq!( + super::workgroup_arn("123456789012", "cn-north-1", "wg"), + "arn:aws-cn:athena:cn-north-1:123456789012:workgroup/wg" + ); + assert_eq!( + super::datacatalog_arn("123456789012", "cn-north-1", "cat"), + "arn:aws-cn:athena:cn-north-1:123456789012:datacatalog/cat" + ); + assert_eq!( + super::capacity_reservation_arn("123456789012", "us-gov-west-1", "cr"), + "arn:aws-us-gov:athena:us-gov-west-1:123456789012:capacity-reservation/cr" + ); + assert_eq!( + super::workgroup_arn("123456789012", "us-east-1", "wg"), + "arn:aws:athena:us-east-1:123456789012:workgroup/wg" + ); + + let svc = AthenaService::new(SharedAthenaState::default()); + let mut create = req( + "CreateWorkGroup", + json!({ "Name": "cnwg", "Tags": [{ "Key": "team", "Value": "data" }] }), + ); + create.region = "cn-north-1".to_string(); + svc.create_work_group(&create).unwrap(); + let mut list = req( + "ListTagsForResource", + json!({ "ResourceARN": "arn:aws-cn:athena:cn-north-1:123456789012:workgroup/cnwg" }), + ); + list.region = "cn-north-1".to_string(); + let tags = parse_json(&svc.list_tags_for_resource(&list).unwrap()); + assert_eq!(tags["Tags"], json!([{ "Key": "team", "Value": "data" }])); + } } #[cfg(test)] diff --git a/crates/fakecloud-bedrock/src/prompt_routers.rs b/crates/fakecloud-bedrock/src/prompt_routers.rs index a873e130a..3126f21c9 100644 --- a/crates/fakecloud-bedrock/src/prompt_routers.rs +++ b/crates/fakecloud-bedrock/src/prompt_routers.rs @@ -71,9 +71,9 @@ pub(crate) fn get_prompt_router( "promptRouterArn": router.prompt_router_arn, "promptRouterName": router.prompt_router_name, "description": router.description, - "models": ensure_router_models(&router.models), + "models": ensure_router_models(&router.models, &router.prompt_router_arn), "routingCriteria": ensure_routing_criteria(&router.routing_criteria), - "fallbackModel": ensure_fallback_model(&router.fallback_model), + "fallbackModel": ensure_fallback_model(&router.fallback_model, &router.prompt_router_arn), "status": router.status, "type": router.prompt_router_type, "createdAt": router.created_at.to_rfc3339(), @@ -118,8 +118,8 @@ pub(crate) fn list_prompt_routers( "promptRouterName": r.prompt_router_name, "description": r.description, "routingCriteria": ensure_routing_criteria(&r.routing_criteria), - "models": ensure_router_models(&r.models), - "fallbackModel": ensure_fallback_model(&r.fallback_model), + "models": ensure_router_models(&r.models, &r.prompt_router_arn), + "fallbackModel": ensure_fallback_model(&r.fallback_model, &r.prompt_router_arn), "status": r.status, "type": r.prompt_router_type, "createdAt": r.created_at.to_rfc3339(), @@ -148,28 +148,35 @@ fn ensure_routing_criteria(criteria: &Value) -> Value { json!({ "responseQualityDifference": 0.0 }) } +/// The foundation model a router defaults to, in the router's own region and +/// partition. +fn default_router_model_arn(router_arn: &str) -> String { + let region = router_arn + .split(':') + .nth(3) + .filter(|r| !r.is_empty()) + .unwrap_or("us-east-1"); + crate::arns::foundation_model_arn(region, "amazon.titan-text-express-v1") +} + /// Default the router's target models to a single Titan entry when none were /// supplied at create time, matching the required Smithy field. -fn ensure_router_models(models: &Value) -> Value { +fn ensure_router_models(models: &Value, router_arn: &str) -> Value { if let Some(arr) = models.as_array() { if !arr.is_empty() { return models.clone(); } } - json!([{ - "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-text-express-v1" - }]) + json!([{ "modelArn": default_router_model_arn(router_arn) }]) } /// Default the fallback model when not provided. `fallbackModel` is required /// in the summary even though it's optional on create. -fn ensure_fallback_model(fallback: &Value) -> Value { +fn ensure_fallback_model(fallback: &Value, router_arn: &str) -> Value { if fallback.get("modelArn").is_some() { return fallback.clone(); } - json!({ - "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-text-express-v1" - }) + json!({ "modelArn": default_router_model_arn(router_arn) }) } pub(crate) fn delete_prompt_router( @@ -274,6 +281,40 @@ mod tests { assert!(get_prompt_router(&s, &req(), "my-router").is_ok()); } + /// A router created without models defaults them to a foundation model in + /// the router's own region and partition. + #[test] + fn default_models_use_the_routers_partition() { + let s = shared(); + let mut r = req(); + r.region = "cn-north-1".to_string(); + let resp = create_prompt_router(&s, &r, &json!({ "promptRouterName": "cn" })).unwrap(); + let v: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap(); + let arn = v["promptRouterArn"].as_str().unwrap().to_string(); + assert!(arn.starts_with("arn:aws-cn:bedrock:cn-north-1:"), "{arn}"); + let got = get_prompt_router(&s, &r, &arn).unwrap(); + let g: Value = serde_json::from_slice(got.body.expect_bytes()).unwrap(); + let titan = "arn:aws-cn:bedrock:cn-north-1::foundation-model/amazon.titan-text-express-v1"; + assert_eq!(g["models"], json!([{ "modelArn": titan }])); + assert_eq!(g["fallbackModel"], json!({ "modelArn": titan })); + let listed = list_prompt_routers(&s, &r).unwrap(); + let l: Value = serde_json::from_slice(listed.body.expect_bytes()).unwrap(); + assert_eq!( + l["promptRouterSummaries"][0]["fallbackModel"]["modelArn"], + titan + ); + + // Commercial output is unchanged. + let arn = create_prompt_router(&s, &req(), &json!({ "promptRouterName": "us" })).unwrap(); + let v: Value = serde_json::from_slice(arn.body.expect_bytes()).unwrap(); + let got = get_prompt_router(&s, &req(), v["promptRouterArn"].as_str().unwrap()).unwrap(); + let g: Value = serde_json::from_slice(got.body.expect_bytes()).unwrap(); + assert_eq!( + g["fallbackModel"]["modelArn"], + "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-text-express-v1" + ); + } + #[test] fn get_unknown_returns_not_found() { let s = shared(); diff --git a/crates/fakecloud-cloudwatch/src/lib.rs b/crates/fakecloud-cloudwatch/src/lib.rs index 7b18593bb..e812004bb 100644 --- a/crates/fakecloud-cloudwatch/src/lib.rs +++ b/crates/fakecloud-cloudwatch/src/lib.rs @@ -21,7 +21,7 @@ mod tests; pub use delivery::CloudwatchDeliveryImpl; pub use service::CloudWatchService; pub use state::{ - AlarmMetricQuery, AlarmMetricStat, AlarmState, CloudWatchAccounts, CloudWatchSnapshot, - CloudWatchState, Dashboard, MetricAlarm, MetricDatum, SharedCloudWatchState, - CLOUDWATCH_SNAPSHOT_SCHEMA_VERSION, + alarm_arn, AlarmMetricQuery, AlarmMetricStat, AlarmState, CloudWatchAccounts, + CloudWatchSnapshot, CloudWatchState, Dashboard, MetricAlarm, MetricDatum, + SharedCloudWatchState, CLOUDWATCH_SNAPSHOT_SCHEMA_VERSION, }; diff --git a/crates/fakecloud-cloudwatch/src/state.rs b/crates/fakecloud-cloudwatch/src/state.rs index cb8f1749f..142d1d733 100644 --- a/crates/fakecloud-cloudwatch/src/state.rs +++ b/crates/fakecloud-cloudwatch/src/state.rs @@ -373,7 +373,7 @@ pub struct AlarmHistoryItem { /// The ARN of alarm `name` (metric, composite or log alarm) in `region`'s /// partition. -pub(crate) fn alarm_arn(region: &str, account_id: &str, name: &str) -> String { +pub fn alarm_arn(region: &str, account_id: &str, name: &str) -> String { fakecloud_aws::arn::Arn::regional("cloudwatch", region, account_id, &format!("alarm:{name}")) .to_string() } diff --git a/crates/fakecloud-eventbridge/src/lib.rs b/crates/fakecloud-eventbridge/src/lib.rs index 63ce6d43b..2ef088ab0 100644 --- a/crates/fakecloud-eventbridge/src/lib.rs +++ b/crates/fakecloud-eventbridge/src/lib.rs @@ -8,6 +8,7 @@ pub(crate) mod state; pub use service::helpers::parse_target; pub use service::EventBridgeService; pub use state::{ - ApiDestination, Archive, Connection, Endpoint, EventBridgeSnapshot, EventBridgeState, EventBus, - EventRule, EventTarget, SharedEventBridgeState, EVENTBRIDGE_SNAPSHOT_SCHEMA_VERSION, + bus_arn, rule_arn, ApiDestination, Archive, Connection, Endpoint, EventBridgeSnapshot, + EventBridgeState, EventBus, EventRule, EventTarget, SharedEventBridgeState, + EVENTBRIDGE_SNAPSHOT_SCHEMA_VERSION, }; diff --git a/crates/fakecloud-eventbridge/src/state.rs b/crates/fakecloud-eventbridge/src/state.rs index 28c33cba4..65b4b737c 100644 --- a/crates/fakecloud-eventbridge/src/state.rs +++ b/crates/fakecloud-eventbridge/src/state.rs @@ -49,13 +49,13 @@ pub(crate) fn arn_with_request_region(arn: &str, region: &str) -> String { } /// The ARN of the event bus `name` in `region`'s partition. -pub(crate) fn bus_arn(region: &str, account_id: &str, name: &str) -> String { +pub fn bus_arn(region: &str, account_id: &str, name: &str) -> String { Arn::regional("events", region, account_id, &format!("event-bus/{name}")).to_string() } /// The ARN of rule `name` on bus `bus`. Rules on the default bus leave the bus /// out of the resource path, as AWS does. -pub(crate) fn rule_arn(region: &str, account_id: &str, bus: &str, name: &str) -> String { +pub fn rule_arn(region: &str, account_id: &str, bus: &str, name: &str) -> String { let resource = if bus == "default" { format!("rule/{name}") } else { diff --git a/crates/fakecloud-sns/src/helpers.rs b/crates/fakecloud-sns/src/helpers.rs index 9353b248f..6e21c9e4e 100644 --- a/crates/fakecloud-sns/src/helpers.rs +++ b/crates/fakecloud-sns/src/helpers.rs @@ -38,7 +38,7 @@ pub(crate) fn is_mutating_action(action: &str) -> bool { } /// The ARN of topic `name` in `region`'s partition. -pub(crate) fn topic_arn(region: &str, account_id: &str, name: &str) -> String { +pub fn topic_arn(region: &str, account_id: &str, name: &str) -> String { fakecloud_aws::arn::Arn::regional("sns", region, account_id, name).to_string() } diff --git a/crates/fakecloud-sns/src/lib.rs b/crates/fakecloud-sns/src/lib.rs index 809f15337..83756cb2e 100644 --- a/crates/fakecloud-sns/src/lib.rs +++ b/crates/fakecloud-sns/src/lib.rs @@ -5,7 +5,7 @@ pub mod signing; pub mod simulation; pub(crate) mod state; -pub use service::SnsService; +pub use service::{topic_arn, SnsService}; pub use state::{ SharedSnsState, SnsSnapshot, SnsState, SnsSubscription, SnsTopic, SNS_SNAPSHOT_SCHEMA_VERSION, }; diff --git a/crates/fakecloud-sns/src/service.rs b/crates/fakecloud-sns/src/service.rs index c8148b836..2a3cf008f 100644 --- a/crates/fakecloud-sns/src/service.rs +++ b/crates/fakecloud-sns/src/service.rs @@ -1903,6 +1903,7 @@ mod service_sms; #[path = "helpers.rs"] mod helpers; +pub use helpers::topic_arn; pub(crate) use helpers::*; #[cfg(test)] diff --git a/crates/fakecloud-ssm/src/lib.rs b/crates/fakecloud-ssm/src/lib.rs index 0dcfc758f..2048434db 100644 --- a/crates/fakecloud-ssm/src/lib.rs +++ b/crates/fakecloud-ssm/src/lib.rs @@ -1,7 +1,7 @@ pub(crate) mod service; pub(crate) mod state; -pub use service::SsmService; +pub use service::{param_arn, SsmService}; pub use state::{ ParameterPolicyEvent, SharedSsmState, SsmParameter, SsmParameterVersion, SsmSnapshot, SsmState, SSM_SNAPSHOT_SCHEMA_VERSION, diff --git a/crates/fakecloud-ssm/src/service/mod.rs b/crates/fakecloud-ssm/src/service/mod.rs index dd241b42b..ada6f6e67 100644 --- a/crates/fakecloud-ssm/src/service/mod.rs +++ b/crates/fakecloud-ssm/src/service/mod.rs @@ -10,6 +10,7 @@ mod maintenance; mod misc; mod ops; mod parameters; +pub use parameters::param_arn; mod patches; mod resource_sync; mod sessions; diff --git a/crates/fakecloud-ssm/src/service/parameters.rs b/crates/fakecloud-ssm/src/service/parameters.rs index 7e8040dea..63f8b029a 100644 --- a/crates/fakecloud-ssm/src/service/parameters.rs +++ b/crates/fakecloud-ssm/src/service/parameters.rs @@ -1871,7 +1871,7 @@ pub(super) fn remove_param( parameters.remove(&alt) } -pub(super) fn param_arn(region: &str, account_id: &str, name: &str) -> String { +pub fn param_arn(region: &str, account_id: &str, name: &str) -> String { let resource = if name.starts_with('/') { format!("parameter{name}") } else { From cc5d3d1613f9138dabd203c160d2bc932b84f329 Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Tue, 29 Sep 2026 02:17:20 -0300 Subject: [PATCH 3/3] fix: address review of the final partition batch Build the default KMS key ARNs in RDS, EFS, MSK and Timestream with Arn::regional directly so those crates do not pull fakecloud-kms (and its crypto dependencies) into their build graph; fakecloud-kms stays a dev-dependency for the equality tests. Parse the Bedrock prompt router ARN with Arn's FromStr and fall back to the request's region, not a fixed us-east-1, when it does not parse. Collapse Athena's three ARN builders into one athena_arn, exported at the crate root for the CloudFormation provisioner. --- crates/fakecloud-athena/src/lib.rs | 2 +- .../fakecloud-athena/src/service/capacity.rs | 6 ++- .../src/service/data_catalogs.rs | 2 +- crates/fakecloud-athena/src/service/mod.rs | 39 ++++----------- .../src/service/work_groups.rs | 2 +- .../fakecloud-bedrock/src/prompt_routers.rs | 47 +++++++++++++------ crates/fakecloud-efs/Cargo.toml | 2 +- crates/fakecloud-efs/src/service.rs | 8 +++- crates/fakecloud-kafka/Cargo.toml | 2 +- crates/fakecloud-kafka/src/builders.rs | 3 +- crates/fakecloud-rds/Cargo.toml | 2 +- crates/fakecloud-rds/src/extras/mod.rs | 1 + crates/fakecloud-rds/src/extras/parse.rs | 4 +- crates/fakecloud-timestream/Cargo.toml | 2 +- crates/fakecloud-timestream/src/service.rs | 14 ++++-- 15 files changed, 75 insertions(+), 61 deletions(-) diff --git a/crates/fakecloud-athena/src/lib.rs b/crates/fakecloud-athena/src/lib.rs index 2f41ed399..e6f37140d 100644 --- a/crates/fakecloud-athena/src/lib.rs +++ b/crates/fakecloud-athena/src/lib.rs @@ -2,7 +2,7 @@ pub(crate) mod service; pub(crate) mod sql; pub(crate) mod state; -pub use service::AthenaService; +pub use service::{athena_arn, AthenaService}; pub use state::{ AthenaAccounts, AthenaSnapshot, DataCatalog, NamedQuery, PreparedStatement, SharedAthenaState, WorkGroup, ATHENA_SNAPSHOT_SCHEMA_VERSION, diff --git a/crates/fakecloud-athena/src/service/capacity.rs b/crates/fakecloud-athena/src/service/capacity.rs index dcad896b2..8ab12ca33 100644 --- a/crates/fakecloud-athena/src/service/capacity.rs +++ b/crates/fakecloud-athena/src/service/capacity.rs @@ -31,7 +31,11 @@ impl AthenaService { last_allocation: Some(Utc::now()), last_successful_allocation_time: Some(Utc::now()), }; - let arn = capacity_reservation_arn(&req.account_id, &req.region, &name); + let arn = athena_arn( + &req.region, + &req.account_id, + &format!("capacity-reservation/{name}"), + ); account.capacity_reservations.insert(name, cr); if !tags.is_empty() { account.tags.insert(arn, tags); diff --git a/crates/fakecloud-athena/src/service/data_catalogs.rs b/crates/fakecloud-athena/src/service/data_catalogs.rs index b34281b1e..3f19d96dc 100644 --- a/crates/fakecloud-athena/src/service/data_catalogs.rs +++ b/crates/fakecloud-athena/src/service/data_catalogs.rs @@ -45,7 +45,7 @@ impl AthenaService { connection_type, error: None, }; - let arn = datacatalog_arn(&req.account_id, &req.region, &name); + let arn = athena_arn(&req.region, &req.account_id, &format!("datacatalog/{name}")); account.data_catalogs.insert(name, cat); if !tags.is_empty() { account.tags.insert(arn, tags); diff --git a/crates/fakecloud-athena/src/service/mod.rs b/crates/fakecloud-athena/src/service/mod.rs index 5ba3a2f98..ef64689cb 100644 --- a/crates/fakecloud-athena/src/service/mod.rs +++ b/crates/fakecloud-athena/src/service/mod.rs @@ -658,37 +658,16 @@ fn synth_uuid() -> String { Uuid::new_v4().to_string() } -fn workgroup_arn(account_id: &str, region: &str, name: &str) -> String { +/// An Athena ARN (`resource` is e.g. `workgroup/`, +/// `datacatalog/` or `capacity-reservation/`), in `region`'s +/// partition. An empty region falls back to `us-east-1`. +pub fn athena_arn(region: &str, account_id: &str, resource: &str) -> String { let region = if region.is_empty() { "us-east-1" } else { region }; - Arn::regional("athena", region, account_id, &format!("workgroup/{name}")).to_string() -} - -fn datacatalog_arn(account_id: &str, region: &str, name: &str) -> String { - let region = if region.is_empty() { - "us-east-1" - } else { - region - }; - Arn::regional("athena", region, account_id, &format!("datacatalog/{name}")).to_string() -} - -fn capacity_reservation_arn(account_id: &str, region: &str, name: &str) -> String { - let region = if region.is_empty() { - "us-east-1" - } else { - region - }; - Arn::regional( - "athena", - region, - account_id, - &format!("capacity-reservation/{name}"), - ) - .to_string() + Arn::regional("athena", region, account_id, resource).to_string() } fn parse_string_list(value: Option<&Value>) -> Vec { @@ -2011,19 +1990,19 @@ mod tests { #[test] fn arns_use_the_regions_partition() { assert_eq!( - super::workgroup_arn("123456789012", "cn-north-1", "wg"), + super::athena_arn("cn-north-1", "123456789012", "workgroup/wg"), "arn:aws-cn:athena:cn-north-1:123456789012:workgroup/wg" ); assert_eq!( - super::datacatalog_arn("123456789012", "cn-north-1", "cat"), + super::athena_arn("cn-north-1", "123456789012", "datacatalog/cat"), "arn:aws-cn:athena:cn-north-1:123456789012:datacatalog/cat" ); assert_eq!( - super::capacity_reservation_arn("123456789012", "us-gov-west-1", "cr"), + super::athena_arn("us-gov-west-1", "123456789012", "capacity-reservation/cr"), "arn:aws-us-gov:athena:us-gov-west-1:123456789012:capacity-reservation/cr" ); assert_eq!( - super::workgroup_arn("123456789012", "us-east-1", "wg"), + super::athena_arn("us-east-1", "123456789012", "workgroup/wg"), "arn:aws:athena:us-east-1:123456789012:workgroup/wg" ); diff --git a/crates/fakecloud-athena/src/service/work_groups.rs b/crates/fakecloud-athena/src/service/work_groups.rs index c692eb69e..3ff4f0cc1 100644 --- a/crates/fakecloud-athena/src/service/work_groups.rs +++ b/crates/fakecloud-athena/src/service/work_groups.rs @@ -37,7 +37,7 @@ impl AthenaService { creation_time: Utc::now(), engine_version, }; - let arn = workgroup_arn(&req.account_id, &req.region, &name); + let arn = athena_arn(&req.region, &req.account_id, &format!("workgroup/{name}")); account.work_groups.insert(name, wg); if !tags.is_empty() { account.tags.insert(arn, tags); diff --git a/crates/fakecloud-bedrock/src/prompt_routers.rs b/crates/fakecloud-bedrock/src/prompt_routers.rs index 3126f21c9..8671b3889 100644 --- a/crates/fakecloud-bedrock/src/prompt_routers.rs +++ b/crates/fakecloud-bedrock/src/prompt_routers.rs @@ -71,9 +71,9 @@ pub(crate) fn get_prompt_router( "promptRouterArn": router.prompt_router_arn, "promptRouterName": router.prompt_router_name, "description": router.description, - "models": ensure_router_models(&router.models, &router.prompt_router_arn), + "models": ensure_router_models(&router.models, &router.prompt_router_arn, &req.region), "routingCriteria": ensure_routing_criteria(&router.routing_criteria), - "fallbackModel": ensure_fallback_model(&router.fallback_model, &router.prompt_router_arn), + "fallbackModel": ensure_fallback_model(&router.fallback_model, &router.prompt_router_arn, &req.region), "status": router.status, "type": router.prompt_router_type, "createdAt": router.created_at.to_rfc3339(), @@ -118,8 +118,8 @@ pub(crate) fn list_prompt_routers( "promptRouterName": r.prompt_router_name, "description": r.description, "routingCriteria": ensure_routing_criteria(&r.routing_criteria), - "models": ensure_router_models(&r.models, &r.prompt_router_arn), - "fallbackModel": ensure_fallback_model(&r.fallback_model, &r.prompt_router_arn), + "models": ensure_router_models(&r.models, &r.prompt_router_arn, &req.region), + "fallbackModel": ensure_fallback_model(&r.fallback_model, &r.prompt_router_arn, &req.region), "status": r.status, "type": r.prompt_router_type, "createdAt": r.created_at.to_rfc3339(), @@ -149,34 +149,36 @@ fn ensure_routing_criteria(criteria: &Value) -> Value { } /// The foundation model a router defaults to, in the router's own region and -/// partition. -fn default_router_model_arn(router_arn: &str) -> String { - let region = router_arn - .split(':') - .nth(3) +/// partition. The router's stored ARN names that region; `request_region` is +/// the source of truth only when the ARN does not parse. +fn default_router_model_arn(router_arn: &str, request_region: &str) -> String { + let parsed = router_arn.parse::().ok(); + let region = parsed + .as_ref() + .map(|a| a.region.as_str()) .filter(|r| !r.is_empty()) - .unwrap_or("us-east-1"); + .unwrap_or(request_region); crate::arns::foundation_model_arn(region, "amazon.titan-text-express-v1") } /// Default the router's target models to a single Titan entry when none were /// supplied at create time, matching the required Smithy field. -fn ensure_router_models(models: &Value, router_arn: &str) -> Value { +fn ensure_router_models(models: &Value, router_arn: &str, request_region: &str) -> Value { if let Some(arr) = models.as_array() { if !arr.is_empty() { return models.clone(); } } - json!([{ "modelArn": default_router_model_arn(router_arn) }]) + json!([{ "modelArn": default_router_model_arn(router_arn, request_region) }]) } /// Default the fallback model when not provided. `fallbackModel` is required /// in the summary even though it's optional on create. -fn ensure_fallback_model(fallback: &Value, router_arn: &str) -> Value { +fn ensure_fallback_model(fallback: &Value, router_arn: &str, request_region: &str) -> Value { if fallback.get("modelArn").is_some() { return fallback.clone(); } - json!({ "modelArn": default_router_model_arn(router_arn) }) + json!({ "modelArn": default_router_model_arn(router_arn, request_region) }) } pub(crate) fn delete_prompt_router( @@ -315,6 +317,23 @@ mod tests { ); } + /// The router's stored ARN names the region; only an ARN that does not + /// parse falls back to the request's region, never to a fixed one. + #[test] + fn default_model_region_comes_from_the_router_arn_then_the_request() { + assert_eq!( + default_router_model_arn( + "arn:aws-us-gov:bedrock:us-gov-west-1:123456789012:prompt-router/x", + "cn-north-1" + ), + "arn:aws-us-gov:bedrock:us-gov-west-1::foundation-model/amazon.titan-text-express-v1" + ); + assert_eq!( + default_router_model_arn("not-an-arn", "cn-north-1"), + "arn:aws-cn:bedrock:cn-north-1::foundation-model/amazon.titan-text-express-v1" + ); + } + #[test] fn get_unknown_returns_not_found() { let s = shared(); diff --git a/crates/fakecloud-efs/Cargo.toml b/crates/fakecloud-efs/Cargo.toml index e758dc9d5..99cb568d2 100644 --- a/crates/fakecloud-efs/Cargo.toml +++ b/crates/fakecloud-efs/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } -fakecloud-kms = { workspace = true } fakecloud-persistence = { workspace = true } fakecloud-ec2 = { workspace = true } async-trait = { workspace = true } @@ -26,5 +25,6 @@ tracing = { workspace = true } uuid = { workspace = true } [dev-dependencies] +fakecloud-kms = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } bytes = { workspace = true } diff --git a/crates/fakecloud-efs/src/service.rs b/crates/fakecloud-efs/src/service.rs index a02143b4b..971afb131 100644 --- a/crates/fakecloud-efs/src/service.rs +++ b/crates/fakecloud-efs/src/service.rs @@ -683,7 +683,13 @@ impl EfsService { &hex17()[..4], &hex17()[..12.min(hex17().len())] ); - fakecloud_kms::kms_key_arn(&ctx.region, &ctx.account, &key_id) + fakecloud_aws::arn::Arn::regional( + "kms", + &ctx.region, + &ctx.account, + &format!("key/{key_id}"), + ) + .to_string() }); fs.insert("KmsKeyId".into(), json!(kms)); } diff --git a/crates/fakecloud-kafka/Cargo.toml b/crates/fakecloud-kafka/Cargo.toml index 14a51df67..2a661924c 100644 --- a/crates/fakecloud-kafka/Cargo.toml +++ b/crates/fakecloud-kafka/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } -fakecloud-kms = { workspace = true } fakecloud-persistence = { workspace = true } async-trait = { workspace = true } base64 = { workspace = true } @@ -26,6 +25,7 @@ tracing = { workspace = true } uuid = { workspace = true } [dev-dependencies] +fakecloud-kms = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } bytes = { workspace = true } http = { workspace = true } diff --git a/crates/fakecloud-kafka/src/builders.rs b/crates/fakecloud-kafka/src/builders.rs index 11bc55bbe..fe5095de9 100644 --- a/crates/fakecloud-kafka/src/builders.rs +++ b/crates/fakecloud-kafka/src/builders.rs @@ -34,7 +34,8 @@ pub(crate) fn default_bngi() -> Value { /// (real MSK always echoes a resolved key ARN, so `encryption_info` round-trips /// and `MatchResourceAttrRegionalARN(..., "kms", "key/.+")` matches). fn default_kms_key_arn(region: &str, account: &str) -> String { - fakecloud_kms::kms_key_arn(region, account, &Uuid::new_v4().to_string()) + fakecloud_aws::arn::Arn::regional("kms", region, account, &format!("key/{}", Uuid::new_v4())) + .to_string() } /// Fill an `EncryptionInfo` with the sub-objects + AWS defaults `DescribeCluster` diff --git a/crates/fakecloud-rds/Cargo.toml b/crates/fakecloud-rds/Cargo.toml index bd7bcfd00..8fbcb9c58 100644 --- a/crates/fakecloud-rds/Cargo.toml +++ b/crates/fakecloud-rds/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } -fakecloud-kms = { workspace = true } fakecloud-k8s = { workspace = true } fakecloud-persistence = { workspace = true } k8s-openapi = { workspace = true } @@ -31,6 +30,7 @@ tracing = { workspace = true } tempfile = { workspace = true } [dev-dependencies] +fakecloud-kms = { workspace = true } # The opt-in k8s-integration test drives a real cluster directly. kube = { workspace = true } diff --git a/crates/fakecloud-rds/src/extras/mod.rs b/crates/fakecloud-rds/src/extras/mod.rs index a91bf537a..8bbb9bd72 100644 --- a/crates/fakecloud-rds/src/extras/mod.rs +++ b/crates/fakecloud-rds/src/extras/mod.rs @@ -15,6 +15,7 @@ use http::StatusCode; use serde_json::{json, Value}; use std::collections::BTreeMap; +use fakecloud_aws::arn::Arn; use fakecloud_aws::xml::xml_escape; use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError}; diff --git a/crates/fakecloud-rds/src/extras/parse.rs b/crates/fakecloud-rds/src/extras/parse.rs index 4879e51bd..d5eca34c9 100644 --- a/crates/fakecloud-rds/src/extras/parse.rs +++ b/crates/fakecloud-rds/src/extras/parse.rs @@ -144,9 +144,9 @@ pub(super) fn format_kms_arn(input: &str, region: &str, account_id: &str) -> Str return input.to_string(); } if input.starts_with("alias/") { - return fakecloud_kms::kms_alias_arn(region, account_id, input); + return Arn::regional("kms", region, account_id, input).to_string(); } - fakecloud_kms::kms_key_arn(region, account_id, input) + Arn::regional("kms", region, account_id, &format!("key/{input}")).to_string() } /// Render a whole extras category as a Describe list response. diff --git a/crates/fakecloud-timestream/Cargo.toml b/crates/fakecloud-timestream/Cargo.toml index 4f1d1d691..7b0b2415f 100644 --- a/crates/fakecloud-timestream/Cargo.toml +++ b/crates/fakecloud-timestream/Cargo.toml @@ -10,7 +10,6 @@ version.workspace = true [dependencies] fakecloud-aws = { workspace = true } fakecloud-core = { workspace = true } -fakecloud-kms = { workspace = true } fakecloud-persistence = { workspace = true } async-trait = { workspace = true } chrono = { workspace = true } @@ -25,5 +24,6 @@ tracing = { workspace = true } uuid = { workspace = true } [dev-dependencies] +fakecloud-kms = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } bytes = { workspace = true } diff --git a/crates/fakecloud-timestream/src/service.rs b/crates/fakecloud-timestream/src/service.rs index 00702c2aa..6b3934c49 100644 --- a/crates/fakecloud-timestream/src/service.rs +++ b/crates/fakecloud-timestream/src/service.rs @@ -323,11 +323,15 @@ impl TimestreamService { name: name.clone(), arn: arn.clone(), kms_key_id: kms.or_else(|| { - Some(fakecloud_kms::kms_key_arn( - ®ion, - &account, - "timestream-default", - )) + Some( + fakecloud_aws::arn::Arn::regional( + "kms", + ®ion, + &account, + "key/timestream-default", + ) + .to_string(), + ) }), table_count: 0, creation_time: now,