1use std::collections::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
28enum VersionIdempotency {
31 NotFound,
33 Match,
37 Conflict,
40}
41
42fn check_secret_version_idempotency(
51 versions: &HashMap<String, SecretVersion>,
52 version_id: &str,
53 existing_plaintext: Option<String>,
54 secret_string: &Option<String>,
55 secret_binary: &Option<Vec<u8>>,
56) -> VersionIdempotency {
57 let Some(existing) = versions.get(version_id) else {
58 return VersionIdempotency::NotFound;
59 };
60 if &existing_plaintext == secret_string && &existing.secret_binary == secret_binary {
61 VersionIdempotency::Match
62 } else {
63 VersionIdempotency::Conflict
64 }
65}
66
67fn is_mutating_action(action: &str) -> bool {
69 matches!(
70 action,
71 "CreateSecret"
72 | "PutSecretValue"
73 | "UpdateSecret"
74 | "DeleteSecret"
75 | "RestoreSecret"
76 | "TagResource"
77 | "UntagResource"
78 | "RotateSecret"
79 | "CancelRotateSecret"
80 | "UpdateSecretVersionStage"
81 | "PutResourcePolicy"
82 | "DeleteResourcePolicy"
83 | "ReplicateSecretToRegions"
84 | "RemoveRegionsFromReplication"
85 | "StopReplicationToReplica"
86 )
87}
88
89pub struct SecretsManagerService {
90 state: SharedSecretsManagerState,
91 delivery_bus: Option<Arc<DeliveryBus>>,
92 snapshot_store: Option<Arc<dyn SnapshotStore>>,
93 snapshot_lock: Arc<AsyncMutex<()>>,
94 kms_hook: Option<Arc<dyn fakecloud_core::delivery::KmsHook>>,
95}
96
97impl SecretsManagerService {
98 pub fn new(state: SharedSecretsManagerState) -> Self {
99 Self {
100 state,
101 delivery_bus: None,
102 snapshot_store: None,
103 snapshot_lock: Arc::new(AsyncMutex::new(())),
104 kms_hook: None,
105 }
106 }
107
108 pub fn with_delivery(mut self, delivery_bus: Arc<DeliveryBus>) -> Self {
109 self.delivery_bus = Some(delivery_bus);
110 self
111 }
112
113 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
114 self.snapshot_store = Some(store);
115 self
116 }
117
118 pub fn with_kms_hook(mut self, hook: Arc<dyn fakecloud_core::delivery::KmsHook>) -> Self {
119 self.kms_hook = Some(hook);
120 self
121 }
122
123 fn maybe_encrypt_secret_string(
124 &self,
125 account_id: &str,
126 region: &str,
127 secret_arn: &str,
128 kms_key_id: Option<&str>,
129 plaintext: Option<String>,
130 ) -> Option<String> {
131 let pt = plaintext?;
132 let (Some(hook), Some(key)) = (&self.kms_hook, kms_key_id) else {
133 return Some(pt);
134 };
135 let key = if key.is_empty() {
136 "aws/secretsmanager"
137 } else {
138 key
139 };
140 let mut ctx = HashMap::new();
141 ctx.insert(
142 "aws:secretsmanager:secretArn".to_string(),
143 secret_arn.to_string(),
144 );
145 match hook.encrypt(
146 account_id,
147 region,
148 key,
149 pt.as_bytes(),
150 "secretsmanager.amazonaws.com",
151 ctx,
152 ) {
153 Ok(ciphertext) => Some(ciphertext),
154 Err(err) => {
155 tracing::warn!(
156 secret_arn = %secret_arn,
157 error = %err,
158 "KMS encrypt failed for secret; storing plaintext"
159 );
160 Some(pt)
161 }
162 }
163 }
164
165 fn maybe_decrypt_secret_string(
166 &self,
167 account_id: &str,
168 secret_arn: &str,
169 kms_key_id: Option<&str>,
170 stored: Option<&str>,
171 ) -> Option<String> {
172 let stored = stored?;
173 let (Some(hook), Some(_)) = (&self.kms_hook, kms_key_id) else {
174 return Some(stored.to_string());
175 };
176 let mut ctx = HashMap::new();
177 ctx.insert(
178 "aws:secretsmanager:secretArn".to_string(),
179 secret_arn.to_string(),
180 );
181 match hook.decrypt(account_id, stored, "secretsmanager.amazonaws.com", ctx) {
182 Ok(bytes) => Some(String::from_utf8_lossy(&bytes).to_string()),
183 Err(_) => Some(stored.to_string()),
184 }
185 }
186
187 async fn save_snapshot(&self) {
191 let Some(store) = self.snapshot_store.clone() else {
192 return;
193 };
194 let _guard = self.snapshot_lock.lock().await;
195 let snapshot = SecretsManagerSnapshot {
196 schema_version: SECRETSMANAGER_SNAPSHOT_SCHEMA_VERSION,
197 state: None,
198 accounts: Some(self.state.read().clone()),
199 };
200 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
201 let bytes = serde_json::to_vec(&snapshot)
202 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
203 store.save(&bytes)
204 })
205 .await;
206 match join {
207 Ok(Ok(())) => {}
208 Ok(Err(err)) => tracing::error!(%err, "failed to write secretsmanager snapshot"),
209 Err(err) => tracing::error!(%err, "secretsmanager snapshot task panicked"),
210 }
211 }
212
213 fn create_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
214 let input = CreateSecretInput::from_body(&req.json_body())?;
215 let has_value = input.secret_string.is_some() || input.secret_binary.is_some();
216
217 let mut accounts = self.state.write();
218 let state = accounts.get_or_create(&req.account_id);
219
220 if let Some(existing) = state.secrets.get(&input.name) {
221 if let Some(ref token) = input.client_request_token {
222 let existing_plaintext = existing.versions.get(token).and_then(|v| {
223 self.maybe_decrypt_secret_string(
224 &req.account_id,
225 &existing.arn,
226 existing.kms_key_id.as_deref(),
227 v.secret_string.as_deref(),
228 )
229 });
230 match check_secret_version_idempotency(
231 &existing.versions,
232 token,
233 existing_plaintext,
234 &input.secret_string,
235 &input.secret_binary,
236 ) {
237 VersionIdempotency::Match => {
238 let mut response = json!({
239 "ARN": existing.arn,
240 "Name": existing.name,
241 "VersionId": token,
242 });
243 if !has_value {
244 response.as_object_mut().unwrap().remove("VersionId");
245 }
246 return Ok(AwsResponse::ok_json(response));
247 }
248 VersionIdempotency::Conflict => {
249 return Err(AwsServiceError::aws_error(
250 StatusCode::BAD_REQUEST,
251 "ResourceExistsException",
252 format!(
253 "You can't use ClientRequestToken {token} because that value is already in use for a version of secret {}.",
254 existing.arn
255 ),
256 ));
257 }
258 VersionIdempotency::NotFound => {}
259 }
260 }
261 return Err(AwsServiceError::aws_error(
262 StatusCode::BAD_REQUEST,
263 "ResourceExistsException",
264 format!(
265 "The operation failed because the secret {} already exists.",
266 input.name
267 ),
268 ));
269 }
270
271 let arn = format!(
272 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
273 req.region,
274 req.account_id,
275 input.name,
276 &uuid::Uuid::new_v4().to_string()[..6]
277 );
278
279 let now = Utc::now();
280
281 let (versions, current_version_id, version_id_for_response) = if has_value {
282 let vid = input
283 .client_request_token
284 .clone()
285 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
286 let stored_string = self.maybe_encrypt_secret_string(
287 &req.account_id,
288 &req.region,
289 &arn,
290 input.kms_key_id.as_deref(),
291 input.secret_string,
292 );
293 let version = SecretVersion {
294 version_id: vid.clone(),
295 secret_string: stored_string,
296 secret_binary: input.secret_binary,
297 stages: vec!["AWSCURRENT".to_string()],
298 created_at: now,
299 };
300 let mut versions = std::collections::HashMap::new();
301 versions.insert(vid.clone(), version);
302 (versions, Some(vid.clone()), Some(vid))
303 } else {
304 (std::collections::HashMap::new(), None, None)
305 };
306
307 let tags_ever_set = !input.tags.is_empty();
308 let secret = Secret {
309 name: input.name.clone(),
310 arn: arn.clone(),
311 description: input.description,
312 kms_key_id: input.kms_key_id,
313 versions,
314 current_version_id,
315 tags: input.tags,
316 tags_ever_set,
317 deleted: false,
318 deletion_date: None,
319 created_at: now,
320 last_changed_at: now,
321 last_accessed_at: None,
322 rotation_enabled: None,
323 rotation_lambda_arn: None,
324 rotation_rules: None,
325 last_rotated_at: None,
326 resource_policy: None,
327 };
328
329 state.secrets.insert(input.name.clone(), secret);
330
331 let mut response = json!({
332 "ARN": arn,
333 "Name": input.name,
334 });
335 if let Some(vid) = version_id_for_response {
336 response["VersionId"] = json!(vid);
337 }
338
339 Ok(AwsResponse::ok_json(response))
340 }
341
342 fn get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
343 let body = req.json_body();
344 let secret_id = require_secret_id(&body)?;
345 validate_optional_string_length("versionId", body["VersionId"].as_str(), 32, 64)?;
346 validate_optional_string_length("versionStage", body["VersionStage"].as_str(), 1, 256)?;
347
348 let mut accounts = self.state.write();
349 let state = accounts.get_or_create(&req.account_id);
350 let secret = self.find_secret_mut(state, &secret_id)?;
351
352 if secret.deleted {
353 return Err(AwsServiceError::aws_error(
354 StatusCode::BAD_REQUEST,
355 "InvalidRequestException",
356 "You can't perform this operation on the secret because it was marked for deletion.",
357 ));
358 }
359
360 let requested_stage = body["VersionStage"].as_str().unwrap_or("AWSCURRENT");
361
362 let version_id = body["VersionId"]
364 .as_str()
365 .map(|s| s.to_string())
366 .or_else(|| {
367 secret
368 .versions
369 .iter()
370 .find(|(_, v)| v.stages.contains(&requested_stage.to_string()))
371 .map(|(id, _)| id.clone())
372 });
373
374 let version_id = match version_id {
375 Some(vid) => vid,
376 None => {
377 return Err(AwsServiceError::aws_error(
379 StatusCode::NOT_FOUND,
380 "ResourceNotFoundException",
381 format!(
382 "Secrets Manager can't find the specified secret value for staging label: {requested_stage}"
383 ),
384 ));
385 }
386 };
387
388 let version = secret.versions.get(&version_id).ok_or_else(|| {
389 AwsServiceError::aws_error(
390 StatusCode::NOT_FOUND,
391 "ResourceNotFoundException",
392 format!(
393 "Secrets Manager can't find the specified secret value for VersionId: {version_id}"
394 ),
395 )
396 })?;
397
398 if body["VersionId"].as_str().is_some() {
400 if let Some(stage) = body["VersionStage"].as_str() {
401 if !version.stages.contains(&stage.to_string()) {
402 return Err(AwsServiceError::aws_error(
403 StatusCode::NOT_FOUND,
404 "ResourceNotFoundException",
405 "You provided a VersionStage that is not associated to the provided VersionId.",
406 ));
407 }
408 }
409 }
410
411 secret.last_accessed_at = Some(Utc::now());
413
414 let mut response = json!({
415 "ARN": secret.arn,
416 "Name": secret.name,
417 "VersionId": version.version_id,
418 "VersionStages": version.stages,
419 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
420 });
421
422 let kms_for_decrypt = secret.kms_key_id.clone();
423 let arn_for_decrypt = secret.arn.clone();
424 if let Some(ref s) = version.secret_string {
425 let plaintext = self
426 .maybe_decrypt_secret_string(
427 &req.account_id,
428 &arn_for_decrypt,
429 kms_for_decrypt.as_deref(),
430 Some(s.as_str()),
431 )
432 .unwrap_or_else(|| s.clone());
433 response["SecretString"] = json!(plaintext);
434 }
435 if let Some(ref b) = version.secret_binary {
436 response["SecretBinary"] = json!(base64_encode(b));
437 }
438
439 Ok(AwsResponse::ok_json(response))
440 }
441
442 fn put_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
443 let body = req.json_body();
444 let secret_id = require_secret_id(&body)?;
445 validate_optional_string_length(
446 "clientRequestToken",
447 body["ClientRequestToken"].as_str(),
448 32,
449 64,
450 )?;
451 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
452
453 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
454 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
455
456 if secret_string.is_none() && secret_binary.is_none() {
458 return Err(AwsServiceError::aws_error(
459 StatusCode::BAD_REQUEST,
460 "InvalidRequestException",
461 "You must provide either SecretString or SecretBinary.",
462 ));
463 }
464
465 let mut accounts = self.state.write();
466 let state = accounts.get_or_create(&req.account_id);
467 let secret = match self.find_secret_mut(state, &secret_id) {
468 Ok(s) => s,
469 Err(_) => {
470 return Err(AwsServiceError::aws_error(
471 StatusCode::NOT_FOUND,
472 "ResourceNotFoundException",
473 "Secrets Manager can't find the specified secret.",
474 ));
475 }
476 };
477
478 if secret.deleted {
479 return Err(AwsServiceError::aws_error(
480 StatusCode::BAD_REQUEST,
481 "InvalidRequestException",
482 "You can't perform this operation on the secret because it was marked for deletion.",
483 ));
484 }
485
486 let now = Utc::now();
487 let version_id = body["ClientRequestToken"]
488 .as_str()
489 .map(|s| s.to_string())
490 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
491
492 let existing_plaintext = secret.versions.get(&version_id).and_then(|v| {
493 self.maybe_decrypt_secret_string(
494 &req.account_id,
495 &secret.arn,
496 secret.kms_key_id.as_deref(),
497 v.secret_string.as_deref(),
498 )
499 });
500 match check_secret_version_idempotency(
501 &secret.versions,
502 &version_id,
503 existing_plaintext,
504 &secret_string,
505 &secret_binary,
506 ) {
507 VersionIdempotency::Match => {
508 let existing_stages = secret.versions[&version_id].stages.clone();
509 return Ok(AwsResponse::ok_json(json!({
510 "ARN": secret.arn,
511 "Name": secret.name,
512 "VersionId": version_id,
513 "VersionStages": existing_stages,
514 })));
515 }
516 VersionIdempotency::Conflict => {
517 return Err(AwsServiceError::aws_error(
518 StatusCode::BAD_REQUEST,
519 "ResourceExistsException",
520 format!(
521 "You can't use ClientRequestToken {version_id} because that value is already in use for a version of secret {}.",
522 secret.arn
523 ),
524 ));
525 }
526 VersionIdempotency::NotFound => {}
527 }
528
529 let mut version_stages: Vec<String> = body["VersionStages"]
530 .as_array()
531 .map(|arr| {
532 arr.iter()
533 .filter_map(|v| v.as_str().map(|s| s.to_string()))
534 .collect()
535 })
536 .unwrap_or_else(|| vec!["AWSCURRENT".to_string()]);
537
538 let has_current = secret
540 .versions
541 .values()
542 .any(|v| v.stages.contains(&"AWSCURRENT".to_string()));
543 if !has_current && !version_stages.contains(&"AWSCURRENT".to_string()) {
544 version_stages.push("AWSCURRENT".to_string());
545 }
546
547 if version_stages.contains(&"AWSCURRENT".to_string()) {
549 if let Some(ref old_vid) = secret.current_version_id.clone() {
550 if let Some(old_version) = secret.versions.get_mut(old_vid) {
551 old_version.stages.retain(|s| s != "AWSCURRENT");
552 if !old_version.stages.contains(&"AWSPREVIOUS".to_string()) {
553 old_version.stages.push("AWSPREVIOUS".to_string());
554 }
555 }
556 for (id, v) in secret.versions.iter_mut() {
558 if id != old_vid {
559 v.stages.retain(|s| s != "AWSPREVIOUS");
560 }
561 }
562 }
563 secret.current_version_id = Some(version_id.clone());
564 }
565
566 for stage in &version_stages {
568 if stage == "AWSCURRENT" || stage == "AWSPREVIOUS" {
569 continue;
570 }
571 for v in secret.versions.values_mut() {
572 v.stages.retain(|s| s != stage);
573 }
574 }
575
576 secret.versions.retain(|_, v| !v.stages.is_empty());
578
579 let kms_key_for_enc = secret.kms_key_id.clone();
580 let arn_for_enc = secret.arn.clone();
581 let stored_secret_string = self.maybe_encrypt_secret_string(
582 &req.account_id,
583 &req.region,
584 &arn_for_enc,
585 kms_key_for_enc.as_deref(),
586 secret_string,
587 );
588 let version = SecretVersion {
589 version_id: version_id.clone(),
590 secret_string: stored_secret_string,
591 secret_binary,
592 stages: version_stages.clone(),
593 created_at: now,
594 };
595
596 secret.versions.insert(version_id.clone(), version);
597 secret.last_changed_at = now;
598
599 let response = json!({
600 "ARN": secret.arn,
601 "Name": secret.name,
602 "VersionId": version_id,
603 "VersionStages": version_stages,
604 });
605
606 Ok(AwsResponse::ok_json(response))
607 }
608
609 fn update_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
610 let body = req.json_body();
611 let secret_id = require_secret_id(&body)?;
612 validate_optional_string_length(
613 "clientRequestToken",
614 body["ClientRequestToken"].as_str(),
615 32,
616 64,
617 )?;
618 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
619 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
620 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
621
622 let mut accounts = self.state.write();
623 let state = accounts.get_or_create(&req.account_id);
624 let secret = match self.find_secret_mut(state, &secret_id) {
625 Ok(s) => s,
626 Err(_) => {
627 return Err(AwsServiceError::aws_error(
628 StatusCode::NOT_FOUND,
629 "ResourceNotFoundException",
630 "Secrets Manager can't find the specified secret.",
631 ));
632 }
633 };
634
635 if secret.deleted {
636 return Err(AwsServiceError::aws_error(
637 StatusCode::BAD_REQUEST,
638 "InvalidRequestException",
639 "You can't perform this operation on the secret because it was marked for deletion.",
640 ));
641 }
642
643 if let Some(desc) = body["Description"].as_str() {
644 secret.description = Some(desc.to_string());
645 }
646 if let Some(kms) = body["KmsKeyId"].as_str() {
647 secret.kms_key_id = Some(kms.to_string());
648 }
649
650 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
652 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
653
654 let version_id = if secret_string.is_some() || secret_binary.is_some() {
655 let vid = body["ClientRequestToken"]
656 .as_str()
657 .map(|s| s.to_string())
658 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
659
660 let existing_plaintext = secret.versions.get(&vid).and_then(|v| {
661 self.maybe_decrypt_secret_string(
662 &req.account_id,
663 &secret.arn,
664 secret.kms_key_id.as_deref(),
665 v.secret_string.as_deref(),
666 )
667 });
668 match check_secret_version_idempotency(
669 &secret.versions,
670 &vid,
671 existing_plaintext,
672 &secret_string,
673 &secret_binary,
674 ) {
675 VersionIdempotency::Match => {
676 return Ok(AwsResponse::ok_json(json!({
677 "ARN": secret.arn,
678 "Name": secret.name,
679 "VersionId": vid,
680 })));
681 }
682 VersionIdempotency::Conflict => {
683 return Err(AwsServiceError::aws_error(
684 StatusCode::BAD_REQUEST,
685 "ResourceExistsException",
686 format!(
687 "You can't use ClientRequestToken {vid} because that value is already in use for a version of secret {}.",
688 secret.arn
689 ),
690 ));
691 }
692 VersionIdempotency::NotFound => {}
693 }
694
695 let now = Utc::now();
696
697 if let Some(ref old_vid) = secret.current_version_id.clone() {
699 if let Some(old_v) = secret.versions.get_mut(old_vid) {
700 old_v.stages.retain(|s| s != "AWSCURRENT");
701 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
702 old_v.stages.push("AWSPREVIOUS".to_string());
703 }
704 }
705 }
706
707 let version = SecretVersion {
708 version_id: vid.clone(),
709 secret_string,
710 secret_binary,
711 stages: vec!["AWSCURRENT".to_string()],
712 created_at: now,
713 };
714 secret.versions.insert(vid.clone(), version);
715 secret.current_version_id = Some(vid.clone());
716 secret.last_changed_at = now;
717 Some(vid)
718 } else {
719 secret.last_changed_at = Utc::now();
720 None
721 };
722
723 let mut response = json!({
724 "ARN": secret.arn,
725 "Name": secret.name,
726 });
727 if let Some(vid) = version_id {
728 response["VersionId"] = json!(vid);
729 }
730
731 Ok(AwsResponse::ok_json(response))
732 }
733
734 fn delete_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
735 let body = req.json_body();
736 let secret_id = require_secret_id(&body)?;
737
738 let force_delete = body["ForceDeleteWithoutRecovery"]
739 .as_bool()
740 .unwrap_or(false);
741 let recovery_window = body.get("RecoveryWindowInDays").and_then(|v| v.as_i64());
742
743 if let Some(days) = recovery_window {
745 if !(7..=30).contains(&days) {
746 return Err(AwsServiceError::aws_error(
747 StatusCode::BAD_REQUEST,
748 "InvalidParameterException",
749 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: RecoveryWindowInDays value must be between 7 and 30 days (inclusive).",
750 ));
751 }
752 }
753
754 if force_delete && recovery_window.is_some() {
756 return Err(AwsServiceError::aws_error(
757 StatusCode::BAD_REQUEST,
758 "InvalidParameterException",
759 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: You can't use ForceDeleteWithoutRecovery in conjunction with RecoveryWindowInDays.",
760 ));
761 }
762
763 let mut accounts = self.state.write();
764 let state = accounts.get_or_create(&req.account_id);
765
766 if force_delete {
767 match self.find_secret_mut(state, &secret_id) {
769 Ok(secret) => {
770 let arn = secret.arn.clone();
771 let name = secret.name.clone();
772 let deletion_date = Utc::now();
773 state.secrets.remove(&name);
774 let response = json!({
775 "ARN": arn,
776 "Name": name,
777 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
778 });
779 return Ok(AwsResponse::ok_json(response));
780 }
781 Err(_) => {
782 let arn = format!(
784 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
785 req.region,
786 req.account_id,
787 secret_id,
788 &uuid::Uuid::new_v4().to_string()[..6]
789 );
790 let deletion_date = Utc::now();
791 let response = json!({
792 "ARN": arn,
793 "Name": secret_id,
794 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
795 });
796 return Ok(AwsResponse::ok_json(response));
797 }
798 }
799 }
800
801 let secret = self.find_secret_mut(state, &secret_id)?;
802
803 if secret.deleted {
804 return Err(AwsServiceError::aws_error(
805 StatusCode::BAD_REQUEST,
806 "InvalidRequestException",
807 "You can't perform this operation on the secret because it was already scheduled for deletion.",
808 ));
809 }
810
811 let now = Utc::now();
812 let days = recovery_window.unwrap_or(30);
813 let deletion_date = now + chrono::Duration::days(days);
814 secret.deleted = true;
815 secret.deletion_date = Some(deletion_date);
816
817 let response = json!({
818 "ARN": secret.arn,
819 "Name": secret.name,
820 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
821 });
822
823 Ok(AwsResponse::ok_json(response))
824 }
825
826 fn restore_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
827 let body = req.json_body();
828 let secret_id = require_secret_id(&body)?;
829
830 let mut accounts = self.state.write();
831 let state = accounts.get_or_create(&req.account_id);
832 let secret = self.find_secret_mut(state, &secret_id)?;
833
834 secret.deleted = false;
836 secret.deletion_date = None;
837
838 let response = json!({
839 "ARN": secret.arn,
840 "Name": secret.name,
841 });
842
843 Ok(AwsResponse::ok_json(response))
844 }
845
846 fn describe_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
847 let body = req.json_body();
848 let secret_id = require_secret_id(&body)?;
849
850 let accounts = self.state.read();
851 let empty = SecretsManagerState::new(&req.account_id, &req.region);
852 let state = accounts.get(&req.account_id).unwrap_or(&empty);
853 let secret = self.find_secret_ref(state, &secret_id)?;
854
855 let mut response = json!({
856 "ARN": secret.arn,
857 "Name": secret.name,
858 "CreatedDate": secret.created_at.timestamp_millis() as f64 / 1000.0,
859 "LastChangedDate": secret.last_changed_at.timestamp_millis() as f64 / 1000.0,
860 });
861
862 if !secret.versions.is_empty() {
863 let mut version_ids_to_stages: serde_json::Map<String, Value> = serde_json::Map::new();
864 for (vid, version) in &secret.versions {
865 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
866 }
867 response["VersionIdsToStages"] = Value::Object(version_ids_to_stages);
868 }
869
870 if let Some(ref desc) = secret.description {
871 if !desc.is_empty() {
872 response["Description"] = json!(desc);
873 }
874 }
875
876 if secret.tags_ever_set || !secret.tags.is_empty() {
877 response["Tags"] = json!(tags_to_json(&secret.tags));
878 }
879
880 if let Some(ref kms) = secret.kms_key_id {
881 response["KmsKeyId"] = json!(kms);
882 }
883 if secret.deleted {
884 response["DeletedDate"] = json!(secret
885 .deletion_date
886 .map(|d| d.timestamp_millis() as f64 / 1000.0));
887 }
888 if let Some(rotation_enabled) = secret.rotation_enabled {
889 response["RotationEnabled"] = json!(rotation_enabled);
890 }
891 if let Some(ref lambda_arn) = secret.rotation_lambda_arn {
892 response["RotationLambdaARN"] = json!(lambda_arn);
893 }
894 if let Some(ref rules) = secret.rotation_rules {
895 let mut rules_json = json!({});
896 if let Some(days) = rules.automatically_after_days {
897 rules_json["AutomaticallyAfterDays"] = json!(days);
898 }
899 response["RotationRules"] = rules_json;
900 }
901 if let Some(last_rotated) = secret.last_rotated_at {
902 response["LastRotatedDate"] = json!(last_rotated.timestamp_millis() as f64 / 1000.0);
903 }
904 if secret.rotation_enabled == Some(true) {
906 if let Some(ref rules) = secret.rotation_rules {
907 if let Some(days) = rules.automatically_after_days {
908 let base = secret.last_rotated_at.unwrap_or(secret.created_at);
909 let next = base + chrono::Duration::days(days);
910 response["NextRotationDate"] = json!(next.timestamp_millis() as f64 / 1000.0);
911 }
912 }
913 }
914
915 Ok(AwsResponse::ok_json(response))
916 }
917
918 fn list_secrets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
919 let body = req.json_body();
920 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
921 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
922 validate_optional_enum("sortBy", body["SortBy"].as_str(), &["name", "created-date"])?;
923 validate_optional_enum("sortOrder", body["SortOrder"].as_str(), &["asc", "desc"])?;
924 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
925 let next_token = body["NextToken"].as_str();
926 let filters = body["Filters"].as_array();
927 let include_deleted = body["IncludePlannedDeletion"].as_bool().unwrap_or(false);
928
929 if let Some(filters) = filters {
931 for filter in filters {
932 let key = filter["Key"].as_str().unwrap_or("");
933 let values = filter["Values"].as_array();
934
935 if key.is_empty() {
936 return Err(AwsServiceError::aws_error(
937 StatusCode::BAD_REQUEST,
938 "InvalidParameterException",
939 "Invalid filter key",
940 ));
941 }
942
943 let valid_keys = [
944 "all",
945 "name",
946 "tag-key",
947 "description",
948 "tag-value",
949 "owning-service",
950 "primary-region",
951 ];
952 if !valid_keys.contains(&key) {
953 return Err(AwsServiceError::aws_error(
954 StatusCode::BAD_REQUEST,
955 "ValidationException",
956 format!(
957 "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]",
958 key
959 ),
960 ));
961 }
962
963 if values.is_none() || values.unwrap().is_empty() {
964 return Err(AwsServiceError::aws_error(
965 StatusCode::BAD_REQUEST,
966 "InvalidParameterException",
967 format!("Invalid filter values for key: {key}"),
968 ));
969 }
970 }
971 }
972
973 let accounts = self.state.read();
974 let empty = SecretsManagerState::new(&req.account_id, &req.region);
975 let state = accounts.get(&req.account_id).unwrap_or(&empty);
976
977 let mut secrets: Vec<&Secret> = state
978 .secrets
979 .values()
980 .filter(|s| {
981 if s.deleted && !include_deleted {
983 return false;
984 }
985
986 if let Some(filters) = filters {
987 for filter in filters {
988 let key = filter["Key"].as_str().unwrap_or("");
989 let values: Vec<&str> = filter["Values"]
990 .as_array()
991 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
992 .unwrap_or_default();
993
994 let matches = match key {
995 "name" => filter_name(s, &values),
996 "description" => filter_description(s, &values),
997 "tag-key" => filter_tag_key(s, &values),
998 "tag-value" => filter_tag_value(s, &values),
999 "all" => filter_all(s, &values),
1000 "owning-service" => false,
1001 "primary-region" => false,
1002 _ => true,
1003 };
1004
1005 if !matches {
1006 return false;
1007 }
1008 }
1009 }
1010 true
1011 })
1012 .collect();
1013 secrets.sort_by_key(|a| a.created_at);
1014
1015 let start_idx = if let Some(token) = next_token {
1017 secrets.iter().position(|s| s.name == token).unwrap_or(0)
1018 } else {
1019 0
1020 };
1021
1022 let page: Vec<Value> = secrets
1023 .iter()
1024 .skip(start_idx)
1025 .take(max_results)
1026 .map(|s| {
1027 let mut entry = json!({
1028 "ARN": s.arn,
1029 "Name": s.name,
1030 "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
1031 "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
1032 });
1033
1034 if !s.versions.is_empty() {
1035 let mut version_ids_to_stages: serde_json::Map<String, Value> =
1036 serde_json::Map::new();
1037 for (vid, version) in &s.versions {
1038 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
1039 }
1040 entry["SecretVersionsToStages"] = Value::Object(version_ids_to_stages);
1041 }
1042
1043 if let Some(ref desc) = s.description {
1044 if !desc.is_empty() {
1045 entry["Description"] = json!(desc);
1046 }
1047 }
1048
1049 if s.tags_ever_set || !s.tags.is_empty() {
1050 entry["Tags"] = json!(tags_to_json(&s.tags));
1051 }
1052
1053 if let Some(ref kms) = s.kms_key_id {
1054 entry["KmsKeyId"] = json!(kms);
1055 }
1056 if s.deleted {
1057 entry["DeletedDate"] = json!(s
1058 .deletion_date
1059 .map(|d| d.timestamp_millis() as f64 / 1000.0));
1060 }
1061 entry
1062 })
1063 .collect();
1064
1065 let has_more = start_idx + max_results < secrets.len();
1066 let mut response = json!({
1067 "SecretList": page,
1068 });
1069 if has_more {
1070 if let Some(next) = secrets.get(start_idx + max_results) {
1071 response["NextToken"] = json!(next.name);
1072 }
1073 }
1074
1075 Ok(AwsResponse::ok_json(response))
1076 }
1077
1078 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1079 let body = req.json_body();
1080 let secret_id = require_secret_id(&body)?;
1081
1082 let new_tags = parse_tags(&body["Tags"]);
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 if !new_tags.is_empty() {
1089 secret.tags_ever_set = true;
1090 }
1091 for (k, v) in new_tags {
1092 if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
1094 existing.1 = v;
1095 } else {
1096 secret.tags.push((k, v));
1097 }
1098 }
1099
1100 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1101 }
1102
1103 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1104 let body = req.json_body();
1105 let secret_id = require_secret_id(&body)?;
1106
1107 let tag_keys: Vec<String> = body["TagKeys"]
1108 .as_array()
1109 .map(|arr| {
1110 arr.iter()
1111 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1112 .collect()
1113 })
1114 .unwrap_or_default();
1115
1116 let mut accounts = self.state.write();
1117 let state = accounts.get_or_create(&req.account_id);
1118 let secret = self.find_secret_mut(state, &secret_id)?;
1119
1120 secret.tags.retain(|(k, _)| !tag_keys.contains(k));
1121
1122 Ok(AwsResponse::json(StatusCode::OK, "{}"))
1123 }
1124
1125 fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1126 let body = req.json_body();
1127 let secret_id = require_secret_id(&body)?;
1128 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1129
1130 let accounts = self.state.read();
1131 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1132 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1133 let secret = self.find_secret_ref(state, &secret_id)?;
1134
1135 let versions: Vec<Value> = secret
1136 .versions
1137 .values()
1138 .map(|v| {
1139 json!({
1140 "VersionId": v.version_id,
1141 "VersionStages": v.stages,
1142 "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
1143 })
1144 })
1145 .collect();
1146
1147 let response = json!({
1148 "ARN": secret.arn,
1149 "Name": secret.name,
1150 "Versions": versions,
1151 });
1152
1153 Ok(AwsResponse::ok_json(response))
1154 }
1155
1156 fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1157 let body = req.json_body();
1158 let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
1159
1160 if length < 4 {
1161 return Err(AwsServiceError::aws_error(
1162 StatusCode::BAD_REQUEST,
1163 "InvalidParameterException",
1164 "InvalidParameterException",
1165 ));
1166 }
1167 if length > 4096 {
1168 return Err(AwsServiceError::aws_error(
1169 StatusCode::BAD_REQUEST,
1170 "InvalidParameterValue",
1171 "InvalidParameterValue",
1172 ));
1173 }
1174
1175 let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
1176 let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
1177 let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
1178 let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
1179 let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
1180 let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
1181 validate_optional_string_length(
1182 "excludeCharacters",
1183 body["ExcludeCharacters"].as_str(),
1184 0,
1185 4096,
1186 )?;
1187 let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
1188
1189 let lowercase = "abcdefghijklmnopqrstuvwxyz";
1190 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1191 let digits = "0123456789";
1192 let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
1193
1194 let mut char_pool = String::new();
1195 let mut required_chars: Vec<String> = Vec::new();
1196
1197 if !exclude_lowercase {
1198 let filtered: String = lowercase
1199 .chars()
1200 .filter(|c| !exclude_chars.contains(*c))
1201 .collect();
1202 if !filtered.is_empty() {
1203 required_chars.push(filtered.clone());
1204 char_pool.push_str(&filtered);
1205 }
1206 }
1207 if !exclude_uppercase {
1208 let filtered: String = uppercase
1209 .chars()
1210 .filter(|c| !exclude_chars.contains(*c))
1211 .collect();
1212 if !filtered.is_empty() {
1213 required_chars.push(filtered.clone());
1214 char_pool.push_str(&filtered);
1215 }
1216 }
1217 if !exclude_numbers {
1218 let filtered: String = digits
1219 .chars()
1220 .filter(|c| !exclude_chars.contains(*c))
1221 .collect();
1222 if !filtered.is_empty() {
1223 required_chars.push(filtered.clone());
1224 char_pool.push_str(&filtered);
1225 }
1226 }
1227 if !exclude_punctuation {
1228 let filtered: String = punctuation
1229 .chars()
1230 .filter(|c| !exclude_chars.contains(*c))
1231 .collect();
1232 if !filtered.is_empty() {
1233 required_chars.push(filtered.clone());
1234 char_pool.push_str(&filtered);
1235 }
1236 }
1237 if include_space && !exclude_chars.contains(' ') {
1238 char_pool.push(' ');
1239 }
1240
1241 if char_pool.is_empty() {
1242 return Err(AwsServiceError::aws_error(
1243 StatusCode::BAD_REQUEST,
1244 "InvalidParameterException",
1245 "InvalidParameterException",
1246 ));
1247 }
1248
1249 let pool_bytes: Vec<char> = char_pool.chars().collect();
1250 let mut password = String::with_capacity(length);
1251
1252 if require_each {
1254 for category in &required_chars {
1256 let chars: Vec<char> = category.chars().collect();
1257 let idx = simple_random() % chars.len();
1258 password.push(chars[idx]);
1259 }
1260 if include_space && !exclude_chars.contains(' ') {
1261 password.push(' ');
1262 }
1263 }
1264
1265 while password.len() < length {
1267 let idx = simple_random() % pool_bytes.len();
1268 password.push(pool_bytes[idx]);
1269 }
1270
1271 let mut chars: Vec<char> = password.chars().collect();
1273 for i in (1..chars.len()).rev() {
1274 let j = simple_random() % (i + 1);
1275 chars.swap(i, j);
1276 }
1277 let password: String = chars.into_iter().take(length).collect();
1278
1279 let response = json!({
1280 "RandomPassword": password,
1281 });
1282
1283 Ok(AwsResponse::ok_json(response))
1284 }
1285
1286 fn rotate_secret(
1287 &self,
1288 req: &AwsRequest,
1289 ) -> Result<(AwsResponse, Option<RotationInvocation>), AwsServiceError> {
1290 let body = req.json_body();
1291 let secret_id = require_secret_id(&body)?;
1292
1293 if let Some(token) = body["ClientRequestToken"].as_str() {
1295 if token.len() < 32 || token.len() > 64 {
1296 return Err(AwsServiceError::aws_error(
1297 StatusCode::BAD_REQUEST,
1298 "InvalidParameterException",
1299 "ClientRequestToken must be 32-64 characters long.",
1300 ));
1301 }
1302 }
1303
1304 if let Some(arn) = body["RotationLambdaARN"].as_str() {
1306 if arn.len() > 2048 {
1307 return Err(AwsServiceError::aws_error(
1308 StatusCode::BAD_REQUEST,
1309 "InvalidParameterException",
1310 "RotationLambdaARN length must be less than or equal to 2048.",
1311 ));
1312 }
1313 }
1314
1315 if let Some(rules) = body["RotationRules"].as_object() {
1317 if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1318 if !(1..=1000).contains(&days) {
1319 return Err(AwsServiceError::aws_error(
1320 StatusCode::BAD_REQUEST,
1321 "InvalidParameterException",
1322 "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1323 ));
1324 }
1325 }
1326 }
1327
1328 let mut accounts = self.state.write();
1329 let state = accounts.get_or_create(&req.account_id);
1330 let secret = self.find_secret_mut(state, &secret_id)?;
1331
1332 if secret.deleted {
1333 return Err(AwsServiceError::aws_error(
1334 StatusCode::BAD_REQUEST,
1335 "InvalidRequestException",
1336 "You can't perform this operation on the secret because it was marked for deletion.",
1337 ));
1338 }
1339
1340 if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1342 secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1343 }
1344
1345 if let Some(rules) = body["RotationRules"].as_object() {
1346 let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1347 secret.rotation_rules = Some(RotationRules {
1348 automatically_after_days: days,
1349 });
1350 }
1351
1352 secret.rotation_enabled = Some(true);
1353 let now = Utc::now();
1354 secret.last_rotated_at = Some(now);
1355 secret.last_changed_at = now;
1356
1357 let version_id = body["ClientRequestToken"]
1358 .as_str()
1359 .map(|s| s.to_string())
1360 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1361
1362 let has_lambda =
1363 body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1364 let lambda_arn = secret.rotation_lambda_arn.clone();
1365
1366 let mut invocation = None;
1368 if let Some(current_vid) = secret.current_version_id.clone() {
1369 let current_value = secret.versions.get(¤t_vid).cloned();
1370
1371 if let Some(cv) = current_value {
1372 if has_lambda {
1373 if let Some(ref arn) = lambda_arn {
1380 invocation = Some(RotationInvocation {
1381 lambda_arn: arn.clone(),
1382 secret_id: secret.arn.clone(),
1383 client_request_token: version_id.clone(),
1384 });
1385 }
1386 } else {
1387 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1390 old_v.stages.retain(|s| s != "AWSCURRENT");
1391 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1392 old_v.stages.push("AWSPREVIOUS".to_string());
1393 }
1394 }
1395 let version = SecretVersion {
1396 version_id: version_id.clone(),
1397 secret_string: cv.secret_string.clone(),
1398 secret_binary: cv.secret_binary.clone(),
1399 stages: vec!["AWSCURRENT".to_string()],
1400 created_at: now,
1401 };
1402 secret.versions.insert(version_id.clone(), version);
1403 secret.current_version_id = Some(version_id.clone());
1404 }
1405 }
1406 }
1407
1408 let response = json!({
1409 "ARN": secret.arn,
1410 "Name": secret.name,
1411 "VersionId": version_id,
1412 });
1413
1414 Ok((AwsResponse::ok_json(response), invocation))
1415 }
1416
1417 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1418 let body = req.json_body();
1419 let secret_id = require_secret_id(&body)?;
1420
1421 let mut accounts = self.state.write();
1422 let state = accounts.get_or_create(&req.account_id);
1423 let secret = self.find_secret_mut(state, &secret_id)?;
1424
1425 if secret.deleted {
1426 return Err(AwsServiceError::aws_error(
1427 StatusCode::BAD_REQUEST,
1428 "InvalidRequestException",
1429 "You can't perform this operation on the secret because it was marked for deletion.",
1430 ));
1431 }
1432
1433 if secret.rotation_enabled != Some(true) {
1434 return Err(AwsServiceError::aws_error(
1435 StatusCode::BAD_REQUEST,
1436 "InvalidRequestException",
1437 "You can't cancel rotation for a secret that does not have rotation enabled.",
1438 ));
1439 }
1440
1441 secret.rotation_enabled = Some(false);
1442
1443 let response = json!({
1444 "ARN": secret.arn,
1445 "Name": secret.name,
1446 });
1447
1448 Ok(AwsResponse::ok_json(response))
1449 }
1450
1451 fn update_secret_version_stage(
1452 &self,
1453 req: &AwsRequest,
1454 ) -> Result<AwsResponse, AwsServiceError> {
1455 let body = req.json_body();
1456 let secret_id = require_secret_id(&body)?;
1457 let version_stage = body["VersionStage"]
1458 .as_str()
1459 .ok_or_else(|| {
1460 AwsServiceError::aws_error(
1461 StatusCode::BAD_REQUEST,
1462 "InvalidParameterException",
1463 "VersionStage is required",
1464 )
1465 })?
1466 .to_string();
1467 validate_string_length("versionStage", &version_stage, 1, 256)?;
1468 validate_optional_string_length(
1469 "removeFromVersionId",
1470 body["RemoveFromVersionId"].as_str(),
1471 32,
1472 64,
1473 )?;
1474 validate_optional_string_length(
1475 "moveToVersionId",
1476 body["MoveToVersionId"].as_str(),
1477 32,
1478 64,
1479 )?;
1480
1481 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1482 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1483
1484 let mut accounts = self.state.write();
1485 let state = accounts.get_or_create(&req.account_id);
1486 let secret = self.find_secret_mut(state, &secret_id)?;
1487
1488 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1490 let current_holder = secret
1492 .versions
1493 .iter()
1494 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1495 .map(|(id, _)| id.clone());
1496
1497 if let Some(current_vid) = current_holder {
1498 return Err(AwsServiceError::aws_error(
1499 StatusCode::BAD_REQUEST,
1500 "InvalidParameterException",
1501 format!(
1502 "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."
1503 ),
1504 ));
1505 }
1506 }
1507
1508 if let Some(ref remove_vid) = remove_from {
1510 if let Some(version) = secret.versions.get_mut(remove_vid) {
1511 version.stages.retain(|s| s != &version_stage);
1512 if version_stage == "AWSCURRENT" {
1514 for (id, v) in secret.versions.iter_mut() {
1516 if id != remove_vid {
1517 v.stages.retain(|s| s != "AWSPREVIOUS");
1518 }
1519 }
1520 if let Some(v) = secret.versions.get_mut(remove_vid) {
1522 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1523 v.stages.push("AWSPREVIOUS".to_string());
1524 }
1525 }
1526 }
1527 }
1528 }
1529
1530 if let Some(ref move_vid) = move_to {
1532 if let Some(version) = secret.versions.get_mut(move_vid) {
1533 if !version.stages.contains(&version_stage) {
1534 version.stages.push(version_stage.clone());
1535 }
1536 }
1537 if version_stage == "AWSCURRENT" {
1539 secret.current_version_id = Some(move_vid.clone());
1540 }
1541 }
1542
1543 let response = json!({
1544 "ARN": secret.arn,
1545 "Name": secret.name,
1546 });
1547
1548 Ok(AwsResponse::ok_json(response))
1549 }
1550
1551 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1552 let body = req.json_body();
1553 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1554 let secret_id_list = body["SecretIdList"].as_array();
1555 let filters = body["Filters"].as_array();
1556 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1557
1558 if secret_id_list.is_some() && filters.is_some() {
1560 return Err(AwsServiceError::aws_error(
1561 StatusCode::BAD_REQUEST,
1562 "InvalidParameterException",
1563 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1564 ));
1565 }
1566
1567 if max_results.is_some() && filters.is_none() {
1569 return Err(AwsServiceError::aws_error(
1570 StatusCode::BAD_REQUEST,
1571 "InvalidParameterException",
1572 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1573 ));
1574 }
1575
1576 let accounts = self.state.read();
1577 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1578 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1579 let mut secret_values: Vec<Value> = Vec::new();
1580 let mut errors: Vec<Value> = Vec::new();
1581
1582 if let Some(id_list) = secret_id_list {
1583 for id_val in id_list {
1584 let sid = id_val.as_str().unwrap_or("");
1585 match self.find_secret_ref(state, sid) {
1586 Ok(secret) => {
1587 if secret.deleted {
1588 errors.push(json!({
1589 "SecretId": sid,
1590 "ErrorCode": "InvalidRequestException",
1591 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1592 }));
1593 } else if let Some(ref current_vid) = secret.current_version_id {
1594 if let Some(version) = secret.versions.get(current_vid) {
1595 let mut entry = json!({
1596 "ARN": secret.arn,
1597 "Name": secret.name,
1598 "VersionId": version.version_id,
1599 "VersionStages": version.stages,
1600 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1601 });
1602 if let Some(ref s) = version.secret_string {
1603 entry["SecretString"] = json!(s);
1604 }
1605 if let Some(ref b) = version.secret_binary {
1606 entry["SecretBinary"] = json!(base64_encode(b));
1607 }
1608 secret_values.push(entry);
1609 } else {
1610 errors.push(json!({
1611 "SecretId": sid,
1612 "ErrorCode": "ResourceNotFoundException",
1613 "Message": "Secrets Manager can't find the specified secret.",
1614 }));
1615 }
1616 } else {
1617 errors.push(json!({
1618 "SecretId": sid,
1619 "ErrorCode": "ResourceNotFoundException",
1620 "Message": "Secrets Manager can't find the specified secret.",
1621 }));
1622 }
1623 }
1624 Err(_) => {
1625 errors.push(json!({
1626 "SecretId": sid,
1627 "ErrorCode": "ResourceNotFoundException",
1628 "Message": "Secrets Manager can't find the specified secret.",
1629 }));
1630 }
1631 }
1632 }
1633 } else if let Some(filters) = filters {
1634 let matching: Vec<&Secret> = state
1636 .secrets
1637 .values()
1638 .filter(|s| {
1639 if s.deleted {
1640 return false;
1641 }
1642 for filter in filters {
1643 let key = filter["Key"].as_str().unwrap_or("");
1644 let values: Vec<&str> = filter["Values"]
1645 .as_array()
1646 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1647 .unwrap_or_default();
1648 let matches = match key {
1649 "name" => filter_name(s, &values),
1650 "description" => filter_description(s, &values),
1651 "tag-key" => filter_tag_key(s, &values),
1652 "tag-value" => filter_tag_value(s, &values),
1653 "all" => filter_all(s, &values),
1654 _ => true,
1655 };
1656 if !matches {
1657 return false;
1658 }
1659 }
1660 true
1661 })
1662 .collect();
1663
1664 let limit = max_results.unwrap_or(100) as usize;
1665 let mut no_value_found = false;
1666 let mut matching = matching;
1667 matching.sort_by(|a, b| a.name.cmp(&b.name));
1668
1669 for secret in matching.iter().take(limit) {
1670 if let Some(ref current_vid) = secret.current_version_id {
1671 if let Some(version) = secret.versions.get(current_vid) {
1672 let mut entry = json!({
1673 "ARN": secret.arn,
1674 "Name": secret.name,
1675 "VersionId": version.version_id,
1676 "VersionStages": version.stages,
1677 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1678 });
1679 if let Some(ref s) = version.secret_string {
1680 entry["SecretString"] = json!(s);
1681 }
1682 if let Some(ref b) = version.secret_binary {
1683 entry["SecretBinary"] = json!(base64_encode(b));
1684 }
1685 secret_values.push(entry);
1686 } else {
1687 no_value_found = true;
1688 }
1689 } else {
1690 no_value_found = true;
1691 }
1692 }
1693
1694 if no_value_found && secret_values.is_empty() {
1695 return Err(AwsServiceError::aws_error(
1696 StatusCode::NOT_FOUND,
1697 "ResourceNotFoundException",
1698 "Secrets Manager can't find the specified secret.",
1699 ));
1700 }
1701 }
1702
1703 let mut response = json!({
1704 "SecretValues": secret_values,
1705 "Errors": errors,
1706 });
1707
1708 if errors.is_empty() {
1710 response.as_object_mut().unwrap().remove("Errors");
1711 }
1712
1713 Ok(AwsResponse::ok_json(response))
1714 }
1715
1716 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1717 let body = req.json_body();
1718 let secret_id = require_secret_id(&body)?;
1719
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 let secret = self.find_secret_ref(state, &secret_id)?;
1724
1725 let mut response = json!({
1726 "ARN": secret.arn,
1727 "Name": secret.name,
1728 });
1729
1730 if let Some(ref policy) = secret.resource_policy {
1731 response["ResourcePolicy"] = json!(policy);
1732 }
1733
1734 Ok(AwsResponse::ok_json(response))
1735 }
1736
1737 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1738 let body = req.json_body();
1739 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1740 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1741 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1742 AwsServiceError::aws_error(
1743 StatusCode::BAD_REQUEST,
1744 "InvalidParameterException",
1745 "ResourcePolicy must be a string",
1746 )
1747 })?;
1748 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1749
1750 if let Some(secret_id) = body["SecretId"].as_str() {
1752 let accounts = self.state.read();
1753 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1754 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1755 self.find_secret_key(state, secret_id)?;
1756 }
1757
1758 let response = json!({
1759 "PolicyValidationPassed": true,
1760 "ValidationErrors": [],
1761 });
1762 Ok(AwsResponse::ok_json(response))
1763 }
1764
1765 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1766 let body = req.json_body();
1767 let secret_id = require_secret_id(&body)?;
1768 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1769 validate_optional_string_length(
1770 "resourcePolicy",
1771 body["ResourcePolicy"].as_str(),
1772 1,
1773 20480,
1774 )?;
1775 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1776
1777 let mut accounts = self.state.write();
1778 let state = accounts.get_or_create(&req.account_id);
1779 let secret = self.find_secret_mut(state, &secret_id)?;
1780 secret.resource_policy = policy;
1781
1782 let response = json!({
1783 "ARN": secret.arn,
1784 "Name": secret.name,
1785 });
1786
1787 Ok(AwsResponse::ok_json(response))
1788 }
1789
1790 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1791 let body = req.json_body();
1792 let secret_id = require_secret_id(&body)?;
1793
1794 let mut accounts = self.state.write();
1795 let state = accounts.get_or_create(&req.account_id);
1796 let secret = self.find_secret_mut(state, &secret_id)?;
1797 secret.resource_policy = None;
1798
1799 let response = json!({
1800 "ARN": secret.arn,
1801 "Name": secret.name,
1802 });
1803
1804 Ok(AwsResponse::ok_json(response))
1805 }
1806
1807 fn replicate_secret_to_regions(
1808 &self,
1809 req: &AwsRequest,
1810 ) -> Result<AwsResponse, AwsServiceError> {
1811 let body = req.json_body();
1812 let secret_id = require_secret_id(&body)?;
1813
1814 let accounts = self.state.read();
1815 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1816 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1817 let secret = self.find_secret_ref(state, &secret_id)?;
1818
1819 let response = json!({
1820 "ARN": secret.arn,
1821 "ReplicationStatus": [],
1822 });
1823 Ok(AwsResponse::ok_json(response))
1824 }
1825
1826 fn remove_regions_from_replication(
1827 &self,
1828 req: &AwsRequest,
1829 ) -> Result<AwsResponse, AwsServiceError> {
1830 let body = req.json_body();
1831 let secret_id = require_secret_id(&body)?;
1832
1833 let accounts = self.state.read();
1834 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1835 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1836 let secret = self.find_secret_ref(state, &secret_id)?;
1837
1838 let response = json!({
1839 "ARN": secret.arn,
1840 "ReplicationStatus": [],
1841 });
1842 Ok(AwsResponse::ok_json(response))
1843 }
1844
1845 fn stop_replication_to_replica(
1846 &self,
1847 req: &AwsRequest,
1848 ) -> Result<AwsResponse, AwsServiceError> {
1849 let body = req.json_body();
1850 let secret_id = require_secret_id(&body)?;
1851
1852 let accounts = self.state.read();
1853 let empty = SecretsManagerState::new(&req.account_id, &req.region);
1854 let state = accounts.get(&req.account_id).unwrap_or(&empty);
1855 let secret = self.find_secret_ref(state, &secret_id)?;
1856
1857 let response = json!({
1858 "ARN": secret.arn,
1859 });
1860 Ok(AwsResponse::ok_json(response))
1861 }
1862
1863 fn find_secret_mut<'a>(
1865 &self,
1866 state: &'a mut crate::state::SecretsManagerState,
1867 secret_id: &str,
1868 ) -> Result<&'a mut Secret, AwsServiceError> {
1869 let key = self.find_secret_key(state, secret_id)?;
1870 Ok(state.secrets.get_mut(&key).unwrap())
1871 }
1872
1873 fn find_secret_key(
1874 &self,
1875 state: &crate::state::SecretsManagerState,
1876 secret_id: &str,
1877 ) -> Result<String, AwsServiceError> {
1878 if state.secrets.contains_key(secret_id) {
1879 return Ok(secret_id.to_string());
1880 }
1881
1882 for secret in state.secrets.values() {
1883 if secret.arn == secret_id {
1884 return Ok(secret.name.clone());
1885 }
1886 }
1887
1888 if secret_id.starts_with("arn:aws:secretsmanager:") {
1889 for secret in state.secrets.values() {
1890 if secret.arn.starts_with(secret_id) {
1891 return Ok(secret.name.clone());
1892 }
1893 }
1894 }
1895
1896 Err(AwsServiceError::aws_error(
1897 StatusCode::NOT_FOUND,
1898 "ResourceNotFoundException",
1899 "Secrets Manager can't find the specified secret.",
1900 ))
1901 }
1902
1903 fn find_secret_ref<'a>(
1905 &self,
1906 state: &'a crate::state::SecretsManagerState,
1907 secret_id: &str,
1908 ) -> Result<&'a Secret, AwsServiceError> {
1909 if let Some(secret) = state.secrets.get(secret_id) {
1910 return Ok(secret);
1911 }
1912
1913 for secret in state.secrets.values() {
1915 if secret.arn == secret_id {
1916 return Ok(secret);
1917 }
1918 }
1919
1920 if secret_id.starts_with("arn:aws:secretsmanager:") {
1922 for secret in state.secrets.values() {
1923 if secret.arn.starts_with(secret_id) {
1924 return Ok(secret);
1925 }
1926 }
1927 }
1928
1929 Err(AwsServiceError::aws_error(
1930 StatusCode::NOT_FOUND,
1931 "ResourceNotFoundException",
1932 "Secrets Manager can't find the specified secret.",
1933 ))
1934 }
1935}
1936
1937struct CreateSecretInput {
1939 name: String,
1940 client_request_token: Option<String>,
1941 description: Option<String>,
1942 kms_key_id: Option<String>,
1943 secret_string: Option<String>,
1944 secret_binary: Option<Vec<u8>>,
1945 tags: Vec<(String, String)>,
1946}
1947
1948impl CreateSecretInput {
1949 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
1950 validate_required("Name", &body["Name"])?;
1951 let name = body["Name"]
1952 .as_str()
1953 .ok_or_else(|| {
1954 AwsServiceError::aws_error(
1955 StatusCode::BAD_REQUEST,
1956 "InvalidParameterException",
1957 "Name is required",
1958 )
1959 })?
1960 .to_string();
1961 validate_string_length("name", &name, 1, 512)?;
1962 validate_optional_string_length(
1963 "clientRequestToken",
1964 body["ClientRequestToken"].as_str(),
1965 32,
1966 64,
1967 )?;
1968 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
1969 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
1970 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
1971
1972 Ok(Self {
1973 name,
1974 client_request_token: body["ClientRequestToken"].as_str().map(|s| s.to_string()),
1975 description: body["Description"].as_str().map(|s| s.to_string()),
1976 kms_key_id: body["KmsKeyId"].as_str().map(|s| s.to_string()),
1977 secret_string: body["SecretString"].as_str().map(|s| s.to_string()),
1978 secret_binary: body["SecretBinary"].as_str().and_then(base64_decode),
1979 tags: parse_tags(&body["Tags"]),
1980 })
1981 }
1982}
1983
1984fn require_secret_id(body: &Value) -> Result<String, AwsServiceError> {
1985 let id = body["SecretId"].as_str().ok_or_else(|| {
1986 AwsServiceError::aws_error(
1987 StatusCode::BAD_REQUEST,
1988 "InvalidParameterException",
1989 "SecretId is required",
1990 )
1991 })?;
1992 validate_string_length("secretId", id, 1, 2048)?;
1993 Ok(id.to_string())
1994}
1995
1996fn parse_tags(tags_val: &Value) -> Vec<(String, String)> {
1997 tags_val
1998 .as_array()
1999 .map(|arr| {
2000 arr.iter()
2001 .filter_map(|t| {
2002 let key = t["Key"].as_str()?;
2003 let value = t["Value"].as_str()?;
2004 Some((key.to_string(), value.to_string()))
2005 })
2006 .collect()
2007 })
2008 .unwrap_or_default()
2009}
2010
2011fn tags_to_json(tags: &[(String, String)]) -> Vec<Value> {
2012 tags.iter()
2013 .map(|(k, v)| json!({"Key": k, "Value": v}))
2014 .collect()
2015}
2016
2017fn split_words(text: &str) -> Vec<String> {
2022 let mut all_words = Vec::new();
2024 for space_part in text.split_whitespace() {
2025 all_words.extend(split_words_no_space(space_part));
2026 }
2027 all_words
2028}
2029
2030fn split_words_no_space(text: &str) -> Vec<String> {
2031 let special_chars = ['/', '-', '_', '+', '=', '.', '@'];
2032
2033 if text.len() == 1 && special_chars.contains(&text.chars().next().unwrap_or(' ')) {
2035 return vec![];
2036 }
2037
2038 let present: Vec<char> = special_chars
2040 .iter()
2041 .filter(|&&c| text.contains(c))
2042 .copied()
2043 .collect();
2044
2045 if present.len() > 1 {
2046 return vec![text.to_string()];
2048 }
2049
2050 if present.len() == 1 {
2051 let ch = present[0];
2052 let parts: Vec<&str> = text.split(ch).filter(|s| !s.is_empty()).collect();
2053 let mut result = Vec::new();
2054 for part in parts {
2055 result.extend(split_by_uppercase(part));
2056 }
2057 return result;
2058 }
2059
2060 split_by_uppercase(text)
2062}
2063
2064fn split_by_uppercase(text: &str) -> Vec<String> {
2067 let chars: Vec<char> = text.chars().collect();
2070 let mut words = Vec::new();
2071 let mut last_end = 0;
2072 let mut i = 0;
2073
2074 while i < chars.len() {
2075 if !chars[i].is_ascii_lowercase()
2077 && i + 1 < chars.len()
2078 && chars[i + 1].is_ascii_lowercase()
2079 {
2080 if i > last_end {
2082 let between: String = chars[last_end..i].iter().collect();
2083 let trimmed = between.trim().to_string();
2084 if !trimmed.is_empty() {
2085 words.push(trimmed);
2086 }
2087 }
2088
2089 let start = i;
2091 i += 2;
2092 while i < chars.len() && chars[i].is_ascii_lowercase() {
2093 i += 1;
2094 }
2095 let word: String = chars[start..i].iter().collect();
2096 let trimmed = word.trim().to_string();
2097 if !trimmed.is_empty() {
2098 words.push(trimmed);
2099 }
2100 last_end = i;
2101 } else {
2102 i += 1;
2103 }
2104 }
2105
2106 if last_end < chars.len() {
2108 let after: String = chars[last_end..].iter().collect();
2109 let trimmed = after.trim().to_string();
2110 if !trimmed.is_empty() {
2111 words.push(trimmed);
2112 }
2113 }
2114
2115 words
2116}
2117
2118fn match_pattern(pattern: &str, value: &str, match_prefix: bool, case_sensitive: bool) -> bool {
2122 if match_prefix {
2123 if case_sensitive {
2124 value.starts_with(pattern)
2125 } else {
2126 value.to_lowercase().starts_with(&pattern.to_lowercase())
2127 }
2128 } else {
2129 let mut pattern_words = split_words(pattern);
2130 if pattern_words.is_empty() {
2131 return false;
2132 }
2133 let mut value_words = split_words(value);
2134 if !case_sensitive {
2135 pattern_words = pattern_words.iter().map(|w| w.to_lowercase()).collect();
2136 value_words = value_words.iter().map(|w| w.to_lowercase()).collect();
2137 }
2138 for pw in &pattern_words {
2139 if !value_words.iter().any(|vw| vw.starts_with(pw.as_str())) {
2140 return false;
2141 }
2142 }
2143 true
2144 }
2145}
2146
2147fn matcher(patterns: &[&str], strings: &[&str], match_prefix: bool, case_sensitive: bool) -> bool {
2150 for pattern in patterns.iter().filter(|p| p.starts_with('!')) {
2152 let inner = &pattern[1..];
2153 for s in strings {
2154 if !match_pattern(inner, s, match_prefix, case_sensitive) {
2155 return true;
2156 }
2157 }
2158 }
2159
2160 for pattern in patterns.iter().filter(|p| !p.starts_with('!')) {
2162 for s in strings {
2163 if match_pattern(pattern, s, match_prefix, case_sensitive) {
2164 return true;
2165 }
2166 }
2167 }
2168 false
2169}
2170
2171fn filter_name(secret: &Secret, values: &[&str]) -> bool {
2173 matcher(values, &[secret.name.as_str()], true, true)
2174}
2175
2176fn filter_description(secret: &Secret, values: &[&str]) -> bool {
2178 match secret.description.as_deref() {
2179 Some(desc) if !desc.is_empty() => matcher(values, &[desc], false, false),
2180 _ => false,
2181 }
2182}
2183
2184fn filter_tag_key(secret: &Secret, values: &[&str]) -> bool {
2186 if secret.tags.is_empty() {
2187 return false;
2188 }
2189 let keys: Vec<&str> = secret.tags.iter().map(|(k, _)| k.as_str()).collect();
2190 matcher(values, &keys, true, true)
2191}
2192
2193fn filter_tag_value(secret: &Secret, values: &[&str]) -> bool {
2195 if secret.tags.is_empty() {
2196 return false;
2197 }
2198 let vals: Vec<&str> = secret.tags.iter().map(|(_, v)| v.as_str()).collect();
2199 matcher(values, &vals, true, true)
2200}
2201
2202fn filter_all(secret: &Secret, values: &[&str]) -> bool {
2204 let mut attributes: Vec<&str> = vec![secret.name.as_str()];
2205 if let Some(ref desc) = secret.description {
2206 if !desc.is_empty() {
2207 attributes.push(desc.as_str());
2208 }
2209 }
2210 for (k, v) in &secret.tags {
2211 attributes.push(k.as_str());
2212 attributes.push(v.as_str());
2213 }
2214 matcher(values, &attributes, false, false)
2215}
2216
2217fn simple_random() -> usize {
2218 use std::collections::hash_map::RandomState;
2219 use std::hash::{BuildHasher, Hasher};
2220 let s = RandomState::new();
2221 let mut hasher = s.build_hasher();
2222 hasher.write_usize(0);
2223 hasher.finish() as usize
2224}
2225
2226#[async_trait]
2227impl AwsService for SecretsManagerService {
2228 fn service_name(&self) -> &str {
2229 "secretsmanager"
2230 }
2231
2232 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2233 let mutates = is_mutating_action(req.action.as_str());
2234 let result = match req.action.as_str() {
2235 "CreateSecret" => self.create_secret(&req),
2236 "GetSecretValue" => self.get_secret_value(&req),
2237 "PutSecretValue" => self.put_secret_value(&req),
2238 "UpdateSecret" => self.update_secret(&req),
2239 "DeleteSecret" => self.delete_secret(&req),
2240 "RestoreSecret" => self.restore_secret(&req),
2241 "DescribeSecret" => self.describe_secret(&req),
2242 "ListSecrets" => self.list_secrets(&req),
2243 "TagResource" => self.tag_resource(&req),
2244 "UntagResource" => self.untag_resource(&req),
2245 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
2246 "GetRandomPassword" => self.get_random_password(&req),
2247 "RotateSecret" => {
2248 let (response, invocation) = self.rotate_secret(&req)?;
2249 if let Some(inv) = invocation {
2250 if let Some(ref bus) = self.delivery_bus {
2251 let bus = bus.clone();
2252 tokio::spawn(async move {
2254 for step in &["createSecret", "setSecret", "testSecret", "finishSecret"]
2255 {
2256 let payload = serde_json::json!({
2257 "SecretId": inv.secret_id,
2258 "ClientRequestToken": inv.client_request_token,
2259 "Step": step,
2260 });
2261 let payload_str = payload.to_string();
2262 match bus.invoke_lambda(&inv.lambda_arn, &payload_str).await {
2263 Some(Ok(_)) => {}
2264 Some(Err(e)) => {
2265 tracing::warn!(
2266 step = step,
2267 error = %e,
2268 "rotation Lambda invocation failed"
2269 );
2270 }
2271 None => {
2272 tracing::warn!(
2273 lambda_arn = %inv.lambda_arn,
2274 step = step,
2275 "rotation Lambda delivery not configured; \
2276 Lambda invocation skipped"
2277 );
2278 break;
2279 }
2280 }
2281 }
2282 });
2283 }
2284 }
2285 Ok(response)
2286 }
2287 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2288 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2289 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2290 "GetResourcePolicy" => self.get_resource_policy(&req),
2291 "PutResourcePolicy" => self.put_resource_policy(&req),
2292 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2293 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2294 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2295 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2296 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2297 _ => Err(AwsServiceError::action_not_implemented(
2298 "secretsmanager",
2299 &req.action,
2300 )),
2301 };
2302 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
2303 self.save_snapshot().await;
2304 }
2305 result
2306 }
2307
2308 fn supported_actions(&self) -> &[&str] {
2309 &[
2310 "CreateSecret",
2311 "GetSecretValue",
2312 "PutSecretValue",
2313 "UpdateSecret",
2314 "DeleteSecret",
2315 "RestoreSecret",
2316 "DescribeSecret",
2317 "ListSecrets",
2318 "TagResource",
2319 "UntagResource",
2320 "ListSecretVersionIds",
2321 "GetRandomPassword",
2322 "RotateSecret",
2323 "CancelRotateSecret",
2324 "UpdateSecretVersionStage",
2325 "BatchGetSecretValue",
2326 "GetResourcePolicy",
2327 "PutResourcePolicy",
2328 "DeleteResourcePolicy",
2329 "ValidateResourcePolicy",
2330 "ReplicateSecretToRegions",
2331 "RemoveRegionsFromReplication",
2332 "StopReplicationToReplica",
2333 ]
2334 }
2335}
2336
2337fn base64_decode(input: &str) -> Option<Vec<u8>> {
2338 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2339 let mut buf = Vec::new();
2340 let mut bits: u32 = 0;
2341 let mut count = 0;
2342 for &b in input.as_bytes() {
2343 if b == b'=' || b == b'\n' || b == b'\r' {
2344 continue;
2345 }
2346 let val = table.iter().position(|&c| c == b)? as u32;
2347 bits = (bits << 6) | val;
2348 count += 1;
2349 if count == 4 {
2350 buf.push((bits >> 16) as u8);
2351 buf.push((bits >> 8) as u8);
2352 buf.push(bits as u8);
2353 bits = 0;
2354 count = 0;
2355 }
2356 }
2357 match count {
2358 2 => {
2359 bits <<= 12;
2360 buf.push((bits >> 16) as u8);
2361 }
2362 3 => {
2363 bits <<= 6;
2364 buf.push((bits >> 16) as u8);
2365 buf.push((bits >> 8) as u8);
2366 }
2367 _ => {}
2368 }
2369 Some(buf)
2370}
2371
2372fn base64_encode(input: &[u8]) -> String {
2373 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2374 let mut result = String::new();
2375 for chunk in input.chunks(3) {
2376 let b0 = chunk[0] as u32;
2377 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
2378 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
2379 let triple = (b0 << 16) | (b1 << 8) | b2;
2380 result.push(table[((triple >> 18) & 0x3F) as usize] as char);
2381 result.push(table[((triple >> 12) & 0x3F) as usize] as char);
2382 if chunk.len() > 1 {
2383 result.push(table[((triple >> 6) & 0x3F) as usize] as char);
2384 } else {
2385 result.push('=');
2386 }
2387 if chunk.len() > 2 {
2388 result.push(table[(triple & 0x3F) as usize] as char);
2389 } else {
2390 result.push('=');
2391 }
2392 }
2393 result
2394}
2395
2396#[cfg(test)]
2397mod tests {
2398 use super::*;
2399 use bytes::Bytes;
2400 use http::{HeaderMap, Method};
2401 use parking_lot::RwLock;
2402 use std::collections::HashMap;
2403 use std::sync::Arc;
2404
2405 fn make_state() -> SharedSecretsManagerState {
2406 Arc::new(RwLock::new(
2407 fakecloud_core::multi_account::MultiAccountState::new(
2408 "123456789012",
2409 "us-east-1",
2410 "http://localhost:4566",
2411 ),
2412 ))
2413 }
2414
2415 fn expect_err(result: Result<AwsResponse, AwsServiceError>) -> AwsServiceError {
2416 match result {
2417 Err(e) => e,
2418 Ok(_) => panic!("expected error, got Ok"),
2419 }
2420 }
2421
2422 fn make_request(action: &str, body: &str) -> AwsRequest {
2423 AwsRequest {
2424 service: "secretsmanager".to_string(),
2425 action: action.to_string(),
2426 region: "us-east-1".to_string(),
2427 account_id: "123456789012".to_string(),
2428 request_id: "test-request-id".to_string(),
2429 headers: HeaderMap::new(),
2430 query_params: HashMap::new(),
2431 body: Bytes::from(body.to_string()),
2432 body_stream: parking_lot::Mutex::new(None),
2433 path_segments: vec![],
2434 raw_path: "/".to_string(),
2435 raw_query: String::new(),
2436 method: Method::POST,
2437 is_query_protocol: false,
2438 access_key_id: None,
2439 principal: None,
2440 }
2441 }
2442
2443 #[tokio::test]
2444 async fn test_create_and_get_secret() {
2445 let state = make_state();
2446 let svc = SecretsManagerService::new(state);
2447
2448 let req = make_request(
2449 "CreateSecret",
2450 r#"{"Name": "test/secret", "SecretString": "mysecretvalue"}"#,
2451 );
2452 let resp = svc.handle(req).await.unwrap();
2453 assert_eq!(resp.status, StatusCode::OK);
2454 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2455 assert_eq!(body["Name"], "test/secret");
2456 assert!(body["ARN"].as_str().unwrap().contains("test/secret"));
2457
2458 let req = make_request("GetSecretValue", r#"{"SecretId": "test/secret"}"#);
2459 let resp = svc.handle(req).await.unwrap();
2460 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2461 assert_eq!(body["SecretString"], "mysecretvalue");
2462 }
2463
2464 #[tokio::test]
2465 async fn test_create_secret_without_value() {
2466 let state = make_state();
2467 let svc = SecretsManagerService::new(state);
2468
2469 let req = make_request("CreateSecret", r#"{"Name": "empty-secret"}"#);
2470 let resp = svc.handle(req).await.unwrap();
2471 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2472 assert_eq!(body["Name"], "empty-secret");
2473 assert!(body.get("VersionId").is_none());
2474 }
2475
2476 #[tokio::test]
2477 async fn test_put_secret_value_creates_version() {
2478 let state = make_state();
2479 let svc = SecretsManagerService::new(state);
2480
2481 let req = make_request(
2482 "CreateSecret",
2483 r#"{"Name": "versioned", "SecretString": "v1"}"#,
2484 );
2485 svc.handle(req).await.unwrap();
2486
2487 let req = make_request(
2488 "PutSecretValue",
2489 r#"{"SecretId": "versioned", "SecretString": "v2"}"#,
2490 );
2491 let resp = svc.handle(req).await.unwrap();
2492 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2493 assert_eq!(body["Name"], "versioned");
2494
2495 let req = make_request("GetSecretValue", r#"{"SecretId": "versioned"}"#);
2497 let resp = svc.handle(req).await.unwrap();
2498 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2499 assert_eq!(body["SecretString"], "v2");
2500 }
2501
2502 #[tokio::test]
2503 async fn test_delete_and_restore_secret() {
2504 let state = make_state();
2505 let svc = SecretsManagerService::new(state);
2506
2507 let req = make_request(
2508 "CreateSecret",
2509 r#"{"Name": "deleteme", "SecretString": "value"}"#,
2510 );
2511 svc.handle(req).await.unwrap();
2512
2513 let req = make_request("DeleteSecret", r#"{"SecretId": "deleteme"}"#);
2515 let resp = svc.handle(req).await.unwrap();
2516 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2517 assert!(body["DeletionDate"].as_f64().is_some());
2518
2519 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2521 assert!(svc.handle(req).await.is_err());
2522
2523 let req = make_request("RestoreSecret", r#"{"SecretId": "deleteme"}"#);
2525 let resp = svc.handle(req).await.unwrap();
2526 assert_eq!(resp.status, StatusCode::OK);
2527
2528 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2530 let resp = svc.handle(req).await.unwrap();
2531 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2532 assert_eq!(body["SecretString"], "value");
2533 }
2534
2535 #[tokio::test]
2536 async fn test_list_secrets() {
2537 let state = make_state();
2538 let svc = SecretsManagerService::new(state);
2539
2540 for name in &["alpha", "beta", "gamma"] {
2541 let req = make_request(
2542 "CreateSecret",
2543 &format!(r#"{{"Name": "{name}", "SecretString": "val"}}"#),
2544 );
2545 svc.handle(req).await.unwrap();
2546 }
2547
2548 let req = make_request("ListSecrets", "{}");
2549 let resp = svc.handle(req).await.unwrap();
2550 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2551 assert_eq!(body["SecretList"].as_array().unwrap().len(), 3);
2552 }
2553
2554 #[tokio::test]
2555 async fn test_tags() {
2556 let state = make_state();
2557 let svc = SecretsManagerService::new(state);
2558
2559 let req = make_request(
2560 "CreateSecret",
2561 r#"{"Name": "tagged", "SecretString": "val"}"#,
2562 );
2563 svc.handle(req).await.unwrap();
2564
2565 let req = make_request(
2566 "TagResource",
2567 r#"{"SecretId": "tagged", "Tags": [{"Key": "env", "Value": "prod"}]}"#,
2568 );
2569 svc.handle(req).await.unwrap();
2570
2571 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2572 let resp = svc.handle(req).await.unwrap();
2573 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2574 let tags = body["Tags"].as_array().unwrap();
2575 assert!(tags
2576 .iter()
2577 .any(|t| t["Key"] == "env" && t["Value"] == "prod"));
2578
2579 let req = make_request(
2580 "UntagResource",
2581 r#"{"SecretId": "tagged", "TagKeys": ["env"]}"#,
2582 );
2583 svc.handle(req).await.unwrap();
2584
2585 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2586 let resp = svc.handle(req).await.unwrap();
2587 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2588 assert_eq!(body["Tags"].as_array().unwrap().len(), 0);
2590 }
2591
2592 #[tokio::test]
2593 async fn test_get_random_password() {
2594 let state = make_state();
2595 let svc = SecretsManagerService::new(state);
2596
2597 let req = make_request("GetRandomPassword", "{}");
2598 let resp = svc.handle(req).await.unwrap();
2599 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2600 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 32);
2601 }
2602
2603 #[tokio::test]
2604 async fn test_replication_ops_return_arn() {
2605 let state = make_state();
2606 let svc = SecretsManagerService::new(state);
2607
2608 let req = make_request(
2609 "CreateSecret",
2610 r#"{"Name": "repl-secret", "SecretString": "val"}"#,
2611 );
2612 let resp = svc.handle(req).await.unwrap();
2613 let create_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2614 let expected_arn = create_body["ARN"].as_str().unwrap();
2615
2616 for action in &[
2617 "ReplicateSecretToRegions",
2618 "RemoveRegionsFromReplication",
2619 "StopReplicationToReplica",
2620 ] {
2621 let req = make_request(action, r#"{"SecretId": "repl-secret"}"#);
2622 let resp = svc.handle(req).await.unwrap();
2623 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2624 assert_eq!(
2625 body["ARN"].as_str().unwrap(),
2626 expected_arn,
2627 "{action} should return the secret's actual ARN"
2628 );
2629 }
2630 }
2631
2632 #[tokio::test]
2633 async fn test_secret_id_length_validation() {
2634 let state = make_state();
2635 let svc = SecretsManagerService::new(state);
2636
2637 let long_id = "x".repeat(2049);
2639 let req = make_request("GetSecretValue", &format!(r#"{{"SecretId": "{long_id}"}}"#));
2640 match svc.handle(req).await {
2641 Err(e) => assert!(e.to_string().contains("ValidationException")),
2642 Ok(_) => panic!("expected ValidationException"),
2643 }
2644 }
2645
2646 #[tokio::test]
2647 async fn test_name_length_validation() {
2648 let state = make_state();
2649 let svc = SecretsManagerService::new(state);
2650
2651 let long_name = "x".repeat(513);
2653 let req = make_request(
2654 "CreateSecret",
2655 &format!(r#"{{"Name": "{long_name}", "SecretString": "val"}}"#),
2656 );
2657 match svc.handle(req).await {
2658 Err(e) => assert!(e.to_string().contains("ValidationException")),
2659 Ok(_) => panic!("expected ValidationException"),
2660 }
2661 }
2662
2663 #[tokio::test]
2664 async fn test_next_token_length_validation() {
2665 let state = make_state();
2666 let svc = SecretsManagerService::new(state);
2667
2668 let long_token = "x".repeat(4097);
2670 let req = make_request(
2671 "ListSecrets",
2672 &format!(r#"{{"NextToken": "{long_token}"}}"#),
2673 );
2674 match svc.handle(req).await {
2675 Err(e) => assert!(e.to_string().contains("ValidationException")),
2676 Ok(_) => panic!("expected ValidationException"),
2677 }
2678 }
2679
2680 #[tokio::test]
2681 async fn test_client_request_token_length_validation() {
2682 let state = make_state();
2683 let svc = SecretsManagerService::new(state);
2684
2685 let req = make_request(
2687 "CreateSecret",
2688 r#"{"Name": "test", "SecretString": "val", "ClientRequestToken": "short"}"#,
2689 );
2690 match svc.handle(req).await {
2691 Err(e) => assert!(e.to_string().contains("ValidationException")),
2692 Ok(_) => panic!("expected ValidationException"),
2693 }
2694 }
2695
2696 #[tokio::test]
2697 async fn test_rotate_secret_with_lambda_creates_pending_version() {
2698 let state = make_state();
2699 let svc = SecretsManagerService::new(state.clone());
2700
2701 let req = make_request(
2703 "CreateSecret",
2704 r#"{"Name": "rotate-me", "SecretString": "old-password"}"#,
2705 );
2706 svc.handle(req).await.unwrap();
2707
2708 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2710 let body = serde_json::json!({
2711 "SecretId": "rotate-me",
2712 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:rotator",
2713 "ClientRequestToken": token,
2714 });
2715 let req = make_request("RotateSecret", &body.to_string());
2716 let resp = svc.handle(req).await.unwrap();
2717 assert_eq!(resp.status, StatusCode::OK);
2718 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2719 assert_eq!(resp_body["VersionId"], token);
2720
2721 let _accts = state.read();
2725 let s = _accts.default_ref();
2726 let secret = s.secrets.get("rotate-me").unwrap();
2727 assert!(
2728 !secret.versions.contains_key(token),
2729 "AWSPENDING version must not be pre-created; the rotation Lambda creates it"
2730 );
2731
2732 assert_eq!(
2734 secret.rotation_lambda_arn.as_deref(),
2735 Some("arn:aws:lambda:us-east-1:123456789012:function:rotator")
2736 );
2737 assert_eq!(secret.rotation_enabled, Some(true));
2738 }
2739
2740 #[tokio::test]
2741 async fn test_rotate_secret_without_lambda_promotes_directly() {
2742 let state = make_state();
2743 let svc = SecretsManagerService::new(state.clone());
2744
2745 let req = make_request(
2747 "CreateSecret",
2748 r#"{"Name": "rotate-no-lambda", "SecretString": "value1"}"#,
2749 );
2750 svc.handle(req).await.unwrap();
2751
2752 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2754 let body = serde_json::json!({
2755 "SecretId": "rotate-no-lambda",
2756 "ClientRequestToken": token,
2757 });
2758 let req = make_request("RotateSecret", &body.to_string());
2759 svc.handle(req).await.unwrap();
2760
2761 let _accts = state.read();
2763 let s = _accts.default_ref();
2764 let secret = s.secrets.get("rotate-no-lambda").unwrap();
2765 let new_ver = secret.versions.get(token).unwrap();
2766 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2767 assert_eq!(secret.current_version_id.as_deref(), Some(token));
2768 }
2769
2770 #[tokio::test]
2771 async fn test_rotate_secret_stores_rotation_config() {
2772 let state = make_state();
2773 let svc = SecretsManagerService::new(state.clone());
2774
2775 let req = make_request(
2776 "CreateSecret",
2777 r#"{"Name": "rot-cfg", "SecretString": "pw"}"#,
2778 );
2779 svc.handle(req).await.unwrap();
2780
2781 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2782 let body = serde_json::json!({
2783 "SecretId": "rot-cfg",
2784 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:my-rotator",
2785 "RotationRules": { "AutomaticallyAfterDays": 30 },
2786 "ClientRequestToken": token,
2787 });
2788 let req = make_request("RotateSecret", &body.to_string());
2789 let resp = svc.handle(req).await.unwrap();
2790 assert_eq!(resp.status, StatusCode::OK);
2791
2792 let _accts = state.read();
2793 let s = _accts.default_ref();
2794 let secret = s.secrets.get("rot-cfg").unwrap();
2795 assert_eq!(secret.rotation_enabled, Some(true));
2796 assert_eq!(
2797 secret.rotation_lambda_arn.as_deref(),
2798 Some("arn:aws:lambda:us-east-1:123456789012:function:my-rotator")
2799 );
2800 assert!(secret.last_rotated_at.is_some());
2801 let rules = secret.rotation_rules.as_ref().unwrap();
2802 assert_eq!(rules.automatically_after_days, Some(30));
2803
2804 assert!(!secret.versions.contains_key(token));
2808 }
2809
2810 #[tokio::test]
2811 async fn test_rotate_secret_version_stages_change() {
2812 let state = make_state();
2813 let svc = SecretsManagerService::new(state.clone());
2814
2815 let req = make_request(
2816 "CreateSecret",
2817 r#"{"Name": "rot-stages", "SecretString": "original"}"#,
2818 );
2819 svc.handle(req).await.unwrap();
2820
2821 let original_vid = {
2823 let _accts = state.read();
2824 let s = _accts.default_ref();
2825 let secret = s.secrets.get("rot-stages").unwrap();
2826 secret.current_version_id.clone().unwrap()
2827 };
2828
2829 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2831 let body = serde_json::json!({
2832 "SecretId": "rot-stages",
2833 "ClientRequestToken": token,
2834 });
2835 let req = make_request("RotateSecret", &body.to_string());
2836 svc.handle(req).await.unwrap();
2837
2838 let _accts = state.read();
2839 let s = _accts.default_ref();
2840 let secret = s.secrets.get("rot-stages").unwrap();
2841
2842 let new_ver = secret.versions.get(token).unwrap();
2844 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2845
2846 let old_ver = secret.versions.get(&original_vid).unwrap();
2848 assert!(old_ver.stages.contains(&"AWSPREVIOUS".to_string()));
2849 assert!(!old_ver.stages.contains(&"AWSCURRENT".to_string()));
2850 }
2851
2852 #[tokio::test]
2853 async fn test_cancel_rotate_secret() {
2854 let state = make_state();
2855 let svc = SecretsManagerService::new(state.clone());
2856
2857 let req = make_request(
2858 "CreateSecret",
2859 r#"{"Name": "cancel-rot", "SecretString": "pw"}"#,
2860 );
2861 svc.handle(req).await.unwrap();
2862
2863 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2865 let body = serde_json::json!({
2866 "SecretId": "cancel-rot",
2867 "ClientRequestToken": token,
2868 });
2869 let req = make_request("RotateSecret", &body.to_string());
2870 svc.handle(req).await.unwrap();
2871
2872 {
2874 let _accts = state.read();
2875 let s = _accts.default_ref();
2876 let secret = s.secrets.get("cancel-rot").unwrap();
2877 assert_eq!(secret.rotation_enabled, Some(true));
2878 }
2879
2880 let req = make_request("CancelRotateSecret", r#"{"SecretId": "cancel-rot"}"#);
2882 let resp = svc.handle(req).await.unwrap();
2883 assert_eq!(resp.status, StatusCode::OK);
2884 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2885 assert_eq!(body["Name"], "cancel-rot");
2886
2887 let _accts = state.read();
2889 let s = _accts.default_ref();
2890 let secret = s.secrets.get("cancel-rot").unwrap();
2891 assert_eq!(secret.rotation_enabled, Some(false));
2892 }
2893
2894 #[tokio::test]
2895 async fn test_cancel_rotate_secret_fails_when_not_enabled() {
2896 let state = make_state();
2897 let svc = SecretsManagerService::new(state);
2898
2899 let req = make_request(
2900 "CreateSecret",
2901 r#"{"Name": "no-rot", "SecretString": "pw"}"#,
2902 );
2903 svc.handle(req).await.unwrap();
2904
2905 let req = make_request("CancelRotateSecret", r#"{"SecretId": "no-rot"}"#);
2906 let result = svc.handle(req).await;
2907 assert!(result.is_err());
2908 }
2909
2910 #[tokio::test]
2911 async fn test_batch_get_secret_value_multiple() {
2912 let state = make_state();
2913 let svc = SecretsManagerService::new(state);
2914
2915 for (name, val) in &[("batch-a", "va"), ("batch-b", "vb"), ("batch-c", "vc")] {
2916 let req = make_request(
2917 "CreateSecret",
2918 &format!(r#"{{"Name": "{name}", "SecretString": "{val}"}}"#),
2919 );
2920 svc.handle(req).await.unwrap();
2921 }
2922
2923 let body = serde_json::json!({
2924 "SecretIdList": ["batch-a", "batch-b", "batch-c"]
2925 });
2926 let req = make_request("BatchGetSecretValue", &body.to_string());
2927 let resp = svc.handle(req).await.unwrap();
2928 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2929
2930 let values = resp_body["SecretValues"].as_array().unwrap();
2931 assert_eq!(values.len(), 3);
2932
2933 let names: Vec<&str> = values.iter().map(|v| v["Name"].as_str().unwrap()).collect();
2935 assert!(names.contains(&"batch-a"));
2936 assert!(names.contains(&"batch-b"));
2937 assert!(names.contains(&"batch-c"));
2938
2939 assert!(resp_body.get("Errors").is_none());
2941 }
2942
2943 #[tokio::test]
2944 async fn test_batch_get_secret_value_with_missing() {
2945 let state = make_state();
2946 let svc = SecretsManagerService::new(state);
2947
2948 let req = make_request(
2949 "CreateSecret",
2950 r#"{"Name": "exists", "SecretString": "val"}"#,
2951 );
2952 svc.handle(req).await.unwrap();
2953
2954 let body = serde_json::json!({
2955 "SecretIdList": ["exists", "nonexistent"]
2956 });
2957 let req = make_request("BatchGetSecretValue", &body.to_string());
2958 let resp = svc.handle(req).await.unwrap();
2959 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2960
2961 let values = resp_body["SecretValues"].as_array().unwrap();
2962 assert_eq!(values.len(), 1);
2963 assert_eq!(values[0]["Name"], "exists");
2964
2965 let errors = resp_body["Errors"].as_array().unwrap();
2966 assert_eq!(errors.len(), 1);
2967 assert_eq!(errors[0]["SecretId"], "nonexistent");
2968 assert_eq!(errors[0]["ErrorCode"], "ResourceNotFoundException");
2969 }
2970
2971 #[tokio::test]
2972 async fn test_update_secret_changes_description_and_kms() {
2973 let state = make_state();
2974 let svc = SecretsManagerService::new(state);
2975
2976 let req = make_request(
2977 "CreateSecret",
2978 r#"{"Name": "updatable", "SecretString": "val", "Description": "old desc"}"#,
2979 );
2980 svc.handle(req).await.unwrap();
2981
2982 let body = serde_json::json!({
2984 "SecretId": "updatable",
2985 "Description": "new desc",
2986 "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/my-key"
2987 });
2988 let req = make_request("UpdateSecret", &body.to_string());
2989 let resp = svc.handle(req).await.unwrap();
2990 assert_eq!(resp.status, StatusCode::OK);
2991 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2992 assert_eq!(resp_body["Name"], "updatable");
2993 assert!(resp_body.get("VersionId").is_none());
2995
2996 let req = make_request("DescribeSecret", r#"{"SecretId": "updatable"}"#);
2998 let resp = svc.handle(req).await.unwrap();
2999 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3000 assert_eq!(body["Description"], "new desc");
3001 assert_eq!(
3002 body["KmsKeyId"],
3003 "arn:aws:kms:us-east-1:123456789012:key/my-key"
3004 );
3005 }
3006
3007 #[tokio::test]
3008 async fn test_update_secret_with_new_value() {
3009 let state = make_state();
3010 let svc = SecretsManagerService::new(state);
3011
3012 let req = make_request(
3013 "CreateSecret",
3014 r#"{"Name": "upd-val", "SecretString": "old"}"#,
3015 );
3016 svc.handle(req).await.unwrap();
3017
3018 let body = serde_json::json!({
3020 "SecretId": "upd-val",
3021 "SecretString": "new-value"
3022 });
3023 let req = make_request("UpdateSecret", &body.to_string());
3024 let resp = svc.handle(req).await.unwrap();
3025 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3026 assert!(resp_body["VersionId"].as_str().is_some());
3027
3028 let req = make_request("GetSecretValue", r#"{"SecretId": "upd-val"}"#);
3030 let resp = svc.handle(req).await.unwrap();
3031 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3032 assert_eq!(body["SecretString"], "new-value");
3033 }
3034
3035 #[tokio::test]
3036 async fn test_get_random_password_custom_length() {
3037 let state = make_state();
3038 let svc = SecretsManagerService::new(state);
3039
3040 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 64}"#);
3041 let resp = svc.handle(req).await.unwrap();
3042 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3043 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 64);
3044 }
3045
3046 #[tokio::test]
3047 async fn test_get_random_password_exclude_chars() {
3048 let state = make_state();
3049 let svc = SecretsManagerService::new(state);
3050
3051 let req = make_request(
3052 "GetRandomPassword",
3053 r#"{"PasswordLength": 100, "ExcludeCharacters": "abc123"}"#,
3054 );
3055 let resp = svc.handle(req).await.unwrap();
3056 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3057 let password = body["RandomPassword"].as_str().unwrap();
3058 assert_eq!(password.len(), 100);
3059 assert!(!password.contains('a'));
3060 assert!(!password.contains('b'));
3061 assert!(!password.contains('c'));
3062 assert!(!password.contains('1'));
3063 assert!(!password.contains('2'));
3064 assert!(!password.contains('3'));
3065 }
3066
3067 #[tokio::test]
3068 async fn test_get_random_password_exclude_types() {
3069 let state = make_state();
3070 let svc = SecretsManagerService::new(state);
3071
3072 let body = serde_json::json!({
3074 "PasswordLength": 50,
3075 "ExcludeUppercase": true,
3076 "ExcludeNumbers": true,
3077 "ExcludePunctuation": true,
3078 "RequireEachIncludedType": false,
3079 });
3080 let req = make_request("GetRandomPassword", &body.to_string());
3081 let resp = svc.handle(req).await.unwrap();
3082 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3083 let password = resp_body["RandomPassword"].as_str().unwrap();
3084 assert_eq!(password.len(), 50);
3085 assert!(password.chars().all(|c| c.is_ascii_lowercase()));
3086 }
3087
3088 #[tokio::test]
3089 async fn test_get_random_password_too_short() {
3090 let state = make_state();
3091 let svc = SecretsManagerService::new(state);
3092
3093 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 3}"#);
3094 assert!(svc.handle(req).await.is_err());
3095 }
3096
3097 #[tokio::test]
3098 async fn test_get_random_password_too_long() {
3099 let state = make_state();
3100 let svc = SecretsManagerService::new(state);
3101
3102 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 4097}"#);
3103 assert!(svc.handle(req).await.is_err());
3104 }
3105
3106 #[tokio::test]
3107 async fn test_update_secret_version_stage_move_current() {
3108 let state = make_state();
3109 let svc = SecretsManagerService::new(state.clone());
3110
3111 let req = make_request(
3112 "CreateSecret",
3113 r#"{"Name": "stage-test", "SecretString": "v1"}"#,
3114 );
3115 svc.handle(req).await.unwrap();
3116
3117 let req = make_request(
3119 "PutSecretValue",
3120 r#"{"SecretId": "stage-test", "SecretString": "v2"}"#,
3121 );
3122 svc.handle(req).await.unwrap();
3123
3124 let (v1_id, v2_id) = {
3126 let _accts = state.read();
3127 let s = _accts.default_ref();
3128 let secret = s.secrets.get("stage-test").unwrap();
3129 let current = secret.current_version_id.clone().unwrap();
3130 let previous = secret
3131 .versions
3132 .iter()
3133 .find(|(id, _)| **id != current)
3134 .map(|(id, _)| id.clone())
3135 .unwrap();
3136 (previous, current)
3137 };
3138
3139 let body = serde_json::json!({
3141 "SecretId": "stage-test",
3142 "VersionStage": "AWSCURRENT",
3143 "MoveToVersionId": v1_id,
3144 "RemoveFromVersionId": v2_id,
3145 });
3146 let req = make_request("UpdateSecretVersionStage", &body.to_string());
3147 let resp = svc.handle(req).await.unwrap();
3148 assert_eq!(resp.status, StatusCode::OK);
3149
3150 let _accts = state.read();
3152 let s = _accts.default_ref();
3153 let secret = s.secrets.get("stage-test").unwrap();
3154 let v1 = secret.versions.get(&v1_id).unwrap();
3155 assert!(v1.stages.contains(&"AWSCURRENT".to_string()));
3156
3157 let v2 = secret.versions.get(&v2_id).unwrap();
3159 assert!(v2.stages.contains(&"AWSPREVIOUS".to_string()));
3160 assert!(!v2.stages.contains(&"AWSCURRENT".to_string()));
3161
3162 assert_eq!(secret.current_version_id.as_deref(), Some(v1_id.as_str()));
3163 }
3164
3165 #[tokio::test]
3166 async fn test_update_secret_version_stage_custom_label() {
3167 let state = make_state();
3168 let svc = SecretsManagerService::new(state.clone());
3169
3170 let req = make_request(
3171 "CreateSecret",
3172 r#"{"Name": "custom-stage", "SecretString": "v1"}"#,
3173 );
3174 svc.handle(req).await.unwrap();
3175
3176 let vid = {
3177 let _accts = state.read();
3178 let s = _accts.default_ref();
3179 s.secrets
3180 .get("custom-stage")
3181 .unwrap()
3182 .current_version_id
3183 .clone()
3184 .unwrap()
3185 };
3186
3187 let body = serde_json::json!({
3189 "SecretId": "custom-stage",
3190 "VersionStage": "MYAPP_LIVE",
3191 "MoveToVersionId": vid,
3192 });
3193 let req = make_request("UpdateSecretVersionStage", &body.to_string());
3194 svc.handle(req).await.unwrap();
3195
3196 let _accts = state.read();
3197 let s = _accts.default_ref();
3198 let secret = s.secrets.get("custom-stage").unwrap();
3199 let ver = secret.versions.get(&vid).unwrap();
3200 assert!(ver.stages.contains(&"MYAPP_LIVE".to_string()));
3201 assert!(ver.stages.contains(&"AWSCURRENT".to_string()));
3202 }
3203
3204 #[tokio::test]
3205 async fn test_validate_resource_policy() {
3206 let state = make_state();
3207 let svc = SecretsManagerService::new(state);
3208
3209 let policy = serde_json::json!({
3210 "Version": "2012-10-17",
3211 "Statement": [{
3212 "Effect": "Allow",
3213 "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
3214 "Action": "secretsmanager:GetSecretValue",
3215 "Resource": "*"
3216 }]
3217 });
3218
3219 let body = serde_json::json!({
3220 "ResourcePolicy": policy.to_string(),
3221 });
3222 let req = make_request("ValidateResourcePolicy", &body.to_string());
3223 let resp = svc.handle(req).await.unwrap();
3224 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3225 assert_eq!(resp_body["PolicyValidationPassed"], true);
3226 assert_eq!(resp_body["ValidationErrors"].as_array().unwrap().len(), 0);
3227 }
3228
3229 #[tokio::test]
3230 async fn test_validate_resource_policy_requires_policy() {
3231 let state = make_state();
3232 let svc = SecretsManagerService::new(state);
3233
3234 let req = make_request("ValidateResourcePolicy", r#"{}"#);
3235 assert!(svc.handle(req).await.is_err());
3236 }
3237
3238 #[tokio::test]
3239 async fn test_put_get_delete_resource_policy() {
3240 let state = make_state();
3241 let svc = SecretsManagerService::new(state);
3242
3243 let req = make_request(
3244 "CreateSecret",
3245 r#"{"Name": "policy-secret", "SecretString": "val"}"#,
3246 );
3247 svc.handle(req).await.unwrap();
3248
3249 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3251 let resp = svc.handle(req).await.unwrap();
3252 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3253 assert_eq!(body["Name"], "policy-secret");
3254 assert!(body.get("ResourcePolicy").is_none());
3255
3256 let policy = r#"{"Version":"2012-10-17","Statement":[]}"#;
3258 let put_body = serde_json::json!({
3259 "SecretId": "policy-secret",
3260 "ResourcePolicy": policy,
3261 });
3262 let req = make_request("PutResourcePolicy", &put_body.to_string());
3263 let resp = svc.handle(req).await.unwrap();
3264 assert_eq!(resp.status, StatusCode::OK);
3265
3266 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3268 let resp = svc.handle(req).await.unwrap();
3269 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3270 assert_eq!(body["ResourcePolicy"], policy);
3271
3272 let req = make_request("DeleteResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3274 let resp = svc.handle(req).await.unwrap();
3275 assert_eq!(resp.status, StatusCode::OK);
3276
3277 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3279 let resp = svc.handle(req).await.unwrap();
3280 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3281 assert!(body.get("ResourcePolicy").is_none());
3282 }
3283
3284 #[tokio::test]
3285 async fn test_batch_get_secret_value_with_deleted() {
3286 let state = make_state();
3287 let svc = SecretsManagerService::new(state);
3288
3289 let req = make_request(
3290 "CreateSecret",
3291 r#"{"Name": "batch-del", "SecretString": "val"}"#,
3292 );
3293 svc.handle(req).await.unwrap();
3294
3295 let req = make_request("DeleteSecret", r#"{"SecretId": "batch-del"}"#);
3297 svc.handle(req).await.unwrap();
3298
3299 let body = serde_json::json!({
3300 "SecretIdList": ["batch-del"]
3301 });
3302 let req = make_request("BatchGetSecretValue", &body.to_string());
3303 let resp = svc.handle(req).await.unwrap();
3304 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3305
3306 assert_eq!(resp_body["SecretValues"].as_array().unwrap().len(), 0);
3308 let errors = resp_body["Errors"].as_array().unwrap();
3309 assert_eq!(errors.len(), 1);
3310 assert_eq!(errors[0]["ErrorCode"], "InvalidRequestException");
3311 }
3312
3313 #[tokio::test]
3316 async fn create_secret_idempotent_same_value() {
3317 let state = make_state();
3318 let svc = SecretsManagerService::new(state);
3319
3320 let token = "a".repeat(32);
3321 let body = serde_json::json!({
3322 "Name": "idem",
3323 "SecretString": "val",
3324 "ClientRequestToken": token,
3325 });
3326 let req = make_request("CreateSecret", &body.to_string());
3327 svc.handle(req).await.unwrap();
3328
3329 let req = make_request("CreateSecret", &body.to_string());
3331 let resp = svc.handle(req).await.unwrap();
3332 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3333 assert_eq!(b["Name"], "idem");
3334 assert_eq!(b["VersionId"], token);
3335 }
3336
3337 #[tokio::test]
3338 async fn create_secret_idempotent_conflict() {
3339 let state = make_state();
3340 let svc = SecretsManagerService::new(state);
3341
3342 let token = "a".repeat(32);
3343 let body = serde_json::json!({
3344 "Name": "conflict",
3345 "SecretString": "val1",
3346 "ClientRequestToken": token,
3347 });
3348 let req = make_request("CreateSecret", &body.to_string());
3349 svc.handle(req).await.unwrap();
3350
3351 let body2 = serde_json::json!({
3353 "Name": "conflict",
3354 "SecretString": "val2",
3355 "ClientRequestToken": token,
3356 });
3357 let req = make_request("CreateSecret", &body2.to_string());
3358 let err = expect_err(svc.handle(req).await);
3359 assert!(err.to_string().contains("ResourceExistsException"));
3360 }
3361
3362 #[tokio::test]
3363 async fn create_secret_duplicate_name_no_token() {
3364 let state = make_state();
3365 let svc = SecretsManagerService::new(state);
3366
3367 let req = make_request("CreateSecret", r#"{"Name": "dup", "SecretString": "v1"}"#);
3368 svc.handle(req).await.unwrap();
3369
3370 let req = make_request("CreateSecret", r#"{"Name": "dup", "SecretString": "v2"}"#);
3371 let err = expect_err(svc.handle(req).await);
3372 assert!(err.to_string().contains("ResourceExistsException"));
3373 }
3374
3375 #[tokio::test]
3376 async fn create_secret_with_tags_and_description() {
3377 let state = make_state();
3378 let svc = SecretsManagerService::new(state);
3379
3380 let body = serde_json::json!({
3381 "Name": "full-secret",
3382 "SecretString": "v",
3383 "Description": "my secret desc",
3384 "KmsKeyId": "alias/my-key",
3385 "Tags": [{"Key": "env", "Value": "staging"}],
3386 });
3387 let req = make_request("CreateSecret", &body.to_string());
3388 svc.handle(req).await.unwrap();
3389
3390 let req = make_request("DescribeSecret", r#"{"SecretId": "full-secret"}"#);
3391 let resp = svc.handle(req).await.unwrap();
3392 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3393 assert_eq!(b["Description"], "my secret desc");
3394 assert_eq!(b["KmsKeyId"], "alias/my-key");
3395 assert_eq!(b["Tags"][0]["Key"], "env");
3396 }
3397
3398 #[tokio::test]
3401 async fn put_secret_value_requires_value() {
3402 let state = make_state();
3403 let svc = SecretsManagerService::new(state);
3404
3405 let req = make_request(
3406 "CreateSecret",
3407 r#"{"Name": "novalue", "SecretString": "v"}"#,
3408 );
3409 svc.handle(req).await.unwrap();
3410
3411 let req = make_request("PutSecretValue", r#"{"SecretId": "novalue"}"#);
3412 let err = expect_err(svc.handle(req).await);
3413 assert!(err.to_string().contains("InvalidRequestException"));
3414 }
3415
3416 #[tokio::test]
3417 async fn put_secret_value_not_found() {
3418 let state = make_state();
3419 let svc = SecretsManagerService::new(state);
3420
3421 let req = make_request(
3422 "PutSecretValue",
3423 r#"{"SecretId": "ghost", "SecretString": "v"}"#,
3424 );
3425 let err = expect_err(svc.handle(req).await);
3426 assert!(err.to_string().contains("ResourceNotFoundException"));
3427 }
3428
3429 #[tokio::test]
3430 async fn put_secret_value_on_deleted_secret() {
3431 let state = make_state();
3432 let svc = SecretsManagerService::new(state);
3433
3434 let req = make_request(
3435 "CreateSecret",
3436 r#"{"Name": "del-put", "SecretString": "v"}"#,
3437 );
3438 svc.handle(req).await.unwrap();
3439 let req = make_request("DeleteSecret", r#"{"SecretId": "del-put"}"#);
3440 svc.handle(req).await.unwrap();
3441
3442 let req = make_request(
3443 "PutSecretValue",
3444 r#"{"SecretId": "del-put", "SecretString": "v2"}"#,
3445 );
3446 let err = expect_err(svc.handle(req).await);
3447 assert!(err.to_string().contains("InvalidRequestException"));
3448 }
3449
3450 #[tokio::test]
3451 async fn put_secret_value_idempotent_match() {
3452 let state = make_state();
3453 let svc = SecretsManagerService::new(state);
3454
3455 let req = make_request(
3456 "CreateSecret",
3457 r#"{"Name": "put-idem", "SecretString": "original"}"#,
3458 );
3459 svc.handle(req).await.unwrap();
3460
3461 let token = "b".repeat(32);
3462 let body = serde_json::json!({
3463 "SecretId": "put-idem",
3464 "SecretString": "new-val",
3465 "ClientRequestToken": token,
3466 });
3467 let req = make_request("PutSecretValue", &body.to_string());
3468 svc.handle(req).await.unwrap();
3469
3470 let req = make_request("PutSecretValue", &body.to_string());
3472 let resp = svc.handle(req).await.unwrap();
3473 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3474 assert_eq!(b["VersionId"], token);
3475 }
3476
3477 #[tokio::test]
3478 async fn put_secret_value_idempotent_conflict() {
3479 let state = make_state();
3480 let svc = SecretsManagerService::new(state);
3481
3482 let req = make_request(
3483 "CreateSecret",
3484 r#"{"Name": "put-conflict", "SecretString": "original"}"#,
3485 );
3486 svc.handle(req).await.unwrap();
3487
3488 let token = "c".repeat(32);
3489 let body = serde_json::json!({
3490 "SecretId": "put-conflict",
3491 "SecretString": "val-a",
3492 "ClientRequestToken": token,
3493 });
3494 let req = make_request("PutSecretValue", &body.to_string());
3495 svc.handle(req).await.unwrap();
3496
3497 let body2 = serde_json::json!({
3499 "SecretId": "put-conflict",
3500 "SecretString": "val-b",
3501 "ClientRequestToken": token,
3502 });
3503 let req = make_request("PutSecretValue", &body2.to_string());
3504 let err = expect_err(svc.handle(req).await);
3505 assert!(err.to_string().contains("ResourceExistsException"));
3506 }
3507
3508 #[tokio::test]
3509 async fn put_secret_value_with_custom_stages() {
3510 let state = make_state();
3511 let svc = SecretsManagerService::new(state.clone());
3512
3513 let req = make_request(
3514 "CreateSecret",
3515 r#"{"Name": "staged", "SecretString": "v1"}"#,
3516 );
3517 svc.handle(req).await.unwrap();
3518
3519 let body = serde_json::json!({
3520 "SecretId": "staged",
3521 "SecretString": "v2",
3522 "VersionStages": ["AWSCURRENT", "MYAPP_V2"],
3523 });
3524 let req = make_request("PutSecretValue", &body.to_string());
3525 let resp = svc.handle(req).await.unwrap();
3526 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3527 let stages = b["VersionStages"].as_array().unwrap();
3528 assert!(stages.iter().any(|s| s == "MYAPP_V2"));
3529 }
3530
3531 #[tokio::test]
3534 async fn update_secret_not_found() {
3535 let state = make_state();
3536 let svc = SecretsManagerService::new(state);
3537
3538 let body = serde_json::json!({
3539 "SecretId": "ghost",
3540 "Description": "new",
3541 });
3542 let req = make_request("UpdateSecret", &body.to_string());
3543 let err = expect_err(svc.handle(req).await);
3544 assert!(err.to_string().contains("ResourceNotFoundException"));
3545 }
3546
3547 #[tokio::test]
3548 async fn update_secret_on_deleted() {
3549 let state = make_state();
3550 let svc = SecretsManagerService::new(state);
3551
3552 let req = make_request(
3553 "CreateSecret",
3554 r#"{"Name": "upd-del", "SecretString": "v"}"#,
3555 );
3556 svc.handle(req).await.unwrap();
3557 let req = make_request("DeleteSecret", r#"{"SecretId": "upd-del"}"#);
3558 svc.handle(req).await.unwrap();
3559
3560 let body = serde_json::json!({
3561 "SecretId": "upd-del",
3562 "Description": "new",
3563 });
3564 let req = make_request("UpdateSecret", &body.to_string());
3565 let err = expect_err(svc.handle(req).await);
3566 assert!(err.to_string().contains("InvalidRequestException"));
3567 }
3568
3569 #[tokio::test]
3570 async fn update_secret_idempotent_match() {
3571 let state = make_state();
3572 let svc = SecretsManagerService::new(state);
3573
3574 let req = make_request(
3575 "CreateSecret",
3576 r#"{"Name": "upd-idem", "SecretString": "orig"}"#,
3577 );
3578 svc.handle(req).await.unwrap();
3579
3580 let token = "d".repeat(32);
3581 let body = serde_json::json!({
3582 "SecretId": "upd-idem",
3583 "SecretString": "new-val",
3584 "ClientRequestToken": token,
3585 });
3586 let req = make_request("UpdateSecret", &body.to_string());
3587 svc.handle(req).await.unwrap();
3588
3589 let req = make_request("UpdateSecret", &body.to_string());
3591 let resp = svc.handle(req).await.unwrap();
3592 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3593 assert_eq!(b["VersionId"], token);
3594 }
3595
3596 #[tokio::test]
3599 async fn delete_secret_force() {
3600 let state = make_state();
3601 let svc = SecretsManagerService::new(state.clone());
3602
3603 let req = make_request(
3604 "CreateSecret",
3605 r#"{"Name": "force-del", "SecretString": "v"}"#,
3606 );
3607 svc.handle(req).await.unwrap();
3608
3609 let body = serde_json::json!({
3610 "SecretId": "force-del",
3611 "ForceDeleteWithoutRecovery": true,
3612 });
3613 let req = make_request("DeleteSecret", &body.to_string());
3614 let resp = svc.handle(req).await.unwrap();
3615 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3616 assert_eq!(b["Name"], "force-del");
3617
3618 let _accts = state.read();
3620 let s = _accts.default_ref();
3621 assert!(!s.secrets.contains_key("force-del"));
3622 }
3623
3624 #[tokio::test]
3625 async fn delete_secret_force_nonexistent() {
3626 let state = make_state();
3627 let svc = SecretsManagerService::new(state);
3628
3629 let body = serde_json::json!({
3630 "SecretId": "not-here",
3631 "ForceDeleteWithoutRecovery": true,
3632 });
3633 let req = make_request("DeleteSecret", &body.to_string());
3634 let resp = svc.handle(req).await.unwrap();
3635 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3636 assert_eq!(b["Name"], "not-here");
3637 }
3638
3639 #[tokio::test]
3640 async fn delete_secret_recovery_window() {
3641 let state = make_state();
3642 let svc = SecretsManagerService::new(state);
3643
3644 let req = make_request(
3645 "CreateSecret",
3646 r#"{"Name": "rec-win", "SecretString": "v"}"#,
3647 );
3648 svc.handle(req).await.unwrap();
3649
3650 let body = serde_json::json!({
3651 "SecretId": "rec-win",
3652 "RecoveryWindowInDays": 7,
3653 });
3654 let req = make_request("DeleteSecret", &body.to_string());
3655 let resp = svc.handle(req).await.unwrap();
3656 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3657 assert!(b["DeletionDate"].as_f64().is_some());
3658 }
3659
3660 #[tokio::test]
3661 async fn delete_secret_invalid_recovery_window() {
3662 let state = make_state();
3663 let svc = SecretsManagerService::new(state);
3664
3665 let req = make_request(
3666 "CreateSecret",
3667 r#"{"Name": "bad-win", "SecretString": "v"}"#,
3668 );
3669 svc.handle(req).await.unwrap();
3670
3671 let body = serde_json::json!({
3673 "SecretId": "bad-win",
3674 "RecoveryWindowInDays": 3,
3675 });
3676 let req = make_request("DeleteSecret", &body.to_string());
3677 let err = expect_err(svc.handle(req).await);
3678 assert!(err.to_string().contains("InvalidParameterException"));
3679
3680 let body = serde_json::json!({
3682 "SecretId": "bad-win",
3683 "RecoveryWindowInDays": 31,
3684 });
3685 let req = make_request("DeleteSecret", &body.to_string());
3686 let err = expect_err(svc.handle(req).await);
3687 assert!(err.to_string().contains("InvalidParameterException"));
3688 }
3689
3690 #[tokio::test]
3691 async fn delete_secret_force_and_recovery_conflict() {
3692 let state = make_state();
3693 let svc = SecretsManagerService::new(state);
3694
3695 let req = make_request("CreateSecret", r#"{"Name": "both", "SecretString": "v"}"#);
3696 svc.handle(req).await.unwrap();
3697
3698 let body = serde_json::json!({
3699 "SecretId": "both",
3700 "ForceDeleteWithoutRecovery": true,
3701 "RecoveryWindowInDays": 7,
3702 });
3703 let req = make_request("DeleteSecret", &body.to_string());
3704 let err = expect_err(svc.handle(req).await);
3705 assert!(err.to_string().contains("InvalidParameterException"));
3706 }
3707
3708 #[tokio::test]
3709 async fn delete_already_deleted_secret() {
3710 let state = make_state();
3711 let svc = SecretsManagerService::new(state);
3712
3713 let req = make_request(
3714 "CreateSecret",
3715 r#"{"Name": "dbl-del", "SecretString": "v"}"#,
3716 );
3717 svc.handle(req).await.unwrap();
3718
3719 let req = make_request("DeleteSecret", r#"{"SecretId": "dbl-del"}"#);
3720 svc.handle(req).await.unwrap();
3721
3722 let req = make_request("DeleteSecret", r#"{"SecretId": "dbl-del"}"#);
3723 let err = expect_err(svc.handle(req).await);
3724 assert!(err.to_string().contains("InvalidRequestException"));
3725 }
3726
3727 #[tokio::test]
3730 async fn get_secret_value_by_version_id() {
3731 let state = make_state();
3732 let svc = SecretsManagerService::new(state.clone());
3733
3734 let req = make_request(
3735 "CreateSecret",
3736 r#"{"Name": "ver-get", "SecretString": "v1"}"#,
3737 );
3738 svc.handle(req).await.unwrap();
3739
3740 let v1_id = {
3741 let _accts = state.read();
3742 let s = _accts.default_ref();
3743 s.secrets
3744 .get("ver-get")
3745 .unwrap()
3746 .current_version_id
3747 .clone()
3748 .unwrap()
3749 };
3750
3751 let req = make_request(
3752 "PutSecretValue",
3753 r#"{"SecretId": "ver-get", "SecretString": "v2"}"#,
3754 );
3755 svc.handle(req).await.unwrap();
3756
3757 let body = serde_json::json!({
3759 "SecretId": "ver-get",
3760 "VersionId": v1_id,
3761 "VersionStage": "AWSPREVIOUS",
3762 });
3763 let req = make_request("GetSecretValue", &body.to_string());
3764 let resp = svc.handle(req).await.unwrap();
3765 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3766 assert_eq!(b["SecretString"], "v1");
3767 }
3768
3769 #[tokio::test]
3770 async fn get_secret_value_version_stage_mismatch() {
3771 let state = make_state();
3772 let svc = SecretsManagerService::new(state.clone());
3773
3774 let req = make_request(
3775 "CreateSecret",
3776 r#"{"Name": "mismatch", "SecretString": "v1"}"#,
3777 );
3778 svc.handle(req).await.unwrap();
3779
3780 let vid = {
3781 let _accts = state.read();
3782 let s = _accts.default_ref();
3783 s.secrets
3784 .get("mismatch")
3785 .unwrap()
3786 .current_version_id
3787 .clone()
3788 .unwrap()
3789 };
3790
3791 let body = serde_json::json!({
3793 "SecretId": "mismatch",
3794 "VersionId": vid,
3795 "VersionStage": "AWSPREVIOUS",
3796 });
3797 let req = make_request("GetSecretValue", &body.to_string());
3798 let err = expect_err(svc.handle(req).await);
3799 assert!(err.to_string().contains("ResourceNotFoundException"));
3800 }
3801
3802 #[tokio::test]
3803 async fn get_secret_value_not_found() {
3804 let state = make_state();
3805 let svc = SecretsManagerService::new(state);
3806
3807 let req = make_request("GetSecretValue", r#"{"SecretId": "nope"}"#);
3808 let err = expect_err(svc.handle(req).await);
3809 assert!(err.to_string().contains("ResourceNotFoundException"));
3810 }
3811
3812 #[tokio::test]
3813 async fn get_secret_value_no_versions() {
3814 let state = make_state();
3815 let svc = SecretsManagerService::new(state);
3816
3817 let req = make_request("CreateSecret", r#"{"Name": "empty-ver"}"#);
3818 svc.handle(req).await.unwrap();
3819
3820 let req = make_request("GetSecretValue", r#"{"SecretId": "empty-ver"}"#);
3821 let err = expect_err(svc.handle(req).await);
3822 assert!(err.to_string().contains("ResourceNotFoundException"));
3823 }
3824
3825 #[tokio::test]
3826 async fn get_secret_value_with_binary() {
3827 let state = make_state();
3828 let svc = SecretsManagerService::new(state);
3829
3830 let body = serde_json::json!({
3832 "Name": "bin-secret",
3833 "SecretBinary": "SGVsbG8=", });
3835 let req = make_request("CreateSecret", &body.to_string());
3836 svc.handle(req).await.unwrap();
3837
3838 let req = make_request("GetSecretValue", r#"{"SecretId": "bin-secret"}"#);
3839 let resp = svc.handle(req).await.unwrap();
3840 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3841 assert!(b.get("SecretBinary").is_some());
3842 assert!(b.get("SecretString").is_none());
3843 }
3844
3845 #[tokio::test]
3848 async fn list_secrets_filter_by_name() {
3849 let state = make_state();
3850 let svc = SecretsManagerService::new(state);
3851
3852 for name in &["prod/db", "prod/api", "staging/db"] {
3853 let body = serde_json::json!({"Name": name, "SecretString": "v"});
3854 let req = make_request("CreateSecret", &body.to_string());
3855 svc.handle(req).await.unwrap();
3856 }
3857
3858 let body = serde_json::json!({
3859 "Filters": [{"Key": "name", "Values": ["prod/"]}]
3860 });
3861 let req = make_request("ListSecrets", &body.to_string());
3862 let resp = svc.handle(req).await.unwrap();
3863 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3864 assert_eq!(b["SecretList"].as_array().unwrap().len(), 2);
3865 }
3866
3867 #[tokio::test]
3868 async fn list_secrets_filter_by_tag_key() {
3869 let state = make_state();
3870 let svc = SecretsManagerService::new(state);
3871
3872 let body = serde_json::json!({
3873 "Name": "tagged-s",
3874 "SecretString": "v",
3875 "Tags": [{"Key": "team", "Value": "backend"}],
3876 });
3877 let req = make_request("CreateSecret", &body.to_string());
3878 svc.handle(req).await.unwrap();
3879
3880 let body = serde_json::json!({"Name": "untagged-s", "SecretString": "v"});
3881 let req = make_request("CreateSecret", &body.to_string());
3882 svc.handle(req).await.unwrap();
3883
3884 let body = serde_json::json!({
3885 "Filters": [{"Key": "tag-key", "Values": ["team"]}]
3886 });
3887 let req = make_request("ListSecrets", &body.to_string());
3888 let resp = svc.handle(req).await.unwrap();
3889 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3890 assert_eq!(b["SecretList"].as_array().unwrap().len(), 1);
3891 assert_eq!(b["SecretList"][0]["Name"], "tagged-s");
3892 }
3893
3894 #[tokio::test]
3895 async fn list_secrets_filter_by_description() {
3896 let state = make_state();
3897 let svc = SecretsManagerService::new(state);
3898
3899 let body = serde_json::json!({
3900 "Name": "desc-match",
3901 "SecretString": "v",
3902 "Description": "Database credentials for production",
3903 });
3904 let req = make_request("CreateSecret", &body.to_string());
3905 svc.handle(req).await.unwrap();
3906
3907 let body = serde_json::json!({"Name": "no-desc", "SecretString": "v"});
3908 let req = make_request("CreateSecret", &body.to_string());
3909 svc.handle(req).await.unwrap();
3910
3911 let body = serde_json::json!({
3912 "Filters": [{"Key": "description", "Values": ["Database"]}]
3913 });
3914 let req = make_request("ListSecrets", &body.to_string());
3915 let resp = svc.handle(req).await.unwrap();
3916 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3917 assert_eq!(b["SecretList"].as_array().unwrap().len(), 1);
3918 }
3919
3920 #[tokio::test]
3921 async fn list_secrets_include_planned_deletion() {
3922 let state = make_state();
3923 let svc = SecretsManagerService::new(state);
3924
3925 let req = make_request("CreateSecret", r#"{"Name": "alive", "SecretString": "v"}"#);
3926 svc.handle(req).await.unwrap();
3927
3928 let req = make_request("CreateSecret", r#"{"Name": "doomed", "SecretString": "v"}"#);
3929 svc.handle(req).await.unwrap();
3930 let req = make_request("DeleteSecret", r#"{"SecretId": "doomed"}"#);
3931 svc.handle(req).await.unwrap();
3932
3933 let req = make_request("ListSecrets", "{}");
3935 let resp = svc.handle(req).await.unwrap();
3936 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3937 assert_eq!(b["SecretList"].as_array().unwrap().len(), 1);
3938
3939 let body = serde_json::json!({"IncludePlannedDeletion": true});
3941 let req = make_request("ListSecrets", &body.to_string());
3942 let resp = svc.handle(req).await.unwrap();
3943 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3944 assert_eq!(b["SecretList"].as_array().unwrap().len(), 2);
3945 }
3946
3947 #[tokio::test]
3948 async fn list_secrets_pagination() {
3949 let state = make_state();
3950 let svc = SecretsManagerService::new(state);
3951
3952 for i in 0..5 {
3953 let body = serde_json::json!({
3954 "Name": format!("page-{i}"),
3955 "SecretString": "v",
3956 });
3957 let req = make_request("CreateSecret", &body.to_string());
3958 svc.handle(req).await.unwrap();
3959 }
3960
3961 let body = serde_json::json!({"MaxResults": 2});
3962 let req = make_request("ListSecrets", &body.to_string());
3963 let resp = svc.handle(req).await.unwrap();
3964 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3965 assert_eq!(b["SecretList"].as_array().unwrap().len(), 2);
3966 assert!(b["NextToken"].as_str().is_some());
3967 }
3968
3969 #[tokio::test]
3970 async fn list_secrets_invalid_filter_key() {
3971 let state = make_state();
3972 let svc = SecretsManagerService::new(state);
3973
3974 let body = serde_json::json!({
3975 "Filters": [{"Key": "bogus", "Values": ["x"]}]
3976 });
3977 let req = make_request("ListSecrets", &body.to_string());
3978 let err = expect_err(svc.handle(req).await);
3979 assert!(err.to_string().contains("ValidationException"));
3980 }
3981
3982 #[tokio::test]
3983 async fn list_secrets_empty_filter_values() {
3984 let state = make_state();
3985 let svc = SecretsManagerService::new(state);
3986
3987 let body = serde_json::json!({
3988 "Filters": [{"Key": "name", "Values": []}]
3989 });
3990 let req = make_request("ListSecrets", &body.to_string());
3991 let err = expect_err(svc.handle(req).await);
3992 assert!(err.to_string().contains("InvalidParameterException"));
3993 }
3994
3995 #[tokio::test]
3998 async fn list_secret_version_ids() {
3999 let state = make_state();
4000 let svc = SecretsManagerService::new(state);
4001
4002 let req = make_request(
4003 "CreateSecret",
4004 r#"{"Name": "multi-ver", "SecretString": "v1"}"#,
4005 );
4006 svc.handle(req).await.unwrap();
4007
4008 let req = make_request(
4009 "PutSecretValue",
4010 r#"{"SecretId": "multi-ver", "SecretString": "v2"}"#,
4011 );
4012 svc.handle(req).await.unwrap();
4013
4014 let req = make_request("ListSecretVersionIds", r#"{"SecretId": "multi-ver"}"#);
4015 let resp = svc.handle(req).await.unwrap();
4016 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4017 assert_eq!(b["Name"], "multi-ver");
4018 assert_eq!(b["Versions"].as_array().unwrap().len(), 2);
4019 }
4020
4021 #[tokio::test]
4024 async fn describe_secret_with_rotation_and_next_date() {
4025 let state = make_state();
4026 let svc = SecretsManagerService::new(state);
4027
4028 let req = make_request(
4029 "CreateSecret",
4030 r#"{"Name": "rot-desc", "SecretString": "pw"}"#,
4031 );
4032 svc.handle(req).await.unwrap();
4033
4034 let token = "e".repeat(32);
4035 let body = serde_json::json!({
4036 "SecretId": "rot-desc",
4037 "RotationRules": {"AutomaticallyAfterDays": 14},
4038 "ClientRequestToken": token,
4039 });
4040 let req = make_request("RotateSecret", &body.to_string());
4041 svc.handle(req).await.unwrap();
4042
4043 let req = make_request("DescribeSecret", r#"{"SecretId": "rot-desc"}"#);
4044 let resp = svc.handle(req).await.unwrap();
4045 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4046 assert_eq!(b["RotationEnabled"], true);
4047 assert!(b["LastRotatedDate"].as_f64().is_some());
4048 assert!(b["NextRotationDate"].as_f64().is_some());
4049 assert_eq!(b["RotationRules"]["AutomaticallyAfterDays"], 14);
4050 }
4051
4052 #[tokio::test]
4053 async fn describe_secret_deleted_shows_deletion_date() {
4054 let state = make_state();
4055 let svc = SecretsManagerService::new(state);
4056
4057 let req = make_request(
4058 "CreateSecret",
4059 r#"{"Name": "del-desc", "SecretString": "v"}"#,
4060 );
4061 svc.handle(req).await.unwrap();
4062 let req = make_request("DeleteSecret", r#"{"SecretId": "del-desc"}"#);
4063 svc.handle(req).await.unwrap();
4064
4065 let req = make_request("DescribeSecret", r#"{"SecretId": "del-desc"}"#);
4066 let resp = svc.handle(req).await.unwrap();
4067 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4068 assert!(b["DeletedDate"].as_f64().is_some());
4069 }
4070
4071 #[tokio::test]
4074 async fn batch_get_secret_value_both_list_and_filters() {
4075 let state = make_state();
4076 let svc = SecretsManagerService::new(state);
4077
4078 let body = serde_json::json!({
4079 "SecretIdList": ["a"],
4080 "Filters": [{"Key": "name", "Values": ["a"]}],
4081 });
4082 let req = make_request("BatchGetSecretValue", &body.to_string());
4083 let err = expect_err(svc.handle(req).await);
4084 assert!(err.to_string().contains("InvalidParameterException"));
4085 }
4086
4087 #[tokio::test]
4088 async fn batch_get_secret_value_max_results_without_filters() {
4089 let state = make_state();
4090 let svc = SecretsManagerService::new(state);
4091
4092 let body = serde_json::json!({
4093 "SecretIdList": ["a"],
4094 "MaxResults": 10,
4095 });
4096 let req = make_request("BatchGetSecretValue", &body.to_string());
4097 let err = expect_err(svc.handle(req).await);
4098 assert!(err.to_string().contains("InvalidParameterException"));
4099 }
4100
4101 #[tokio::test]
4102 async fn batch_get_secret_value_with_filters() {
4103 let state = make_state();
4104 let svc = SecretsManagerService::new(state);
4105
4106 for name in &["batch-f-a", "batch-f-b", "other-c"] {
4107 let body = serde_json::json!({"Name": name, "SecretString": "v"});
4108 let req = make_request("CreateSecret", &body.to_string());
4109 svc.handle(req).await.unwrap();
4110 }
4111
4112 let body = serde_json::json!({
4113 "Filters": [{"Key": "name", "Values": ["batch-f"]}],
4114 });
4115 let req = make_request("BatchGetSecretValue", &body.to_string());
4116 let resp = svc.handle(req).await.unwrap();
4117 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4118 assert_eq!(b["SecretValues"].as_array().unwrap().len(), 2);
4119 }
4120
4121 #[tokio::test]
4124 async fn rotate_secret_invalid_token_length() {
4125 let state = make_state();
4126 let svc = SecretsManagerService::new(state);
4127
4128 let req = make_request(
4129 "CreateSecret",
4130 r#"{"Name": "rot-val", "SecretString": "v"}"#,
4131 );
4132 svc.handle(req).await.unwrap();
4133
4134 let body = serde_json::json!({
4135 "SecretId": "rot-val",
4136 "ClientRequestToken": "short",
4137 });
4138 let req = make_request("RotateSecret", &body.to_string());
4139 let err = expect_err(svc.handle(req).await);
4140 assert!(err.to_string().contains("InvalidParameterException"));
4141 }
4142
4143 #[tokio::test]
4144 async fn rotate_secret_invalid_rules() {
4145 let state = make_state();
4146 let svc = SecretsManagerService::new(state);
4147
4148 let req = make_request(
4149 "CreateSecret",
4150 r#"{"Name": "rot-rules", "SecretString": "v"}"#,
4151 );
4152 svc.handle(req).await.unwrap();
4153
4154 let body = serde_json::json!({
4155 "SecretId": "rot-rules",
4156 "RotationRules": {"AutomaticallyAfterDays": 0},
4157 });
4158 let req = make_request("RotateSecret", &body.to_string());
4159 let err = expect_err(svc.handle(req).await);
4160 assert!(err.to_string().contains("InvalidParameterException"));
4161 }
4162
4163 #[tokio::test]
4164 async fn rotate_secret_on_deleted() {
4165 let state = make_state();
4166 let svc = SecretsManagerService::new(state);
4167
4168 let req = make_request(
4169 "CreateSecret",
4170 r#"{"Name": "rot-del", "SecretString": "v"}"#,
4171 );
4172 svc.handle(req).await.unwrap();
4173 let req = make_request("DeleteSecret", r#"{"SecretId": "rot-del"}"#);
4174 svc.handle(req).await.unwrap();
4175
4176 let body = serde_json::json!({"SecretId": "rot-del"});
4177 let req = make_request("RotateSecret", &body.to_string());
4178 let err = expect_err(svc.handle(req).await);
4179 assert!(err.to_string().contains("InvalidRequestException"));
4180 }
4181
4182 #[tokio::test]
4185 async fn cancel_rotate_on_deleted() {
4186 let state = make_state();
4187 let svc = SecretsManagerService::new(state);
4188
4189 let req = make_request("CreateSecret", r#"{"Name": "cr-del", "SecretString": "v"}"#);
4190 svc.handle(req).await.unwrap();
4191 let req = make_request("DeleteSecret", r#"{"SecretId": "cr-del"}"#);
4192 svc.handle(req).await.unwrap();
4193
4194 let req = make_request("CancelRotateSecret", r#"{"SecretId": "cr-del"}"#);
4195 let err = expect_err(svc.handle(req).await);
4196 assert!(err.to_string().contains("InvalidRequestException"));
4197 }
4198
4199 #[tokio::test]
4202 async fn update_version_stage_missing_remove_from() {
4203 let state = make_state();
4204 let svc = SecretsManagerService::new(state.clone());
4205
4206 let req = make_request(
4207 "CreateSecret",
4208 r#"{"Name": "stage-err", "SecretString": "v1"}"#,
4209 );
4210 svc.handle(req).await.unwrap();
4211
4212 let req = make_request(
4213 "PutSecretValue",
4214 r#"{"SecretId": "stage-err", "SecretString": "v2"}"#,
4215 );
4216 svc.handle(req).await.unwrap();
4217
4218 let new_vid = {
4219 let _accts = state.read();
4220 let s = _accts.default_ref();
4221 let secret = s.secrets.get("stage-err").unwrap();
4222 secret
4223 .versions
4224 .iter()
4225 .find(|(_, v)| v.stages.contains(&"AWSPREVIOUS".to_string()))
4226 .map(|(id, _)| id.clone())
4227 .unwrap()
4228 };
4229
4230 let body = serde_json::json!({
4232 "SecretId": "stage-err",
4233 "VersionStage": "AWSCURRENT",
4234 "MoveToVersionId": new_vid,
4235 });
4236 let req = make_request("UpdateSecretVersionStage", &body.to_string());
4237 let err = expect_err(svc.handle(req).await);
4238 assert!(err.to_string().contains("InvalidParameterException"));
4239 }
4240
4241 #[tokio::test]
4244 async fn find_secret_by_arn() {
4245 let state = make_state();
4246 let svc = SecretsManagerService::new(state);
4247
4248 let req = make_request(
4249 "CreateSecret",
4250 r#"{"Name": "arn-lookup", "SecretString": "v"}"#,
4251 );
4252 let resp = svc.handle(req).await.unwrap();
4253 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4254 let arn = b["ARN"].as_str().unwrap();
4255
4256 let body = serde_json::json!({"SecretId": arn});
4258 let req = make_request("GetSecretValue", &body.to_string());
4259 let resp = svc.handle(req).await.unwrap();
4260 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4261 assert_eq!(b["SecretString"], "v");
4262 }
4263
4264 #[tokio::test]
4265 async fn find_secret_by_partial_arn() {
4266 let state = make_state();
4267 let svc = SecretsManagerService::new(state);
4268
4269 let req = make_request(
4270 "CreateSecret",
4271 r#"{"Name": "partial-arn", "SecretString": "v"}"#,
4272 );
4273 svc.handle(req).await.unwrap();
4274
4275 let partial = "arn:aws:secretsmanager:us-east-1:123456789012:secret:partial-arn";
4277 let body = serde_json::json!({"SecretId": partial});
4278 let req = make_request("GetSecretValue", &body.to_string());
4279 let resp = svc.handle(req).await.unwrap();
4280 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4281 assert_eq!(b["SecretString"], "v");
4282 }
4283
4284 #[tokio::test]
4287 async fn validate_resource_policy_with_secret_id() {
4288 let state = make_state();
4289 let svc = SecretsManagerService::new(state);
4290
4291 let req = make_request(
4292 "CreateSecret",
4293 r#"{"Name": "pol-val", "SecretString": "v"}"#,
4294 );
4295 svc.handle(req).await.unwrap();
4296
4297 let body = serde_json::json!({
4298 "SecretId": "pol-val",
4299 "ResourcePolicy": r#"{"Version":"2012-10-17","Statement":[]}"#,
4300 });
4301 let req = make_request("ValidateResourcePolicy", &body.to_string());
4302 let resp = svc.handle(req).await.unwrap();
4303 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4304 assert_eq!(b["PolicyValidationPassed"], true);
4305 }
4306
4307 #[tokio::test]
4308 async fn validate_resource_policy_nonexistent_secret() {
4309 let state = make_state();
4310 let svc = SecretsManagerService::new(state);
4311
4312 let body = serde_json::json!({
4313 "SecretId": "ghost",
4314 "ResourcePolicy": r#"{"Version":"2012-10-17","Statement":[]}"#,
4315 });
4316 let req = make_request("ValidateResourcePolicy", &body.to_string());
4317 let err = expect_err(svc.handle(req).await);
4318 assert!(err.to_string().contains("ResourceNotFoundException"));
4319 }
4320
4321 #[tokio::test]
4324 async fn tag_resource_updates_existing_tag() {
4325 let state = make_state();
4326 let svc = SecretsManagerService::new(state);
4327
4328 let body = serde_json::json!({
4329 "Name": "tag-upd",
4330 "SecretString": "v",
4331 "Tags": [{"Key": "env", "Value": "dev"}],
4332 });
4333 let req = make_request("CreateSecret", &body.to_string());
4334 svc.handle(req).await.unwrap();
4335
4336 let body = serde_json::json!({
4338 "SecretId": "tag-upd",
4339 "Tags": [{"Key": "env", "Value": "prod"}],
4340 });
4341 let req = make_request("TagResource", &body.to_string());
4342 svc.handle(req).await.unwrap();
4343
4344 let req = make_request("DescribeSecret", r#"{"SecretId": "tag-upd"}"#);
4345 let resp = svc.handle(req).await.unwrap();
4346 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4347 let tags = b["Tags"].as_array().unwrap();
4348 assert_eq!(tags.len(), 1);
4349 assert_eq!(tags[0]["Value"], "prod");
4350 }
4351
4352 #[tokio::test]
4355 async fn unsupported_action_returns_error() {
4356 let state = make_state();
4357 let svc = SecretsManagerService::new(state);
4358
4359 let req = make_request("BogusAction", "{}");
4360 let err = expect_err(svc.handle(req).await);
4361 assert!(err.to_string().contains("BogusAction"));
4362 }
4363
4364 #[test]
4367 fn test_split_words_basic() {
4368 assert_eq!(split_words("hello"), vec!["hello"]);
4369 assert_eq!(split_words("HelloWorld"), vec!["Hello", "World"]);
4370 assert_eq!(split_words("my/secret/name"), vec!["my", "secret", "name"]);
4371 assert_eq!(split_words("my-secret-name"), vec!["my", "secret", "name"]);
4372 assert_eq!(split_words("my_secret_name"), vec!["my", "secret", "name"]);
4373 }
4374
4375 #[test]
4376 fn test_split_words_multiple_delimiters() {
4377 assert_eq!(split_words("my/secret-name"), vec!["my/secret-name"]);
4379 }
4380
4381 #[test]
4382 fn test_split_words_with_spaces() {
4383 let words = split_words("hello world");
4384 assert_eq!(words, vec!["hello", "world"]);
4385 }
4386
4387 #[test]
4388 fn test_match_pattern_prefix() {
4389 assert!(match_pattern("prod", "production", true, true));
4390 assert!(!match_pattern("Prod", "production", true, true));
4391 assert!(match_pattern("Prod", "production", true, false));
4392 }
4393
4394 #[test]
4395 fn test_match_pattern_word() {
4396 assert!(match_pattern("hello", "HelloWorld", false, false));
4397 assert!(match_pattern("world", "HelloWorld", false, false));
4398 }
4399
4400 #[test]
4401 fn test_matcher_negation() {
4402 assert!(matcher(&["!prod"], &["staging"], true, true));
4404 }
4405
4406 #[test]
4407 fn test_base64_roundtrip() {
4408 let data = b"Hello, World!";
4409 let encoded = base64_encode(data);
4410 let decoded = base64_decode(&encoded).unwrap();
4411 assert_eq!(&decoded, data);
4412 }
4413
4414 #[test]
4415 fn test_base64_decode_invalid() {
4416 assert!(base64_decode("!!!").is_none());
4418 }
4419
4420 #[test]
4421 fn test_check_version_idempotency() {
4422 let mut versions = HashMap::new();
4423 versions.insert(
4424 "v1".to_string(),
4425 SecretVersion {
4426 version_id: "v1".to_string(),
4427 secret_string: Some("hello".to_string()),
4428 secret_binary: None,
4429 stages: vec!["AWSCURRENT".to_string()],
4430 created_at: Utc::now(),
4431 },
4432 );
4433
4434 assert!(matches!(
4436 check_secret_version_idempotency(&versions, "v2", None, &Some("x".to_string()), &None),
4437 VersionIdempotency::NotFound
4438 ));
4439
4440 assert!(matches!(
4442 check_secret_version_idempotency(
4443 &versions,
4444 "v1",
4445 Some("hello".to_string()),
4446 &Some("hello".to_string()),
4447 &None
4448 ),
4449 VersionIdempotency::Match
4450 ));
4451
4452 assert!(matches!(
4454 check_secret_version_idempotency(
4455 &versions,
4456 "v1",
4457 Some("hello".to_string()),
4458 &Some("different".to_string()),
4459 &None
4460 ),
4461 VersionIdempotency::Conflict
4462 ));
4463 }
4464
4465 #[test]
4466 fn test_is_mutating_action() {
4467 assert!(is_mutating_action("CreateSecret"));
4468 assert!(is_mutating_action("DeleteSecret"));
4469 assert!(is_mutating_action("TagResource"));
4470 assert!(!is_mutating_action("GetSecretValue"));
4471 assert!(!is_mutating_action("ListSecrets"));
4472 assert!(!is_mutating_action("DescribeSecret"));
4473 }
4474
4475 #[test]
4476 fn test_parse_tags_empty() {
4477 let val = serde_json::json!(null);
4478 assert_eq!(parse_tags(&val), vec![]);
4479 }
4480
4481 #[test]
4482 fn test_tags_to_json_roundtrip() {
4483 let tags = vec![
4484 ("k1".to_string(), "v1".to_string()),
4485 ("k2".to_string(), "v2".to_string()),
4486 ];
4487 let json = tags_to_json(&tags);
4488 assert_eq!(json.len(), 2);
4489 assert_eq!(json[0]["Key"], "k1");
4490 assert_eq!(json[1]["Value"], "v2");
4491 }
4492
4493 #[test]
4494 fn test_filter_name_prefix() {
4495 let secret = Secret {
4496 name: "prod/database".to_string(),
4497 arn: "arn".to_string(),
4498 description: None,
4499 kms_key_id: None,
4500 versions: HashMap::new(),
4501 current_version_id: None,
4502 tags: vec![],
4503 tags_ever_set: false,
4504 deleted: false,
4505 deletion_date: None,
4506 created_at: Utc::now(),
4507 last_changed_at: Utc::now(),
4508 last_accessed_at: None,
4509 rotation_enabled: None,
4510 rotation_lambda_arn: None,
4511 rotation_rules: None,
4512 last_rotated_at: None,
4513 resource_policy: None,
4514 };
4515 assert!(filter_name(&secret, &["prod/"]));
4516 assert!(!filter_name(&secret, &["staging/"]));
4517 }
4518
4519 #[test]
4520 fn test_filter_tag_value() {
4521 let secret = Secret {
4522 name: "s".to_string(),
4523 arn: "arn".to_string(),
4524 description: None,
4525 kms_key_id: None,
4526 versions: HashMap::new(),
4527 current_version_id: None,
4528 tags: vec![("env".to_string(), "production".to_string())],
4529 tags_ever_set: true,
4530 deleted: false,
4531 deletion_date: None,
4532 created_at: Utc::now(),
4533 last_changed_at: Utc::now(),
4534 last_accessed_at: None,
4535 rotation_enabled: None,
4536 rotation_lambda_arn: None,
4537 rotation_rules: None,
4538 last_rotated_at: None,
4539 resource_policy: None,
4540 };
4541 assert!(filter_tag_value(&secret, &["prod"]));
4542 assert!(!filter_tag_value(&secret, &["staging"]));
4543 }
4544
4545 #[test]
4546 fn test_filter_all_searches_name_desc_tags() {
4547 let secret = Secret {
4548 name: "my-secret".to_string(),
4549 arn: "arn".to_string(),
4550 description: Some("important database".to_string()),
4551 kms_key_id: None,
4552 versions: HashMap::new(),
4553 current_version_id: None,
4554 tags: vec![("team".to_string(), "backend".to_string())],
4555 tags_ever_set: true,
4556 deleted: false,
4557 deletion_date: None,
4558 created_at: Utc::now(),
4559 last_changed_at: Utc::now(),
4560 last_accessed_at: None,
4561 rotation_enabled: None,
4562 rotation_lambda_arn: None,
4563 rotation_rules: None,
4564 last_rotated_at: None,
4565 resource_policy: None,
4566 };
4567 assert!(filter_all(&secret, &["my"]));
4569 assert!(filter_all(&secret, &["database"]));
4571 assert!(filter_all(&secret, &["team"]));
4573 assert!(filter_all(&secret, &["backend"]));
4575 assert!(!filter_all(&secret, &["zzzz"]));
4577 }
4578}