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 response["RotationRules"] = rules_json;
875 }
876 if let Some(last_rotated) = secret.last_rotated_at {
877 response["LastRotatedDate"] = json!(last_rotated.timestamp_millis() as f64 / 1000.0);
878 }
879 if !secret.replica_regions.is_empty() {
880 response["ReplicationStatus"] = replication_status_json(&secret.replica_regions);
881 }
882 if secret.rotation_enabled == Some(true) {
884 if let Some(ref rules) = secret.rotation_rules {
885 if let Some(days) = rules.automatically_after_days {
886 let base = secret.last_rotated_at.unwrap_or(secret.created_at);
887 let next = base + chrono::Duration::days(days);
888 response["NextRotationDate"] = json!(next.timestamp_millis() as f64 / 1000.0);
889 }
890 }
891 }
892
893 Ok(AwsResponse::ok_json(response))
894 }
895
896 fn list_secrets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
897 let body = req.json_body();
898 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
899 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
900 validate_optional_enum("sortBy", body["SortBy"].as_str(), &["name", "created-date"])?;
901 validate_optional_enum("sortOrder", body["SortOrder"].as_str(), &["asc", "desc"])?;
902 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
903 let next_token = body["NextToken"].as_str();
904 let filters = body["Filters"].as_array();
905 let include_deleted = body["IncludePlannedDeletion"].as_bool().unwrap_or(false);
906
907 if let Some(filters) = filters {
909 for filter in filters {
910 let key = filter["Key"].as_str().unwrap_or("");
911 let values = filter["Values"].as_array();
912
913 if key.is_empty() {
914 return Err(AwsServiceError::aws_error(
915 StatusCode::BAD_REQUEST,
916 "InvalidParameterException",
917 "Invalid filter key",
918 ));
919 }
920
921 let valid_keys = [
922 "all",
923 "name",
924 "tag-key",
925 "description",
926 "tag-value",
927 "owning-service",
928 "primary-region",
929 ];
930 if !valid_keys.contains(&key) {
931 return Err(AwsServiceError::aws_error(
932 StatusCode::BAD_REQUEST,
933 "ValidationException",
934 format!(
935 "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]",
936 key
937 ),
938 ));
939 }
940
941 if values.is_none() || values.unwrap().is_empty() {
942 return Err(AwsServiceError::aws_error(
943 StatusCode::BAD_REQUEST,
944 "InvalidParameterException",
945 format!("Invalid filter values for key: {key}"),
946 ));
947 }
948 }
949 }
950
951 let accounts = self.state.read();
952 let empty = SecretsManagerState::new(&req.account_id, &req.region);
953 let state = accounts.get(&req.account_id).unwrap_or(&empty);
954
955 let mut secrets: Vec<&Secret> = state
956 .secrets
957 .values()
958 .filter(|s| {
959 if s.deleted && !include_deleted {
961 return false;
962 }
963
964 if let Some(filters) = filters {
965 for filter in filters {
966 let key = filter["Key"].as_str().unwrap_or("");
967 let values: Vec<&str> = filter["Values"]
968 .as_array()
969 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
970 .unwrap_or_default();
971
972 let matches = match key {
973 "name" => filter_name(s, &values),
974 "description" => filter_description(s, &values),
975 "tag-key" => filter_tag_key(s, &values),
976 "tag-value" => filter_tag_value(s, &values),
977 "all" => filter_all(s, &values),
978 "owning-service" => false,
979 "primary-region" => false,
980 _ => true,
981 };
982
983 if !matches {
984 return false;
985 }
986 }
987 }
988 true
989 })
990 .collect();
991 secrets.sort_by_key(|a| a.created_at);
992
993 let start_idx = if let Some(token) = next_token {
995 secrets.iter().position(|s| s.name == token).unwrap_or(0)
996 } else {
997 0
998 };
999
1000 let page: Vec<Value> = secrets
1001 .iter()
1002 .skip(start_idx)
1003 .take(max_results)
1004 .map(|s| {
1005 let mut version_ids_to_stages: serde_json::Map<String, Value> =
1010 serde_json::Map::new();
1011 for (vid, version) in &s.versions {
1012 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
1013 }
1014 let mut entry = json!({
1015 "ARN": s.arn,
1016 "Name": s.name,
1017 "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
1018 "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
1019 "Description": s.description.clone().unwrap_or_default(),
1020 "SecretVersionsToStages": Value::Object(version_ids_to_stages),
1021 });
1022
1023 if s.tags_ever_set || !s.tags.is_empty() {
1024 entry["Tags"] = json!(tags_to_json(&s.tags));
1025 }
1026
1027 if let Some(ref kms) = s.kms_key_id {
1028 entry["KmsKeyId"] = json!(kms);
1029 }
1030 if s.deleted {
1031 entry["DeletedDate"] = json!(s
1032 .deletion_date
1033 .map(|d| d.timestamp_millis() as f64 / 1000.0));
1034 }
1035 entry
1036 })
1037 .collect();
1038
1039 let has_more = start_idx + max_results < secrets.len();
1040 let mut response = json!({
1041 "SecretList": page,
1042 });
1043 if has_more {
1044 if let Some(next) = secrets.get(start_idx + max_results) {
1045 response["NextToken"] = json!(next.name);
1046 }
1047 }
1048
1049 Ok(AwsResponse::ok_json(response))
1050 }
1051
1052 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1053 let body = req.json_body();
1054 let secret_id = require_secret_id(&body)?;
1055
1056 let new_tags = parse_tags(&body["Tags"]);
1057
1058 let mut accounts = self.state.write();
1059 let state = accounts.get_or_create(&req.account_id);
1060 let secret = self.find_secret_mut(state, &secret_id)?;
1061
1062 if !new_tags.is_empty() {
1063 secret.tags_ever_set = true;
1064 }
1065 for (k, v) in new_tags {
1066 if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
1068 existing.1 = v;
1069 } else {
1070 secret.tags.push((k, v));
1071 }
1072 }
1073
1074 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1075 }
1076
1077 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1078 let body = req.json_body();
1079 let secret_id = require_secret_id(&body)?;
1080
1081 let tag_keys: Vec<String> = body["TagKeys"]
1082 .as_array()
1083 .map(|arr| {
1084 arr.iter()
1085 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1086 .collect()
1087 })
1088 .unwrap_or_default();
1089
1090 let mut accounts = self.state.write();
1091 let state = accounts.get_or_create(&req.account_id);
1092 let secret = self.find_secret_mut(state, &secret_id)?;
1093
1094 secret.tags.retain(|(k, _)| !tag_keys.contains(k));
1095
1096 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1097 }
1098
1099 fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1100 let body = req.json_body();
1101 let secret_id = require_secret_id(&body)?;
1102 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1103 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
1104 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
1105 let next_token = body["NextToken"].as_str();
1106
1107 let accounts = self.state.read();
1108 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1109 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1110 let secret = self.find_secret_ref(state, &secret_id)?;
1111
1112 let mut versions: Vec<&_> = secret.versions.values().collect();
1115 versions.sort_by(|a, b| {
1116 b.created_at
1117 .cmp(&a.created_at)
1118 .then_with(|| a.version_id.cmp(&b.version_id))
1119 });
1120
1121 let start_idx = if let Some(token) = next_token {
1122 versions
1123 .iter()
1124 .position(|v| v.version_id == token)
1125 .unwrap_or(versions.len())
1126 } else {
1127 0
1128 };
1129
1130 let page: Vec<Value> = versions
1131 .iter()
1132 .skip(start_idx)
1133 .take(max_results)
1134 .map(|v| {
1135 json!({
1136 "VersionId": v.version_id,
1137 "VersionStages": v.stages,
1138 "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
1139 })
1140 })
1141 .collect();
1142
1143 let mut response = json!({
1144 "ARN": secret.arn,
1145 "Name": secret.name,
1146 "Versions": page,
1147 });
1148 if start_idx + max_results < versions.len() {
1149 if let Some(next) = versions.get(start_idx + max_results) {
1150 response["NextToken"] = json!(next.version_id);
1151 }
1152 }
1153
1154 Ok(AwsResponse::ok_json(response))
1155 }
1156
1157 fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1158 let body = req.json_body();
1159 let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
1160
1161 if length < 4 {
1162 return Err(AwsServiceError::aws_error(
1163 StatusCode::BAD_REQUEST,
1164 "InvalidParameterException",
1165 "InvalidParameterException",
1166 ));
1167 }
1168 if length > 4096 {
1169 return Err(AwsServiceError::aws_error(
1170 StatusCode::BAD_REQUEST,
1171 "InvalidParameterValue",
1172 "InvalidParameterValue",
1173 ));
1174 }
1175
1176 let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
1177 let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
1178 let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
1179 let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
1180 let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
1181 let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
1182 validate_optional_string_length(
1183 "excludeCharacters",
1184 body["ExcludeCharacters"].as_str(),
1185 0,
1186 4096,
1187 )?;
1188 let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
1189
1190 let lowercase = "abcdefghijklmnopqrstuvwxyz";
1191 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1192 let digits = "0123456789";
1193 let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
1194
1195 let mut char_pool = String::new();
1196 let mut required_chars: Vec<String> = Vec::new();
1197
1198 if !exclude_lowercase {
1199 let filtered: String = lowercase
1200 .chars()
1201 .filter(|c| !exclude_chars.contains(*c))
1202 .collect();
1203 if !filtered.is_empty() {
1204 required_chars.push(filtered.clone());
1205 char_pool.push_str(&filtered);
1206 }
1207 }
1208 if !exclude_uppercase {
1209 let filtered: String = uppercase
1210 .chars()
1211 .filter(|c| !exclude_chars.contains(*c))
1212 .collect();
1213 if !filtered.is_empty() {
1214 required_chars.push(filtered.clone());
1215 char_pool.push_str(&filtered);
1216 }
1217 }
1218 if !exclude_numbers {
1219 let filtered: String = digits
1220 .chars()
1221 .filter(|c| !exclude_chars.contains(*c))
1222 .collect();
1223 if !filtered.is_empty() {
1224 required_chars.push(filtered.clone());
1225 char_pool.push_str(&filtered);
1226 }
1227 }
1228 if !exclude_punctuation {
1229 let filtered: String = punctuation
1230 .chars()
1231 .filter(|c| !exclude_chars.contains(*c))
1232 .collect();
1233 if !filtered.is_empty() {
1234 required_chars.push(filtered.clone());
1235 char_pool.push_str(&filtered);
1236 }
1237 }
1238 if include_space && !exclude_chars.contains(' ') {
1239 char_pool.push(' ');
1240 }
1241
1242 if char_pool.is_empty() {
1243 return Err(AwsServiceError::aws_error(
1244 StatusCode::BAD_REQUEST,
1245 "InvalidParameterException",
1246 "InvalidParameterException",
1247 ));
1248 }
1249
1250 let pool_bytes: Vec<char> = char_pool.chars().collect();
1251 let mut password = String::with_capacity(length);
1252
1253 if require_each {
1255 for category in &required_chars {
1257 let chars: Vec<char> = category.chars().collect();
1258 let idx = simple_random() % chars.len();
1259 password.push(chars[idx]);
1260 }
1261 if include_space && !exclude_chars.contains(' ') {
1262 password.push(' ');
1263 }
1264 }
1265
1266 while password.len() < length {
1268 let idx = simple_random() % pool_bytes.len();
1269 password.push(pool_bytes[idx]);
1270 }
1271
1272 let mut chars: Vec<char> = password.chars().collect();
1274 for i in (1..chars.len()).rev() {
1275 let j = simple_random() % (i + 1);
1276 chars.swap(i, j);
1277 }
1278 let password: String = chars.into_iter().take(length).collect();
1279
1280 let response = json!({
1281 "RandomPassword": password,
1282 });
1283
1284 Ok(AwsResponse::ok_json(response))
1285 }
1286
1287 fn rotate_secret(
1288 &self,
1289 req: &AwsRequest,
1290 ) -> Result<(AwsResponse, Option<RotationInvocation>), AwsServiceError> {
1291 let body = req.json_body();
1292 let secret_id = require_secret_id(&body)?;
1293
1294 if let Some(token) = body["ClientRequestToken"].as_str() {
1296 if token.len() < 32 || token.len() > 64 {
1297 return Err(AwsServiceError::aws_error(
1298 StatusCode::BAD_REQUEST,
1299 "InvalidParameterException",
1300 "ClientRequestToken must be 32-64 characters long.",
1301 ));
1302 }
1303 }
1304
1305 if let Some(arn) = body["RotationLambdaARN"].as_str() {
1307 if arn.len() > 2048 {
1308 return Err(AwsServiceError::aws_error(
1309 StatusCode::BAD_REQUEST,
1310 "InvalidParameterException",
1311 "RotationLambdaARN length must be less than or equal to 2048.",
1312 ));
1313 }
1314 }
1315
1316 if let Some(rules) = body["RotationRules"].as_object() {
1318 if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1319 if !(1..=1000).contains(&days) {
1320 return Err(AwsServiceError::aws_error(
1321 StatusCode::BAD_REQUEST,
1322 "InvalidParameterException",
1323 "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1324 ));
1325 }
1326 }
1327 }
1328
1329 let mut accounts = self.state.write();
1330 let state = accounts.get_or_create(&req.account_id);
1331 let secret = self.find_secret_mut(state, &secret_id)?;
1332
1333 if secret.deleted {
1334 return Err(AwsServiceError::aws_error(
1335 StatusCode::BAD_REQUEST,
1336 "InvalidRequestException",
1337 "You can't perform this operation on the secret because it was marked for deletion.",
1338 ));
1339 }
1340
1341 if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1343 secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1344 }
1345
1346 if let Some(rules) = body["RotationRules"].as_object() {
1347 let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1348 secret.rotation_rules = Some(RotationRules {
1349 automatically_after_days: days,
1350 });
1351 }
1352
1353 secret.rotation_enabled = Some(true);
1354 let now = Utc::now();
1355 secret.last_rotated_at = Some(now);
1356 secret.last_changed_at = now;
1357
1358 let version_id = body["ClientRequestToken"]
1359 .as_str()
1360 .map(|s| s.to_string())
1361 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1362
1363 let has_lambda =
1364 body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1365 let lambda_arn = secret.rotation_lambda_arn.clone();
1366
1367 let mut invocation = None;
1369 if let Some(current_vid) = secret.current_version_id.clone() {
1370 let current_value = secret.versions.get(¤t_vid).cloned();
1371
1372 if let Some(cv) = current_value {
1373 if has_lambda {
1374 if let Some(ref arn) = lambda_arn {
1381 invocation = Some(RotationInvocation {
1382 lambda_arn: arn.clone(),
1383 secret_id: secret.arn.clone(),
1384 client_request_token: version_id.clone(),
1385 });
1386 }
1387 } else {
1388 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1391 old_v.stages.retain(|s| s != "AWSCURRENT");
1392 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1393 old_v.stages.push("AWSPREVIOUS".to_string());
1394 }
1395 }
1396 let version = SecretVersion {
1397 version_id: version_id.clone(),
1398 secret_string: cv.secret_string.clone(),
1399 secret_binary: cv.secret_binary.clone(),
1400 stages: vec!["AWSCURRENT".to_string()],
1401 created_at: now,
1402 };
1403 secret.versions.insert(version_id.clone(), version);
1404 secret.current_version_id = Some(version_id.clone());
1405 }
1406 }
1407 }
1408
1409 let response = json!({
1410 "ARN": secret.arn,
1411 "Name": secret.name,
1412 "VersionId": version_id,
1413 });
1414
1415 Ok((AwsResponse::ok_json(response), invocation))
1416 }
1417
1418 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1419 let body = req.json_body();
1420 let secret_id = require_secret_id(&body)?;
1421
1422 let mut accounts = self.state.write();
1423 let state = accounts.get_or_create(&req.account_id);
1424 let secret = self.find_secret_mut(state, &secret_id)?;
1425
1426 if secret.deleted {
1427 return Err(AwsServiceError::aws_error(
1428 StatusCode::BAD_REQUEST,
1429 "InvalidRequestException",
1430 "You can't perform this operation on the secret because it was marked for deletion.",
1431 ));
1432 }
1433
1434 if secret.rotation_enabled != Some(true) {
1435 return Err(AwsServiceError::aws_error(
1436 StatusCode::BAD_REQUEST,
1437 "InvalidRequestException",
1438 "You can't cancel rotation for a secret that does not have rotation enabled.",
1439 ));
1440 }
1441
1442 secret.rotation_enabled = Some(false);
1443
1444 let response = json!({
1445 "ARN": secret.arn,
1446 "Name": secret.name,
1447 });
1448
1449 Ok(AwsResponse::ok_json(response))
1450 }
1451
1452 fn update_secret_version_stage(
1453 &self,
1454 req: &AwsRequest,
1455 ) -> Result<AwsResponse, AwsServiceError> {
1456 let body = req.json_body();
1457 let secret_id = require_secret_id(&body)?;
1458 let version_stage = body["VersionStage"]
1459 .as_str()
1460 .ok_or_else(|| {
1461 AwsServiceError::aws_error(
1462 StatusCode::BAD_REQUEST,
1463 "InvalidParameterException",
1464 "VersionStage is required",
1465 )
1466 })?
1467 .to_string();
1468 validate_string_length("versionStage", &version_stage, 1, 256)?;
1469 validate_optional_string_length(
1470 "removeFromVersionId",
1471 body["RemoveFromVersionId"].as_str(),
1472 32,
1473 64,
1474 )?;
1475 validate_optional_string_length(
1476 "moveToVersionId",
1477 body["MoveToVersionId"].as_str(),
1478 32,
1479 64,
1480 )?;
1481
1482 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1483 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1484
1485 let mut accounts = self.state.write();
1486 let state = accounts.get_or_create(&req.account_id);
1487 let secret = self.find_secret_mut(state, &secret_id)?;
1488
1489 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1491 let current_holder = secret
1493 .versions
1494 .iter()
1495 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1496 .map(|(id, _)| id.clone());
1497
1498 if let Some(current_vid) = current_holder {
1499 return Err(AwsServiceError::aws_error(
1500 StatusCode::BAD_REQUEST,
1501 "InvalidParameterException",
1502 format!(
1503 "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."
1504 ),
1505 ));
1506 }
1507 }
1508
1509 if let Some(ref remove_vid) = remove_from {
1511 if let Some(version) = secret.versions.get_mut(remove_vid) {
1512 version.stages.retain(|s| s != &version_stage);
1513 if version_stage == "AWSCURRENT" {
1515 for (id, v) in secret.versions.iter_mut() {
1517 if id != remove_vid {
1518 v.stages.retain(|s| s != "AWSPREVIOUS");
1519 }
1520 }
1521 if let Some(v) = secret.versions.get_mut(remove_vid) {
1523 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1524 v.stages.push("AWSPREVIOUS".to_string());
1525 }
1526 }
1527 }
1528 }
1529 }
1530
1531 if let Some(ref move_vid) = move_to {
1533 if let Some(version) = secret.versions.get_mut(move_vid) {
1534 if !version.stages.contains(&version_stage) {
1535 version.stages.push(version_stage.clone());
1536 }
1537 }
1538 if version_stage == "AWSCURRENT" {
1540 secret.current_version_id = Some(move_vid.clone());
1541 }
1542 }
1543
1544 let response = json!({
1545 "ARN": secret.arn,
1546 "Name": secret.name,
1547 });
1548
1549 Ok(AwsResponse::ok_json(response))
1550 }
1551
1552 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1553 let body = req.json_body();
1554 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1555 let secret_id_list = body["SecretIdList"].as_array();
1556 let filters = body["Filters"].as_array();
1557 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1558
1559 if secret_id_list.is_some() && filters.is_some() {
1561 return Err(AwsServiceError::aws_error(
1562 StatusCode::BAD_REQUEST,
1563 "InvalidParameterException",
1564 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1565 ));
1566 }
1567
1568 if max_results.is_some() && filters.is_none() {
1570 return Err(AwsServiceError::aws_error(
1571 StatusCode::BAD_REQUEST,
1572 "InvalidParameterException",
1573 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1574 ));
1575 }
1576
1577 let accounts = self.state.read();
1578 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1579 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1580 let mut secret_values: Vec<Value> = Vec::new();
1581 let mut errors: Vec<Value> = Vec::new();
1582
1583 if let Some(id_list) = secret_id_list {
1584 for id_val in id_list {
1585 let sid = id_val.as_str().unwrap_or("");
1586 match self.find_secret_ref(state, sid) {
1587 Ok(secret) => {
1588 if secret.deleted {
1589 errors.push(json!({
1590 "SecretId": sid,
1591 "ErrorCode": "InvalidRequestException",
1592 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1593 }));
1594 } else if let Some(ref current_vid) = secret.current_version_id {
1595 if let Some(version) = secret.versions.get(current_vid) {
1596 let mut entry = json!({
1597 "ARN": secret.arn,
1598 "Name": secret.name,
1599 "VersionId": version.version_id,
1600 "VersionStages": version.stages,
1601 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1602 });
1603 if let Some(ref s) = version.secret_string {
1604 let plaintext = self
1609 .maybe_decrypt_secret_string(
1610 &req.account_id,
1611 &secret.arn,
1612 secret.kms_key_id.as_deref(),
1613 Some(s.as_str()),
1614 )
1615 .unwrap_or_else(|| s.clone());
1616 entry["SecretString"] = json!(plaintext);
1617 }
1618 if let Some(ref b) = version.secret_binary {
1619 entry["SecretBinary"] = json!(base64_encode(b));
1620 }
1621 secret_values.push(entry);
1622 } else {
1623 errors.push(json!({
1624 "SecretId": sid,
1625 "ErrorCode": "ResourceNotFoundException",
1626 "Message": "Secrets Manager can't find the specified secret.",
1627 }));
1628 }
1629 } else {
1630 errors.push(json!({
1631 "SecretId": sid,
1632 "ErrorCode": "ResourceNotFoundException",
1633 "Message": "Secrets Manager can't find the specified secret.",
1634 }));
1635 }
1636 }
1637 Err(_) => {
1638 errors.push(json!({
1639 "SecretId": sid,
1640 "ErrorCode": "ResourceNotFoundException",
1641 "Message": "Secrets Manager can't find the specified secret.",
1642 }));
1643 }
1644 }
1645 }
1646 } else if let Some(filters) = filters {
1647 let matching: Vec<&Secret> = state
1649 .secrets
1650 .values()
1651 .filter(|s| {
1652 if s.deleted {
1653 return false;
1654 }
1655 for filter in filters {
1656 let key = filter["Key"].as_str().unwrap_or("");
1657 let values: Vec<&str> = filter["Values"]
1658 .as_array()
1659 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1660 .unwrap_or_default();
1661 let matches = match key {
1662 "name" => filter_name(s, &values),
1663 "description" => filter_description(s, &values),
1664 "tag-key" => filter_tag_key(s, &values),
1665 "tag-value" => filter_tag_value(s, &values),
1666 "all" => filter_all(s, &values),
1667 _ => true,
1668 };
1669 if !matches {
1670 return false;
1671 }
1672 }
1673 true
1674 })
1675 .collect();
1676
1677 let limit = max_results.unwrap_or(100) as usize;
1678 let mut no_value_found = false;
1679 let mut matching = matching;
1680 matching.sort_by(|a, b| a.name.cmp(&b.name));
1681
1682 for secret in matching.iter().take(limit) {
1683 if let Some(ref current_vid) = secret.current_version_id {
1684 if let Some(version) = secret.versions.get(current_vid) {
1685 let mut entry = json!({
1686 "ARN": secret.arn,
1687 "Name": secret.name,
1688 "VersionId": version.version_id,
1689 "VersionStages": version.stages,
1690 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1691 });
1692 if let Some(ref s) = version.secret_string {
1693 let plaintext = self
1696 .maybe_decrypt_secret_string(
1697 &req.account_id,
1698 &secret.arn,
1699 secret.kms_key_id.as_deref(),
1700 Some(s.as_str()),
1701 )
1702 .unwrap_or_else(|| s.clone());
1703 entry["SecretString"] = json!(plaintext);
1704 }
1705 if let Some(ref b) = version.secret_binary {
1706 entry["SecretBinary"] = json!(base64_encode(b));
1707 }
1708 secret_values.push(entry);
1709 } else {
1710 no_value_found = true;
1711 }
1712 } else {
1713 no_value_found = true;
1714 }
1715 }
1716
1717 if no_value_found && secret_values.is_empty() {
1718 return Err(AwsServiceError::aws_error(
1719 StatusCode::NOT_FOUND,
1720 "ResourceNotFoundException",
1721 "Secrets Manager can't find the specified secret.",
1722 ));
1723 }
1724 }
1725
1726 let mut response = json!({
1727 "SecretValues": secret_values,
1728 "Errors": errors,
1729 });
1730
1731 if errors.is_empty() {
1733 response.as_object_mut().unwrap().remove("Errors");
1734 }
1735
1736 Ok(AwsResponse::ok_json(response))
1737 }
1738
1739 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1740 let body = req.json_body();
1741 let secret_id = require_secret_id(&body)?;
1742
1743 let accounts = self.state.read();
1744 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1745 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1746 let secret = self.find_secret_ref(state, &secret_id)?;
1747
1748 let mut response = json!({
1751 "ARN": secret.arn,
1752 "Name": secret.name,
1753 });
1754 if let Some(ref policy) = secret.resource_policy {
1755 response["ResourcePolicy"] = json!(policy);
1756 }
1757
1758 Ok(AwsResponse::ok_json(response))
1759 }
1760
1761 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1762 let body = req.json_body();
1763 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1764 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1765 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1766 AwsServiceError::aws_error(
1767 StatusCode::BAD_REQUEST,
1768 "InvalidParameterException",
1769 "ResourcePolicy must be a string",
1770 )
1771 })?;
1772 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1773
1774 if let Some(secret_id) = body["SecretId"].as_str() {
1776 let accounts = self.state.read();
1777 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1778 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1779 self.find_secret_key(state, secret_id)?;
1780 }
1781
1782 let response = json!({
1783 "PolicyValidationPassed": true,
1784 "ValidationErrors": [],
1785 });
1786 Ok(AwsResponse::ok_json(response))
1787 }
1788
1789 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1790 let body = req.json_body();
1791 let secret_id = require_secret_id(&body)?;
1792 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1793 validate_optional_string_length(
1794 "resourcePolicy",
1795 body["ResourcePolicy"].as_str(),
1796 1,
1797 20480,
1798 )?;
1799 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1800
1801 let mut accounts = self.state.write();
1802 let state = accounts.get_or_create(&req.account_id);
1803 let secret = self.find_secret_mut(state, &secret_id)?;
1804 secret.resource_policy = policy;
1805
1806 let response = json!({
1807 "ARN": secret.arn,
1808 "Name": secret.name,
1809 });
1810
1811 Ok(AwsResponse::ok_json(response))
1812 }
1813
1814 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1815 let body = req.json_body();
1816 let secret_id = require_secret_id(&body)?;
1817
1818 let mut accounts = self.state.write();
1819 let state = accounts.get_or_create(&req.account_id);
1820 let secret = self.find_secret_mut(state, &secret_id)?;
1821 secret.resource_policy = None;
1822
1823 let response = json!({
1824 "ARN": secret.arn,
1825 "Name": secret.name,
1826 });
1827
1828 Ok(AwsResponse::ok_json(response))
1829 }
1830
1831 fn replicate_secret_to_regions(
1832 &self,
1833 req: &AwsRequest,
1834 ) -> Result<AwsResponse, AwsServiceError> {
1835 let body = req.json_body();
1836 let secret_id = require_secret_id(&body)?;
1837 let add_regions: Vec<String> = body["AddReplicaRegions"]
1839 .as_array()
1840 .map(|arr| {
1841 arr.iter()
1842 .filter_map(|r| r["Region"].as_str().map(String::from))
1843 .collect()
1844 })
1845 .unwrap_or_default();
1846
1847 let mut accounts = self.state.write();
1848 let state = accounts.get_or_create(&req.account_id);
1849 let secret = self.find_secret_mut(state, &secret_id)?;
1850 for region in add_regions {
1851 if !secret.replica_regions.contains(®ion) {
1852 secret.replica_regions.push(region);
1853 }
1854 }
1855 let response = json!({
1856 "ARN": secret.arn,
1857 "ReplicationStatus": replication_status_json(&secret.replica_regions),
1858 });
1859 Ok(AwsResponse::ok_json(response))
1860 }
1861
1862 fn remove_regions_from_replication(
1863 &self,
1864 req: &AwsRequest,
1865 ) -> Result<AwsResponse, AwsServiceError> {
1866 let body = req.json_body();
1867 let secret_id = require_secret_id(&body)?;
1868 let remove_regions: Vec<String> = body["RemoveReplicaRegions"]
1869 .as_array()
1870 .map(|arr| {
1871 arr.iter()
1872 .filter_map(|r| r.as_str().map(String::from))
1873 .collect()
1874 })
1875 .unwrap_or_default();
1876
1877 let mut accounts = self.state.write();
1878 let state = accounts.get_or_create(&req.account_id);
1879 let secret = self.find_secret_mut(state, &secret_id)?;
1880 secret
1881 .replica_regions
1882 .retain(|r| !remove_regions.contains(r));
1883 let response = json!({
1884 "ARN": secret.arn,
1885 "ReplicationStatus": replication_status_json(&secret.replica_regions),
1886 });
1887 Ok(AwsResponse::ok_json(response))
1888 }
1889
1890 fn stop_replication_to_replica(
1891 &self,
1892 req: &AwsRequest,
1893 ) -> Result<AwsResponse, AwsServiceError> {
1894 let body = req.json_body();
1895 let secret_id = require_secret_id(&body)?;
1896
1897 let accounts = self.state.read();
1898 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1899 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1900 let secret = self.find_secret_ref(state, &secret_id)?;
1901
1902 let response = json!({
1903 "ARN": secret.arn,
1904 });
1905 Ok(AwsResponse::ok_json(response))
1906 }
1907
1908 fn find_secret_mut<'a>(
1910 &self,
1911 state: &'a mut crate::state::SecretsManagerState,
1912 secret_id: &str,
1913 ) -> Result<&'a mut Secret, AwsServiceError> {
1914 let key = self.find_secret_key(state, secret_id)?;
1915 Ok(state.secrets.get_mut(&key).unwrap())
1916 }
1917
1918 fn find_secret_key(
1919 &self,
1920 state: &crate::state::SecretsManagerState,
1921 secret_id: &str,
1922 ) -> Result<String, AwsServiceError> {
1923 if state.secrets.contains_key(secret_id) {
1924 return Ok(secret_id.to_string());
1925 }
1926
1927 for secret in state.secrets.values() {
1928 if secret.arn == secret_id {
1929 return Ok(secret.name.clone());
1930 }
1931 }
1932
1933 if secret_id.starts_with("arn:aws:secretsmanager:") {
1934 for secret in state.secrets.values() {
1935 if secret.arn.starts_with(secret_id) {
1936 return Ok(secret.name.clone());
1937 }
1938 }
1939 }
1940
1941 Err(AwsServiceError::aws_error(
1942 StatusCode::NOT_FOUND,
1943 "ResourceNotFoundException",
1944 "Secrets Manager can't find the specified secret.",
1945 ))
1946 }
1947
1948 fn find_secret_ref<'a>(
1950 &self,
1951 state: &'a crate::state::SecretsManagerState,
1952 secret_id: &str,
1953 ) -> Result<&'a Secret, AwsServiceError> {
1954 if let Some(secret) = state.secrets.get(secret_id) {
1955 return Ok(secret);
1956 }
1957
1958 for secret in state.secrets.values() {
1960 if secret.arn == secret_id {
1961 return Ok(secret);
1962 }
1963 }
1964
1965 if secret_id.starts_with("arn:aws:secretsmanager:") {
1967 for secret in state.secrets.values() {
1968 if secret.arn.starts_with(secret_id) {
1969 return Ok(secret);
1970 }
1971 }
1972 }
1973
1974 Err(AwsServiceError::aws_error(
1975 StatusCode::NOT_FOUND,
1976 "ResourceNotFoundException",
1977 "Secrets Manager can't find the specified secret.",
1978 ))
1979 }
1980}
1981
1982pub async fn save_secretsmanager_snapshot(
1989 state: &SharedSecretsManagerState,
1990 store: Option<Arc<dyn SnapshotStore>>,
1991 lock: &AsyncMutex<()>,
1992) {
1993 let Some(store) = store else {
1994 return;
1995 };
1996 let _guard = lock.lock().await;
1997 let snapshot = SecretsManagerSnapshot {
1998 schema_version: SECRETSMANAGER_SNAPSHOT_SCHEMA_VERSION,
1999 state: None,
2000 accounts: Some(state.read().clone()),
2001 };
2002 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
2003 let bytes = serde_json::to_vec(&snapshot)
2004 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
2005 store.save(&bytes)
2006 })
2007 .await;
2008 match join {
2009 Ok(Ok(())) => {}
2010 Ok(Err(err)) => tracing::error!(%err, "failed to write secretsmanager snapshot"),
2011 Err(err) => tracing::error!(%err, "secretsmanager snapshot task panicked"),
2012 }
2013}
2014
2015struct CreateSecretInput {
2017 name: String,
2018 client_request_token: Option<String>,
2019 description: Option<String>,
2020 kms_key_id: Option<String>,
2021 secret_string: Option<String>,
2022 secret_binary: Option<Vec<u8>>,
2023 tags: Vec<(String, String)>,
2024}
2025
2026impl CreateSecretInput {
2027 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
2028 validate_required("Name", &body["Name"])?;
2029 let name = body["Name"]
2030 .as_str()
2031 .ok_or_else(|| {
2032 AwsServiceError::aws_error(
2033 StatusCode::BAD_REQUEST,
2034 "InvalidParameterException",
2035 "Name is required",
2036 )
2037 })?
2038 .to_string();
2039 validate_string_length("name", &name, 1, 512)?;
2040 validate_optional_string_length(
2041 "clientRequestToken",
2042 body["ClientRequestToken"].as_str(),
2043 32,
2044 64,
2045 )?;
2046 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
2047 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
2048 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
2049
2050 Ok(Self {
2051 name,
2052 client_request_token: body["ClientRequestToken"].as_str().map(|s| s.to_string()),
2053 description: body["Description"].as_str().map(|s| s.to_string()),
2054 kms_key_id: body["KmsKeyId"].as_str().map(|s| s.to_string()),
2055 secret_string: body["SecretString"].as_str().map(|s| s.to_string()),
2056 secret_binary: body["SecretBinary"].as_str().and_then(base64_decode),
2057 tags: parse_tags(&body["Tags"]),
2058 })
2059 }
2060}
2061
2062#[async_trait]
2063impl AwsService for SecretsManagerService {
2064 fn service_name(&self) -> &str {
2065 "secretsmanager"
2066 }
2067
2068 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2069 let mutates = is_mutating_action(req.action.as_str());
2070 let result = match req.action.as_str() {
2071 "CreateSecret" => self.create_secret(&req),
2072 "GetSecretValue" => self.get_secret_value(&req),
2073 "PutSecretValue" => self.put_secret_value(&req),
2074 "UpdateSecret" => self.update_secret(&req),
2075 "DeleteSecret" => self.delete_secret(&req),
2076 "RestoreSecret" => self.restore_secret(&req),
2077 "DescribeSecret" => self.describe_secret(&req),
2078 "ListSecrets" => self.list_secrets(&req),
2079 "TagResource" => self.tag_resource(&req),
2080 "UntagResource" => self.untag_resource(&req),
2081 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
2082 "GetRandomPassword" => self.get_random_password(&req),
2083 "RotateSecret" => {
2084 let (response, invocation) = self.rotate_secret(&req)?;
2085 if let Some(inv) = invocation {
2086 if let Some(ref bus) = self.delivery_bus {
2087 let bus = bus.clone();
2088 tokio::spawn(async move {
2090 for step in &["createSecret", "setSecret", "testSecret", "finishSecret"]
2091 {
2092 let payload = serde_json::json!({
2093 "SecretId": inv.secret_id,
2094 "ClientRequestToken": inv.client_request_token,
2095 "Step": step,
2096 });
2097 let payload_str = payload.to_string();
2098 match bus.invoke_lambda(&inv.lambda_arn, &payload_str).await {
2099 Some(Ok(_)) => {}
2100 Some(Err(e)) => {
2101 tracing::warn!(
2102 step = step,
2103 error = %e,
2104 "rotation Lambda invocation failed"
2105 );
2106 }
2107 None => {
2108 tracing::warn!(
2109 lambda_arn = %inv.lambda_arn,
2110 step = step,
2111 "rotation Lambda delivery not configured; \
2112 Lambda invocation skipped"
2113 );
2114 break;
2115 }
2116 }
2117 }
2118 });
2119 }
2120 }
2121 Ok(response)
2122 }
2123 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2124 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2125 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2126 "GetResourcePolicy" => self.get_resource_policy(&req),
2127 "PutResourcePolicy" => self.put_resource_policy(&req),
2128 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2129 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2130 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2131 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2132 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2133 _ => Err(AwsServiceError::action_not_implemented(
2134 "secretsmanager",
2135 &req.action,
2136 )),
2137 };
2138 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
2139 self.save_snapshot().await;
2140 }
2141 result.map_err(remap_validation_error)
2142 }
2143
2144 fn supported_actions(&self) -> &[&str] {
2145 &[
2146 "CreateSecret",
2147 "GetSecretValue",
2148 "PutSecretValue",
2149 "UpdateSecret",
2150 "DeleteSecret",
2151 "RestoreSecret",
2152 "DescribeSecret",
2153 "ListSecrets",
2154 "TagResource",
2155 "UntagResource",
2156 "ListSecretVersionIds",
2157 "GetRandomPassword",
2158 "RotateSecret",
2159 "CancelRotateSecret",
2160 "UpdateSecretVersionStage",
2161 "BatchGetSecretValue",
2162 "GetResourcePolicy",
2163 "PutResourcePolicy",
2164 "DeleteResourcePolicy",
2165 "ValidateResourcePolicy",
2166 "ReplicateSecretToRegions",
2167 "RemoveRegionsFromReplication",
2168 "StopReplicationToReplica",
2169 ]
2170 }
2171}
2172
2173#[path = "service_helpers.rs"]
2174mod service_helpers;
2175pub(crate) use service_helpers::*;
2176
2177fn replication_status_json(regions: &[String]) -> Value {
2186 Value::Array(
2187 regions
2188 .iter()
2189 .map(|r| {
2190 json!({
2191 "Region": r,
2192 "Status": "InSync",
2193 "StatusMessage": "Replication succeeded",
2194 })
2195 })
2196 .collect(),
2197 )
2198}
2199
2200fn remap_validation_error(err: AwsServiceError) -> AwsServiceError {
2201 match err {
2202 AwsServiceError::AwsError {
2203 status,
2204 code,
2205 message,
2206 extra_fields,
2207 headers,
2208 } if code == "ValidationException" => AwsServiceError::AwsError {
2209 status,
2210 code: "InvalidParameterException".to_string(),
2211 message,
2212 extra_fields,
2213 headers,
2214 },
2215 other => other,
2216 }
2217}
2218
2219fn secret_owner_account(secret_id: &str, caller_account: &str) -> String {
2223 if !secret_id.starts_with("arn:aws:secretsmanager:") {
2224 return caller_account.to_string();
2225 }
2226 let parts: Vec<&str> = secret_id.splitn(7, ':').collect();
2227 if parts.len() < 5 {
2228 return caller_account.to_string();
2229 }
2230 let account = parts[4];
2231 if account.is_empty() {
2232 caller_account.to_string()
2233 } else {
2234 account.to_string()
2235 }
2236}
2237
2238fn resource_policy_allows(policy_doc: &str, caller_account: &str, secret_arn: &str) -> bool {
2243 if policy_doc.is_empty() {
2244 return false;
2245 }
2246 use fakecloud_core::auth::{Principal, PrincipalType};
2247 use fakecloud_iam::evaluator::{evaluate, EvalRequest, PolicyDocument};
2248 let doc = PolicyDocument::parse(policy_doc);
2249 let principal_arn = Arn::global("iam", caller_account, "root").to_string();
2250 let principal = Principal {
2251 arn: principal_arn.clone(),
2252 user_id: principal_arn.clone(),
2253 account_id: caller_account.to_string(),
2254 principal_type: PrincipalType::User,
2255 source_identity: None,
2256 tags: None,
2257 };
2258 let req = EvalRequest {
2259 principal: &principal,
2260 action: "secretsmanager:GetSecretValue".to_string(),
2261 resource: secret_arn.to_string(),
2262 context: Default::default(),
2263 };
2264 matches!(
2265 evaluate(&[doc], &req),
2266 fakecloud_iam::evaluator::Decision::Allow
2267 )
2268}
2269
2270#[cfg(test)]
2271#[path = "service_tests.rs"]
2272mod tests;