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