1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use base64::Engine;
6use chrono::Utc;
7use http::StatusCode;
8use serde_json::{json, Value};
9use tokio::sync::Mutex as AsyncMutex;
10use uuid::Uuid;
11
12use fakecloud_aws::arn::Arn;
13use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
14use fakecloud_core::validation::*;
15use fakecloud_persistence::SnapshotStore;
16
17use crate::state::{
18 CustomKeyStore, KeyRotation, KmsAlias, KmsGrant, KmsKey, KmsSnapshot, KmsState, SharedKmsState,
19 KMS_SNAPSHOT_SCHEMA_VERSION,
20};
21
22const FAKE_ENVELOPE_PREFIX: &str = "fakecloud-kms:";
23const IMPORTED_ENVELOPE_PREFIX: &str = "fakecloud-imported:";
24
25pub(crate) struct DecodedCiphertext {
30 source_arn: String,
31 plaintext_b64: String,
32}
33
34const VALID_KEY_SPECS: &[&str] = &[
35 "ECC_NIST_P256",
36 "ECC_NIST_P384",
37 "ECC_NIST_P521",
38 "ECC_SECG_P256K1",
39 "HMAC_224",
40 "HMAC_256",
41 "HMAC_384",
42 "HMAC_512",
43 "RSA_2048",
44 "RSA_3072",
45 "RSA_4096",
46 "SM2",
47 "SYMMETRIC_DEFAULT",
48];
49
50const VALID_SIGNING_ALGORITHMS: &[&str] = &[
51 "RSASSA_PKCS1_V1_5_SHA_256",
52 "RSASSA_PKCS1_V1_5_SHA_384",
53 "RSASSA_PKCS1_V1_5_SHA_512",
54 "RSASSA_PSS_SHA_256",
55 "RSASSA_PSS_SHA_384",
56 "RSASSA_PSS_SHA_512",
57 "ECDSA_SHA_256",
58 "ECDSA_SHA_384",
59 "ECDSA_SHA_512",
60];
61
62static KMS_ACTIONS: &[&str] = &[
66 "CreateKey",
67 "DescribeKey",
68 "GetKeyLastUsage",
69 "ListKeys",
70 "EnableKey",
71 "DisableKey",
72 "ScheduleKeyDeletion",
73 "CancelKeyDeletion",
74 "Encrypt",
75 "Decrypt",
76 "ReEncrypt",
77 "GenerateDataKey",
78 "GenerateDataKeyWithoutPlaintext",
79 "GenerateRandom",
80 "CreateAlias",
81 "DeleteAlias",
82 "UpdateAlias",
83 "ListAliases",
84 "TagResource",
85 "UntagResource",
86 "ListResourceTags",
87 "UpdateKeyDescription",
88 "GetKeyPolicy",
89 "PutKeyPolicy",
90 "ListKeyPolicies",
91 "GetKeyRotationStatus",
92 "EnableKeyRotation",
93 "DisableKeyRotation",
94 "RotateKeyOnDemand",
95 "ListKeyRotations",
96 "Sign",
97 "Verify",
98 "GetPublicKey",
99 "CreateGrant",
100 "ListGrants",
101 "ListRetirableGrants",
102 "RevokeGrant",
103 "RetireGrant",
104 "GenerateMac",
105 "VerifyMac",
106 "ReplicateKey",
107 "GenerateDataKeyPair",
108 "GenerateDataKeyPairWithoutPlaintext",
109 "DeriveSharedSecret",
110 "GetParametersForImport",
111 "ImportKeyMaterial",
112 "DeleteImportedKeyMaterial",
113 "UpdatePrimaryRegion",
114 "CreateCustomKeyStore",
115 "DeleteCustomKeyStore",
116 "DescribeCustomKeyStores",
117 "ConnectCustomKeyStore",
118 "DisconnectCustomKeyStore",
119 "UpdateCustomKeyStore",
120];
121
122pub struct KmsService {
123 state: SharedKmsState,
124 snapshot_store: Option<Arc<dyn SnapshotStore>>,
125 snapshot_lock: Arc<AsyncMutex<()>>,
126}
127
128impl KmsService {
129 pub fn new(state: SharedKmsState) -> Self {
130 if tokio::runtime::Handle::try_current().is_ok() {
136 tokio::task::spawn_blocking(|| {
137 let _ = self::asym::generate_keypair("RSA_2048");
138 let _ = self::asym::generate_keypair("RSA_3072");
139 let _ = self::asym::generate_keypair("RSA_4096");
140 });
141 }
142 Self {
143 state,
144 snapshot_store: None,
145 snapshot_lock: Arc::new(AsyncMutex::new(())),
146 }
147 }
148
149 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
150 self.snapshot_store = Some(store);
151 self
152 }
153
154 async fn save_snapshot(&self) {
158 save_kms_snapshot(
159 &self.state,
160 self.snapshot_store.clone(),
161 &self.snapshot_lock,
162 )
163 .await;
164 }
165
166 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
171 let store = self.snapshot_store.clone()?;
172 let state = self.state.clone();
173 let lock = self.snapshot_lock.clone();
174 Some(Arc::new(move || {
175 let state = state.clone();
176 let store = store.clone();
177 let lock = lock.clone();
178 Box::pin(async move {
179 save_kms_snapshot(&state, Some(store), &lock).await;
180 })
181 }))
182 }
183}
184
185pub async fn save_kms_snapshot(
191 state: &SharedKmsState,
192 store: Option<Arc<dyn SnapshotStore>>,
193 lock: &AsyncMutex<()>,
194) {
195 let Some(store) = store else {
196 return;
197 };
198 let _guard = lock.lock().await;
199 let snapshot = KmsSnapshot {
200 schema_version: KMS_SNAPSHOT_SCHEMA_VERSION,
201 state: None,
202 accounts: Some(state.read().clone()),
203 };
204 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
205 let bytes = serde_json::to_vec(&snapshot)
206 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
207 store.save(&bytes)
208 })
209 .await;
210 match join {
211 Ok(Ok(())) => {}
212 Ok(Err(err)) => tracing::error!(%err, "failed to write kms snapshot"),
213 Err(err) => tracing::error!(%err, "kms snapshot task panicked"),
214 }
215}
216
217#[async_trait]
218impl AwsService for KmsService {
219 fn service_name(&self) -> &str {
220 "kms"
221 }
222
223 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
224 let mutates = is_mutating_action(req.action.as_str());
225 let result = match req.action.as_str() {
226 "CreateKey" => self.create_key(&req),
227 "DescribeKey" => self.describe_key(&req),
228 "GetKeyLastUsage" => self.get_key_last_usage(&req),
229 "ListKeys" => self.list_keys(&req),
230 "EnableKey" => self.enable_key(&req),
231 "DisableKey" => self.disable_key(&req),
232 "ScheduleKeyDeletion" => self.schedule_key_deletion(&req),
233 "CancelKeyDeletion" => self.cancel_key_deletion(&req),
234 "Encrypt" => self.encrypt(&req),
235 "Decrypt" => self.decrypt(&req),
236 "ReEncrypt" => self.re_encrypt(&req),
237 "GenerateDataKey" => self.generate_data_key(&req),
238 "GenerateDataKeyWithoutPlaintext" => self.generate_data_key_without_plaintext(&req),
239 "GenerateRandom" => self.generate_random(&req),
240 "CreateAlias" => self.create_alias(&req),
241 "DeleteAlias" => self.delete_alias(&req),
242 "UpdateAlias" => self.update_alias(&req),
243 "ListAliases" => self.list_aliases(&req),
244 "TagResource" => self.tag_resource(&req),
245 "UntagResource" => self.untag_resource(&req),
246 "ListResourceTags" => self.list_resource_tags(&req),
247 "UpdateKeyDescription" => self.update_key_description(&req),
248 "GetKeyPolicy" => self.get_key_policy(&req),
249 "PutKeyPolicy" => self.put_key_policy(&req),
250 "ListKeyPolicies" => self.list_key_policies(&req),
251 "GetKeyRotationStatus" => self.get_key_rotation_status(&req),
252 "EnableKeyRotation" => self.enable_key_rotation(&req),
253 "DisableKeyRotation" => self.disable_key_rotation(&req),
254 "RotateKeyOnDemand" => self.rotate_key_on_demand(&req),
255 "ListKeyRotations" => self.list_key_rotations(&req),
256 "Sign" => self.sign(&req),
257 "Verify" => self.verify(&req),
258 "GetPublicKey" => self.get_public_key(&req),
259 "CreateGrant" => self.create_grant(&req),
260 "ListGrants" => self.list_grants(&req),
261 "ListRetirableGrants" => self.list_retirable_grants(&req),
262 "RevokeGrant" => self.revoke_grant(&req),
263 "RetireGrant" => self.retire_grant(&req),
264 "GenerateMac" => self.generate_mac(&req),
265 "VerifyMac" => self.verify_mac(&req),
266 "ReplicateKey" => self.replicate_key(&req),
267 "GenerateDataKeyPair" => self.generate_data_key_pair(&req),
268 "GenerateDataKeyPairWithoutPlaintext" => {
269 self.generate_data_key_pair_without_plaintext(&req)
270 }
271 "DeriveSharedSecret" => self.derive_shared_secret(&req),
272 "GetParametersForImport" => self.get_parameters_for_import(&req),
273 "ImportKeyMaterial" => self.import_key_material(&req),
274 "DeleteImportedKeyMaterial" => self.delete_imported_key_material(&req),
275 "UpdatePrimaryRegion" => self.update_primary_region(&req),
276 "CreateCustomKeyStore" => self.create_custom_key_store(&req),
277 "DeleteCustomKeyStore" => self.delete_custom_key_store(&req),
278 "DescribeCustomKeyStores" => self.describe_custom_key_stores(&req),
279 "ConnectCustomKeyStore" => self.connect_custom_key_store(&req),
280 "DisconnectCustomKeyStore" => self.disconnect_custom_key_store(&req),
281 "UpdateCustomKeyStore" => self.update_custom_key_store(&req),
282 _ => Err(AwsServiceError::action_not_implemented("kms", &req.action)),
283 };
284 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
285 self.save_snapshot().await;
286 }
287 result
288 }
289
290 fn supported_actions(&self) -> &[&str] {
291 KMS_ACTIONS
292 }
293
294 fn iam_enforceable(&self) -> bool {
295 true
296 }
297
298 fn iam_action_for(&self, request: &AwsRequest) -> Option<fakecloud_core::auth::IamAction> {
299 let action = KMS_ACTIONS.iter().copied().find(|a| *a == request.action)?;
300 let resource = kms_resource_for(action, &self.state, request);
301 Some(fakecloud_core::auth::IamAction {
302 service: "kms",
303 action,
304 resource,
305 })
306 }
307
308 fn resource_tags_for(
309 &self,
310 resource_arn: &str,
311 ) -> Option<std::collections::HashMap<String, String>> {
312 if resource_arn == "*" {
313 return Some(std::collections::HashMap::new());
314 }
315 let key_id = resource_arn.rsplit_once(":key/")?.1;
316 let account_id = resource_arn.split(':').nth(4).unwrap_or("").to_string();
317 let accounts = self.state.read();
318 let state = accounts.get(&account_id)?;
319 let key = state.keys.get(key_id)?;
320 Some(
321 key.tags
322 .iter()
323 .map(|(k, v)| (k.clone(), v.clone()))
324 .collect(),
325 )
326 }
327
328 fn request_tags_from(
329 &self,
330 request: &AwsRequest,
331 action: &str,
332 ) -> Option<std::collections::HashMap<String, String>> {
333 match action {
334 "CreateKey" | "TagResource" => {
335 let body = request.json_body();
336 let mut tags = std::collections::HashMap::new();
337 if let Some(arr) = body["Tags"].as_array() {
338 for tag in arr {
339 if let (Some(k), Some(v)) =
340 (tag["TagKey"].as_str(), tag["TagValue"].as_str())
341 {
342 tags.insert(k.to_string(), v.to_string());
343 }
344 }
345 }
346 Some(tags)
347 }
348 _ => Some(std::collections::HashMap::new()),
349 }
350 }
351}
352
353struct CreateKeyInput {
355 custom_key_store_id: Option<String>,
356 description: String,
357 key_usage: String,
358 key_spec: String,
359 origin: String,
360 multi_region: bool,
361 policy: Option<String>,
362 tags: BTreeMap<String, String>,
363}
364
365impl CreateKeyInput {
366 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
367 recoded("CustomKeyStoreInvalidStateException", || {
370 validate_optional_string_length(
371 "customKeyStoreId",
372 body["CustomKeyStoreId"].as_str(),
373 1,
374 64,
375 )
376 })?;
377 recoded("UnsupportedOperationException", || {
378 validate_optional_string_length("description", body["Description"].as_str(), 0, 8192)
379 })?;
380 recoded("UnsupportedOperationException", || {
381 validate_optional_enum(
382 "keyUsage",
383 body["KeyUsage"].as_str(),
384 &[
385 "SIGN_VERIFY",
386 "ENCRYPT_DECRYPT",
387 "GENERATE_VERIFY_MAC",
388 "KEY_AGREEMENT",
389 ],
390 )
391 })?;
392 recoded("UnsupportedOperationException", || {
393 validate_optional_enum(
394 "origin",
395 body["Origin"].as_str(),
396 &["AWS_KMS", "EXTERNAL", "AWS_CLOUDHSM", "EXTERNAL_KEY_STORE"],
397 )
398 })?;
399 recoded("MalformedPolicyDocumentException", || {
400 validate_optional_string_length("policy", body["Policy"].as_str(), 1, 131072)
401 })?;
402 recoded("XksKeyInvalidConfigurationException", || {
403 validate_optional_string_length("xksKeyId", body["XksKeyId"].as_str(), 1, 64)
404 })?;
405
406 let key_spec = body["KeySpec"]
407 .as_str()
408 .or_else(|| body["CustomerMasterKeySpec"].as_str())
409 .unwrap_or("SYMMETRIC_DEFAULT")
410 .to_string();
411 if !VALID_KEY_SPECS.contains(&key_spec.as_str()) {
412 return Err(AwsServiceError::aws_error(
413 StatusCode::BAD_REQUEST,
414 "UnsupportedOperationException",
415 format!(
416 "1 validation error detected: Value '{key_spec}' at 'KeySpec' failed to satisfy constraint: Member must satisfy enum value set: {}",
417 fmt_enum_set(&VALID_KEY_SPECS.iter().map(|s| s.to_string()).collect::<Vec<_>>())
418 ),
419 ));
420 }
421
422 let tags: BTreeMap<String, String> = body["Tags"]
423 .as_array()
424 .map(|arr| {
425 arr.iter()
426 .filter_map(|t| {
427 let k = t["TagKey"].as_str()?;
428 let v = t["TagValue"].as_str()?;
429 Some((k.to_string(), v.to_string()))
430 })
431 .collect()
432 })
433 .unwrap_or_default();
434
435 Ok(Self {
436 custom_key_store_id: body["CustomKeyStoreId"].as_str().map(|s| s.to_string()),
437 description: body["Description"].as_str().unwrap_or("").to_string(),
438 key_usage: body["KeyUsage"]
439 .as_str()
440 .unwrap_or("ENCRYPT_DECRYPT")
441 .to_string(),
442 key_spec,
443 origin: body["Origin"].as_str().unwrap_or("AWS_KMS").to_string(),
444 multi_region: body["MultiRegion"].as_bool().unwrap_or(false),
445 policy: body["Policy"].as_str().map(|s| s.to_string()),
446 tags,
447 })
448 }
449}
450
451impl KmsService {
452 fn resolve_key_id_for(
453 &self,
454 account_id: &str,
455 region: &str,
456 key_id_or_arn: &str,
457 ) -> Option<String> {
458 let accounts = self.state.read();
459 let empty = KmsState::new(account_id, region);
460 let state = accounts.get(account_id).unwrap_or(&empty);
461 Self::resolve_key_id_with_state(state, key_id_or_arn)
462 }
463
464 pub(crate) fn resolve_key_id_with_state(
465 state: &crate::state::KmsState,
466 key_id_or_arn: &str,
467 ) -> Option<String> {
468 if state.keys.contains_key(key_id_or_arn) {
470 return Some(key_id_or_arn.to_string());
471 }
472
473 if key_id_or_arn.starts_with("arn:aws:kms:") {
475 if key_id_or_arn.contains(":key/") {
477 if let Some(id) = key_id_or_arn.rsplit('/').next() {
478 if state.keys.contains_key(id) {
479 return Some(id.to_string());
480 }
481 }
482 }
483 if key_id_or_arn.contains(":alias/") {
485 if let Some(alias_part) = key_id_or_arn.split(':').next_back() {
486 if let Some(alias) = state.aliases.get(alias_part) {
487 return Some(alias.target_key_id.clone());
488 }
489 }
490 }
491 }
492
493 if key_id_or_arn.starts_with("alias/") {
495 if let Some(alias) = state.aliases.get(key_id_or_arn) {
496 return Some(alias.target_key_id.clone());
497 }
498 }
499
500 None
501 }
502
503 fn require_key_id(body: &Value) -> Result<String, AwsServiceError> {
504 body["KeyId"]
505 .as_str()
506 .map(|s| s.to_string())
507 .ok_or_else(|| {
508 AwsServiceError::aws_error(
509 StatusCode::BAD_REQUEST,
510 "ValidationException",
511 "KeyId is required",
512 )
513 })
514 }
515
516 fn resolve_required_key(
517 &self,
518 req: &AwsRequest,
519 body: &Value,
520 ) -> Result<String, AwsServiceError> {
521 let key_id_input = Self::require_key_id(body)?;
522 self.resolve_key_id_for(&req.account_id, &req.region, &key_id_input)
523 .ok_or_else(|| {
524 AwsServiceError::aws_error(
525 StatusCode::BAD_REQUEST,
526 "NotFoundException",
527 format!("Key '{key_id_input}' does not exist"),
528 )
529 })
530 }
531
532 fn create_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
533 let input = CreateKeyInput::from_body(&req.json_body())?;
534
535 let mut accounts = self.state.write();
536 let state = accounts.get_or_create(&req.account_id);
537
538 let key_id = if input.multi_region {
539 format!("mrk-{}", Uuid::new_v4().as_simple())
540 } else {
541 Uuid::new_v4().to_string()
542 };
543
544 let arn = format!(
545 "arn:aws:kms:{}:{}:key/{}",
546 state.region, state.account_id, key_id
547 );
548 let now = Utc::now().timestamp() as f64;
549
550 let signing_algs = if input.key_usage == "SIGN_VERIFY" {
551 signing_algorithms_for_key_spec(&input.key_spec)
552 } else {
553 None
554 };
555 let encryption_algs = encryption_algorithms_for_key(&input.key_usage, &input.key_spec);
556 let mac_algs = if input.key_usage == "GENERATE_VERIFY_MAC" {
557 mac_algorithms_for_key_spec(&input.key_spec)
558 } else {
559 None
560 };
561
562 let key_policy = input
563 .policy
564 .unwrap_or_else(|| default_key_policy(&state.account_id));
565
566 let mut asym_priv: Option<Vec<u8>> = None;
567 let mut asym_pub: Option<Vec<u8>> = None;
568 if let Some((p, k)) = asym::generate_keypair(&input.key_spec).map_err(|e| {
569 AwsServiceError::aws_error(
570 StatusCode::INTERNAL_SERVER_ERROR,
571 "KMSInternalException",
572 format!("failed to generate asymmetric key: {e}"),
573 )
574 })? {
575 asym_priv = Some(p);
576 asym_pub = Some(k);
577 } else if let Some((p, k)) = asym_ecdsa::generate_keypair(&input.key_spec).map_err(|e| {
578 AwsServiceError::aws_error(
579 StatusCode::INTERNAL_SERVER_ERROR,
580 "KMSInternalException",
581 format!("failed to generate ecdsa key: {e}"),
582 )
583 })? {
584 asym_priv = Some(p);
585 asym_pub = Some(k);
586 }
587
588 let is_asymmetric = input.key_spec.starts_with("ECC_")
593 || input.key_spec.starts_with("RSA_")
594 || input.key_spec == "SM2";
595 if is_asymmetric && asym_priv.is_none() {
596 return Err(AwsServiceError::aws_error(
597 StatusCode::BAD_REQUEST,
598 "UnsupportedOperationException",
599 format!(
600 "KeySpec '{}' is not supported by this fakecloud build; \
601 no fake-signature fallback is provided",
602 input.key_spec
603 ),
604 ));
605 }
606
607 let key = KmsKey {
608 key_id: key_id.clone(),
609 arn: arn.clone(),
610 creation_date: now,
611 description: input.description,
612 enabled: true,
613 key_usage: input.key_usage,
614 key_spec: input.key_spec,
615 key_manager: "CUSTOMER".to_string(),
616 key_state: "Enabled".to_string(),
617 deletion_date: None,
618 tags: input.tags,
619 policy: key_policy,
620 key_rotation_enabled: false,
621 origin: input.origin,
622 multi_region: input.multi_region,
623 rotations: Vec::new(),
624 signing_algorithms: signing_algs,
625 encryption_algorithms: encryption_algs,
626 mac_algorithms: mac_algs,
627 custom_key_store_id: input.custom_key_store_id,
628 imported_key_material: false,
629 imported_material_bytes: None,
630 private_key_seed: rand_bytes(32),
631 primary_region: None,
632 asymmetric_private_key_der: asym_priv,
633 asymmetric_public_key_der: asym_pub,
634 };
635
636 let metadata = key_metadata_json(&key, &state.account_id);
637 state.keys.insert(key_id, key);
638
639 Ok(AwsResponse::json(
640 StatusCode::OK,
641 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
642 ))
643 }
644
645 fn describe_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
646 let body = req.json_body();
647 let key_id_input = body["KeyId"].as_str().ok_or_else(|| {
648 AwsServiceError::aws_error(
649 StatusCode::BAD_REQUEST,
650 "ValidationException",
651 "KeyId is required",
652 )
653 })?;
654
655 let accounts = self.state.read();
656 let empty = KmsState::new(&req.account_id, &req.region);
657 let state = accounts.get(&req.account_id).unwrap_or(&empty);
658
659 let resolved = Self::resolve_key_id_with_state(state, key_id_input).ok_or_else(|| {
661 AwsServiceError::aws_error(
662 StatusCode::BAD_REQUEST,
663 "NotFoundException",
664 format!("Key '{key_id_input}' does not exist"),
665 )
666 })?;
667
668 let key = state.keys.get(&resolved).ok_or_else(|| {
669 AwsServiceError::aws_error(
670 StatusCode::BAD_REQUEST,
671 "NotFoundException",
672 format!("Key '{key_id_input}' does not exist"),
673 )
674 })?;
675
676 check_policy_deny(key, "kms:DescribeKey")?;
678
679 let metadata = key_metadata_json(key, &state.account_id);
680 Ok(AwsResponse::json(
681 StatusCode::OK,
682 serde_json::to_string(&json!({ "KeyMetadata": metadata })).unwrap(),
683 ))
684 }
685
686 fn get_key_last_usage(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
687 let body = req.json_body();
688 let key_id_input = body["KeyId"].as_str().ok_or_else(|| {
689 AwsServiceError::aws_error(
690 StatusCode::BAD_REQUEST,
691 "ValidationException",
692 "KeyId is required",
693 )
694 })?;
695
696 let accounts = self.state.read();
697 let empty = KmsState::new(&req.account_id, &req.region);
698 let state = accounts.get(&req.account_id).unwrap_or(&empty);
699
700 let resolved = Self::resolve_key_id_with_state(state, key_id_input).ok_or_else(|| {
701 AwsServiceError::aws_error(
702 StatusCode::BAD_REQUEST,
703 "NotFoundException",
704 format!("Key '{key_id_input}' does not exist"),
705 )
706 })?;
707 let key = state.keys.get(&resolved).ok_or_else(|| {
708 AwsServiceError::aws_error(
709 StatusCode::BAD_REQUEST,
710 "NotFoundException",
711 format!("Key '{key_id_input}' does not exist"),
712 )
713 })?;
714
715 Ok(AwsResponse::json(
719 StatusCode::OK,
720 serde_json::to_string(&json!({
721 "KeyId": key.key_id,
722 "KeyCreationDate": key.creation_date,
723 "TrackingStartDate": key.creation_date,
724 }))
725 .unwrap(),
726 ))
727 }
728
729 fn list_keys(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
730 let body = req.json_body();
731
732 recoded("InvalidMarkerException", || {
736 validate_optional_json_range("limit", &body["Limit"], 1, 1000)
737 })?;
738 recoded("InvalidMarkerException", || {
739 validate_optional_string_length("marker", body["Marker"].as_str(), 1, 320)
740 })?;
741
742 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
743 let marker = body["Marker"].as_str();
744
745 let accounts = self.state.read();
746 let empty = KmsState::new(&req.account_id, &req.region);
747 let state = accounts.get(&req.account_id).unwrap_or(&empty);
748 let all_keys: Vec<Value> = state
749 .keys
750 .values()
751 .map(|k| {
752 json!({
753 "KeyId": k.key_id,
754 "KeyArn": k.arn,
755 })
756 })
757 .collect();
758
759 let start = if let Some(m) = marker {
760 all_keys
761 .iter()
762 .position(|k| k["KeyId"].as_str() == Some(m))
763 .map(|pos| pos + 1)
764 .unwrap_or(0)
765 } else {
766 0
767 };
768
769 let page = &all_keys[start..all_keys.len().min(start + limit)];
770 let truncated = start + limit < all_keys.len();
771
772 let mut result = json!({
773 "Keys": page,
774 "Truncated": truncated,
775 });
776
777 if truncated {
778 if let Some(last) = page.last() {
779 result["NextMarker"] = last["KeyId"].clone();
780 }
781 }
782
783 Ok(AwsResponse::json(
784 StatusCode::OK,
785 serde_json::to_string(&result).unwrap(),
786 ))
787 }
788
789 fn enable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
790 let body = req.json_body();
791 let resolved = self.resolve_required_key(req, &body)?;
792
793 let mut accounts = self.state.write();
794 let state = accounts.get_or_create(&req.account_id);
795 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
796 AwsServiceError::aws_error(
797 StatusCode::INTERNAL_SERVER_ERROR,
798 "KMSInternalException",
799 "Key state became inconsistent",
800 )
801 })?;
802 key.enabled = true;
803 key.key_state = "Enabled".to_string();
804
805 Ok(AwsResponse::json(StatusCode::OK, "{}"))
806 }
807
808 fn disable_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
809 let body = req.json_body();
810 let resolved = self.resolve_required_key(req, &body)?;
811
812 let mut accounts = self.state.write();
813 let state = accounts.get_or_create(&req.account_id);
814 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
815 AwsServiceError::aws_error(
816 StatusCode::INTERNAL_SERVER_ERROR,
817 "KMSInternalException",
818 "Key state became inconsistent",
819 )
820 })?;
821 key.enabled = false;
822 key.key_state = "Disabled".to_string();
823
824 Ok(AwsResponse::json(StatusCode::OK, "{}"))
825 }
826
827 fn schedule_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
828 let body = req.json_body();
829 let resolved = self.resolve_required_key(req, &body)?;
830 let pending_days = body["PendingWindowInDays"].as_i64().unwrap_or(30);
831
832 let mut accounts = self.state.write();
833 let state = accounts.get_or_create(&req.account_id);
834 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
835 AwsServiceError::aws_error(
836 StatusCode::INTERNAL_SERVER_ERROR,
837 "KMSInternalException",
838 "Key state became inconsistent",
839 )
840 })?;
841 let deletion_date =
842 Utc::now().timestamp() as f64 + (pending_days as f64 * 24.0 * 60.0 * 60.0);
843 key.key_state = "PendingDeletion".to_string();
844 key.enabled = false;
845 key.deletion_date = Some(deletion_date);
846
847 Ok(AwsResponse::json(
848 StatusCode::OK,
849 serde_json::to_string(&json!({
850 "KeyId": key.key_id,
851 "DeletionDate": deletion_date,
852 "KeyState": "PendingDeletion",
853 "PendingWindowInDays": pending_days,
854 }))
855 .unwrap(),
856 ))
857 }
858
859 fn cancel_key_deletion(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
860 let body = req.json_body();
861 let resolved = self.resolve_required_key(req, &body)?;
862
863 let mut accounts = self.state.write();
864 let state = accounts.get_or_create(&req.account_id);
865 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
866 AwsServiceError::aws_error(
867 StatusCode::INTERNAL_SERVER_ERROR,
868 "KMSInternalException",
869 "Key state became inconsistent",
870 )
871 })?;
872 key.key_state = "Disabled".to_string();
873 key.deletion_date = None;
874
875 Ok(AwsResponse::json(
876 StatusCode::OK,
877 serde_json::to_string(&json!({
878 "KeyId": key.key_id,
879 }))
880 .unwrap(),
881 ))
882 }
883
884 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
885 let body = req.json_body();
886 let key_id = Self::require_key_id(&body)?;
887
888 let resolved = self
889 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
890 .ok_or_else(|| {
891 AwsServiceError::aws_error(
892 StatusCode::BAD_REQUEST,
893 "NotFoundException",
894 format!("Invalid keyId {key_id}"),
895 )
896 })?;
897
898 let mut accounts = self.state.write();
899 let state = accounts.get_or_create(&req.account_id);
900 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
901 AwsServiceError::aws_error(
902 StatusCode::INTERNAL_SERVER_ERROR,
903 "KMSInternalException",
904 "Key state became inconsistent",
905 )
906 })?;
907
908 fakecloud_core::tags::apply_tags(&mut key.tags, &body, "Tags", "TagKey", "TagValue")
909 .map_err(|f| {
910 AwsServiceError::aws_error(
911 StatusCode::BAD_REQUEST,
912 "ValidationException",
913 format!("{f} must be a list"),
914 )
915 })?;
916
917 Ok(AwsResponse::json(StatusCode::OK, "{}"))
918 }
919
920 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
921 let body = req.json_body();
922 let key_id = Self::require_key_id(&body)?;
923
924 let resolved = self
925 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
926 .ok_or_else(|| {
927 AwsServiceError::aws_error(
928 StatusCode::BAD_REQUEST,
929 "NotFoundException",
930 format!("Invalid keyId {key_id}"),
931 )
932 })?;
933
934 let mut accounts = self.state.write();
935 let state = accounts.get_or_create(&req.account_id);
936 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
937 AwsServiceError::aws_error(
938 StatusCode::INTERNAL_SERVER_ERROR,
939 "KMSInternalException",
940 "Key state became inconsistent",
941 )
942 })?;
943
944 fakecloud_core::tags::remove_tags(&mut key.tags, &body, "TagKeys").map_err(|f| {
945 AwsServiceError::aws_error(
946 StatusCode::BAD_REQUEST,
947 "ValidationException",
948 format!("{f} must be a list"),
949 )
950 })?;
951
952 Ok(AwsResponse::json(StatusCode::OK, "{}"))
953 }
954
955 fn list_resource_tags(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
956 let body = req.json_body();
957 let key_id = Self::require_key_id(&body)?;
958
959 let resolved = self
960 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
961 .ok_or_else(|| {
962 AwsServiceError::aws_error(
963 StatusCode::BAD_REQUEST,
964 "NotFoundException",
965 format!("Invalid keyId {key_id}"),
966 )
967 })?;
968
969 let accounts = self.state.read();
970 let empty = KmsState::new(&req.account_id, &req.region);
971 let state = accounts.get(&req.account_id).unwrap_or(&empty);
972 let key = state.keys.get(&resolved).ok_or_else(|| {
973 AwsServiceError::aws_error(
974 StatusCode::INTERNAL_SERVER_ERROR,
975 "KMSInternalException",
976 "Key state became inconsistent",
977 )
978 })?;
979 let tags = fakecloud_core::tags::tags_to_json(&key.tags, "TagKey", "TagValue");
980
981 Ok(AwsResponse::json(
982 StatusCode::OK,
983 serde_json::to_string(&json!({
984 "Tags": tags,
985 "Truncated": false,
986 }))
987 .unwrap(),
988 ))
989 }
990
991 fn update_key_description(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
992 let body = req.json_body();
993 let resolved = self.resolve_required_key(req, &body)?;
994 let description = body["Description"].as_str().unwrap_or("").to_string();
995
996 let mut accounts = self.state.write();
997 let state = accounts.get_or_create(&req.account_id);
998 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
999 AwsServiceError::aws_error(
1000 StatusCode::INTERNAL_SERVER_ERROR,
1001 "KMSInternalException",
1002 "Key state became inconsistent",
1003 )
1004 })?;
1005 key.description = description;
1006
1007 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1008 }
1009
1010 fn get_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1011 let body = req.json_body();
1012 let key_id = Self::require_key_id(&body)?;
1013
1014 if key_id.starts_with("alias/") {
1016 return Err(AwsServiceError::aws_error(
1017 StatusCode::BAD_REQUEST,
1018 "NotFoundException",
1019 format!("Invalid keyId {key_id}"),
1020 ));
1021 }
1022
1023 let resolved = self
1024 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1025 .ok_or_else(|| {
1026 AwsServiceError::aws_error(
1027 StatusCode::BAD_REQUEST,
1028 "NotFoundException",
1029 format!("Key '{key_id}' does not exist"),
1030 )
1031 })?;
1032
1033 let accounts = self.state.read();
1034 let empty = KmsState::new(&req.account_id, &req.region);
1035 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1036 let key = state.keys.get(&resolved).ok_or_else(|| {
1037 AwsServiceError::aws_error(
1038 StatusCode::INTERNAL_SERVER_ERROR,
1039 "KMSInternalException",
1040 "Key state became inconsistent",
1041 )
1042 })?;
1043
1044 Ok(AwsResponse::json(
1045 StatusCode::OK,
1046 serde_json::to_string(&json!({
1047 "Policy": key.policy,
1048 }))
1049 .unwrap(),
1050 ))
1051 }
1052
1053 fn put_key_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1054 let body = req.json_body();
1055 let key_id = Self::require_key_id(&body)?;
1056
1057 if key_id.starts_with("alias/") {
1059 return Err(AwsServiceError::aws_error(
1060 StatusCode::BAD_REQUEST,
1061 "NotFoundException",
1062 format!("Invalid keyId {key_id}"),
1063 ));
1064 }
1065
1066 let resolved = self
1067 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1068 .ok_or_else(|| {
1069 AwsServiceError::aws_error(
1070 StatusCode::BAD_REQUEST,
1071 "NotFoundException",
1072 format!("Key '{key_id}' does not exist"),
1073 )
1074 })?;
1075
1076 let policy = body["Policy"].as_str().unwrap_or("").to_string();
1077
1078 let mut accounts = self.state.write();
1079 let state = accounts.get_or_create(&req.account_id);
1080 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1081 AwsServiceError::aws_error(
1082 StatusCode::INTERNAL_SERVER_ERROR,
1083 "KMSInternalException",
1084 "Key state became inconsistent",
1085 )
1086 })?;
1087 key.policy = policy;
1088
1089 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1090 }
1091
1092 fn list_key_policies(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1093 let body = req.json_body();
1094 let _resolved = self.resolve_required_key(req, &body)?;
1095
1096 Ok(AwsResponse::json(
1097 StatusCode::OK,
1098 serde_json::to_string(&json!({
1099 "PolicyNames": ["default"],
1100 "Truncated": false,
1101 }))
1102 .unwrap(),
1103 ))
1104 }
1105
1106 fn get_key_rotation_status(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1107 let body = req.json_body();
1108 let key_id = Self::require_key_id(&body)?;
1109
1110 let resolved = self
1112 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1113 .ok_or_else(|| {
1114 AwsServiceError::aws_error(
1115 StatusCode::BAD_REQUEST,
1116 "NotFoundException",
1117 format!("Key '{key_id}' does not exist"),
1118 )
1119 })?;
1120
1121 let accounts = self.state.read();
1122 let empty = KmsState::new(&req.account_id, &req.region);
1123 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1124 let key = state.keys.get(&resolved).ok_or_else(|| {
1125 AwsServiceError::aws_error(
1126 StatusCode::INTERNAL_SERVER_ERROR,
1127 "KMSInternalException",
1128 "Key state became inconsistent",
1129 )
1130 })?;
1131
1132 Ok(AwsResponse::json(
1133 StatusCode::OK,
1134 serde_json::to_string(&json!({
1135 "KeyRotationEnabled": key.key_rotation_enabled,
1136 }))
1137 .unwrap(),
1138 ))
1139 }
1140
1141 fn enable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1142 let body = req.json_body();
1143 let key_id = Self::require_key_id(&body)?;
1144
1145 let resolved = self
1149 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1150 .ok_or_else(|| {
1151 AwsServiceError::aws_error(
1152 StatusCode::BAD_REQUEST,
1153 "NotFoundException",
1154 format!("Key '{key_id}' does not exist"),
1155 )
1156 })?;
1157
1158 let mut accounts = self.state.write();
1159 let state = accounts.get_or_create(&req.account_id);
1160 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1161 AwsServiceError::aws_error(
1162 StatusCode::INTERNAL_SERVER_ERROR,
1163 "KMSInternalException",
1164 "Key state became inconsistent",
1165 )
1166 })?;
1167 key.key_rotation_enabled = true;
1168
1169 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1170 }
1171
1172 fn disable_key_rotation(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1173 let body = req.json_body();
1174 let key_id = Self::require_key_id(&body)?;
1175
1176 let resolved = self
1178 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1179 .ok_or_else(|| {
1180 AwsServiceError::aws_error(
1181 StatusCode::BAD_REQUEST,
1182 "NotFoundException",
1183 format!("Key '{key_id}' does not exist"),
1184 )
1185 })?;
1186
1187 let mut accounts = self.state.write();
1188 let state = accounts.get_or_create(&req.account_id);
1189 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1190 AwsServiceError::aws_error(
1191 StatusCode::INTERNAL_SERVER_ERROR,
1192 "KMSInternalException",
1193 "Key state became inconsistent",
1194 )
1195 })?;
1196 key.key_rotation_enabled = false;
1197
1198 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1199 }
1200
1201 fn rotate_key_on_demand(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1202 let body = req.json_body();
1203 let resolved = self.resolve_required_key(req, &body)?;
1204
1205 let mut accounts = self.state.write();
1206 let state = accounts.get_or_create(&req.account_id);
1207 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1208 AwsServiceError::aws_error(
1209 StatusCode::INTERNAL_SERVER_ERROR,
1210 "KMSInternalException",
1211 "Key state became inconsistent",
1212 )
1213 })?;
1214
1215 let rotation = KeyRotation {
1216 key_id: key.key_id.clone(),
1217 rotation_date: Utc::now().timestamp() as f64,
1218 rotation_type: "ON_DEMAND".to_string(),
1219 };
1220 key.rotations.push(rotation);
1221
1222 Ok(AwsResponse::json(
1223 StatusCode::OK,
1224 serde_json::to_string(&json!({
1225 "KeyId": key.key_id,
1226 }))
1227 .unwrap(),
1228 ))
1229 }
1230
1231 fn list_key_rotations(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1232 let body = req.json_body();
1233 let resolved = self.resolve_required_key(req, &body)?;
1234 validate_optional_json_range("limit", &body["Limit"], 1, 1000)?;
1235 let limit = body["Limit"].as_i64().unwrap_or(1000) as usize;
1236 let marker = body["Marker"].as_str();
1237
1238 let accounts = self.state.read();
1239 let empty = KmsState::new(&req.account_id, &req.region);
1240 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1241 let key = state.keys.get(&resolved).ok_or_else(|| {
1242 AwsServiceError::aws_error(
1243 StatusCode::INTERNAL_SERVER_ERROR,
1244 "KMSInternalException",
1245 "Key state became inconsistent",
1246 )
1247 })?;
1248
1249 let start_index = if let Some(marker) = marker {
1250 marker.parse::<usize>().unwrap_or(0)
1251 } else {
1252 0
1253 };
1254
1255 let rotations: Vec<Value> = key
1256 .rotations
1257 .iter()
1258 .skip(start_index)
1259 .take(limit)
1260 .map(|r| {
1261 json!({
1262 "KeyId": r.key_id,
1263 "RotationDate": r.rotation_date,
1264 "RotationType": r.rotation_type,
1265 })
1266 })
1267 .collect();
1268
1269 let total_after_start = key.rotations.len().saturating_sub(start_index);
1270 let truncated = total_after_start > limit;
1271
1272 let mut response = json!({
1273 "Rotations": rotations,
1274 "Truncated": truncated,
1275 });
1276
1277 if truncated {
1278 response["NextMarker"] = json!((start_index + limit).to_string());
1279 }
1280
1281 Ok(AwsResponse::json(
1282 StatusCode::OK,
1283 serde_json::to_string(&response).unwrap(),
1284 ))
1285 }
1286
1287 fn replicate_key(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1288 let body = req.json_body();
1289 let key_id = Self::require_key_id(&body)?;
1290 let replica_region = body["ReplicaRegion"].as_str().unwrap_or("").to_string();
1291
1292 let resolved = self
1293 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1294 .ok_or_else(|| {
1295 AwsServiceError::aws_error(
1296 StatusCode::BAD_REQUEST,
1297 "NotFoundException",
1298 format!("Key '{key_id}' does not exist"),
1299 )
1300 })?;
1301
1302 let mut accounts = self.state.write();
1303 let state = accounts.get_or_create(&req.account_id);
1304
1305 let source_key = state
1308 .keys
1309 .get(&resolved)
1310 .ok_or_else(|| {
1311 AwsServiceError::aws_error(
1312 StatusCode::INTERNAL_SERVER_ERROR,
1313 "KMSInternalException",
1314 "Key state became inconsistent",
1315 )
1316 })?
1317 .clone();
1318 let account_id = state.account_id.clone();
1319 let source_region = state.region.clone();
1320
1321 let replica_arn = format!(
1322 "arn:aws:kms:{}:{}:key/{}",
1323 replica_region, account_id, source_key.key_id
1324 );
1325
1326 let metadata = json!({
1327 "KeyId": source_key.key_id,
1328 "Arn": replica_arn,
1329 "AWSAccountId": account_id,
1330 "CreationDate": source_key.creation_date,
1331 "Description": source_key.description,
1332 "Enabled": source_key.enabled,
1333 "KeyUsage": source_key.key_usage,
1334 "KeySpec": source_key.key_spec,
1335 "CustomerMasterKeySpec": source_key.key_spec,
1336 "KeyManager": source_key.key_manager,
1337 "KeyState": source_key.key_state,
1338 "Origin": source_key.origin,
1339 "MultiRegion": true,
1340 "MultiRegionConfiguration": {
1341 "MultiRegionKeyType": "REPLICA",
1342 "PrimaryKey": {
1343 "Arn": source_key.arn,
1344 "Region": source_region,
1345 },
1346 "ReplicaKeys": [],
1347 },
1348 });
1349
1350 let replica_storage_key = format!("{}:{}", replica_region, source_key.key_id);
1351 let source_policy = source_key.policy.clone();
1352 let replica_key = KmsKey {
1353 arn: replica_arn,
1354 deletion_date: None,
1355 key_rotation_enabled: false,
1356 multi_region: true,
1357 rotations: Vec::new(),
1358 custom_key_store_id: None,
1359 imported_key_material: false,
1360 imported_material_bytes: None,
1361 private_key_seed: rand_bytes(32),
1362 primary_region: None,
1363 ..source_key
1364 };
1365
1366 state.keys.insert(replica_storage_key, replica_key);
1367
1368 Ok(AwsResponse::json(
1369 StatusCode::OK,
1370 serde_json::to_string(&json!({
1371 "ReplicaKeyMetadata": metadata,
1372 "ReplicaPolicy": source_policy,
1373 }))
1374 .unwrap(),
1375 ))
1376 }
1377
1378 fn update_primary_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1379 let body = req.json_body();
1380 let key_id = Self::require_key_id(&body)?;
1381 let primary_region = body["PrimaryRegion"]
1382 .as_str()
1383 .ok_or_else(|| {
1384 AwsServiceError::aws_error(
1385 StatusCode::BAD_REQUEST,
1386 "ValidationException",
1387 "PrimaryRegion is required",
1388 )
1389 })?
1390 .to_string();
1391
1392 let resolved = self
1393 .resolve_key_id_for(&req.account_id, &req.region, &key_id)
1394 .ok_or_else(|| {
1395 AwsServiceError::aws_error(
1396 StatusCode::BAD_REQUEST,
1397 "NotFoundException",
1398 format!("Key '{key_id}' does not exist"),
1399 )
1400 })?;
1401
1402 let mut accounts = self.state.write();
1403 let state = accounts.get_or_create(&req.account_id);
1404 let account_id = state.account_id.clone();
1405 let key = state.keys.get_mut(&resolved).ok_or_else(|| {
1406 AwsServiceError::aws_error(
1407 StatusCode::BAD_REQUEST,
1408 "NotFoundException",
1409 format!("Key '{key_id}' does not exist"),
1410 )
1411 })?;
1412
1413 if !key.multi_region {
1414 return Err(AwsServiceError::aws_error(
1415 StatusCode::BAD_REQUEST,
1416 "UnsupportedOperationException",
1417 format!("Key '{}' is not a multi-Region key", key.arn),
1418 ));
1419 }
1420 key.primary_region = Some(primary_region.clone());
1421 key.arn = format!(
1423 "arn:aws:kms:{}:{}:key/{}",
1424 primary_region, account_id, key.key_id
1425 );
1426
1427 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1428 }
1429}
1430
1431#[path = "asym.rs"]
1432pub(crate) mod asym;
1433#[path = "asym_ecdsa.rs"]
1434pub(crate) mod asym_ecdsa;
1435#[path = "mac.rs"]
1436pub(crate) mod mac;
1437#[path = "service_aliases.rs"]
1438mod service_aliases;
1439#[path = "service_crypto.rs"]
1440mod service_crypto;
1441#[path = "service_custom_store.rs"]
1442mod service_custom_store;
1443#[path = "service_grants.rs"]
1444mod service_grants;
1445
1446#[path = "helpers.rs"]
1447mod helpers;
1448pub(crate) use helpers::*;
1449
1450#[path = "provisioner.rs"]
1451pub mod provisioner;
1452
1453#[cfg(test)]
1454#[path = "service_tests.rs"]
1455mod tests;