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