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