1use std::collections::{BTreeMap, HashMap};
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use chrono::Utc;
6use http::StatusCode;
7use serde_json::{json, Value};
8
9use tokio::sync::Mutex as AsyncMutex;
10
11use fakecloud_aws::arn::Arn;
12use fakecloud_core::delivery::DeliveryBus;
13use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
14use fakecloud_core::validation::*;
15use fakecloud_persistence::SnapshotStore;
16
17use crate::state::{
18 RotationRules, Secret, SecretVersion, SecretsManagerSnapshot, SecretsManagerState,
19 SharedSecretsManagerState, SECRETSMANAGER_SNAPSHOT_SCHEMA_VERSION,
20};
21
22struct RotationInvocation {
24 lambda_arn: String,
25 secret_id: String,
26 client_request_token: String,
27}
28
29pub(crate) enum VersionIdempotency {
32 NotFound,
34 Match,
38 Conflict,
41}
42
43pub struct SecretsManagerService {
44 state: SharedSecretsManagerState,
45 delivery_bus: Option<Arc<DeliveryBus>>,
46 snapshot_store: Option<Arc<dyn SnapshotStore>>,
47 snapshot_lock: Arc<AsyncMutex<()>>,
48 kms_hook: Option<Arc<dyn fakecloud_core::delivery::KmsHook>>,
49}
50
51impl SecretsManagerService {
52 pub fn new(state: SharedSecretsManagerState) -> Self {
53 Self {
54 state,
55 delivery_bus: None,
56 snapshot_store: None,
57 snapshot_lock: Arc::new(AsyncMutex::new(())),
58 kms_hook: None,
59 }
60 }
61
62 pub fn with_delivery(mut self, delivery_bus: Arc<DeliveryBus>) -> Self {
63 self.delivery_bus = Some(delivery_bus);
64 self
65 }
66
67 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
68 self.snapshot_store = Some(store);
69 self
70 }
71
72 pub fn with_kms_hook(mut self, hook: Arc<dyn fakecloud_core::delivery::KmsHook>) -> Self {
73 self.kms_hook = Some(hook);
74 self
75 }
76
77 fn maybe_encrypt_secret_string(
78 &self,
79 account_id: &str,
80 region: &str,
81 secret_arn: &str,
82 kms_key_id: Option<&str>,
83 plaintext: Option<String>,
84 ) -> Option<String> {
85 let pt = plaintext?;
86 let (Some(hook), Some(key)) = (&self.kms_hook, kms_key_id) else {
87 return Some(pt);
88 };
89 let key = if key.is_empty() {
90 "aws/secretsmanager"
91 } else {
92 key
93 };
94 let mut ctx = HashMap::new();
95 ctx.insert(
96 "aws:secretsmanager:secretArn".to_string(),
97 secret_arn.to_string(),
98 );
99 match hook.encrypt(
100 account_id,
101 region,
102 key,
103 pt.as_bytes(),
104 "secretsmanager.amazonaws.com",
105 ctx,
106 ) {
107 Ok(ciphertext) => Some(ciphertext),
108 Err(err) => {
109 tracing::warn!(
110 secret_arn = %secret_arn,
111 error = %err,
112 "KMS encrypt failed for secret; storing plaintext"
113 );
114 Some(pt)
115 }
116 }
117 }
118
119 fn maybe_decrypt_secret_string(
120 &self,
121 account_id: &str,
122 secret_arn: &str,
123 kms_key_id: Option<&str>,
124 stored: Option<&str>,
125 ) -> Option<String> {
126 let stored = stored?;
127 let (Some(hook), Some(_)) = (&self.kms_hook, kms_key_id) else {
128 return Some(stored.to_string());
129 };
130 let mut ctx = HashMap::new();
131 ctx.insert(
132 "aws:secretsmanager:secretArn".to_string(),
133 secret_arn.to_string(),
134 );
135 match hook.decrypt(account_id, stored, "secretsmanager.amazonaws.com", ctx) {
136 Ok(bytes) => Some(String::from_utf8_lossy(&bytes).to_string()),
137 Err(_) => Some(stored.to_string()),
138 }
139 }
140
141 async fn save_snapshot(&self) {
145 save_secretsmanager_snapshot(
146 &self.state,
147 self.snapshot_store.clone(),
148 &self.snapshot_lock,
149 )
150 .await;
151 }
152
153 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
159 let store = self.snapshot_store.clone()?;
160 let state = self.state.clone();
161 let lock = self.snapshot_lock.clone();
162 Some(Arc::new(move || {
163 let state = state.clone();
164 let store = store.clone();
165 let lock = lock.clone();
166 Box::pin(async move {
167 save_secretsmanager_snapshot(&state, Some(store), &lock).await;
168 })
169 }))
170 }
171
172 fn create_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
173 let input = CreateSecretInput::from_body(&req.json_body())?;
174 let has_value = input.secret_string.is_some() || input.secret_binary.is_some();
175
176 let mut accounts = self.state.write();
177 let state = accounts.get_or_create(&req.account_id);
178
179 if let Some(existing) = state.secrets.get(&input.name) {
180 if let Some(ref token) = input.client_request_token {
181 let existing_plaintext = existing.versions.get(token).and_then(|v| {
182 self.maybe_decrypt_secret_string(
183 &req.account_id,
184 &existing.arn,
185 existing.kms_key_id.as_deref(),
186 v.secret_string.as_deref(),
187 )
188 });
189 match check_secret_version_idempotency(
190 &existing.versions,
191 token,
192 existing_plaintext,
193 &input.secret_string,
194 &input.secret_binary,
195 ) {
196 VersionIdempotency::Match => {
197 let mut response = json!({
198 "ARN": existing.arn,
199 "Name": existing.name,
200 "VersionId": token,
201 });
202 if !has_value {
203 response.as_object_mut().unwrap().remove("VersionId");
204 }
205 return Ok(AwsResponse::ok_json(response));
206 }
207 VersionIdempotency::Conflict => {
208 return Err(AwsServiceError::aws_error(
209 StatusCode::BAD_REQUEST,
210 "ResourceExistsException",
211 format!(
212 "You can't use ClientRequestToken {token} because that value is already in use for a version of secret {}.",
213 existing.arn
214 ),
215 ));
216 }
217 VersionIdempotency::NotFound => {}
218 }
219 }
220 return Err(AwsServiceError::aws_error(
221 StatusCode::BAD_REQUEST,
222 "ResourceExistsException",
223 format!(
224 "The operation failed because the secret {} already exists.",
225 input.name
226 ),
227 ));
228 }
229
230 let arn = format!(
231 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
232 req.region,
233 req.account_id,
234 input.name,
235 &uuid::Uuid::new_v4().to_string()[..6]
236 );
237
238 let now = Utc::now();
239
240 let (versions, current_version_id, version_id_for_response) = if has_value {
241 let vid = input
242 .client_request_token
243 .clone()
244 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
245 let stored_string = self.maybe_encrypt_secret_string(
246 &req.account_id,
247 &req.region,
248 &arn,
249 input.kms_key_id.as_deref(),
250 input.secret_string,
251 );
252 let version = SecretVersion {
253 version_id: vid.clone(),
254 secret_string: stored_string,
255 secret_binary: input.secret_binary,
256 stages: vec!["AWSCURRENT".to_string()],
257 created_at: now,
258 };
259 let mut versions = std::collections::BTreeMap::new();
260 versions.insert(vid.clone(), version);
261 (versions, Some(vid.clone()), Some(vid))
262 } else {
263 (std::collections::BTreeMap::new(), None, None)
264 };
265
266 let tags_ever_set = !input.tags.is_empty();
267 let secret = Secret {
268 name: input.name.clone(),
269 arn: arn.clone(),
270 description: input.description,
271 kms_key_id: input.kms_key_id,
272 versions,
273 current_version_id,
274 tags: input.tags,
275 tags_ever_set,
276 deleted: false,
277 deletion_date: None,
278 created_at: now,
279 last_changed_at: now,
280 last_accessed_at: None,
281 rotation_enabled: None,
282 rotation_lambda_arn: None,
283 rotation_rules: None,
284 last_rotated_at: None,
285 resource_policy: None,
286 replica_regions: Vec::new(),
287 };
288
289 state.secrets.insert(input.name.clone(), secret);
290
291 let mut response = json!({
292 "ARN": arn,
293 "Name": input.name,
294 });
295 if let Some(vid) = version_id_for_response {
296 response["VersionId"] = json!(vid);
297 }
298
299 Ok(AwsResponse::ok_json(response))
300 }
301
302 fn get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
303 let body = req.json_body();
304 let secret_id = require_secret_id(&body)?;
305 validate_optional_string_length("versionId", body["VersionId"].as_str(), 32, 64)?;
306 validate_optional_string_length("versionStage", body["VersionStage"].as_str(), 1, 256)?;
307
308 let owner_account = secret_owner_account(&secret_id, &req.account_id);
312 let mut accounts = self.state.write();
313 let state = accounts.get_or_create(&owner_account);
314 let secret = self.find_secret_mut(state, &secret_id)?;
315 if owner_account != req.account_id {
316 let policy_doc = secret.resource_policy.as_deref().unwrap_or("");
317 let secret_arn = secret.arn.clone();
318 if !resource_policy_allows(policy_doc, &req.account_id, &secret_arn) {
319 return Err(AwsServiceError::aws_error(
320 StatusCode::FORBIDDEN,
321 "AccessDeniedException",
322 "User is not authorized to perform: secretsmanager:GetSecretValue on the requested resource",
323 ));
324 }
325 }
326
327 if secret.deleted {
328 return Err(AwsServiceError::aws_error(
329 StatusCode::BAD_REQUEST,
330 "InvalidRequestException",
331 "You can't perform this operation on the secret because it was marked for deletion.",
332 ));
333 }
334
335 let requested_stage = body["VersionStage"].as_str().unwrap_or("AWSCURRENT");
336
337 let version_id = body["VersionId"]
339 .as_str()
340 .map(|s| s.to_string())
341 .or_else(|| {
342 secret
343 .versions
344 .iter()
345 .find(|(_, v)| v.stages.contains(&requested_stage.to_string()))
346 .map(|(id, _)| id.clone())
347 });
348
349 let version_id = match version_id {
350 Some(vid) => vid,
351 None => {
352 return Err(AwsServiceError::aws_error(
354 StatusCode::NOT_FOUND,
355 "ResourceNotFoundException",
356 format!(
357 "Secrets Manager can't find the specified secret value for staging label: {requested_stage}"
358 ),
359 ));
360 }
361 };
362
363 let version = secret.versions.get(&version_id).ok_or_else(|| {
364 AwsServiceError::aws_error(
365 StatusCode::NOT_FOUND,
366 "ResourceNotFoundException",
367 format!(
368 "Secrets Manager can't find the specified secret value for VersionId: {version_id}"
369 ),
370 )
371 })?;
372
373 if body["VersionId"].as_str().is_some() {
375 if let Some(stage) = body["VersionStage"].as_str() {
376 if !version.stages.contains(&stage.to_string()) {
377 return Err(AwsServiceError::aws_error(
378 StatusCode::NOT_FOUND,
379 "ResourceNotFoundException",
380 "You provided a VersionStage that is not associated to the provided VersionId.",
381 ));
382 }
383 }
384 }
385
386 secret.last_accessed_at = Some(Utc::now());
388
389 let mut response = json!({
390 "ARN": secret.arn,
391 "Name": secret.name,
392 "VersionId": version.version_id,
393 "VersionStages": version.stages,
394 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
395 });
396
397 let kms_for_decrypt = secret.kms_key_id.clone();
398 let arn_for_decrypt = secret.arn.clone();
399 if let Some(ref s) = version.secret_string {
400 let plaintext = self
401 .maybe_decrypt_secret_string(
402 &req.account_id,
403 &arn_for_decrypt,
404 kms_for_decrypt.as_deref(),
405 Some(s.as_str()),
406 )
407 .unwrap_or_else(|| s.clone());
408 response["SecretString"] = json!(plaintext);
409 }
410 if let Some(ref b) = version.secret_binary {
411 response["SecretBinary"] = json!(base64_encode(b));
412 }
413
414 Ok(AwsResponse::ok_json(response))
415 }
416
417 fn put_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
418 let body = req.json_body();
419 let secret_id = require_secret_id(&body)?;
420 validate_optional_string_length(
421 "clientRequestToken",
422 body["ClientRequestToken"].as_str(),
423 32,
424 64,
425 )?;
426 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
427
428 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
429 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
430
431 if secret_string.is_none() && secret_binary.is_none() {
433 return Err(AwsServiceError::aws_error(
434 StatusCode::BAD_REQUEST,
435 "InvalidRequestException",
436 "You must provide either SecretString or SecretBinary.",
437 ));
438 }
439
440 let mut accounts = self.state.write();
441 let state = accounts.get_or_create(&req.account_id);
442 let secret = match self.find_secret_mut(state, &secret_id) {
443 Ok(s) => s,
444 Err(_) => {
445 return Err(AwsServiceError::aws_error(
446 StatusCode::NOT_FOUND,
447 "ResourceNotFoundException",
448 "Secrets Manager can't find the specified secret.",
449 ));
450 }
451 };
452
453 if secret.deleted {
454 return Err(AwsServiceError::aws_error(
455 StatusCode::BAD_REQUEST,
456 "InvalidRequestException",
457 "You can't perform this operation on the secret because it was marked for deletion.",
458 ));
459 }
460
461 let now = Utc::now();
462 let version_id = body["ClientRequestToken"]
463 .as_str()
464 .map(|s| s.to_string())
465 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
466
467 let existing_plaintext = secret.versions.get(&version_id).and_then(|v| {
468 self.maybe_decrypt_secret_string(
469 &req.account_id,
470 &secret.arn,
471 secret.kms_key_id.as_deref(),
472 v.secret_string.as_deref(),
473 )
474 });
475 match check_secret_version_idempotency(
476 &secret.versions,
477 &version_id,
478 existing_plaintext,
479 &secret_string,
480 &secret_binary,
481 ) {
482 VersionIdempotency::Match => {
483 let existing_stages = secret.versions[&version_id].stages.clone();
484 return Ok(AwsResponse::ok_json(json!({
485 "ARN": secret.arn,
486 "Name": secret.name,
487 "VersionId": version_id,
488 "VersionStages": existing_stages,
489 })));
490 }
491 VersionIdempotency::Conflict => {
492 return Err(AwsServiceError::aws_error(
493 StatusCode::BAD_REQUEST,
494 "ResourceExistsException",
495 format!(
496 "You can't use ClientRequestToken {version_id} because that value is already in use for a version of secret {}.",
497 secret.arn
498 ),
499 ));
500 }
501 VersionIdempotency::NotFound => {}
502 }
503
504 let mut version_stages: Vec<String> = body["VersionStages"]
505 .as_array()
506 .map(|arr| {
507 arr.iter()
508 .filter_map(|v| v.as_str().map(|s| s.to_string()))
509 .collect()
510 })
511 .unwrap_or_else(|| vec!["AWSCURRENT".to_string()]);
512
513 let has_current = secret
515 .versions
516 .values()
517 .any(|v| v.stages.contains(&"AWSCURRENT".to_string()));
518 if !has_current && !version_stages.contains(&"AWSCURRENT".to_string()) {
519 version_stages.push("AWSCURRENT".to_string());
520 }
521
522 if version_stages.contains(&"AWSCURRENT".to_string()) {
524 if let Some(ref old_vid) = secret.current_version_id.clone() {
525 if let Some(old_version) = secret.versions.get_mut(old_vid) {
526 old_version.stages.retain(|s| s != "AWSCURRENT");
527 if !old_version.stages.contains(&"AWSPREVIOUS".to_string()) {
528 old_version.stages.push("AWSPREVIOUS".to_string());
529 }
530 }
531 for (id, v) in secret.versions.iter_mut() {
533 if id != old_vid {
534 v.stages.retain(|s| s != "AWSPREVIOUS");
535 }
536 }
537 }
538 secret.current_version_id = Some(version_id.clone());
539 }
540
541 for stage in &version_stages {
543 if stage == "AWSCURRENT" || stage == "AWSPREVIOUS" {
544 continue;
545 }
546 for v in secret.versions.values_mut() {
547 v.stages.retain(|s| s != stage);
548 }
549 }
550
551 secret.versions.retain(|_, v| !v.stages.is_empty());
553
554 let kms_key_for_enc = secret.kms_key_id.clone();
555 let arn_for_enc = secret.arn.clone();
556 let stored_secret_string = self.maybe_encrypt_secret_string(
557 &req.account_id,
558 &req.region,
559 &arn_for_enc,
560 kms_key_for_enc.as_deref(),
561 secret_string,
562 );
563 let version = SecretVersion {
564 version_id: version_id.clone(),
565 secret_string: stored_secret_string,
566 secret_binary,
567 stages: version_stages.clone(),
568 created_at: now,
569 };
570
571 secret.versions.insert(version_id.clone(), version);
572 secret.last_changed_at = now;
573
574 let response = json!({
575 "ARN": secret.arn,
576 "Name": secret.name,
577 "VersionId": version_id,
578 "VersionStages": version_stages,
579 });
580
581 Ok(AwsResponse::ok_json(response))
582 }
583
584 fn update_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
585 let body = req.json_body();
586 let secret_id = require_secret_id(&body)?;
587 validate_optional_string_length(
588 "clientRequestToken",
589 body["ClientRequestToken"].as_str(),
590 32,
591 64,
592 )?;
593 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
594 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
595 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
596
597 let mut accounts = self.state.write();
598 let state = accounts.get_or_create(&req.account_id);
599 let secret = match self.find_secret_mut(state, &secret_id) {
600 Ok(s) => s,
601 Err(_) => {
602 return Err(AwsServiceError::aws_error(
603 StatusCode::NOT_FOUND,
604 "ResourceNotFoundException",
605 "Secrets Manager can't find the specified secret.",
606 ));
607 }
608 };
609
610 if secret.deleted {
611 return Err(AwsServiceError::aws_error(
612 StatusCode::BAD_REQUEST,
613 "InvalidRequestException",
614 "You can't perform this operation on the secret because it was marked for deletion.",
615 ));
616 }
617
618 if let Some(desc) = body["Description"].as_str() {
619 secret.description = Some(desc.to_string());
620 }
621 if let Some(kms) = body["KmsKeyId"].as_str() {
622 secret.kms_key_id = Some(kms.to_string());
623 }
624
625 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
627 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
628
629 let version_id = if secret_string.is_some() || secret_binary.is_some() {
630 let vid = body["ClientRequestToken"]
631 .as_str()
632 .map(|s| s.to_string())
633 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
634
635 let existing_plaintext = secret.versions.get(&vid).and_then(|v| {
636 self.maybe_decrypt_secret_string(
637 &req.account_id,
638 &secret.arn,
639 secret.kms_key_id.as_deref(),
640 v.secret_string.as_deref(),
641 )
642 });
643 match check_secret_version_idempotency(
644 &secret.versions,
645 &vid,
646 existing_plaintext,
647 &secret_string,
648 &secret_binary,
649 ) {
650 VersionIdempotency::Match => {
651 return Ok(AwsResponse::ok_json(json!({
652 "ARN": secret.arn,
653 "Name": secret.name,
654 "VersionId": vid,
655 })));
656 }
657 VersionIdempotency::Conflict => {
658 return Err(AwsServiceError::aws_error(
659 StatusCode::BAD_REQUEST,
660 "ResourceExistsException",
661 format!(
662 "You can't use ClientRequestToken {vid} because that value is already in use for a version of secret {}.",
663 secret.arn
664 ),
665 ));
666 }
667 VersionIdempotency::NotFound => {}
668 }
669
670 let now = Utc::now();
671
672 if let Some(ref old_vid) = secret.current_version_id.clone() {
674 if let Some(old_v) = secret.versions.get_mut(old_vid) {
675 old_v.stages.retain(|s| s != "AWSCURRENT");
676 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
677 old_v.stages.push("AWSPREVIOUS".to_string());
678 }
679 }
680 }
681
682 let version = SecretVersion {
683 version_id: vid.clone(),
684 secret_string,
685 secret_binary,
686 stages: vec!["AWSCURRENT".to_string()],
687 created_at: now,
688 };
689 secret.versions.insert(vid.clone(), version);
690 secret.current_version_id = Some(vid.clone());
691 secret.last_changed_at = now;
692 Some(vid)
693 } else {
694 secret.last_changed_at = Utc::now();
695 None
696 };
697
698 let mut response = json!({
699 "ARN": secret.arn,
700 "Name": secret.name,
701 });
702 if let Some(vid) = version_id {
703 response["VersionId"] = json!(vid);
704 }
705
706 Ok(AwsResponse::ok_json(response))
707 }
708
709 fn delete_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
710 let body = req.json_body();
711 let secret_id = require_secret_id(&body)?;
712
713 let force_delete = body["ForceDeleteWithoutRecovery"]
714 .as_bool()
715 .unwrap_or(false);
716 let recovery_window = body.get("RecoveryWindowInDays").and_then(|v| v.as_i64());
717
718 if let Some(days) = recovery_window {
720 if !(7..=30).contains(&days) {
721 return Err(AwsServiceError::aws_error(
722 StatusCode::BAD_REQUEST,
723 "InvalidParameterException",
724 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: RecoveryWindowInDays value must be between 7 and 30 days (inclusive).",
725 ));
726 }
727 }
728
729 if force_delete && recovery_window.is_some() {
731 return Err(AwsServiceError::aws_error(
732 StatusCode::BAD_REQUEST,
733 "InvalidParameterException",
734 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: You can't use ForceDeleteWithoutRecovery in conjunction with RecoveryWindowInDays.",
735 ));
736 }
737
738 let mut accounts = self.state.write();
739 let state = accounts.get_or_create(&req.account_id);
740
741 if force_delete {
742 match self.find_secret_mut(state, &secret_id) {
744 Ok(secret) => {
745 let arn = secret.arn.clone();
746 let name = secret.name.clone();
747 let deletion_date = Utc::now();
748 state.secrets.remove(&name);
749 let response = json!({
750 "ARN": arn,
751 "Name": name,
752 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
753 });
754 return Ok(AwsResponse::ok_json(response));
755 }
756 Err(_) => {
757 let arn = format!(
759 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
760 req.region,
761 req.account_id,
762 secret_id,
763 &uuid::Uuid::new_v4().to_string()[..6]
764 );
765 let deletion_date = Utc::now();
766 let response = json!({
767 "ARN": arn,
768 "Name": secret_id,
769 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
770 });
771 return Ok(AwsResponse::ok_json(response));
772 }
773 }
774 }
775
776 let secret = self.find_secret_mut(state, &secret_id)?;
777
778 if secret.deleted {
779 return Err(AwsServiceError::aws_error(
780 StatusCode::BAD_REQUEST,
781 "InvalidRequestException",
782 "You can't perform this operation on the secret because it was already scheduled for deletion.",
783 ));
784 }
785
786 let now = Utc::now();
787 let days = recovery_window.unwrap_or(30);
788 let deletion_date = now + chrono::Duration::days(days);
789 secret.deleted = true;
790 secret.deletion_date = Some(deletion_date);
791
792 let response = json!({
793 "ARN": secret.arn,
794 "Name": secret.name,
795 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
796 });
797
798 Ok(AwsResponse::ok_json(response))
799 }
800
801 fn restore_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
802 let body = req.json_body();
803 let secret_id = require_secret_id(&body)?;
804
805 let mut accounts = self.state.write();
806 let state = accounts.get_or_create(&req.account_id);
807 let secret = self.find_secret_mut(state, &secret_id)?;
808
809 secret.deleted = false;
811 secret.deletion_date = None;
812
813 let response = json!({
814 "ARN": secret.arn,
815 "Name": secret.name,
816 });
817
818 Ok(AwsResponse::ok_json(response))
819 }
820
821 fn describe_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
822 let body = req.json_body();
823 let secret_id = require_secret_id(&body)?;
824
825 let accounts = self.state.read();
826 let empty = SecretsManagerState::new(&req.account_id, &req.region);
827 let state = accounts.get(&req.account_id).unwrap_or(&empty);
828 let secret = self.find_secret_ref(state, &secret_id)?;
829
830 let mut response = json!({
831 "ARN": secret.arn,
832 "Name": secret.name,
833 "CreatedDate": secret.created_at.timestamp_millis() as f64 / 1000.0,
834 "LastChangedDate": secret.last_changed_at.timestamp_millis() as f64 / 1000.0,
835 });
836
837 if !secret.versions.is_empty() {
838 let mut version_ids_to_stages: serde_json::Map<String, Value> = serde_json::Map::new();
839 for (vid, version) in &secret.versions {
840 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
841 }
842 response["VersionIdsToStages"] = Value::Object(version_ids_to_stages);
843 }
844
845 if let Some(ref desc) = secret.description {
846 if !desc.is_empty() {
847 response["Description"] = json!(desc);
848 }
849 }
850
851 if secret.tags_ever_set || !secret.tags.is_empty() {
852 response["Tags"] = json!(tags_to_json(&secret.tags));
853 }
854
855 if let Some(ref kms) = secret.kms_key_id {
856 response["KmsKeyId"] = json!(kms);
857 }
858 if secret.deleted {
859 response["DeletedDate"] = json!(secret
860 .deletion_date
861 .map(|d| d.timestamp_millis() as f64 / 1000.0));
862 }
863 if let Some(rotation_enabled) = secret.rotation_enabled {
864 response["RotationEnabled"] = json!(rotation_enabled);
865 }
866 if let Some(ref lambda_arn) = secret.rotation_lambda_arn {
867 response["RotationLambdaARN"] = json!(lambda_arn);
868 }
869 if let Some(ref rules) = secret.rotation_rules {
870 let mut rules_json = json!({});
871 if let Some(days) = rules.automatically_after_days {
872 rules_json["AutomaticallyAfterDays"] = json!(days);
873 }
874 if let Some(ref duration) = rules.duration {
875 rules_json["Duration"] = json!(duration);
876 }
877 if let Some(ref expr) = rules.schedule_expression {
878 rules_json["ScheduleExpression"] = json!(expr);
879 }
880 response["RotationRules"] = rules_json;
881 }
882 if let Some(last_rotated) = secret.last_rotated_at {
883 response["LastRotatedDate"] = json!(last_rotated.timestamp_millis() as f64 / 1000.0);
884 }
885 if !secret.replica_regions.is_empty() {
886 response["ReplicationStatus"] = replication_status_json(&secret.replica_regions);
887 }
888 if secret.rotation_enabled == Some(true) {
890 if let Some(ref rules) = secret.rotation_rules {
891 if let Some(days) = rules.automatically_after_days {
892 let base = secret.last_rotated_at.unwrap_or(secret.created_at);
893 let next = base + chrono::Duration::days(days);
894 response["NextRotationDate"] = json!(next.timestamp_millis() as f64 / 1000.0);
895 }
896 }
897 }
898
899 Ok(AwsResponse::ok_json(response))
900 }
901
902 fn list_secrets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
903 let body = req.json_body();
904 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
905 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
906 validate_optional_enum("sortBy", body["SortBy"].as_str(), &["name", "created-date"])?;
907 validate_optional_enum("sortOrder", body["SortOrder"].as_str(), &["asc", "desc"])?;
908 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
909 let next_token = body["NextToken"].as_str();
910 let filters = body["Filters"].as_array();
911 let include_deleted = body["IncludePlannedDeletion"].as_bool().unwrap_or(false);
912
913 if let Some(filters) = filters {
915 for filter in filters {
916 let key = filter["Key"].as_str().unwrap_or("");
917 let values = filter["Values"].as_array();
918
919 if key.is_empty() {
920 return Err(AwsServiceError::aws_error(
921 StatusCode::BAD_REQUEST,
922 "InvalidParameterException",
923 "Invalid filter key",
924 ));
925 }
926
927 let valid_keys = [
928 "all",
929 "name",
930 "tag-key",
931 "description",
932 "tag-value",
933 "owning-service",
934 "primary-region",
935 ];
936 if !valid_keys.contains(&key) {
937 return Err(AwsServiceError::aws_error(
938 StatusCode::BAD_REQUEST,
939 "ValidationException",
940 format!(
941 "1 validation error detected: Value '{}' at 'filters.1.member.key' failed to satisfy constraint: Member must satisfy enum value set: [all, name, tag-key, description, tag-value]",
942 key
943 ),
944 ));
945 }
946
947 if values.is_none() || values.unwrap().is_empty() {
948 return Err(AwsServiceError::aws_error(
949 StatusCode::BAD_REQUEST,
950 "InvalidParameterException",
951 format!("Invalid filter values for key: {key}"),
952 ));
953 }
954 }
955 }
956
957 let accounts = self.state.read();
958 let empty = SecretsManagerState::new(&req.account_id, &req.region);
959 let state = accounts.get(&req.account_id).unwrap_or(&empty);
960
961 let mut secrets: Vec<&Secret> = state
962 .secrets
963 .values()
964 .filter(|s| {
965 if s.deleted && !include_deleted {
967 return false;
968 }
969
970 if let Some(filters) = filters {
971 for filter in filters {
972 let key = filter["Key"].as_str().unwrap_or("");
973 let values: Vec<&str> = filter["Values"]
974 .as_array()
975 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
976 .unwrap_or_default();
977
978 let matches = match key {
979 "name" => filter_name(s, &values),
980 "description" => filter_description(s, &values),
981 "tag-key" => filter_tag_key(s, &values),
982 "tag-value" => filter_tag_value(s, &values),
983 "all" => filter_all(s, &values),
984 "owning-service" => false,
985 "primary-region" => false,
986 _ => true,
987 };
988
989 if !matches {
990 return false;
991 }
992 }
993 }
994 true
995 })
996 .collect();
997 secrets.sort_by_key(|a| a.created_at);
998
999 let start_idx = if let Some(token) = next_token {
1001 secrets.iter().position(|s| s.name == token).unwrap_or(0)
1002 } else {
1003 0
1004 };
1005
1006 let page: Vec<Value> = secrets
1007 .iter()
1008 .skip(start_idx)
1009 .take(max_results)
1010 .map(|s| {
1011 let mut version_ids_to_stages: serde_json::Map<String, Value> =
1016 serde_json::Map::new();
1017 for (vid, version) in &s.versions {
1018 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
1019 }
1020 let mut entry = json!({
1021 "ARN": s.arn,
1022 "Name": s.name,
1023 "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
1024 "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
1025 "Description": s.description.clone().unwrap_or_default(),
1026 "SecretVersionsToStages": Value::Object(version_ids_to_stages),
1027 });
1028
1029 if s.tags_ever_set || !s.tags.is_empty() {
1030 entry["Tags"] = json!(tags_to_json(&s.tags));
1031 }
1032
1033 if let Some(ref kms) = s.kms_key_id {
1034 entry["KmsKeyId"] = json!(kms);
1035 }
1036 if s.deleted {
1037 entry["DeletedDate"] = json!(s
1038 .deletion_date
1039 .map(|d| d.timestamp_millis() as f64 / 1000.0));
1040 }
1041 entry
1042 })
1043 .collect();
1044
1045 let has_more = start_idx + max_results < secrets.len();
1046 let mut response = json!({
1047 "SecretList": page,
1048 });
1049 if has_more {
1050 if let Some(next) = secrets.get(start_idx + max_results) {
1051 response["NextToken"] = json!(next.name);
1052 }
1053 }
1054
1055 Ok(AwsResponse::ok_json(response))
1056 }
1057
1058 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1059 let body = req.json_body();
1060 let secret_id = require_secret_id(&body)?;
1061
1062 let new_tags = parse_tags(&body["Tags"]);
1063
1064 let mut accounts = self.state.write();
1065 let state = accounts.get_or_create(&req.account_id);
1066 let secret = self.find_secret_mut(state, &secret_id)?;
1067
1068 if !new_tags.is_empty() {
1069 secret.tags_ever_set = true;
1070 }
1071 for (k, v) in new_tags {
1072 if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
1074 existing.1 = v;
1075 } else {
1076 secret.tags.push((k, v));
1077 }
1078 }
1079
1080 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1081 }
1082
1083 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1084 let body = req.json_body();
1085 let secret_id = require_secret_id(&body)?;
1086
1087 let tag_keys: Vec<String> = body["TagKeys"]
1088 .as_array()
1089 .map(|arr| {
1090 arr.iter()
1091 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1092 .collect()
1093 })
1094 .unwrap_or_default();
1095
1096 let mut accounts = self.state.write();
1097 let state = accounts.get_or_create(&req.account_id);
1098 let secret = self.find_secret_mut(state, &secret_id)?;
1099
1100 secret.tags.retain(|(k, _)| !tag_keys.contains(k));
1101
1102 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1103 }
1104
1105 fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1106 let body = req.json_body();
1107 let secret_id = require_secret_id(&body)?;
1108 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1109 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
1110 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
1111 let next_token = body["NextToken"].as_str();
1112
1113 let accounts = self.state.read();
1114 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1115 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1116 let secret = self.find_secret_ref(state, &secret_id)?;
1117
1118 let mut versions: Vec<&_> = secret.versions.values().collect();
1121 versions.sort_by(|a, b| {
1122 b.created_at
1123 .cmp(&a.created_at)
1124 .then_with(|| a.version_id.cmp(&b.version_id))
1125 });
1126
1127 let start_idx = if let Some(token) = next_token {
1128 versions
1129 .iter()
1130 .position(|v| v.version_id == token)
1131 .unwrap_or(versions.len())
1132 } else {
1133 0
1134 };
1135
1136 let page: Vec<Value> = versions
1137 .iter()
1138 .skip(start_idx)
1139 .take(max_results)
1140 .map(|v| {
1141 json!({
1142 "VersionId": v.version_id,
1143 "VersionStages": v.stages,
1144 "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
1145 })
1146 })
1147 .collect();
1148
1149 let mut response = json!({
1150 "ARN": secret.arn,
1151 "Name": secret.name,
1152 "Versions": page,
1153 });
1154 if start_idx + max_results < versions.len() {
1155 if let Some(next) = versions.get(start_idx + max_results) {
1156 response["NextToken"] = json!(next.version_id);
1157 }
1158 }
1159
1160 Ok(AwsResponse::ok_json(response))
1161 }
1162
1163 fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1164 let body = req.json_body();
1165 let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
1166
1167 if length < 4 {
1168 return Err(AwsServiceError::aws_error(
1169 StatusCode::BAD_REQUEST,
1170 "InvalidParameterException",
1171 "InvalidParameterException",
1172 ));
1173 }
1174 if length > 4096 {
1175 return Err(AwsServiceError::aws_error(
1176 StatusCode::BAD_REQUEST,
1177 "InvalidParameterValue",
1178 "InvalidParameterValue",
1179 ));
1180 }
1181
1182 let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
1183 let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
1184 let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
1185 let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
1186 let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
1187 let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
1188 validate_optional_string_length(
1189 "excludeCharacters",
1190 body["ExcludeCharacters"].as_str(),
1191 0,
1192 4096,
1193 )?;
1194 let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
1195
1196 let lowercase = "abcdefghijklmnopqrstuvwxyz";
1197 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1198 let digits = "0123456789";
1199 let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
1200
1201 let mut char_pool = String::new();
1202 let mut required_chars: Vec<String> = Vec::new();
1203
1204 if !exclude_lowercase {
1205 let filtered: String = lowercase
1206 .chars()
1207 .filter(|c| !exclude_chars.contains(*c))
1208 .collect();
1209 if !filtered.is_empty() {
1210 required_chars.push(filtered.clone());
1211 char_pool.push_str(&filtered);
1212 }
1213 }
1214 if !exclude_uppercase {
1215 let filtered: String = uppercase
1216 .chars()
1217 .filter(|c| !exclude_chars.contains(*c))
1218 .collect();
1219 if !filtered.is_empty() {
1220 required_chars.push(filtered.clone());
1221 char_pool.push_str(&filtered);
1222 }
1223 }
1224 if !exclude_numbers {
1225 let filtered: String = digits
1226 .chars()
1227 .filter(|c| !exclude_chars.contains(*c))
1228 .collect();
1229 if !filtered.is_empty() {
1230 required_chars.push(filtered.clone());
1231 char_pool.push_str(&filtered);
1232 }
1233 }
1234 if !exclude_punctuation {
1235 let filtered: String = punctuation
1236 .chars()
1237 .filter(|c| !exclude_chars.contains(*c))
1238 .collect();
1239 if !filtered.is_empty() {
1240 required_chars.push(filtered.clone());
1241 char_pool.push_str(&filtered);
1242 }
1243 }
1244 if include_space && !exclude_chars.contains(' ') {
1245 char_pool.push(' ');
1246 }
1247
1248 if char_pool.is_empty() {
1249 return Err(AwsServiceError::aws_error(
1250 StatusCode::BAD_REQUEST,
1251 "InvalidParameterException",
1252 "InvalidParameterException",
1253 ));
1254 }
1255
1256 let pool_bytes: Vec<char> = char_pool.chars().collect();
1257 let mut password = String::with_capacity(length);
1258
1259 if require_each {
1261 for category in &required_chars {
1263 let chars: Vec<char> = category.chars().collect();
1264 let idx = simple_random() % chars.len();
1265 password.push(chars[idx]);
1266 }
1267 if include_space && !exclude_chars.contains(' ') {
1268 password.push(' ');
1269 }
1270 }
1271
1272 while password.len() < length {
1274 let idx = simple_random() % pool_bytes.len();
1275 password.push(pool_bytes[idx]);
1276 }
1277
1278 let mut chars: Vec<char> = password.chars().collect();
1280 for i in (1..chars.len()).rev() {
1281 let j = simple_random() % (i + 1);
1282 chars.swap(i, j);
1283 }
1284 let password: String = chars.into_iter().take(length).collect();
1285
1286 let response = json!({
1287 "RandomPassword": password,
1288 });
1289
1290 Ok(AwsResponse::ok_json(response))
1291 }
1292
1293 fn rotate_secret(
1294 &self,
1295 req: &AwsRequest,
1296 ) -> Result<(AwsResponse, Option<RotationInvocation>), AwsServiceError> {
1297 let body = req.json_body();
1298 let secret_id = require_secret_id(&body)?;
1299
1300 if let Some(token) = body["ClientRequestToken"].as_str() {
1302 if token.len() < 32 || token.len() > 64 {
1303 return Err(AwsServiceError::aws_error(
1304 StatusCode::BAD_REQUEST,
1305 "InvalidParameterException",
1306 "ClientRequestToken must be 32-64 characters long.",
1307 ));
1308 }
1309 }
1310
1311 if let Some(arn) = body["RotationLambdaARN"].as_str() {
1313 if arn.len() > 2048 {
1314 return Err(AwsServiceError::aws_error(
1315 StatusCode::BAD_REQUEST,
1316 "InvalidParameterException",
1317 "RotationLambdaARN length must be less than or equal to 2048.",
1318 ));
1319 }
1320 }
1321
1322 if let Some(rules) = body["RotationRules"].as_object() {
1324 if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1325 if !(1..=1000).contains(&days) {
1326 return Err(AwsServiceError::aws_error(
1327 StatusCode::BAD_REQUEST,
1328 "InvalidParameterException",
1329 "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1330 ));
1331 }
1332 }
1333 }
1334
1335 let mut accounts = self.state.write();
1336 let state = accounts.get_or_create(&req.account_id);
1337 let secret = self.find_secret_mut(state, &secret_id)?;
1338
1339 if secret.deleted {
1340 return Err(AwsServiceError::aws_error(
1341 StatusCode::BAD_REQUEST,
1342 "InvalidRequestException",
1343 "You can't perform this operation on the secret because it was marked for deletion.",
1344 ));
1345 }
1346
1347 if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1349 secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1350 }
1351
1352 if let Some(rules) = body["RotationRules"].as_object() {
1353 let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1354 secret.rotation_rules = Some(RotationRules {
1355 automatically_after_days: days,
1356 duration: rules
1357 .get("Duration")
1358 .and_then(|v| v.as_str())
1359 .map(String::from),
1360 schedule_expression: rules
1361 .get("ScheduleExpression")
1362 .and_then(|v| v.as_str())
1363 .map(String::from),
1364 });
1365 }
1366
1367 secret.rotation_enabled = Some(true);
1368 let now = Utc::now();
1369 secret.last_rotated_at = Some(now);
1370 secret.last_changed_at = now;
1371
1372 let version_id = body["ClientRequestToken"]
1373 .as_str()
1374 .map(|s| s.to_string())
1375 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1376
1377 let has_lambda =
1378 body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1379 let lambda_arn = secret.rotation_lambda_arn.clone();
1380
1381 let mut invocation = None;
1383 if let Some(current_vid) = secret.current_version_id.clone() {
1384 let current_value = secret.versions.get(¤t_vid).cloned();
1385
1386 if let Some(cv) = current_value {
1387 if has_lambda {
1388 if let Some(ref arn) = lambda_arn {
1395 invocation = Some(RotationInvocation {
1396 lambda_arn: arn.clone(),
1397 secret_id: secret.arn.clone(),
1398 client_request_token: version_id.clone(),
1399 });
1400 }
1401 } else {
1402 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1405 old_v.stages.retain(|s| s != "AWSCURRENT");
1406 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1407 old_v.stages.push("AWSPREVIOUS".to_string());
1408 }
1409 }
1410 let version = SecretVersion {
1411 version_id: version_id.clone(),
1412 secret_string: cv.secret_string.clone(),
1413 secret_binary: cv.secret_binary.clone(),
1414 stages: vec!["AWSCURRENT".to_string()],
1415 created_at: now,
1416 };
1417 secret.versions.insert(version_id.clone(), version);
1418 secret.current_version_id = Some(version_id.clone());
1419 }
1420 }
1421 }
1422
1423 let response = json!({
1424 "ARN": secret.arn,
1425 "Name": secret.name,
1426 "VersionId": version_id,
1427 });
1428
1429 Ok((AwsResponse::ok_json(response), invocation))
1430 }
1431
1432 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1433 let body = req.json_body();
1434 let secret_id = require_secret_id(&body)?;
1435
1436 let mut accounts = self.state.write();
1437 let state = accounts.get_or_create(&req.account_id);
1438 let secret = self.find_secret_mut(state, &secret_id)?;
1439
1440 if secret.deleted {
1441 return Err(AwsServiceError::aws_error(
1442 StatusCode::BAD_REQUEST,
1443 "InvalidRequestException",
1444 "You can't perform this operation on the secret because it was marked for deletion.",
1445 ));
1446 }
1447
1448 if secret.rotation_enabled != Some(true) {
1449 return Err(AwsServiceError::aws_error(
1450 StatusCode::BAD_REQUEST,
1451 "InvalidRequestException",
1452 "You can't cancel rotation for a secret that does not have rotation enabled.",
1453 ));
1454 }
1455
1456 secret.rotation_enabled = Some(false);
1457
1458 let response = json!({
1459 "ARN": secret.arn,
1460 "Name": secret.name,
1461 });
1462
1463 Ok(AwsResponse::ok_json(response))
1464 }
1465
1466 fn update_secret_version_stage(
1467 &self,
1468 req: &AwsRequest,
1469 ) -> Result<AwsResponse, AwsServiceError> {
1470 let body = req.json_body();
1471 let secret_id = require_secret_id(&body)?;
1472 let version_stage = body["VersionStage"]
1473 .as_str()
1474 .ok_or_else(|| {
1475 AwsServiceError::aws_error(
1476 StatusCode::BAD_REQUEST,
1477 "InvalidParameterException",
1478 "VersionStage is required",
1479 )
1480 })?
1481 .to_string();
1482 validate_string_length("versionStage", &version_stage, 1, 256)?;
1483 validate_optional_string_length(
1484 "removeFromVersionId",
1485 body["RemoveFromVersionId"].as_str(),
1486 32,
1487 64,
1488 )?;
1489 validate_optional_string_length(
1490 "moveToVersionId",
1491 body["MoveToVersionId"].as_str(),
1492 32,
1493 64,
1494 )?;
1495
1496 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1497 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1498
1499 let mut accounts = self.state.write();
1500 let state = accounts.get_or_create(&req.account_id);
1501 let secret = self.find_secret_mut(state, &secret_id)?;
1502
1503 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1505 let current_holder = secret
1507 .versions
1508 .iter()
1509 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1510 .map(|(id, _)| id.clone());
1511
1512 if let Some(current_vid) = current_holder {
1513 return Err(AwsServiceError::aws_error(
1514 StatusCode::BAD_REQUEST,
1515 "InvalidParameterException",
1516 format!(
1517 "The parameter RemoveFromVersionId can't be empty. Staging label AWSCURRENT is currently attached to version {current_vid}, so you must explicitly reference that version in RemoveFromVersionId."
1518 ),
1519 ));
1520 }
1521 }
1522
1523 if let Some(ref remove_vid) = remove_from {
1525 if let Some(version) = secret.versions.get_mut(remove_vid) {
1526 version.stages.retain(|s| s != &version_stage);
1527 if version_stage == "AWSCURRENT" {
1529 for (id, v) in secret.versions.iter_mut() {
1531 if id != remove_vid {
1532 v.stages.retain(|s| s != "AWSPREVIOUS");
1533 }
1534 }
1535 if let Some(v) = secret.versions.get_mut(remove_vid) {
1537 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1538 v.stages.push("AWSPREVIOUS".to_string());
1539 }
1540 }
1541 }
1542 }
1543 }
1544
1545 if let Some(ref move_vid) = move_to {
1547 if let Some(version) = secret.versions.get_mut(move_vid) {
1548 if !version.stages.contains(&version_stage) {
1549 version.stages.push(version_stage.clone());
1550 }
1551 }
1552 if version_stage == "AWSCURRENT" {
1554 secret.current_version_id = Some(move_vid.clone());
1555 }
1556 }
1557
1558 let response = json!({
1559 "ARN": secret.arn,
1560 "Name": secret.name,
1561 });
1562
1563 Ok(AwsResponse::ok_json(response))
1564 }
1565
1566 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1567 let body = req.json_body();
1568 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1569 let secret_id_list = body["SecretIdList"].as_array();
1570 let filters = body["Filters"].as_array();
1571 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1572
1573 if secret_id_list.is_some() && filters.is_some() {
1575 return Err(AwsServiceError::aws_error(
1576 StatusCode::BAD_REQUEST,
1577 "InvalidParameterException",
1578 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1579 ));
1580 }
1581
1582 if max_results.is_some() && filters.is_none() {
1584 return Err(AwsServiceError::aws_error(
1585 StatusCode::BAD_REQUEST,
1586 "InvalidParameterException",
1587 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1588 ));
1589 }
1590
1591 let accounts = self.state.read();
1592 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1593 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1594 let mut secret_values: Vec<Value> = Vec::new();
1595 let mut errors: Vec<Value> = Vec::new();
1596
1597 if let Some(id_list) = secret_id_list {
1598 for id_val in id_list {
1599 let sid = id_val.as_str().unwrap_or("");
1600 match self.find_secret_ref(state, sid) {
1601 Ok(secret) => {
1602 if secret.deleted {
1603 errors.push(json!({
1604 "SecretId": sid,
1605 "ErrorCode": "InvalidRequestException",
1606 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1607 }));
1608 } else if let Some(ref current_vid) = secret.current_version_id {
1609 if let Some(version) = secret.versions.get(current_vid) {
1610 let mut entry = json!({
1611 "ARN": secret.arn,
1612 "Name": secret.name,
1613 "VersionId": version.version_id,
1614 "VersionStages": version.stages,
1615 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1616 });
1617 if let Some(ref s) = version.secret_string {
1618 let plaintext = self
1623 .maybe_decrypt_secret_string(
1624 &req.account_id,
1625 &secret.arn,
1626 secret.kms_key_id.as_deref(),
1627 Some(s.as_str()),
1628 )
1629 .unwrap_or_else(|| s.clone());
1630 entry["SecretString"] = json!(plaintext);
1631 }
1632 if let Some(ref b) = version.secret_binary {
1633 entry["SecretBinary"] = json!(base64_encode(b));
1634 }
1635 secret_values.push(entry);
1636 } else {
1637 errors.push(json!({
1638 "SecretId": sid,
1639 "ErrorCode": "ResourceNotFoundException",
1640 "Message": "Secrets Manager can't find the specified secret.",
1641 }));
1642 }
1643 } else {
1644 errors.push(json!({
1645 "SecretId": sid,
1646 "ErrorCode": "ResourceNotFoundException",
1647 "Message": "Secrets Manager can't find the specified secret.",
1648 }));
1649 }
1650 }
1651 Err(_) => {
1652 errors.push(json!({
1653 "SecretId": sid,
1654 "ErrorCode": "ResourceNotFoundException",
1655 "Message": "Secrets Manager can't find the specified secret.",
1656 }));
1657 }
1658 }
1659 }
1660 } else if let Some(filters) = filters {
1661 let matching: Vec<&Secret> = state
1663 .secrets
1664 .values()
1665 .filter(|s| {
1666 if s.deleted {
1667 return false;
1668 }
1669 for filter in filters {
1670 let key = filter["Key"].as_str().unwrap_or("");
1671 let values: Vec<&str> = filter["Values"]
1672 .as_array()
1673 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1674 .unwrap_or_default();
1675 let matches = match key {
1676 "name" => filter_name(s, &values),
1677 "description" => filter_description(s, &values),
1678 "tag-key" => filter_tag_key(s, &values),
1679 "tag-value" => filter_tag_value(s, &values),
1680 "all" => filter_all(s, &values),
1681 _ => true,
1682 };
1683 if !matches {
1684 return false;
1685 }
1686 }
1687 true
1688 })
1689 .collect();
1690
1691 let limit = max_results.unwrap_or(100) as usize;
1692 let mut no_value_found = false;
1693 let mut matching = matching;
1694 matching.sort_by(|a, b| a.name.cmp(&b.name));
1695
1696 for secret in matching.iter().take(limit) {
1697 if let Some(ref current_vid) = secret.current_version_id {
1698 if let Some(version) = secret.versions.get(current_vid) {
1699 let mut entry = json!({
1700 "ARN": secret.arn,
1701 "Name": secret.name,
1702 "VersionId": version.version_id,
1703 "VersionStages": version.stages,
1704 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1705 });
1706 if let Some(ref s) = version.secret_string {
1707 let plaintext = self
1710 .maybe_decrypt_secret_string(
1711 &req.account_id,
1712 &secret.arn,
1713 secret.kms_key_id.as_deref(),
1714 Some(s.as_str()),
1715 )
1716 .unwrap_or_else(|| s.clone());
1717 entry["SecretString"] = json!(plaintext);
1718 }
1719 if let Some(ref b) = version.secret_binary {
1720 entry["SecretBinary"] = json!(base64_encode(b));
1721 }
1722 secret_values.push(entry);
1723 } else {
1724 no_value_found = true;
1725 }
1726 } else {
1727 no_value_found = true;
1728 }
1729 }
1730
1731 if no_value_found && secret_values.is_empty() {
1732 return Err(AwsServiceError::aws_error(
1733 StatusCode::NOT_FOUND,
1734 "ResourceNotFoundException",
1735 "Secrets Manager can't find the specified secret.",
1736 ));
1737 }
1738 }
1739
1740 let mut response = json!({
1741 "SecretValues": secret_values,
1742 "Errors": errors,
1743 });
1744
1745 if errors.is_empty() {
1747 response.as_object_mut().unwrap().remove("Errors");
1748 }
1749
1750 Ok(AwsResponse::ok_json(response))
1751 }
1752
1753 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1754 let body = req.json_body();
1755 let secret_id = require_secret_id(&body)?;
1756
1757 let accounts = self.state.read();
1758 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1759 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1760 let secret = self.find_secret_ref(state, &secret_id)?;
1761
1762 let mut response = json!({
1765 "ARN": secret.arn,
1766 "Name": secret.name,
1767 });
1768 if let Some(ref policy) = secret.resource_policy {
1769 response["ResourcePolicy"] = json!(policy);
1770 }
1771
1772 Ok(AwsResponse::ok_json(response))
1773 }
1774
1775 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1776 let body = req.json_body();
1777 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1778 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1779 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1780 AwsServiceError::aws_error(
1781 StatusCode::BAD_REQUEST,
1782 "InvalidParameterException",
1783 "ResourcePolicy must be a string",
1784 )
1785 })?;
1786 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1787
1788 if let Some(secret_id) = body["SecretId"].as_str() {
1790 let accounts = self.state.read();
1791 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1792 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1793 self.find_secret_key(state, secret_id)?;
1794 }
1795
1796 let response = json!({
1797 "PolicyValidationPassed": true,
1798 "ValidationErrors": [],
1799 });
1800 Ok(AwsResponse::ok_json(response))
1801 }
1802
1803 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1804 let body = req.json_body();
1805 let secret_id = require_secret_id(&body)?;
1806 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1807 validate_optional_string_length(
1808 "resourcePolicy",
1809 body["ResourcePolicy"].as_str(),
1810 1,
1811 20480,
1812 )?;
1813 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1814
1815 let mut accounts = self.state.write();
1816 let state = accounts.get_or_create(&req.account_id);
1817 let secret = self.find_secret_mut(state, &secret_id)?;
1818 secret.resource_policy = policy;
1819
1820 let response = json!({
1821 "ARN": secret.arn,
1822 "Name": secret.name,
1823 });
1824
1825 Ok(AwsResponse::ok_json(response))
1826 }
1827
1828 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1829 let body = req.json_body();
1830 let secret_id = require_secret_id(&body)?;
1831
1832 let mut accounts = self.state.write();
1833 let state = accounts.get_or_create(&req.account_id);
1834 let secret = self.find_secret_mut(state, &secret_id)?;
1835 secret.resource_policy = None;
1836
1837 let response = json!({
1838 "ARN": secret.arn,
1839 "Name": secret.name,
1840 });
1841
1842 Ok(AwsResponse::ok_json(response))
1843 }
1844
1845 fn replicate_secret_to_regions(
1846 &self,
1847 req: &AwsRequest,
1848 ) -> Result<AwsResponse, AwsServiceError> {
1849 let body = req.json_body();
1850 let secret_id = require_secret_id(&body)?;
1851 let add_regions: Vec<String> = body["AddReplicaRegions"]
1853 .as_array()
1854 .map(|arr| {
1855 arr.iter()
1856 .filter_map(|r| r["Region"].as_str().map(String::from))
1857 .collect()
1858 })
1859 .unwrap_or_default();
1860
1861 let mut accounts = self.state.write();
1862 let state = accounts.get_or_create(&req.account_id);
1863 let secret = self.find_secret_mut(state, &secret_id)?;
1864 for region in add_regions {
1865 if !secret.replica_regions.contains(®ion) {
1866 secret.replica_regions.push(region);
1867 }
1868 }
1869 let response = json!({
1870 "ARN": secret.arn,
1871 "ReplicationStatus": replication_status_json(&secret.replica_regions),
1872 });
1873 Ok(AwsResponse::ok_json(response))
1874 }
1875
1876 fn remove_regions_from_replication(
1877 &self,
1878 req: &AwsRequest,
1879 ) -> Result<AwsResponse, AwsServiceError> {
1880 let body = req.json_body();
1881 let secret_id = require_secret_id(&body)?;
1882 let remove_regions: Vec<String> = body["RemoveReplicaRegions"]
1883 .as_array()
1884 .map(|arr| {
1885 arr.iter()
1886 .filter_map(|r| r.as_str().map(String::from))
1887 .collect()
1888 })
1889 .unwrap_or_default();
1890
1891 let mut accounts = self.state.write();
1892 let state = accounts.get_or_create(&req.account_id);
1893 let secret = self.find_secret_mut(state, &secret_id)?;
1894 secret
1895 .replica_regions
1896 .retain(|r| !remove_regions.contains(r));
1897 let response = json!({
1898 "ARN": secret.arn,
1899 "ReplicationStatus": replication_status_json(&secret.replica_regions),
1900 });
1901 Ok(AwsResponse::ok_json(response))
1902 }
1903
1904 fn stop_replication_to_replica(
1905 &self,
1906 req: &AwsRequest,
1907 ) -> Result<AwsResponse, AwsServiceError> {
1908 let body = req.json_body();
1909 let secret_id = require_secret_id(&body)?;
1910
1911 let accounts = self.state.read();
1912 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1913 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1914 let secret = self.find_secret_ref(state, &secret_id)?;
1915
1916 let response = json!({
1917 "ARN": secret.arn,
1918 });
1919 Ok(AwsResponse::ok_json(response))
1920 }
1921
1922 fn find_secret_mut<'a>(
1924 &self,
1925 state: &'a mut crate::state::SecretsManagerState,
1926 secret_id: &str,
1927 ) -> Result<&'a mut Secret, AwsServiceError> {
1928 let key = self.find_secret_key(state, secret_id)?;
1929 Ok(state.secrets.get_mut(&key).unwrap())
1930 }
1931
1932 fn find_secret_key(
1933 &self,
1934 state: &crate::state::SecretsManagerState,
1935 secret_id: &str,
1936 ) -> Result<String, AwsServiceError> {
1937 if state.secrets.contains_key(secret_id) {
1938 return Ok(secret_id.to_string());
1939 }
1940
1941 for secret in state.secrets.values() {
1942 if secret.arn == secret_id {
1943 return Ok(secret.name.clone());
1944 }
1945 }
1946
1947 if secret_id.starts_with("arn:aws:secretsmanager:") {
1948 for secret in state.secrets.values() {
1949 if secret.arn.starts_with(secret_id) {
1950 return Ok(secret.name.clone());
1951 }
1952 }
1953 }
1954
1955 Err(AwsServiceError::aws_error(
1956 StatusCode::NOT_FOUND,
1957 "ResourceNotFoundException",
1958 "Secrets Manager can't find the specified secret.",
1959 ))
1960 }
1961
1962 fn find_secret_ref<'a>(
1964 &self,
1965 state: &'a crate::state::SecretsManagerState,
1966 secret_id: &str,
1967 ) -> Result<&'a Secret, AwsServiceError> {
1968 if let Some(secret) = state.secrets.get(secret_id) {
1969 return Ok(secret);
1970 }
1971
1972 for secret in state.secrets.values() {
1974 if secret.arn == secret_id {
1975 return Ok(secret);
1976 }
1977 }
1978
1979 if secret_id.starts_with("arn:aws:secretsmanager:") {
1981 for secret in state.secrets.values() {
1982 if secret.arn.starts_with(secret_id) {
1983 return Ok(secret);
1984 }
1985 }
1986 }
1987
1988 Err(AwsServiceError::aws_error(
1989 StatusCode::NOT_FOUND,
1990 "ResourceNotFoundException",
1991 "Secrets Manager can't find the specified secret.",
1992 ))
1993 }
1994}
1995
1996pub async fn save_secretsmanager_snapshot(
2003 state: &SharedSecretsManagerState,
2004 store: Option<Arc<dyn SnapshotStore>>,
2005 lock: &AsyncMutex<()>,
2006) {
2007 let Some(store) = store else {
2008 return;
2009 };
2010 let _guard = lock.lock().await;
2011 let snapshot = SecretsManagerSnapshot {
2012 schema_version: SECRETSMANAGER_SNAPSHOT_SCHEMA_VERSION,
2013 state: None,
2014 accounts: Some(state.read().clone()),
2015 };
2016 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
2017 let bytes = serde_json::to_vec(&snapshot)
2018 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
2019 store.save(&bytes)
2020 })
2021 .await;
2022 match join {
2023 Ok(Ok(())) => {}
2024 Ok(Err(err)) => tracing::error!(%err, "failed to write secretsmanager snapshot"),
2025 Err(err) => tracing::error!(%err, "secretsmanager snapshot task panicked"),
2026 }
2027}
2028
2029struct CreateSecretInput {
2031 name: String,
2032 client_request_token: Option<String>,
2033 description: Option<String>,
2034 kms_key_id: Option<String>,
2035 secret_string: Option<String>,
2036 secret_binary: Option<Vec<u8>>,
2037 tags: Vec<(String, String)>,
2038}
2039
2040impl CreateSecretInput {
2041 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
2042 validate_required("Name", &body["Name"])?;
2043 let name = body["Name"]
2044 .as_str()
2045 .ok_or_else(|| {
2046 AwsServiceError::aws_error(
2047 StatusCode::BAD_REQUEST,
2048 "InvalidParameterException",
2049 "Name is required",
2050 )
2051 })?
2052 .to_string();
2053 validate_string_length("name", &name, 1, 512)?;
2054 validate_optional_string_length(
2055 "clientRequestToken",
2056 body["ClientRequestToken"].as_str(),
2057 32,
2058 64,
2059 )?;
2060 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
2061 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
2062 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
2063
2064 Ok(Self {
2065 name,
2066 client_request_token: body["ClientRequestToken"].as_str().map(|s| s.to_string()),
2067 description: body["Description"].as_str().map(|s| s.to_string()),
2068 kms_key_id: body["KmsKeyId"].as_str().map(|s| s.to_string()),
2069 secret_string: body["SecretString"].as_str().map(|s| s.to_string()),
2070 secret_binary: body["SecretBinary"].as_str().and_then(base64_decode),
2071 tags: parse_tags(&body["Tags"]),
2072 })
2073 }
2074}
2075
2076#[async_trait]
2077impl AwsService for SecretsManagerService {
2078 fn service_name(&self) -> &str {
2079 "secretsmanager"
2080 }
2081
2082 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2083 let mutates = is_mutating_action(req.action.as_str());
2084 let result = match req.action.as_str() {
2085 "CreateSecret" => self.create_secret(&req),
2086 "GetSecretValue" => self.get_secret_value(&req),
2087 "PutSecretValue" => self.put_secret_value(&req),
2088 "UpdateSecret" => self.update_secret(&req),
2089 "DeleteSecret" => self.delete_secret(&req),
2090 "RestoreSecret" => self.restore_secret(&req),
2091 "DescribeSecret" => self.describe_secret(&req),
2092 "ListSecrets" => self.list_secrets(&req),
2093 "TagResource" => self.tag_resource(&req),
2094 "UntagResource" => self.untag_resource(&req),
2095 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
2096 "GetRandomPassword" => self.get_random_password(&req),
2097 "RotateSecret" => {
2098 let (response, invocation) = self.rotate_secret(&req)?;
2099 if let Some(inv) = invocation {
2100 if let Some(ref bus) = self.delivery_bus {
2101 let bus = bus.clone();
2102 tokio::spawn(async move {
2104 for step in &["createSecret", "setSecret", "testSecret", "finishSecret"]
2105 {
2106 let payload = serde_json::json!({
2107 "SecretId": inv.secret_id,
2108 "ClientRequestToken": inv.client_request_token,
2109 "Step": step,
2110 });
2111 let payload_str = payload.to_string();
2112 match bus.invoke_lambda(&inv.lambda_arn, &payload_str).await {
2113 Some(Ok(_)) => {}
2114 Some(Err(e)) => {
2115 tracing::warn!(
2116 step = step,
2117 error = %e,
2118 "rotation Lambda invocation failed"
2119 );
2120 }
2121 None => {
2122 tracing::warn!(
2123 lambda_arn = %inv.lambda_arn,
2124 step = step,
2125 "rotation Lambda delivery not configured; \
2126 Lambda invocation skipped"
2127 );
2128 break;
2129 }
2130 }
2131 }
2132 });
2133 }
2134 }
2135 Ok(response)
2136 }
2137 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2138 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2139 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2140 "GetResourcePolicy" => self.get_resource_policy(&req),
2141 "PutResourcePolicy" => self.put_resource_policy(&req),
2142 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2143 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2144 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2145 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2146 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2147 _ => Err(AwsServiceError::action_not_implemented(
2148 "secretsmanager",
2149 &req.action,
2150 )),
2151 };
2152 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
2153 self.save_snapshot().await;
2154 }
2155 result.map_err(remap_validation_error)
2156 }
2157
2158 fn supported_actions(&self) -> &[&str] {
2159 &[
2160 "CreateSecret",
2161 "GetSecretValue",
2162 "PutSecretValue",
2163 "UpdateSecret",
2164 "DeleteSecret",
2165 "RestoreSecret",
2166 "DescribeSecret",
2167 "ListSecrets",
2168 "TagResource",
2169 "UntagResource",
2170 "ListSecretVersionIds",
2171 "GetRandomPassword",
2172 "RotateSecret",
2173 "CancelRotateSecret",
2174 "UpdateSecretVersionStage",
2175 "BatchGetSecretValue",
2176 "GetResourcePolicy",
2177 "PutResourcePolicy",
2178 "DeleteResourcePolicy",
2179 "ValidateResourcePolicy",
2180 "ReplicateSecretToRegions",
2181 "RemoveRegionsFromReplication",
2182 "StopReplicationToReplica",
2183 ]
2184 }
2185}
2186
2187#[path = "service_helpers.rs"]
2188mod service_helpers;
2189pub(crate) use service_helpers::*;
2190
2191fn replication_status_json(regions: &[String]) -> Value {
2200 Value::Array(
2201 regions
2202 .iter()
2203 .map(|r| {
2204 json!({
2205 "Region": r,
2206 "Status": "InSync",
2207 "StatusMessage": "Replication succeeded",
2208 })
2209 })
2210 .collect(),
2211 )
2212}
2213
2214fn remap_validation_error(err: AwsServiceError) -> AwsServiceError {
2215 match err {
2216 AwsServiceError::AwsError {
2217 status,
2218 code,
2219 message,
2220 extra_fields,
2221 headers,
2222 } if code == "ValidationException" => AwsServiceError::AwsError {
2223 status,
2224 code: "InvalidParameterException".to_string(),
2225 message,
2226 extra_fields,
2227 headers,
2228 },
2229 other => other,
2230 }
2231}
2232
2233fn secret_owner_account(secret_id: &str, caller_account: &str) -> String {
2237 if !secret_id.starts_with("arn:aws:secretsmanager:") {
2238 return caller_account.to_string();
2239 }
2240 let parts: Vec<&str> = secret_id.splitn(7, ':').collect();
2241 if parts.len() < 5 {
2242 return caller_account.to_string();
2243 }
2244 let account = parts[4];
2245 if account.is_empty() {
2246 caller_account.to_string()
2247 } else {
2248 account.to_string()
2249 }
2250}
2251
2252fn resource_policy_allows(policy_doc: &str, caller_account: &str, secret_arn: &str) -> bool {
2257 if policy_doc.is_empty() {
2258 return false;
2259 }
2260 use fakecloud_core::auth::{Principal, PrincipalType};
2261 use fakecloud_iam::evaluator::{evaluate, EvalRequest, PolicyDocument};
2262 let doc = PolicyDocument::parse(policy_doc);
2263 let principal_arn = Arn::global("iam", caller_account, "root").to_string();
2264 let principal = Principal {
2265 arn: principal_arn.clone(),
2266 user_id: principal_arn.clone(),
2267 account_id: caller_account.to_string(),
2268 principal_type: PrincipalType::User,
2269 source_identity: None,
2270 tags: None,
2271 };
2272 let req = EvalRequest {
2273 principal: &principal,
2274 action: "secretsmanager:GetSecretValue".to_string(),
2275 resource: secret_arn.to_string(),
2276 context: Default::default(),
2277 };
2278 matches!(
2279 evaluate(&[doc], &req),
2280 fakecloud_iam::evaluator::Decision::Allow
2281 )
2282}
2283
2284#[cfg(test)]
2285#[path = "service_tests.rs"]
2286mod tests;