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