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 = body["SecretBinary"].as_str().and_then(base64_decode);
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 if let Some(old_version) = secret.versions.get_mut(old_vid) {
588 old_version.stages.retain(|s| s != "AWSCURRENT");
589 if !old_version.stages.contains(&"AWSPREVIOUS".to_string()) {
590 old_version.stages.push("AWSPREVIOUS".to_string());
591 }
592 }
593 for (id, v) in secret.versions.iter_mut() {
595 if id != old_vid {
596 v.stages.retain(|s| s != "AWSPREVIOUS");
597 }
598 }
599 }
600 secret.current_version_id = Some(version_id.clone());
601 }
602
603 for stage in &version_stages {
605 if stage == "AWSCURRENT" || stage == "AWSPREVIOUS" {
606 continue;
607 }
608 for v in secret.versions.values_mut() {
609 v.stages.retain(|s| s != stage);
610 }
611 }
612
613 secret.versions.retain(|_, v| !v.stages.is_empty());
615
616 let kms_key_for_enc = secret.kms_key_id.clone();
617 let arn_for_enc = secret.arn.clone();
618 let stored_secret_string = self.maybe_encrypt_secret_string(
619 &req.account_id,
620 &req.region,
621 &arn_for_enc,
622 kms_key_for_enc.as_deref(),
623 secret_string,
624 );
625 let version = SecretVersion {
626 version_id: version_id.clone(),
627 secret_string: stored_secret_string,
628 secret_binary,
629 stages: version_stages.clone(),
630 created_at: now,
631 };
632
633 secret.versions.insert(version_id.clone(), version);
634 secret.last_changed_at = now;
635
636 let response = json!({
637 "ARN": secret.arn,
638 "Name": secret.name,
639 "VersionId": version_id,
640 "VersionStages": version_stages,
641 });
642
643 Ok(AwsResponse::ok_json(response))
644 }
645
646 fn update_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
647 let body = req.json_body();
648 let secret_id = require_secret_id(&body)?;
649 validate_optional_string_length(
650 "clientRequestToken",
651 body["ClientRequestToken"].as_str(),
652 32,
653 64,
654 )?;
655 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
656 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
657 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
658
659 let mut accounts = self.state.write();
660 let state = accounts.get_or_create(&req.account_id);
661 let secret = match self.find_secret_mut(state, &secret_id) {
662 Ok(s) => s,
663 Err(_) => {
664 return Err(AwsServiceError::aws_error(
665 StatusCode::NOT_FOUND,
666 "ResourceNotFoundException",
667 "Secrets Manager can't find the specified secret.",
668 ));
669 }
670 };
671
672 if secret.deleted {
673 return Err(AwsServiceError::aws_error(
674 StatusCode::BAD_REQUEST,
675 "InvalidRequestException",
676 "You can't perform this operation on the secret because it was marked for deletion.",
677 ));
678 }
679
680 if let Some(desc) = body["Description"].as_str() {
681 secret.description = Some(desc.to_string());
682 }
683 if let Some(kms) = body["KmsKeyId"].as_str() {
684 secret.kms_key_id = Some(kms.to_string());
685 }
686
687 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
689 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
690
691 let version_id = if secret_string.is_some() || secret_binary.is_some() {
692 let vid = body["ClientRequestToken"]
693 .as_str()
694 .map(|s| s.to_string())
695 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
696
697 let existing_plaintext = secret.versions.get(&vid).and_then(|v| {
698 self.maybe_decrypt_secret_string(
699 &req.account_id,
700 &secret.arn,
701 secret.kms_key_id.as_deref(),
702 v.secret_string.as_deref(),
703 )
704 });
705 match check_secret_version_idempotency(
706 &secret.versions,
707 &vid,
708 existing_plaintext,
709 &secret_string,
710 &secret_binary,
711 ) {
712 VersionIdempotency::Match => {
713 return Ok(AwsResponse::ok_json(json!({
714 "ARN": secret.arn,
715 "Name": secret.name,
716 "VersionId": vid,
717 })));
718 }
719 VersionIdempotency::Conflict => {
720 return Err(AwsServiceError::aws_error(
721 StatusCode::BAD_REQUEST,
722 "ResourceExistsException",
723 format!(
724 "You can't use ClientRequestToken {vid} because that value is already in use for a version of secret {}.",
725 secret.arn
726 ),
727 ));
728 }
729 VersionIdempotency::NotFound => {}
730 }
731
732 let now = Utc::now();
733
734 if let Some(ref old_vid) = secret.current_version_id.clone() {
736 if let Some(old_v) = secret.versions.get_mut(old_vid) {
737 old_v.stages.retain(|s| s != "AWSCURRENT");
738 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
739 old_v.stages.push("AWSPREVIOUS".to_string());
740 }
741 }
742 }
743
744 let version = SecretVersion {
745 version_id: vid.clone(),
746 secret_string,
747 secret_binary,
748 stages: vec!["AWSCURRENT".to_string()],
749 created_at: now,
750 };
751 secret.versions.insert(vid.clone(), version);
752 secret.current_version_id = Some(vid.clone());
753 secret.last_changed_at = now;
754 Some(vid)
755 } else {
756 secret.last_changed_at = Utc::now();
757 None
758 };
759
760 let mut response = json!({
761 "ARN": secret.arn,
762 "Name": secret.name,
763 });
764 if let Some(vid) = version_id {
765 response["VersionId"] = json!(vid);
766 }
767
768 Ok(AwsResponse::ok_json(response))
769 }
770
771 fn delete_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
772 let body = req.json_body();
773 let secret_id = require_secret_id(&body)?;
774
775 let force_delete = body["ForceDeleteWithoutRecovery"]
776 .as_bool()
777 .unwrap_or(false);
778 let recovery_window = body.get("RecoveryWindowInDays").and_then(|v| v.as_i64());
779
780 if let Some(days) = recovery_window {
782 if !(7..=30).contains(&days) {
783 return Err(AwsServiceError::aws_error(
784 StatusCode::BAD_REQUEST,
785 "InvalidParameterException",
786 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: RecoveryWindowInDays value must be between 7 and 30 days (inclusive).",
787 ));
788 }
789 }
790
791 if force_delete && recovery_window.is_some() {
793 return Err(AwsServiceError::aws_error(
794 StatusCode::BAD_REQUEST,
795 "InvalidParameterException",
796 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: You can't use ForceDeleteWithoutRecovery in conjunction with RecoveryWindowInDays.",
797 ));
798 }
799
800 let mut accounts = self.state.write();
801 let state = accounts.get_or_create(&req.account_id);
802
803 if force_delete {
804 match self.find_secret_mut(state, &secret_id) {
806 Ok(secret) => {
807 let arn = secret.arn.clone();
808 let name = secret.name.clone();
809 let deletion_date = Utc::now();
810 state.secrets.remove(&name);
811 let response = json!({
812 "ARN": arn,
813 "Name": name,
814 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
815 });
816 return Ok(AwsResponse::ok_json(response));
817 }
818 Err(_) => {
819 let arn = format!(
821 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
822 req.region,
823 req.account_id,
824 secret_id,
825 &uuid::Uuid::new_v4().to_string()[..6]
826 );
827 let deletion_date = Utc::now();
828 let response = json!({
829 "ARN": arn,
830 "Name": secret_id,
831 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
832 });
833 return Ok(AwsResponse::ok_json(response));
834 }
835 }
836 }
837
838 let secret = self.find_secret_mut(state, &secret_id)?;
839
840 if secret.deleted {
841 return Err(AwsServiceError::aws_error(
842 StatusCode::BAD_REQUEST,
843 "InvalidRequestException",
844 "You can't perform this operation on the secret because it was already scheduled for deletion.",
845 ));
846 }
847
848 let now = Utc::now();
849 let days = recovery_window.unwrap_or(30);
850 let deletion_date = now + chrono::Duration::days(days);
851 secret.deleted = true;
852 secret.deletion_date = Some(deletion_date);
853
854 let response = json!({
855 "ARN": secret.arn,
856 "Name": secret.name,
857 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
858 });
859
860 Ok(AwsResponse::ok_json(response))
861 }
862
863 fn restore_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
864 let body = req.json_body();
865 let secret_id = require_secret_id(&body)?;
866
867 let mut accounts = self.state.write();
868 let state = accounts.get_or_create(&req.account_id);
869 let secret = self.find_secret_mut(state, &secret_id)?;
870
871 secret.deleted = false;
873 secret.deletion_date = None;
874
875 let response = json!({
876 "ARN": secret.arn,
877 "Name": secret.name,
878 });
879
880 Ok(AwsResponse::ok_json(response))
881 }
882
883 fn describe_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
884 let body = req.json_body();
885 let secret_id = require_secret_id(&body)?;
886
887 let accounts = self.state.read();
888 let empty = SecretsManagerState::new(&req.account_id, &req.region);
889 let state = accounts.get(&req.account_id).unwrap_or(&empty);
890 let secret = self.find_secret_ref(state, &secret_id)?;
891
892 let mut response = json!({
893 "ARN": secret.arn,
894 "Name": secret.name,
895 "CreatedDate": secret.created_at.timestamp_millis() as f64 / 1000.0,
896 "LastChangedDate": secret.last_changed_at.timestamp_millis() as f64 / 1000.0,
897 });
898
899 if !secret.versions.is_empty() {
900 let mut version_ids_to_stages: serde_json::Map<String, Value> = serde_json::Map::new();
901 for (vid, version) in &secret.versions {
902 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
903 }
904 response["VersionIdsToStages"] = Value::Object(version_ids_to_stages);
905 }
906
907 if let Some(ref desc) = secret.description {
908 if !desc.is_empty() {
909 response["Description"] = json!(desc);
910 }
911 }
912
913 if secret.tags_ever_set || !secret.tags.is_empty() {
914 response["Tags"] = json!(tags_to_json(&secret.tags));
915 }
916
917 if let Some(ref kms) = secret.kms_key_id {
918 response["KmsKeyId"] = json!(kms);
919 }
920 if secret.deleted {
921 response["DeletedDate"] = json!(secret
922 .deletion_date
923 .map(|d| d.timestamp_millis() as f64 / 1000.0));
924 }
925 if let Some(rotation_enabled) = secret.rotation_enabled {
926 response["RotationEnabled"] = json!(rotation_enabled);
927 }
928 if let Some(ref lambda_arn) = secret.rotation_lambda_arn {
929 response["RotationLambdaARN"] = json!(lambda_arn);
930 }
931 if let Some(ref rules) = secret.rotation_rules {
932 let mut rules_json = json!({});
933 if let Some(days) = rules.automatically_after_days {
934 rules_json["AutomaticallyAfterDays"] = json!(days);
935 }
936 if let Some(ref duration) = rules.duration {
937 rules_json["Duration"] = json!(duration);
938 }
939 if let Some(ref expr) = rules.schedule_expression {
940 rules_json["ScheduleExpression"] = json!(expr);
941 }
942 response["RotationRules"] = rules_json;
943 }
944 if let Some(last_rotated) = secret.last_rotated_at {
945 response["LastRotatedDate"] = json!(last_rotated.timestamp_millis() as f64 / 1000.0);
946 }
947 if !secret.replica_regions.is_empty() {
948 response["ReplicationStatus"] = replication_status_json(&secret.replica_regions);
949 }
950 if secret.rotation_enabled == Some(true) {
952 if let Some(ref rules) = secret.rotation_rules {
953 if let Some(days) = rules.automatically_after_days {
954 let base = secret.last_rotated_at.unwrap_or(secret.created_at);
955 let next = base + chrono::Duration::days(days);
956 response["NextRotationDate"] = json!(next.timestamp_millis() as f64 / 1000.0);
957 }
958 }
959 }
960
961 Ok(AwsResponse::ok_json(response))
962 }
963
964 fn list_secrets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
965 let body = req.json_body();
966 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
967 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
968 validate_optional_enum("sortBy", body["SortBy"].as_str(), &["name", "created-date"])?;
969 validate_optional_enum("sortOrder", body["SortOrder"].as_str(), &["asc", "desc"])?;
970 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
971 let next_token = body["NextToken"].as_str();
972 let filters = body["Filters"].as_array();
973 let include_deleted = body["IncludePlannedDeletion"].as_bool().unwrap_or(false);
974
975 if let Some(filters) = filters {
977 for filter in filters {
978 let key = filter["Key"].as_str().unwrap_or("");
979 let values = filter["Values"].as_array();
980
981 if key.is_empty() {
982 return Err(AwsServiceError::aws_error(
983 StatusCode::BAD_REQUEST,
984 "InvalidParameterException",
985 "Invalid filter key",
986 ));
987 }
988
989 let valid_keys = [
990 "all",
991 "name",
992 "tag-key",
993 "description",
994 "tag-value",
995 "owning-service",
996 "primary-region",
997 ];
998 if !valid_keys.contains(&key) {
999 return Err(AwsServiceError::aws_error(
1000 StatusCode::BAD_REQUEST,
1001 "ValidationException",
1002 format!(
1003 "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]",
1004 key
1005 ),
1006 ));
1007 }
1008
1009 if values.is_none() || values.unwrap().is_empty() {
1010 return Err(AwsServiceError::aws_error(
1011 StatusCode::BAD_REQUEST,
1012 "InvalidParameterException",
1013 format!("Invalid filter values for key: {key}"),
1014 ));
1015 }
1016 }
1017 }
1018
1019 let accounts = self.state.read();
1020 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1021 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1022
1023 let mut secrets: Vec<&Secret> = state
1024 .secrets
1025 .values()
1026 .filter(|s| {
1027 if s.deleted && !include_deleted {
1029 return false;
1030 }
1031
1032 if let Some(filters) = filters {
1033 for filter in filters {
1034 let key = filter["Key"].as_str().unwrap_or("");
1035 let values: Vec<&str> = filter["Values"]
1036 .as_array()
1037 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1038 .unwrap_or_default();
1039
1040 let matches = match key {
1041 "name" => filter_name(s, &values),
1042 "description" => filter_description(s, &values),
1043 "tag-key" => filter_tag_key(s, &values),
1044 "tag-value" => filter_tag_value(s, &values),
1045 "all" => filter_all(s, &values),
1046 "owning-service" => false,
1047 "primary-region" => false,
1048 _ => true,
1049 };
1050
1051 if !matches {
1052 return false;
1053 }
1054 }
1055 }
1056 true
1057 })
1058 .collect();
1059 secrets.sort_by_key(|a| a.created_at);
1060
1061 let start_idx = if let Some(token) = next_token {
1063 secrets.iter().position(|s| s.name == token).unwrap_or(0)
1064 } else {
1065 0
1066 };
1067
1068 let page: Vec<Value> = secrets
1069 .iter()
1070 .skip(start_idx)
1071 .take(max_results)
1072 .map(|s| {
1073 let mut version_ids_to_stages: serde_json::Map<String, Value> =
1078 serde_json::Map::new();
1079 for (vid, version) in &s.versions {
1080 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
1081 }
1082 let mut entry = json!({
1083 "ARN": s.arn,
1084 "Name": s.name,
1085 "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
1086 "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
1087 "Description": s.description.clone().unwrap_or_default(),
1088 "SecretVersionsToStages": Value::Object(version_ids_to_stages),
1089 });
1090
1091 if s.tags_ever_set || !s.tags.is_empty() {
1092 entry["Tags"] = json!(tags_to_json(&s.tags));
1093 }
1094
1095 if let Some(ref kms) = s.kms_key_id {
1096 entry["KmsKeyId"] = json!(kms);
1097 }
1098 if s.deleted {
1099 entry["DeletedDate"] = json!(s
1100 .deletion_date
1101 .map(|d| d.timestamp_millis() as f64 / 1000.0));
1102 }
1103 entry
1104 })
1105 .collect();
1106
1107 let has_more = start_idx + max_results < secrets.len();
1108 let mut response = json!({
1109 "SecretList": page,
1110 });
1111 if has_more {
1112 if let Some(next) = secrets.get(start_idx + max_results) {
1113 response["NextToken"] = json!(next.name);
1114 }
1115 }
1116
1117 Ok(AwsResponse::ok_json(response))
1118 }
1119
1120 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1121 let body = req.json_body();
1122 let secret_id = require_secret_id(&body)?;
1123
1124 let new_tags = parse_tags(&body["Tags"]);
1125
1126 let mut accounts = self.state.write();
1127 let state = accounts.get_or_create(&req.account_id);
1128 let secret = self.find_secret_mut(state, &secret_id)?;
1129
1130 if !new_tags.is_empty() {
1131 secret.tags_ever_set = true;
1132 }
1133 for (k, v) in new_tags {
1134 if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
1136 existing.1 = v;
1137 } else {
1138 secret.tags.push((k, v));
1139 }
1140 }
1141
1142 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1143 }
1144
1145 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1146 let body = req.json_body();
1147 let secret_id = require_secret_id(&body)?;
1148
1149 let tag_keys: Vec<String> = body["TagKeys"]
1150 .as_array()
1151 .map(|arr| {
1152 arr.iter()
1153 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1154 .collect()
1155 })
1156 .unwrap_or_default();
1157
1158 let mut accounts = self.state.write();
1159 let state = accounts.get_or_create(&req.account_id);
1160 let secret = self.find_secret_mut(state, &secret_id)?;
1161
1162 secret.tags.retain(|(k, _)| !tag_keys.contains(k));
1163
1164 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1165 }
1166
1167 fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1168 let body = req.json_body();
1169 let secret_id = require_secret_id(&body)?;
1170 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1171 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
1172 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
1173 let next_token = body["NextToken"].as_str();
1174
1175 let accounts = self.state.read();
1176 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1177 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1178 let secret = self.find_secret_ref(state, &secret_id)?;
1179
1180 let mut versions: Vec<&_> = secret.versions.values().collect();
1183 versions.sort_by(|a, b| {
1184 b.created_at
1185 .cmp(&a.created_at)
1186 .then_with(|| a.version_id.cmp(&b.version_id))
1187 });
1188
1189 let start_idx = if let Some(token) = next_token {
1190 versions
1191 .iter()
1192 .position(|v| v.version_id == token)
1193 .unwrap_or(versions.len())
1194 } else {
1195 0
1196 };
1197
1198 let page: Vec<Value> = versions
1199 .iter()
1200 .skip(start_idx)
1201 .take(max_results)
1202 .map(|v| {
1203 json!({
1204 "VersionId": v.version_id,
1205 "VersionStages": v.stages,
1206 "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
1207 })
1208 })
1209 .collect();
1210
1211 let mut response = json!({
1212 "ARN": secret.arn,
1213 "Name": secret.name,
1214 "Versions": page,
1215 });
1216 if start_idx + max_results < versions.len() {
1217 if let Some(next) = versions.get(start_idx + max_results) {
1218 response["NextToken"] = json!(next.version_id);
1219 }
1220 }
1221
1222 Ok(AwsResponse::ok_json(response))
1223 }
1224
1225 fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1226 let body = req.json_body();
1227 let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
1228
1229 if length < 4 {
1230 return Err(AwsServiceError::aws_error(
1231 StatusCode::BAD_REQUEST,
1232 "InvalidParameterException",
1233 "InvalidParameterException",
1234 ));
1235 }
1236 if length > 4096 {
1237 return Err(AwsServiceError::aws_error(
1238 StatusCode::BAD_REQUEST,
1239 "InvalidParameterValue",
1240 "InvalidParameterValue",
1241 ));
1242 }
1243
1244 let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
1245 let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
1246 let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
1247 let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
1248 let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
1249 let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
1250 validate_optional_string_length(
1251 "excludeCharacters",
1252 body["ExcludeCharacters"].as_str(),
1253 0,
1254 4096,
1255 )?;
1256 let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
1257
1258 let lowercase = "abcdefghijklmnopqrstuvwxyz";
1259 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1260 let digits = "0123456789";
1261 let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
1262
1263 let mut char_pool = String::new();
1264 let mut required_chars: Vec<String> = Vec::new();
1265
1266 if !exclude_lowercase {
1267 let filtered: String = lowercase
1268 .chars()
1269 .filter(|c| !exclude_chars.contains(*c))
1270 .collect();
1271 if !filtered.is_empty() {
1272 required_chars.push(filtered.clone());
1273 char_pool.push_str(&filtered);
1274 }
1275 }
1276 if !exclude_uppercase {
1277 let filtered: String = uppercase
1278 .chars()
1279 .filter(|c| !exclude_chars.contains(*c))
1280 .collect();
1281 if !filtered.is_empty() {
1282 required_chars.push(filtered.clone());
1283 char_pool.push_str(&filtered);
1284 }
1285 }
1286 if !exclude_numbers {
1287 let filtered: String = digits
1288 .chars()
1289 .filter(|c| !exclude_chars.contains(*c))
1290 .collect();
1291 if !filtered.is_empty() {
1292 required_chars.push(filtered.clone());
1293 char_pool.push_str(&filtered);
1294 }
1295 }
1296 if !exclude_punctuation {
1297 let filtered: String = punctuation
1298 .chars()
1299 .filter(|c| !exclude_chars.contains(*c))
1300 .collect();
1301 if !filtered.is_empty() {
1302 required_chars.push(filtered.clone());
1303 char_pool.push_str(&filtered);
1304 }
1305 }
1306 if include_space && !exclude_chars.contains(' ') {
1307 char_pool.push(' ');
1308 }
1309
1310 if char_pool.is_empty() {
1311 return Err(AwsServiceError::aws_error(
1312 StatusCode::BAD_REQUEST,
1313 "InvalidParameterException",
1314 "InvalidParameterException",
1315 ));
1316 }
1317
1318 let pool_bytes: Vec<char> = char_pool.chars().collect();
1319 let mut password = String::with_capacity(length);
1320
1321 if require_each {
1323 for category in &required_chars {
1325 let chars: Vec<char> = category.chars().collect();
1326 let idx = simple_random() % chars.len();
1327 password.push(chars[idx]);
1328 }
1329 if include_space && !exclude_chars.contains(' ') {
1330 password.push(' ');
1331 }
1332 }
1333
1334 while password.len() < length {
1336 let idx = simple_random() % pool_bytes.len();
1337 password.push(pool_bytes[idx]);
1338 }
1339
1340 let mut chars: Vec<char> = password.chars().collect();
1342 for i in (1..chars.len()).rev() {
1343 let j = simple_random() % (i + 1);
1344 chars.swap(i, j);
1345 }
1346 let password: String = chars.into_iter().take(length).collect();
1347
1348 let response = json!({
1349 "RandomPassword": password,
1350 });
1351
1352 Ok(AwsResponse::ok_json(response))
1353 }
1354
1355 fn rotate_secret(
1356 &self,
1357 req: &AwsRequest,
1358 ) -> Result<(AwsResponse, Option<RotationInvocation>), AwsServiceError> {
1359 let body = req.json_body();
1360 let secret_id = require_secret_id(&body)?;
1361
1362 if let Some(token) = body["ClientRequestToken"].as_str() {
1364 if token.len() < 32 || token.len() > 64 {
1365 return Err(AwsServiceError::aws_error(
1366 StatusCode::BAD_REQUEST,
1367 "InvalidParameterException",
1368 "ClientRequestToken must be 32-64 characters long.",
1369 ));
1370 }
1371 }
1372
1373 if let Some(arn) = body["RotationLambdaARN"].as_str() {
1375 if arn.len() > 2048 {
1376 return Err(AwsServiceError::aws_error(
1377 StatusCode::BAD_REQUEST,
1378 "InvalidParameterException",
1379 "RotationLambdaARN length must be less than or equal to 2048.",
1380 ));
1381 }
1382 }
1383
1384 if let Some(rules) = body["RotationRules"].as_object() {
1386 if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1387 if !(1..=1000).contains(&days) {
1388 return Err(AwsServiceError::aws_error(
1389 StatusCode::BAD_REQUEST,
1390 "InvalidParameterException",
1391 "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1392 ));
1393 }
1394 }
1395 }
1396
1397 let mut accounts = self.state.write();
1398 let state = accounts.get_or_create(&req.account_id);
1399 let secret = self.find_secret_mut(state, &secret_id)?;
1400
1401 if secret.deleted {
1402 return Err(AwsServiceError::aws_error(
1403 StatusCode::BAD_REQUEST,
1404 "InvalidRequestException",
1405 "You can't perform this operation on the secret because it was marked for deletion.",
1406 ));
1407 }
1408
1409 if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1411 secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1412 }
1413
1414 if let Some(rules) = body["RotationRules"].as_object() {
1415 let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1416 secret.rotation_rules = Some(RotationRules {
1417 automatically_after_days: days,
1418 duration: rules
1419 .get("Duration")
1420 .and_then(|v| v.as_str())
1421 .map(String::from),
1422 schedule_expression: rules
1423 .get("ScheduleExpression")
1424 .and_then(|v| v.as_str())
1425 .map(String::from),
1426 });
1427 }
1428
1429 secret.rotation_enabled = Some(true);
1430 let now = Utc::now();
1431 secret.last_changed_at = now;
1432
1433 let rotate_immediately = body["RotateImmediately"].as_bool().unwrap_or(true);
1439
1440 let version_id = body["ClientRequestToken"]
1441 .as_str()
1442 .map(|s| s.to_string())
1443 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1444
1445 let has_lambda =
1446 body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1447 let lambda_arn = secret.rotation_lambda_arn.clone();
1448
1449 let mut invocation = None;
1458 if rotate_immediately {
1459 secret.last_rotated_at = Some(now);
1460 if let Some(current_vid) = secret.current_version_id.clone() {
1461 let current_value = secret.versions.get(¤t_vid).cloned();
1462
1463 if let Some(cv) = current_value {
1464 if has_lambda {
1465 if let Some(ref arn) = lambda_arn {
1472 invocation = Some(RotationInvocation {
1473 lambda_arn: arn.clone(),
1474 secret_id: secret.arn.clone(),
1475 client_request_token: version_id.clone(),
1476 steps: vec![
1477 "createSecret",
1478 "setSecret",
1479 "testSecret",
1480 "finishSecret",
1481 ],
1482 cleanup_pending: None,
1483 });
1484 }
1485 } else {
1486 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1489 old_v.stages.retain(|s| s != "AWSCURRENT");
1490 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1491 old_v.stages.push("AWSPREVIOUS".to_string());
1492 }
1493 }
1494 let version = SecretVersion {
1495 version_id: version_id.clone(),
1496 secret_string: cv.secret_string.clone(),
1497 secret_binary: cv.secret_binary.clone(),
1498 stages: vec!["AWSCURRENT".to_string()],
1499 created_at: now,
1500 };
1501 secret.versions.insert(version_id.clone(), version);
1502 secret.current_version_id = Some(version_id.clone());
1503 }
1504 }
1505 }
1506 } else if has_lambda {
1507 if let (Some(arn), Some(current_vid)) =
1515 (lambda_arn.as_ref(), secret.current_version_id.clone())
1516 {
1517 if let Some(cv) = secret.versions.get(¤t_vid).cloned() {
1518 secret.versions.insert(
1519 version_id.clone(),
1520 SecretVersion {
1521 version_id: version_id.clone(),
1522 secret_string: cv.secret_string.clone(),
1523 secret_binary: cv.secret_binary.clone(),
1524 stages: vec!["AWSPENDING".to_string()],
1525 created_at: now,
1526 },
1527 );
1528 invocation = Some(RotationInvocation {
1529 lambda_arn: arn.clone(),
1530 secret_id: secret.arn.clone(),
1531 client_request_token: version_id.clone(),
1532 steps: vec!["testSecret"],
1533 cleanup_pending: Some(PendingCleanup {
1534 account_id: req.account_id.clone(),
1535 secret_name: secret.name.clone(),
1536 version_id: version_id.clone(),
1537 }),
1538 });
1539 }
1540 }
1541 }
1542
1543 let response = json!({
1544 "ARN": secret.arn,
1545 "Name": secret.name,
1546 "VersionId": version_id,
1547 });
1548
1549 Ok((AwsResponse::ok_json(response), invocation))
1550 }
1551
1552 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1553 let body = req.json_body();
1554 let secret_id = require_secret_id(&body)?;
1555
1556 let mut accounts = self.state.write();
1557 let state = accounts.get_or_create(&req.account_id);
1558 let secret = self.find_secret_mut(state, &secret_id)?;
1559
1560 if secret.deleted {
1561 return Err(AwsServiceError::aws_error(
1562 StatusCode::BAD_REQUEST,
1563 "InvalidRequestException",
1564 "You can't perform this operation on the secret because it was marked for deletion.",
1565 ));
1566 }
1567
1568 if secret.rotation_enabled != Some(true) {
1569 return Err(AwsServiceError::aws_error(
1570 StatusCode::BAD_REQUEST,
1571 "InvalidRequestException",
1572 "You can't cancel rotation for a secret that does not have rotation enabled.",
1573 ));
1574 }
1575
1576 secret.rotation_enabled = Some(false);
1577
1578 let response = json!({
1579 "ARN": secret.arn,
1580 "Name": secret.name,
1581 });
1582
1583 Ok(AwsResponse::ok_json(response))
1584 }
1585
1586 fn update_secret_version_stage(
1587 &self,
1588 req: &AwsRequest,
1589 ) -> Result<AwsResponse, AwsServiceError> {
1590 let body = req.json_body();
1591 let secret_id = require_secret_id(&body)?;
1592 let version_stage = body["VersionStage"]
1593 .as_str()
1594 .ok_or_else(|| {
1595 AwsServiceError::aws_error(
1596 StatusCode::BAD_REQUEST,
1597 "InvalidParameterException",
1598 "VersionStage is required",
1599 )
1600 })?
1601 .to_string();
1602 validate_string_length("versionStage", &version_stage, 1, 256)?;
1603 validate_optional_string_length(
1604 "removeFromVersionId",
1605 body["RemoveFromVersionId"].as_str(),
1606 32,
1607 64,
1608 )?;
1609 validate_optional_string_length(
1610 "moveToVersionId",
1611 body["MoveToVersionId"].as_str(),
1612 32,
1613 64,
1614 )?;
1615
1616 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1617 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1618
1619 let mut accounts = self.state.write();
1620 let state = accounts.get_or_create(&req.account_id);
1621 let secret = self.find_secret_mut(state, &secret_id)?;
1622
1623 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1625 let current_holder = secret
1627 .versions
1628 .iter()
1629 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1630 .map(|(id, _)| id.clone());
1631
1632 if let Some(current_vid) = current_holder {
1633 return Err(AwsServiceError::aws_error(
1634 StatusCode::BAD_REQUEST,
1635 "InvalidParameterException",
1636 format!(
1637 "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."
1638 ),
1639 ));
1640 }
1641 }
1642
1643 if let Some(ref remove_vid) = remove_from {
1645 if let Some(version) = secret.versions.get_mut(remove_vid) {
1646 version.stages.retain(|s| s != &version_stage);
1647 if version_stage == "AWSCURRENT" {
1649 for (id, v) in secret.versions.iter_mut() {
1651 if id != remove_vid {
1652 v.stages.retain(|s| s != "AWSPREVIOUS");
1653 }
1654 }
1655 if let Some(v) = secret.versions.get_mut(remove_vid) {
1657 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1658 v.stages.push("AWSPREVIOUS".to_string());
1659 }
1660 }
1661 }
1662 }
1663 }
1664
1665 if let Some(ref move_vid) = move_to {
1667 if let Some(version) = secret.versions.get_mut(move_vid) {
1668 if !version.stages.contains(&version_stage) {
1669 version.stages.push(version_stage.clone());
1670 }
1671 }
1672 if version_stage == "AWSCURRENT" {
1674 secret.current_version_id = Some(move_vid.clone());
1675 }
1676 }
1677
1678 let response = json!({
1679 "ARN": secret.arn,
1680 "Name": secret.name,
1681 });
1682
1683 Ok(AwsResponse::ok_json(response))
1684 }
1685
1686 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1687 let body = req.json_body();
1688 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1689 let secret_id_list = body["SecretIdList"].as_array();
1690 let filters = body["Filters"].as_array();
1691 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1692
1693 if secret_id_list.is_some() && filters.is_some() {
1695 return Err(AwsServiceError::aws_error(
1696 StatusCode::BAD_REQUEST,
1697 "InvalidParameterException",
1698 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1699 ));
1700 }
1701
1702 if max_results.is_some() && filters.is_none() {
1704 return Err(AwsServiceError::aws_error(
1705 StatusCode::BAD_REQUEST,
1706 "InvalidParameterException",
1707 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1708 ));
1709 }
1710
1711 let accounts = self.state.read();
1712 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1713 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1714 let mut secret_values: Vec<Value> = Vec::new();
1715 let mut errors: Vec<Value> = Vec::new();
1716
1717 if let Some(id_list) = secret_id_list {
1718 for id_val in id_list {
1719 let sid = id_val.as_str().unwrap_or("");
1720 match self.find_secret_ref(state, sid) {
1721 Ok(secret) => {
1722 if secret.deleted {
1723 errors.push(json!({
1724 "SecretId": sid,
1725 "ErrorCode": "InvalidRequestException",
1726 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1727 }));
1728 } else if let Some(ref current_vid) = secret.current_version_id {
1729 if let Some(version) = secret.versions.get(current_vid) {
1730 let mut entry = json!({
1731 "ARN": secret.arn,
1732 "Name": secret.name,
1733 "VersionId": version.version_id,
1734 "VersionStages": version.stages,
1735 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1736 });
1737 if let Some(ref s) = version.secret_string {
1738 let plaintext = self
1743 .maybe_decrypt_secret_string(
1744 &req.account_id,
1745 &secret.arn,
1746 secret.kms_key_id.as_deref(),
1747 Some(s.as_str()),
1748 )
1749 .unwrap_or_else(|| s.clone());
1750 entry["SecretString"] = json!(plaintext);
1751 }
1752 if let Some(ref b) = version.secret_binary {
1753 entry["SecretBinary"] = json!(base64_encode(b));
1754 }
1755 secret_values.push(entry);
1756 } else {
1757 errors.push(json!({
1758 "SecretId": sid,
1759 "ErrorCode": "ResourceNotFoundException",
1760 "Message": "Secrets Manager can't find the specified secret.",
1761 }));
1762 }
1763 } else {
1764 errors.push(json!({
1765 "SecretId": sid,
1766 "ErrorCode": "ResourceNotFoundException",
1767 "Message": "Secrets Manager can't find the specified secret.",
1768 }));
1769 }
1770 }
1771 Err(_) => {
1772 errors.push(json!({
1773 "SecretId": sid,
1774 "ErrorCode": "ResourceNotFoundException",
1775 "Message": "Secrets Manager can't find the specified secret.",
1776 }));
1777 }
1778 }
1779 }
1780 } else if let Some(filters) = filters {
1781 let matching: Vec<&Secret> = state
1783 .secrets
1784 .values()
1785 .filter(|s| {
1786 if s.deleted {
1787 return false;
1788 }
1789 for filter in filters {
1790 let key = filter["Key"].as_str().unwrap_or("");
1791 let values: Vec<&str> = filter["Values"]
1792 .as_array()
1793 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1794 .unwrap_or_default();
1795 let matches = match key {
1796 "name" => filter_name(s, &values),
1797 "description" => filter_description(s, &values),
1798 "tag-key" => filter_tag_key(s, &values),
1799 "tag-value" => filter_tag_value(s, &values),
1800 "all" => filter_all(s, &values),
1801 _ => true,
1802 };
1803 if !matches {
1804 return false;
1805 }
1806 }
1807 true
1808 })
1809 .collect();
1810
1811 let limit = max_results.unwrap_or(100) as usize;
1812 let mut no_value_found = false;
1813 let mut matching = matching;
1814 matching.sort_by(|a, b| a.name.cmp(&b.name));
1815
1816 for secret in matching.iter().take(limit) {
1817 if let Some(ref current_vid) = secret.current_version_id {
1818 if let Some(version) = secret.versions.get(current_vid) {
1819 let mut entry = json!({
1820 "ARN": secret.arn,
1821 "Name": secret.name,
1822 "VersionId": version.version_id,
1823 "VersionStages": version.stages,
1824 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1825 });
1826 if let Some(ref s) = version.secret_string {
1827 let plaintext = self
1830 .maybe_decrypt_secret_string(
1831 &req.account_id,
1832 &secret.arn,
1833 secret.kms_key_id.as_deref(),
1834 Some(s.as_str()),
1835 )
1836 .unwrap_or_else(|| s.clone());
1837 entry["SecretString"] = json!(plaintext);
1838 }
1839 if let Some(ref b) = version.secret_binary {
1840 entry["SecretBinary"] = json!(base64_encode(b));
1841 }
1842 secret_values.push(entry);
1843 } else {
1844 no_value_found = true;
1845 }
1846 } else {
1847 no_value_found = true;
1848 }
1849 }
1850
1851 if no_value_found && secret_values.is_empty() {
1852 return Err(AwsServiceError::aws_error(
1853 StatusCode::NOT_FOUND,
1854 "ResourceNotFoundException",
1855 "Secrets Manager can't find the specified secret.",
1856 ));
1857 }
1858 }
1859
1860 let mut response = json!({
1861 "SecretValues": secret_values,
1862 "Errors": errors,
1863 });
1864
1865 if errors.is_empty() {
1867 response.as_object_mut().unwrap().remove("Errors");
1868 }
1869
1870 Ok(AwsResponse::ok_json(response))
1871 }
1872
1873 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1874 let body = req.json_body();
1875 let secret_id = require_secret_id(&body)?;
1876
1877 let accounts = self.state.read();
1878 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1879 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1880 let secret = self.find_secret_ref(state, &secret_id)?;
1881
1882 let mut response = json!({
1885 "ARN": secret.arn,
1886 "Name": secret.name,
1887 });
1888 if let Some(ref policy) = secret.resource_policy {
1889 response["ResourcePolicy"] = json!(policy);
1890 }
1891
1892 Ok(AwsResponse::ok_json(response))
1893 }
1894
1895 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1896 let body = req.json_body();
1897 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1898 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1899 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1900 AwsServiceError::aws_error(
1901 StatusCode::BAD_REQUEST,
1902 "InvalidParameterException",
1903 "ResourcePolicy must be a string",
1904 )
1905 })?;
1906 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1907
1908 if let Some(secret_id) = body["SecretId"].as_str() {
1910 let accounts = self.state.read();
1911 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1912 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1913 self.find_secret_key(state, secret_id)?;
1914 }
1915
1916 let response = json!({
1917 "PolicyValidationPassed": true,
1918 "ValidationErrors": [],
1919 });
1920 Ok(AwsResponse::ok_json(response))
1921 }
1922
1923 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1924 let body = req.json_body();
1925 let secret_id = require_secret_id(&body)?;
1926 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1927 validate_optional_string_length(
1928 "resourcePolicy",
1929 body["ResourcePolicy"].as_str(),
1930 1,
1931 20480,
1932 )?;
1933 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1934
1935 let mut accounts = self.state.write();
1936 let state = accounts.get_or_create(&req.account_id);
1937 let secret = self.find_secret_mut(state, &secret_id)?;
1938 secret.resource_policy = policy;
1939
1940 let response = json!({
1941 "ARN": secret.arn,
1942 "Name": secret.name,
1943 });
1944
1945 Ok(AwsResponse::ok_json(response))
1946 }
1947
1948 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1949 let body = req.json_body();
1950 let secret_id = require_secret_id(&body)?;
1951
1952 let mut accounts = self.state.write();
1953 let state = accounts.get_or_create(&req.account_id);
1954 let secret = self.find_secret_mut(state, &secret_id)?;
1955 secret.resource_policy = None;
1956
1957 let response = json!({
1958 "ARN": secret.arn,
1959 "Name": secret.name,
1960 });
1961
1962 Ok(AwsResponse::ok_json(response))
1963 }
1964
1965 fn replicate_secret_to_regions(
1966 &self,
1967 req: &AwsRequest,
1968 ) -> Result<AwsResponse, AwsServiceError> {
1969 let body = req.json_body();
1970 let secret_id = require_secret_id(&body)?;
1971 let add_regions: Vec<String> = body["AddReplicaRegions"]
1973 .as_array()
1974 .map(|arr| {
1975 arr.iter()
1976 .filter_map(|r| r["Region"].as_str().map(String::from))
1977 .collect()
1978 })
1979 .unwrap_or_default();
1980
1981 let mut accounts = self.state.write();
1982 let state = accounts.get_or_create(&req.account_id);
1983 let secret = self.find_secret_mut(state, &secret_id)?;
1984 for region in add_regions {
1985 if !secret.replica_regions.contains(®ion) {
1986 secret.replica_regions.push(region);
1987 }
1988 }
1989 let response = json!({
1990 "ARN": secret.arn,
1991 "ReplicationStatus": replication_status_json(&secret.replica_regions),
1992 });
1993 Ok(AwsResponse::ok_json(response))
1994 }
1995
1996 fn remove_regions_from_replication(
1997 &self,
1998 req: &AwsRequest,
1999 ) -> Result<AwsResponse, AwsServiceError> {
2000 let body = req.json_body();
2001 let secret_id = require_secret_id(&body)?;
2002 let remove_regions: Vec<String> = body["RemoveReplicaRegions"]
2003 .as_array()
2004 .map(|arr| {
2005 arr.iter()
2006 .filter_map(|r| r.as_str().map(String::from))
2007 .collect()
2008 })
2009 .unwrap_or_default();
2010
2011 let mut accounts = self.state.write();
2012 let state = accounts.get_or_create(&req.account_id);
2013 let secret = self.find_secret_mut(state, &secret_id)?;
2014 secret
2015 .replica_regions
2016 .retain(|r| !remove_regions.contains(r));
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 stop_replication_to_replica(
2025 &self,
2026 req: &AwsRequest,
2027 ) -> Result<AwsResponse, AwsServiceError> {
2028 let body = req.json_body();
2029 let secret_id = require_secret_id(&body)?;
2030
2031 let accounts = self.state.read();
2032 let empty = SecretsManagerState::new(&req.account_id, &req.region);
2033 let state = accounts.get(&req.account_id).unwrap_or(&empty);
2034 let secret = self.find_secret_ref(state, &secret_id)?;
2035
2036 let response = json!({
2037 "ARN": secret.arn,
2038 });
2039 Ok(AwsResponse::ok_json(response))
2040 }
2041
2042 fn find_secret_mut<'a>(
2044 &self,
2045 state: &'a mut crate::state::SecretsManagerState,
2046 secret_id: &str,
2047 ) -> Result<&'a mut Secret, AwsServiceError> {
2048 let key = self.find_secret_key(state, secret_id)?;
2049 Ok(state.secrets.get_mut(&key).unwrap())
2050 }
2051
2052 fn find_secret_key(
2053 &self,
2054 state: &crate::state::SecretsManagerState,
2055 secret_id: &str,
2056 ) -> Result<String, AwsServiceError> {
2057 if state.secrets.contains_key(secret_id) {
2058 return Ok(secret_id.to_string());
2059 }
2060
2061 for secret in state.secrets.values() {
2062 if secret.arn == secret_id {
2063 return Ok(secret.name.clone());
2064 }
2065 }
2066
2067 if secret_id.starts_with("arn:aws:secretsmanager:") {
2068 for secret in state.secrets.values() {
2069 if secret.arn.starts_with(secret_id) {
2070 return Ok(secret.name.clone());
2071 }
2072 }
2073 }
2074
2075 Err(AwsServiceError::aws_error(
2076 StatusCode::NOT_FOUND,
2077 "ResourceNotFoundException",
2078 "Secrets Manager can't find the specified secret.",
2079 ))
2080 }
2081
2082 fn find_secret_ref<'a>(
2084 &self,
2085 state: &'a crate::state::SecretsManagerState,
2086 secret_id: &str,
2087 ) -> Result<&'a Secret, AwsServiceError> {
2088 if let Some(secret) = state.secrets.get(secret_id) {
2089 return Ok(secret);
2090 }
2091
2092 for secret in state.secrets.values() {
2094 if secret.arn == secret_id {
2095 return Ok(secret);
2096 }
2097 }
2098
2099 if secret_id.starts_with("arn:aws:secretsmanager:") {
2101 for secret in state.secrets.values() {
2102 if secret.arn.starts_with(secret_id) {
2103 return Ok(secret);
2104 }
2105 }
2106 }
2107
2108 Err(AwsServiceError::aws_error(
2109 StatusCode::NOT_FOUND,
2110 "ResourceNotFoundException",
2111 "Secrets Manager can't find the specified secret.",
2112 ))
2113 }
2114}
2115
2116pub async fn save_secretsmanager_snapshot(
2123 state: &SharedSecretsManagerState,
2124 store: Option<Arc<dyn SnapshotStore>>,
2125 lock: &AsyncMutex<()>,
2126) {
2127 let Some(store) = store else {
2128 return;
2129 };
2130 let _guard = lock.lock().await;
2131 let snapshot = SecretsManagerSnapshot {
2132 schema_version: SECRETSMANAGER_SNAPSHOT_SCHEMA_VERSION,
2133 state: None,
2134 accounts: Some(state.read().clone()),
2135 };
2136 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
2137 let bytes = serde_json::to_vec(&snapshot)
2138 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
2139 store.save(&bytes)
2140 })
2141 .await;
2142 match join {
2143 Ok(Ok(())) => {}
2144 Ok(Err(err)) => tracing::error!(%err, "failed to write secretsmanager snapshot"),
2145 Err(err) => tracing::error!(%err, "secretsmanager snapshot task panicked"),
2146 }
2147}
2148
2149struct CreateSecretInput {
2151 name: String,
2152 client_request_token: Option<String>,
2153 description: Option<String>,
2154 kms_key_id: Option<String>,
2155 secret_string: Option<String>,
2156 secret_binary: Option<Vec<u8>>,
2157 tags: Vec<(String, String)>,
2158 add_replica_regions: Vec<String>,
2162}
2163
2164impl CreateSecretInput {
2165 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
2166 validate_required("Name", &body["Name"])?;
2167 let name = body["Name"]
2168 .as_str()
2169 .ok_or_else(|| {
2170 AwsServiceError::aws_error(
2171 StatusCode::BAD_REQUEST,
2172 "InvalidParameterException",
2173 "Name is required",
2174 )
2175 })?
2176 .to_string();
2177 validate_string_length("name", &name, 1, 512)?;
2178 validate_optional_string_length(
2179 "clientRequestToken",
2180 body["ClientRequestToken"].as_str(),
2181 32,
2182 64,
2183 )?;
2184 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
2185 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
2186 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
2187
2188 Ok(Self {
2189 name,
2190 client_request_token: body["ClientRequestToken"].as_str().map(|s| s.to_string()),
2191 description: body["Description"].as_str().map(|s| s.to_string()),
2192 kms_key_id: body["KmsKeyId"].as_str().map(|s| s.to_string()),
2193 secret_string: body["SecretString"].as_str().map(|s| s.to_string()),
2194 secret_binary: body["SecretBinary"].as_str().and_then(base64_decode),
2195 tags: parse_tags(&body["Tags"]),
2196 add_replica_regions: body["AddReplicaRegions"]
2197 .as_array()
2198 .map(|arr| {
2199 arr.iter()
2200 .filter_map(|r| r["Region"].as_str().map(String::from))
2201 .collect()
2202 })
2203 .unwrap_or_default(),
2204 })
2205 }
2206}
2207
2208#[async_trait]
2209impl AwsService for SecretsManagerService {
2210 fn service_name(&self) -> &str {
2211 "secretsmanager"
2212 }
2213
2214 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2215 let mutates = is_mutating_action(req.action.as_str());
2216 let result = match req.action.as_str() {
2217 "CreateSecret" => self.create_secret(&req),
2218 "GetSecretValue" => self.get_secret_value(&req),
2219 "PutSecretValue" => self.put_secret_value(&req),
2220 "UpdateSecret" => self.update_secret(&req),
2221 "DeleteSecret" => self.delete_secret(&req),
2222 "RestoreSecret" => self.restore_secret(&req),
2223 "DescribeSecret" => self.describe_secret(&req),
2224 "ListSecrets" => self.list_secrets(&req),
2225 "TagResource" => self.tag_resource(&req),
2226 "UntagResource" => self.untag_resource(&req),
2227 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
2228 "GetRandomPassword" => self.get_random_password(&req),
2229 "RotateSecret" => {
2230 let (response, invocation) = self.rotate_secret(&req)?;
2231 if let Some(inv) = invocation {
2232 if let Some(cleanup) = inv.cleanup_pending.as_ref() {
2233 if let Some(ref bus) = self.delivery_bus {
2240 run_rotation_steps(bus, &inv).await;
2241 }
2242 remove_rotation_test_pending(&self.state, cleanup);
2243 } else {
2244 let bus = self.delivery_bus.clone();
2248 tokio::spawn(async move {
2249 if let Some(bus) = bus {
2250 run_rotation_steps(&bus, &inv).await;
2251 }
2252 });
2253 }
2254 }
2255 Ok(response)
2256 }
2257 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2258 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2259 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2260 "GetResourcePolicy" => self.get_resource_policy(&req),
2261 "PutResourcePolicy" => self.put_resource_policy(&req),
2262 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2263 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2264 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2265 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2266 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2267 _ => Err(AwsServiceError::action_not_implemented(
2268 "secretsmanager",
2269 &req.action,
2270 )),
2271 };
2272 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
2273 self.save_snapshot().await;
2274 }
2275 result.map_err(remap_validation_error)
2276 }
2277
2278 fn supported_actions(&self) -> &[&str] {
2279 &[
2280 "CreateSecret",
2281 "GetSecretValue",
2282 "PutSecretValue",
2283 "UpdateSecret",
2284 "DeleteSecret",
2285 "RestoreSecret",
2286 "DescribeSecret",
2287 "ListSecrets",
2288 "TagResource",
2289 "UntagResource",
2290 "ListSecretVersionIds",
2291 "GetRandomPassword",
2292 "RotateSecret",
2293 "CancelRotateSecret",
2294 "UpdateSecretVersionStage",
2295 "BatchGetSecretValue",
2296 "GetResourcePolicy",
2297 "PutResourcePolicy",
2298 "DeleteResourcePolicy",
2299 "ValidateResourcePolicy",
2300 "ReplicateSecretToRegions",
2301 "RemoveRegionsFromReplication",
2302 "StopReplicationToReplica",
2303 ]
2304 }
2305}
2306
2307#[path = "service_helpers.rs"]
2308mod service_helpers;
2309pub(crate) use service_helpers::*;
2310
2311fn replication_status_json(regions: &[String]) -> Value {
2320 Value::Array(
2321 regions
2322 .iter()
2323 .map(|r| {
2324 json!({
2325 "Region": r,
2326 "Status": "InSync",
2327 "StatusMessage": "Replication succeeded",
2328 })
2329 })
2330 .collect(),
2331 )
2332}
2333
2334fn remap_validation_error(err: AwsServiceError) -> AwsServiceError {
2335 match err {
2336 AwsServiceError::AwsError {
2337 status,
2338 code,
2339 message,
2340 extra_fields,
2341 headers,
2342 } if code == "ValidationException" => AwsServiceError::AwsError {
2343 status,
2344 code: "InvalidParameterException".to_string(),
2345 message,
2346 extra_fields,
2347 headers,
2348 },
2349 other => other,
2350 }
2351}
2352
2353fn secret_owner_account(secret_id: &str, caller_account: &str) -> String {
2357 if !secret_id.starts_with("arn:aws:secretsmanager:") {
2358 return caller_account.to_string();
2359 }
2360 let parts: Vec<&str> = secret_id.splitn(7, ':').collect();
2361 if parts.len() < 5 {
2362 return caller_account.to_string();
2363 }
2364 let account = parts[4];
2365 if account.is_empty() {
2366 caller_account.to_string()
2367 } else {
2368 account.to_string()
2369 }
2370}
2371
2372fn resource_policy_allows(policy_doc: &str, caller_account: &str, secret_arn: &str) -> bool {
2377 if policy_doc.is_empty() {
2378 return false;
2379 }
2380 use fakecloud_core::auth::{Principal, PrincipalType};
2381 use fakecloud_iam::evaluator::{evaluate, EvalRequest, PolicyDocument};
2382 let doc = PolicyDocument::parse(policy_doc);
2383 let principal_arn = Arn::global("iam", caller_account, "root").to_string();
2384 let principal = Principal {
2385 arn: principal_arn.clone(),
2386 user_id: principal_arn.clone(),
2387 account_id: caller_account.to_string(),
2388 principal_type: PrincipalType::User,
2389 source_identity: None,
2390 tags: None,
2391 };
2392 let req = EvalRequest {
2393 principal: &principal,
2394 action: "secretsmanager:GetSecretValue".to_string(),
2395 resource: secret_arn.to_string(),
2396 context: Default::default(),
2397 };
2398 matches!(
2399 evaluate(&[doc], &req),
2400 fakecloud_iam::evaluator::Decision::Allow
2401 )
2402}
2403
2404#[cfg(test)]
2405#[path = "service_tests.rs"]
2406mod tests;