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 version_ids_to_stages: serde_json::Map<String, Value> =
1000 serde_json::Map::new();
1001 for (vid, version) in &s.versions {
1002 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
1003 }
1004 let mut entry = json!({
1005 "ARN": s.arn,
1006 "Name": s.name,
1007 "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
1008 "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
1009 "Description": s.description.clone().unwrap_or_default(),
1010 "SecretVersionsToStages": Value::Object(version_ids_to_stages),
1011 });
1012
1013 if s.tags_ever_set || !s.tags.is_empty() {
1014 entry["Tags"] = json!(tags_to_json(&s.tags));
1015 }
1016
1017 if let Some(ref kms) = s.kms_key_id {
1018 entry["KmsKeyId"] = json!(kms);
1019 }
1020 if s.deleted {
1021 entry["DeletedDate"] = json!(s
1022 .deletion_date
1023 .map(|d| d.timestamp_millis() as f64 / 1000.0));
1024 }
1025 entry
1026 })
1027 .collect();
1028
1029 let has_more = start_idx + max_results < secrets.len();
1030 let mut response = json!({
1031 "SecretList": page,
1032 });
1033 if has_more {
1034 if let Some(next) = secrets.get(start_idx + max_results) {
1035 response["NextToken"] = json!(next.name);
1036 }
1037 }
1038
1039 Ok(AwsResponse::ok_json(response))
1040 }
1041
1042 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1043 let body = req.json_body();
1044 let secret_id = require_secret_id(&body)?;
1045
1046 let new_tags = parse_tags(&body["Tags"]);
1047
1048 let mut accounts = self.state.write();
1049 let state = accounts.get_or_create(&req.account_id);
1050 let secret = self.find_secret_mut(state, &secret_id)?;
1051
1052 if !new_tags.is_empty() {
1053 secret.tags_ever_set = true;
1054 }
1055 for (k, v) in new_tags {
1056 if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
1058 existing.1 = v;
1059 } else {
1060 secret.tags.push((k, v));
1061 }
1062 }
1063
1064 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1065 }
1066
1067 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1068 let body = req.json_body();
1069 let secret_id = require_secret_id(&body)?;
1070
1071 let tag_keys: Vec<String> = body["TagKeys"]
1072 .as_array()
1073 .map(|arr| {
1074 arr.iter()
1075 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1076 .collect()
1077 })
1078 .unwrap_or_default();
1079
1080 let mut accounts = self.state.write();
1081 let state = accounts.get_or_create(&req.account_id);
1082 let secret = self.find_secret_mut(state, &secret_id)?;
1083
1084 secret.tags.retain(|(k, _)| !tag_keys.contains(k));
1085
1086 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1087 }
1088
1089 fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1090 let body = req.json_body();
1091 let secret_id = require_secret_id(&body)?;
1092 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1093
1094 let accounts = self.state.read();
1095 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1096 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1097 let secret = self.find_secret_ref(state, &secret_id)?;
1098
1099 let versions: Vec<Value> = secret
1100 .versions
1101 .values()
1102 .map(|v| {
1103 json!({
1104 "VersionId": v.version_id,
1105 "VersionStages": v.stages,
1106 "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
1107 })
1108 })
1109 .collect();
1110
1111 let response = json!({
1112 "ARN": secret.arn,
1113 "Name": secret.name,
1114 "Versions": versions,
1115 });
1116
1117 Ok(AwsResponse::ok_json(response))
1118 }
1119
1120 fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1121 let body = req.json_body();
1122 let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
1123
1124 if length < 4 {
1125 return Err(AwsServiceError::aws_error(
1126 StatusCode::BAD_REQUEST,
1127 "InvalidParameterException",
1128 "InvalidParameterException",
1129 ));
1130 }
1131 if length > 4096 {
1132 return Err(AwsServiceError::aws_error(
1133 StatusCode::BAD_REQUEST,
1134 "InvalidParameterValue",
1135 "InvalidParameterValue",
1136 ));
1137 }
1138
1139 let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
1140 let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
1141 let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
1142 let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
1143 let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
1144 let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
1145 validate_optional_string_length(
1146 "excludeCharacters",
1147 body["ExcludeCharacters"].as_str(),
1148 0,
1149 4096,
1150 )?;
1151 let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
1152
1153 let lowercase = "abcdefghijklmnopqrstuvwxyz";
1154 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1155 let digits = "0123456789";
1156 let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
1157
1158 let mut char_pool = String::new();
1159 let mut required_chars: Vec<String> = Vec::new();
1160
1161 if !exclude_lowercase {
1162 let filtered: String = lowercase
1163 .chars()
1164 .filter(|c| !exclude_chars.contains(*c))
1165 .collect();
1166 if !filtered.is_empty() {
1167 required_chars.push(filtered.clone());
1168 char_pool.push_str(&filtered);
1169 }
1170 }
1171 if !exclude_uppercase {
1172 let filtered: String = uppercase
1173 .chars()
1174 .filter(|c| !exclude_chars.contains(*c))
1175 .collect();
1176 if !filtered.is_empty() {
1177 required_chars.push(filtered.clone());
1178 char_pool.push_str(&filtered);
1179 }
1180 }
1181 if !exclude_numbers {
1182 let filtered: String = digits
1183 .chars()
1184 .filter(|c| !exclude_chars.contains(*c))
1185 .collect();
1186 if !filtered.is_empty() {
1187 required_chars.push(filtered.clone());
1188 char_pool.push_str(&filtered);
1189 }
1190 }
1191 if !exclude_punctuation {
1192 let filtered: String = punctuation
1193 .chars()
1194 .filter(|c| !exclude_chars.contains(*c))
1195 .collect();
1196 if !filtered.is_empty() {
1197 required_chars.push(filtered.clone());
1198 char_pool.push_str(&filtered);
1199 }
1200 }
1201 if include_space && !exclude_chars.contains(' ') {
1202 char_pool.push(' ');
1203 }
1204
1205 if char_pool.is_empty() {
1206 return Err(AwsServiceError::aws_error(
1207 StatusCode::BAD_REQUEST,
1208 "InvalidParameterException",
1209 "InvalidParameterException",
1210 ));
1211 }
1212
1213 let pool_bytes: Vec<char> = char_pool.chars().collect();
1214 let mut password = String::with_capacity(length);
1215
1216 if require_each {
1218 for category in &required_chars {
1220 let chars: Vec<char> = category.chars().collect();
1221 let idx = simple_random() % chars.len();
1222 password.push(chars[idx]);
1223 }
1224 if include_space && !exclude_chars.contains(' ') {
1225 password.push(' ');
1226 }
1227 }
1228
1229 while password.len() < length {
1231 let idx = simple_random() % pool_bytes.len();
1232 password.push(pool_bytes[idx]);
1233 }
1234
1235 let mut chars: Vec<char> = password.chars().collect();
1237 for i in (1..chars.len()).rev() {
1238 let j = simple_random() % (i + 1);
1239 chars.swap(i, j);
1240 }
1241 let password: String = chars.into_iter().take(length).collect();
1242
1243 let response = json!({
1244 "RandomPassword": password,
1245 });
1246
1247 Ok(AwsResponse::ok_json(response))
1248 }
1249
1250 fn rotate_secret(
1251 &self,
1252 req: &AwsRequest,
1253 ) -> Result<(AwsResponse, Option<RotationInvocation>), AwsServiceError> {
1254 let body = req.json_body();
1255 let secret_id = require_secret_id(&body)?;
1256
1257 if let Some(token) = body["ClientRequestToken"].as_str() {
1259 if token.len() < 32 || token.len() > 64 {
1260 return Err(AwsServiceError::aws_error(
1261 StatusCode::BAD_REQUEST,
1262 "InvalidParameterException",
1263 "ClientRequestToken must be 32-64 characters long.",
1264 ));
1265 }
1266 }
1267
1268 if let Some(arn) = body["RotationLambdaARN"].as_str() {
1270 if arn.len() > 2048 {
1271 return Err(AwsServiceError::aws_error(
1272 StatusCode::BAD_REQUEST,
1273 "InvalidParameterException",
1274 "RotationLambdaARN length must be less than or equal to 2048.",
1275 ));
1276 }
1277 }
1278
1279 if let Some(rules) = body["RotationRules"].as_object() {
1281 if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1282 if !(1..=1000).contains(&days) {
1283 return Err(AwsServiceError::aws_error(
1284 StatusCode::BAD_REQUEST,
1285 "InvalidParameterException",
1286 "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1287 ));
1288 }
1289 }
1290 }
1291
1292 let mut accounts = self.state.write();
1293 let state = accounts.get_or_create(&req.account_id);
1294 let secret = self.find_secret_mut(state, &secret_id)?;
1295
1296 if secret.deleted {
1297 return Err(AwsServiceError::aws_error(
1298 StatusCode::BAD_REQUEST,
1299 "InvalidRequestException",
1300 "You can't perform this operation on the secret because it was marked for deletion.",
1301 ));
1302 }
1303
1304 if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1306 secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1307 }
1308
1309 if let Some(rules) = body["RotationRules"].as_object() {
1310 let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1311 secret.rotation_rules = Some(RotationRules {
1312 automatically_after_days: days,
1313 });
1314 }
1315
1316 secret.rotation_enabled = Some(true);
1317 let now = Utc::now();
1318 secret.last_rotated_at = Some(now);
1319 secret.last_changed_at = now;
1320
1321 let version_id = body["ClientRequestToken"]
1322 .as_str()
1323 .map(|s| s.to_string())
1324 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1325
1326 let has_lambda =
1327 body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1328 let lambda_arn = secret.rotation_lambda_arn.clone();
1329
1330 let mut invocation = None;
1332 if let Some(current_vid) = secret.current_version_id.clone() {
1333 let current_value = secret.versions.get(¤t_vid).cloned();
1334
1335 if let Some(cv) = current_value {
1336 if has_lambda {
1337 if let Some(ref arn) = lambda_arn {
1344 invocation = Some(RotationInvocation {
1345 lambda_arn: arn.clone(),
1346 secret_id: secret.arn.clone(),
1347 client_request_token: version_id.clone(),
1348 });
1349 }
1350 } else {
1351 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1354 old_v.stages.retain(|s| s != "AWSCURRENT");
1355 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1356 old_v.stages.push("AWSPREVIOUS".to_string());
1357 }
1358 }
1359 let version = SecretVersion {
1360 version_id: version_id.clone(),
1361 secret_string: cv.secret_string.clone(),
1362 secret_binary: cv.secret_binary.clone(),
1363 stages: vec!["AWSCURRENT".to_string()],
1364 created_at: now,
1365 };
1366 secret.versions.insert(version_id.clone(), version);
1367 secret.current_version_id = Some(version_id.clone());
1368 }
1369 }
1370 }
1371
1372 let response = json!({
1373 "ARN": secret.arn,
1374 "Name": secret.name,
1375 "VersionId": version_id,
1376 });
1377
1378 Ok((AwsResponse::ok_json(response), invocation))
1379 }
1380
1381 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1382 let body = req.json_body();
1383 let secret_id = require_secret_id(&body)?;
1384
1385 let mut accounts = self.state.write();
1386 let state = accounts.get_or_create(&req.account_id);
1387 let secret = self.find_secret_mut(state, &secret_id)?;
1388
1389 if secret.deleted {
1390 return Err(AwsServiceError::aws_error(
1391 StatusCode::BAD_REQUEST,
1392 "InvalidRequestException",
1393 "You can't perform this operation on the secret because it was marked for deletion.",
1394 ));
1395 }
1396
1397 if secret.rotation_enabled != Some(true) {
1398 return Err(AwsServiceError::aws_error(
1399 StatusCode::BAD_REQUEST,
1400 "InvalidRequestException",
1401 "You can't cancel rotation for a secret that does not have rotation enabled.",
1402 ));
1403 }
1404
1405 secret.rotation_enabled = Some(false);
1406
1407 let response = json!({
1408 "ARN": secret.arn,
1409 "Name": secret.name,
1410 });
1411
1412 Ok(AwsResponse::ok_json(response))
1413 }
1414
1415 fn update_secret_version_stage(
1416 &self,
1417 req: &AwsRequest,
1418 ) -> Result<AwsResponse, AwsServiceError> {
1419 let body = req.json_body();
1420 let secret_id = require_secret_id(&body)?;
1421 let version_stage = body["VersionStage"]
1422 .as_str()
1423 .ok_or_else(|| {
1424 AwsServiceError::aws_error(
1425 StatusCode::BAD_REQUEST,
1426 "InvalidParameterException",
1427 "VersionStage is required",
1428 )
1429 })?
1430 .to_string();
1431 validate_string_length("versionStage", &version_stage, 1, 256)?;
1432 validate_optional_string_length(
1433 "removeFromVersionId",
1434 body["RemoveFromVersionId"].as_str(),
1435 32,
1436 64,
1437 )?;
1438 validate_optional_string_length(
1439 "moveToVersionId",
1440 body["MoveToVersionId"].as_str(),
1441 32,
1442 64,
1443 )?;
1444
1445 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1446 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1447
1448 let mut accounts = self.state.write();
1449 let state = accounts.get_or_create(&req.account_id);
1450 let secret = self.find_secret_mut(state, &secret_id)?;
1451
1452 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1454 let current_holder = secret
1456 .versions
1457 .iter()
1458 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1459 .map(|(id, _)| id.clone());
1460
1461 if let Some(current_vid) = current_holder {
1462 return Err(AwsServiceError::aws_error(
1463 StatusCode::BAD_REQUEST,
1464 "InvalidParameterException",
1465 format!(
1466 "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."
1467 ),
1468 ));
1469 }
1470 }
1471
1472 if let Some(ref remove_vid) = remove_from {
1474 if let Some(version) = secret.versions.get_mut(remove_vid) {
1475 version.stages.retain(|s| s != &version_stage);
1476 if version_stage == "AWSCURRENT" {
1478 for (id, v) in secret.versions.iter_mut() {
1480 if id != remove_vid {
1481 v.stages.retain(|s| s != "AWSPREVIOUS");
1482 }
1483 }
1484 if let Some(v) = secret.versions.get_mut(remove_vid) {
1486 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1487 v.stages.push("AWSPREVIOUS".to_string());
1488 }
1489 }
1490 }
1491 }
1492 }
1493
1494 if let Some(ref move_vid) = move_to {
1496 if let Some(version) = secret.versions.get_mut(move_vid) {
1497 if !version.stages.contains(&version_stage) {
1498 version.stages.push(version_stage.clone());
1499 }
1500 }
1501 if version_stage == "AWSCURRENT" {
1503 secret.current_version_id = Some(move_vid.clone());
1504 }
1505 }
1506
1507 let response = json!({
1508 "ARN": secret.arn,
1509 "Name": secret.name,
1510 });
1511
1512 Ok(AwsResponse::ok_json(response))
1513 }
1514
1515 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1516 let body = req.json_body();
1517 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1518 let secret_id_list = body["SecretIdList"].as_array();
1519 let filters = body["Filters"].as_array();
1520 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1521
1522 if secret_id_list.is_some() && filters.is_some() {
1524 return Err(AwsServiceError::aws_error(
1525 StatusCode::BAD_REQUEST,
1526 "InvalidParameterException",
1527 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1528 ));
1529 }
1530
1531 if max_results.is_some() && filters.is_none() {
1533 return Err(AwsServiceError::aws_error(
1534 StatusCode::BAD_REQUEST,
1535 "InvalidParameterException",
1536 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1537 ));
1538 }
1539
1540 let accounts = self.state.read();
1541 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1542 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1543 let mut secret_values: Vec<Value> = Vec::new();
1544 let mut errors: Vec<Value> = Vec::new();
1545
1546 if let Some(id_list) = secret_id_list {
1547 for id_val in id_list {
1548 let sid = id_val.as_str().unwrap_or("");
1549 match self.find_secret_ref(state, sid) {
1550 Ok(secret) => {
1551 if secret.deleted {
1552 errors.push(json!({
1553 "SecretId": sid,
1554 "ErrorCode": "InvalidRequestException",
1555 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1556 }));
1557 } else if let Some(ref current_vid) = secret.current_version_id {
1558 if let Some(version) = secret.versions.get(current_vid) {
1559 let mut entry = json!({
1560 "ARN": secret.arn,
1561 "Name": secret.name,
1562 "VersionId": version.version_id,
1563 "VersionStages": version.stages,
1564 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1565 });
1566 if let Some(ref s) = version.secret_string {
1567 entry["SecretString"] = json!(s);
1568 }
1569 if let Some(ref b) = version.secret_binary {
1570 entry["SecretBinary"] = json!(base64_encode(b));
1571 }
1572 secret_values.push(entry);
1573 } else {
1574 errors.push(json!({
1575 "SecretId": sid,
1576 "ErrorCode": "ResourceNotFoundException",
1577 "Message": "Secrets Manager can't find the specified secret.",
1578 }));
1579 }
1580 } else {
1581 errors.push(json!({
1582 "SecretId": sid,
1583 "ErrorCode": "ResourceNotFoundException",
1584 "Message": "Secrets Manager can't find the specified secret.",
1585 }));
1586 }
1587 }
1588 Err(_) => {
1589 errors.push(json!({
1590 "SecretId": sid,
1591 "ErrorCode": "ResourceNotFoundException",
1592 "Message": "Secrets Manager can't find the specified secret.",
1593 }));
1594 }
1595 }
1596 }
1597 } else if let Some(filters) = filters {
1598 let matching: Vec<&Secret> = state
1600 .secrets
1601 .values()
1602 .filter(|s| {
1603 if s.deleted {
1604 return false;
1605 }
1606 for filter in filters {
1607 let key = filter["Key"].as_str().unwrap_or("");
1608 let values: Vec<&str> = filter["Values"]
1609 .as_array()
1610 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1611 .unwrap_or_default();
1612 let matches = match key {
1613 "name" => filter_name(s, &values),
1614 "description" => filter_description(s, &values),
1615 "tag-key" => filter_tag_key(s, &values),
1616 "tag-value" => filter_tag_value(s, &values),
1617 "all" => filter_all(s, &values),
1618 _ => true,
1619 };
1620 if !matches {
1621 return false;
1622 }
1623 }
1624 true
1625 })
1626 .collect();
1627
1628 let limit = max_results.unwrap_or(100) as usize;
1629 let mut no_value_found = false;
1630 let mut matching = matching;
1631 matching.sort_by(|a, b| a.name.cmp(&b.name));
1632
1633 for secret in matching.iter().take(limit) {
1634 if let Some(ref current_vid) = secret.current_version_id {
1635 if let Some(version) = secret.versions.get(current_vid) {
1636 let mut entry = json!({
1637 "ARN": secret.arn,
1638 "Name": secret.name,
1639 "VersionId": version.version_id,
1640 "VersionStages": version.stages,
1641 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1642 });
1643 if let Some(ref s) = version.secret_string {
1644 entry["SecretString"] = json!(s);
1645 }
1646 if let Some(ref b) = version.secret_binary {
1647 entry["SecretBinary"] = json!(base64_encode(b));
1648 }
1649 secret_values.push(entry);
1650 } else {
1651 no_value_found = true;
1652 }
1653 } else {
1654 no_value_found = true;
1655 }
1656 }
1657
1658 if no_value_found && secret_values.is_empty() {
1659 return Err(AwsServiceError::aws_error(
1660 StatusCode::NOT_FOUND,
1661 "ResourceNotFoundException",
1662 "Secrets Manager can't find the specified secret.",
1663 ));
1664 }
1665 }
1666
1667 let mut response = json!({
1668 "SecretValues": secret_values,
1669 "Errors": errors,
1670 });
1671
1672 if errors.is_empty() {
1674 response.as_object_mut().unwrap().remove("Errors");
1675 }
1676
1677 Ok(AwsResponse::ok_json(response))
1678 }
1679
1680 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1681 let body = req.json_body();
1682 let secret_id = require_secret_id(&body)?;
1683
1684 let accounts = self.state.read();
1685 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1686 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1687 let secret = self.find_secret_ref(state, &secret_id)?;
1688
1689 let mut response = json!({
1692 "ARN": secret.arn,
1693 "Name": secret.name,
1694 });
1695 if let Some(ref policy) = secret.resource_policy {
1696 response["ResourcePolicy"] = json!(policy);
1697 }
1698
1699 Ok(AwsResponse::ok_json(response))
1700 }
1701
1702 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1703 let body = req.json_body();
1704 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1705 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1706 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1707 AwsServiceError::aws_error(
1708 StatusCode::BAD_REQUEST,
1709 "InvalidParameterException",
1710 "ResourcePolicy must be a string",
1711 )
1712 })?;
1713 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1714
1715 if let Some(secret_id) = body["SecretId"].as_str() {
1717 let accounts = self.state.read();
1718 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1719 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1720 self.find_secret_key(state, secret_id)?;
1721 }
1722
1723 let response = json!({
1724 "PolicyValidationPassed": true,
1725 "ValidationErrors": [],
1726 });
1727 Ok(AwsResponse::ok_json(response))
1728 }
1729
1730 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1731 let body = req.json_body();
1732 let secret_id = require_secret_id(&body)?;
1733 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1734 validate_optional_string_length(
1735 "resourcePolicy",
1736 body["ResourcePolicy"].as_str(),
1737 1,
1738 20480,
1739 )?;
1740 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1741
1742 let mut accounts = self.state.write();
1743 let state = accounts.get_or_create(&req.account_id);
1744 let secret = self.find_secret_mut(state, &secret_id)?;
1745 secret.resource_policy = policy;
1746
1747 let response = json!({
1748 "ARN": secret.arn,
1749 "Name": secret.name,
1750 });
1751
1752 Ok(AwsResponse::ok_json(response))
1753 }
1754
1755 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1756 let body = req.json_body();
1757 let secret_id = require_secret_id(&body)?;
1758
1759 let mut accounts = self.state.write();
1760 let state = accounts.get_or_create(&req.account_id);
1761 let secret = self.find_secret_mut(state, &secret_id)?;
1762 secret.resource_policy = None;
1763
1764 let response = json!({
1765 "ARN": secret.arn,
1766 "Name": secret.name,
1767 });
1768
1769 Ok(AwsResponse::ok_json(response))
1770 }
1771
1772 fn replicate_secret_to_regions(
1773 &self,
1774 req: &AwsRequest,
1775 ) -> Result<AwsResponse, AwsServiceError> {
1776 let body = req.json_body();
1777 let secret_id = require_secret_id(&body)?;
1778
1779 let accounts = self.state.read();
1780 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1781 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1782 let secret = self.find_secret_ref(state, &secret_id)?;
1783
1784 let response = json!({
1785 "ARN": secret.arn,
1786 "ReplicationStatus": [],
1787 });
1788 Ok(AwsResponse::ok_json(response))
1789 }
1790
1791 fn remove_regions_from_replication(
1792 &self,
1793 req: &AwsRequest,
1794 ) -> Result<AwsResponse, AwsServiceError> {
1795 let body = req.json_body();
1796 let secret_id = require_secret_id(&body)?;
1797
1798 let accounts = self.state.read();
1799 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1800 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1801 let secret = self.find_secret_ref(state, &secret_id)?;
1802
1803 let response = json!({
1804 "ARN": secret.arn,
1805 "ReplicationStatus": [],
1806 });
1807 Ok(AwsResponse::ok_json(response))
1808 }
1809
1810 fn stop_replication_to_replica(
1811 &self,
1812 req: &AwsRequest,
1813 ) -> Result<AwsResponse, AwsServiceError> {
1814 let body = req.json_body();
1815 let secret_id = require_secret_id(&body)?;
1816
1817 let accounts = self.state.read();
1818 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1819 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1820 let secret = self.find_secret_ref(state, &secret_id)?;
1821
1822 let response = json!({
1823 "ARN": secret.arn,
1824 });
1825 Ok(AwsResponse::ok_json(response))
1826 }
1827
1828 fn find_secret_mut<'a>(
1830 &self,
1831 state: &'a mut crate::state::SecretsManagerState,
1832 secret_id: &str,
1833 ) -> Result<&'a mut Secret, AwsServiceError> {
1834 let key = self.find_secret_key(state, secret_id)?;
1835 Ok(state.secrets.get_mut(&key).unwrap())
1836 }
1837
1838 fn find_secret_key(
1839 &self,
1840 state: &crate::state::SecretsManagerState,
1841 secret_id: &str,
1842 ) -> Result<String, AwsServiceError> {
1843 if state.secrets.contains_key(secret_id) {
1844 return Ok(secret_id.to_string());
1845 }
1846
1847 for secret in state.secrets.values() {
1848 if secret.arn == secret_id {
1849 return Ok(secret.name.clone());
1850 }
1851 }
1852
1853 if secret_id.starts_with("arn:aws:secretsmanager:") {
1854 for secret in state.secrets.values() {
1855 if secret.arn.starts_with(secret_id) {
1856 return Ok(secret.name.clone());
1857 }
1858 }
1859 }
1860
1861 Err(AwsServiceError::aws_error(
1862 StatusCode::NOT_FOUND,
1863 "ResourceNotFoundException",
1864 "Secrets Manager can't find the specified secret.",
1865 ))
1866 }
1867
1868 fn find_secret_ref<'a>(
1870 &self,
1871 state: &'a crate::state::SecretsManagerState,
1872 secret_id: &str,
1873 ) -> Result<&'a Secret, AwsServiceError> {
1874 if let Some(secret) = state.secrets.get(secret_id) {
1875 return Ok(secret);
1876 }
1877
1878 for secret in state.secrets.values() {
1880 if secret.arn == secret_id {
1881 return Ok(secret);
1882 }
1883 }
1884
1885 if secret_id.starts_with("arn:aws:secretsmanager:") {
1887 for secret in state.secrets.values() {
1888 if secret.arn.starts_with(secret_id) {
1889 return Ok(secret);
1890 }
1891 }
1892 }
1893
1894 Err(AwsServiceError::aws_error(
1895 StatusCode::NOT_FOUND,
1896 "ResourceNotFoundException",
1897 "Secrets Manager can't find the specified secret.",
1898 ))
1899 }
1900}
1901
1902struct CreateSecretInput {
1904 name: String,
1905 client_request_token: Option<String>,
1906 description: Option<String>,
1907 kms_key_id: Option<String>,
1908 secret_string: Option<String>,
1909 secret_binary: Option<Vec<u8>>,
1910 tags: Vec<(String, String)>,
1911}
1912
1913impl CreateSecretInput {
1914 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
1915 validate_required("Name", &body["Name"])?;
1916 let name = body["Name"]
1917 .as_str()
1918 .ok_or_else(|| {
1919 AwsServiceError::aws_error(
1920 StatusCode::BAD_REQUEST,
1921 "InvalidParameterException",
1922 "Name is required",
1923 )
1924 })?
1925 .to_string();
1926 validate_string_length("name", &name, 1, 512)?;
1927 validate_optional_string_length(
1928 "clientRequestToken",
1929 body["ClientRequestToken"].as_str(),
1930 32,
1931 64,
1932 )?;
1933 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
1934 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
1935 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
1936
1937 Ok(Self {
1938 name,
1939 client_request_token: body["ClientRequestToken"].as_str().map(|s| s.to_string()),
1940 description: body["Description"].as_str().map(|s| s.to_string()),
1941 kms_key_id: body["KmsKeyId"].as_str().map(|s| s.to_string()),
1942 secret_string: body["SecretString"].as_str().map(|s| s.to_string()),
1943 secret_binary: body["SecretBinary"].as_str().and_then(base64_decode),
1944 tags: parse_tags(&body["Tags"]),
1945 })
1946 }
1947}
1948
1949#[async_trait]
1950impl AwsService for SecretsManagerService {
1951 fn service_name(&self) -> &str {
1952 "secretsmanager"
1953 }
1954
1955 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1956 let mutates = is_mutating_action(req.action.as_str());
1957 let result = match req.action.as_str() {
1958 "CreateSecret" => self.create_secret(&req),
1959 "GetSecretValue" => self.get_secret_value(&req),
1960 "PutSecretValue" => self.put_secret_value(&req),
1961 "UpdateSecret" => self.update_secret(&req),
1962 "DeleteSecret" => self.delete_secret(&req),
1963 "RestoreSecret" => self.restore_secret(&req),
1964 "DescribeSecret" => self.describe_secret(&req),
1965 "ListSecrets" => self.list_secrets(&req),
1966 "TagResource" => self.tag_resource(&req),
1967 "UntagResource" => self.untag_resource(&req),
1968 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
1969 "GetRandomPassword" => self.get_random_password(&req),
1970 "RotateSecret" => {
1971 let (response, invocation) = self.rotate_secret(&req)?;
1972 if let Some(inv) = invocation {
1973 if let Some(ref bus) = self.delivery_bus {
1974 let bus = bus.clone();
1975 tokio::spawn(async move {
1977 for step in &["createSecret", "setSecret", "testSecret", "finishSecret"]
1978 {
1979 let payload = serde_json::json!({
1980 "SecretId": inv.secret_id,
1981 "ClientRequestToken": inv.client_request_token,
1982 "Step": step,
1983 });
1984 let payload_str = payload.to_string();
1985 match bus.invoke_lambda(&inv.lambda_arn, &payload_str).await {
1986 Some(Ok(_)) => {}
1987 Some(Err(e)) => {
1988 tracing::warn!(
1989 step = step,
1990 error = %e,
1991 "rotation Lambda invocation failed"
1992 );
1993 }
1994 None => {
1995 tracing::warn!(
1996 lambda_arn = %inv.lambda_arn,
1997 step = step,
1998 "rotation Lambda delivery not configured; \
1999 Lambda invocation skipped"
2000 );
2001 break;
2002 }
2003 }
2004 }
2005 });
2006 }
2007 }
2008 Ok(response)
2009 }
2010 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2011 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2012 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2013 "GetResourcePolicy" => self.get_resource_policy(&req),
2014 "PutResourcePolicy" => self.put_resource_policy(&req),
2015 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2016 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2017 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2018 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2019 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2020 _ => Err(AwsServiceError::action_not_implemented(
2021 "secretsmanager",
2022 &req.action,
2023 )),
2024 };
2025 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
2026 self.save_snapshot().await;
2027 }
2028 result.map_err(remap_validation_error)
2029 }
2030
2031 fn supported_actions(&self) -> &[&str] {
2032 &[
2033 "CreateSecret",
2034 "GetSecretValue",
2035 "PutSecretValue",
2036 "UpdateSecret",
2037 "DeleteSecret",
2038 "RestoreSecret",
2039 "DescribeSecret",
2040 "ListSecrets",
2041 "TagResource",
2042 "UntagResource",
2043 "ListSecretVersionIds",
2044 "GetRandomPassword",
2045 "RotateSecret",
2046 "CancelRotateSecret",
2047 "UpdateSecretVersionStage",
2048 "BatchGetSecretValue",
2049 "GetResourcePolicy",
2050 "PutResourcePolicy",
2051 "DeleteResourcePolicy",
2052 "ValidateResourcePolicy",
2053 "ReplicateSecretToRegions",
2054 "RemoveRegionsFromReplication",
2055 "StopReplicationToReplica",
2056 ]
2057 }
2058}
2059
2060#[path = "service_helpers.rs"]
2061mod service_helpers;
2062pub(crate) use service_helpers::*;
2063
2064fn remap_validation_error(err: AwsServiceError) -> AwsServiceError {
2071 match err {
2072 AwsServiceError::AwsError {
2073 status,
2074 code,
2075 message,
2076 extra_fields,
2077 headers,
2078 } if code == "ValidationException" => AwsServiceError::AwsError {
2079 status,
2080 code: "InvalidParameterException".to_string(),
2081 message,
2082 extra_fields,
2083 headers,
2084 },
2085 other => other,
2086 }
2087}
2088
2089fn secret_owner_account(secret_id: &str, caller_account: &str) -> String {
2093 if !secret_id.starts_with("arn:aws:secretsmanager:") {
2094 return caller_account.to_string();
2095 }
2096 let parts: Vec<&str> = secret_id.splitn(7, ':').collect();
2097 if parts.len() < 5 {
2098 return caller_account.to_string();
2099 }
2100 let account = parts[4];
2101 if account.is_empty() {
2102 caller_account.to_string()
2103 } else {
2104 account.to_string()
2105 }
2106}
2107
2108fn resource_policy_allows(policy_doc: &str, caller_account: &str, secret_arn: &str) -> bool {
2113 if policy_doc.is_empty() {
2114 return false;
2115 }
2116 use fakecloud_core::auth::{Principal, PrincipalType};
2117 use fakecloud_iam::evaluator::{evaluate, EvalRequest, PolicyDocument};
2118 let doc = PolicyDocument::parse(policy_doc);
2119 let principal_arn = format!("arn:aws:iam::{caller_account}:root");
2120 let principal = Principal {
2121 arn: principal_arn.clone(),
2122 user_id: principal_arn.clone(),
2123 account_id: caller_account.to_string(),
2124 principal_type: PrincipalType::User,
2125 source_identity: None,
2126 tags: None,
2127 };
2128 let req = EvalRequest {
2129 principal: &principal,
2130 action: "secretsmanager:GetSecretValue".to_string(),
2131 resource: secret_arn.to_string(),
2132 context: Default::default(),
2133 };
2134 matches!(
2135 evaluate(&[doc], &req),
2136 fakecloud_iam::evaluator::Decision::Allow
2137 )
2138}
2139
2140#[cfg(test)]
2141#[path = "service_tests.rs"]
2142mod tests;