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