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