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