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