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 path_segments: vec![],
2433 raw_path: "/".to_string(),
2434 raw_query: String::new(),
2435 method: Method::POST,
2436 is_query_protocol: false,
2437 access_key_id: None,
2438 principal: None,
2439 }
2440 }
2441
2442 #[tokio::test]
2443 async fn test_create_and_get_secret() {
2444 let state = make_state();
2445 let svc = SecretsManagerService::new(state);
2446
2447 let req = make_request(
2448 "CreateSecret",
2449 r#"{"Name": "test/secret", "SecretString": "mysecretvalue"}"#,
2450 );
2451 let resp = svc.handle(req).await.unwrap();
2452 assert_eq!(resp.status, StatusCode::OK);
2453 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2454 assert_eq!(body["Name"], "test/secret");
2455 assert!(body["ARN"].as_str().unwrap().contains("test/secret"));
2456
2457 let req = make_request("GetSecretValue", r#"{"SecretId": "test/secret"}"#);
2458 let resp = svc.handle(req).await.unwrap();
2459 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2460 assert_eq!(body["SecretString"], "mysecretvalue");
2461 }
2462
2463 #[tokio::test]
2464 async fn test_create_secret_without_value() {
2465 let state = make_state();
2466 let svc = SecretsManagerService::new(state);
2467
2468 let req = make_request("CreateSecret", r#"{"Name": "empty-secret"}"#);
2469 let resp = svc.handle(req).await.unwrap();
2470 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2471 assert_eq!(body["Name"], "empty-secret");
2472 assert!(body.get("VersionId").is_none());
2473 }
2474
2475 #[tokio::test]
2476 async fn test_put_secret_value_creates_version() {
2477 let state = make_state();
2478 let svc = SecretsManagerService::new(state);
2479
2480 let req = make_request(
2481 "CreateSecret",
2482 r#"{"Name": "versioned", "SecretString": "v1"}"#,
2483 );
2484 svc.handle(req).await.unwrap();
2485
2486 let req = make_request(
2487 "PutSecretValue",
2488 r#"{"SecretId": "versioned", "SecretString": "v2"}"#,
2489 );
2490 let resp = svc.handle(req).await.unwrap();
2491 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2492 assert_eq!(body["Name"], "versioned");
2493
2494 let req = make_request("GetSecretValue", r#"{"SecretId": "versioned"}"#);
2496 let resp = svc.handle(req).await.unwrap();
2497 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2498 assert_eq!(body["SecretString"], "v2");
2499 }
2500
2501 #[tokio::test]
2502 async fn test_delete_and_restore_secret() {
2503 let state = make_state();
2504 let svc = SecretsManagerService::new(state);
2505
2506 let req = make_request(
2507 "CreateSecret",
2508 r#"{"Name": "deleteme", "SecretString": "value"}"#,
2509 );
2510 svc.handle(req).await.unwrap();
2511
2512 let req = make_request("DeleteSecret", r#"{"SecretId": "deleteme"}"#);
2514 let resp = svc.handle(req).await.unwrap();
2515 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2516 assert!(body["DeletionDate"].as_f64().is_some());
2517
2518 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2520 assert!(svc.handle(req).await.is_err());
2521
2522 let req = make_request("RestoreSecret", r#"{"SecretId": "deleteme"}"#);
2524 let resp = svc.handle(req).await.unwrap();
2525 assert_eq!(resp.status, StatusCode::OK);
2526
2527 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2529 let resp = svc.handle(req).await.unwrap();
2530 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2531 assert_eq!(body["SecretString"], "value");
2532 }
2533
2534 #[tokio::test]
2535 async fn test_list_secrets() {
2536 let state = make_state();
2537 let svc = SecretsManagerService::new(state);
2538
2539 for name in &["alpha", "beta", "gamma"] {
2540 let req = make_request(
2541 "CreateSecret",
2542 &format!(r#"{{"Name": "{name}", "SecretString": "val"}}"#),
2543 );
2544 svc.handle(req).await.unwrap();
2545 }
2546
2547 let req = make_request("ListSecrets", "{}");
2548 let resp = svc.handle(req).await.unwrap();
2549 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2550 assert_eq!(body["SecretList"].as_array().unwrap().len(), 3);
2551 }
2552
2553 #[tokio::test]
2554 async fn test_tags() {
2555 let state = make_state();
2556 let svc = SecretsManagerService::new(state);
2557
2558 let req = make_request(
2559 "CreateSecret",
2560 r#"{"Name": "tagged", "SecretString": "val"}"#,
2561 );
2562 svc.handle(req).await.unwrap();
2563
2564 let req = make_request(
2565 "TagResource",
2566 r#"{"SecretId": "tagged", "Tags": [{"Key": "env", "Value": "prod"}]}"#,
2567 );
2568 svc.handle(req).await.unwrap();
2569
2570 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2571 let resp = svc.handle(req).await.unwrap();
2572 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2573 let tags = body["Tags"].as_array().unwrap();
2574 assert!(tags
2575 .iter()
2576 .any(|t| t["Key"] == "env" && t["Value"] == "prod"));
2577
2578 let req = make_request(
2579 "UntagResource",
2580 r#"{"SecretId": "tagged", "TagKeys": ["env"]}"#,
2581 );
2582 svc.handle(req).await.unwrap();
2583
2584 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2585 let resp = svc.handle(req).await.unwrap();
2586 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2587 assert_eq!(body["Tags"].as_array().unwrap().len(), 0);
2589 }
2590
2591 #[tokio::test]
2592 async fn test_get_random_password() {
2593 let state = make_state();
2594 let svc = SecretsManagerService::new(state);
2595
2596 let req = make_request("GetRandomPassword", "{}");
2597 let resp = svc.handle(req).await.unwrap();
2598 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2599 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 32);
2600 }
2601
2602 #[tokio::test]
2603 async fn test_replication_ops_return_arn() {
2604 let state = make_state();
2605 let svc = SecretsManagerService::new(state);
2606
2607 let req = make_request(
2608 "CreateSecret",
2609 r#"{"Name": "repl-secret", "SecretString": "val"}"#,
2610 );
2611 let resp = svc.handle(req).await.unwrap();
2612 let create_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2613 let expected_arn = create_body["ARN"].as_str().unwrap();
2614
2615 for action in &[
2616 "ReplicateSecretToRegions",
2617 "RemoveRegionsFromReplication",
2618 "StopReplicationToReplica",
2619 ] {
2620 let req = make_request(action, r#"{"SecretId": "repl-secret"}"#);
2621 let resp = svc.handle(req).await.unwrap();
2622 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2623 assert_eq!(
2624 body["ARN"].as_str().unwrap(),
2625 expected_arn,
2626 "{action} should return the secret's actual ARN"
2627 );
2628 }
2629 }
2630
2631 #[tokio::test]
2632 async fn test_secret_id_length_validation() {
2633 let state = make_state();
2634 let svc = SecretsManagerService::new(state);
2635
2636 let long_id = "x".repeat(2049);
2638 let req = make_request("GetSecretValue", &format!(r#"{{"SecretId": "{long_id}"}}"#));
2639 match svc.handle(req).await {
2640 Err(e) => assert!(e.to_string().contains("ValidationException")),
2641 Ok(_) => panic!("expected ValidationException"),
2642 }
2643 }
2644
2645 #[tokio::test]
2646 async fn test_name_length_validation() {
2647 let state = make_state();
2648 let svc = SecretsManagerService::new(state);
2649
2650 let long_name = "x".repeat(513);
2652 let req = make_request(
2653 "CreateSecret",
2654 &format!(r#"{{"Name": "{long_name}", "SecretString": "val"}}"#),
2655 );
2656 match svc.handle(req).await {
2657 Err(e) => assert!(e.to_string().contains("ValidationException")),
2658 Ok(_) => panic!("expected ValidationException"),
2659 }
2660 }
2661
2662 #[tokio::test]
2663 async fn test_next_token_length_validation() {
2664 let state = make_state();
2665 let svc = SecretsManagerService::new(state);
2666
2667 let long_token = "x".repeat(4097);
2669 let req = make_request(
2670 "ListSecrets",
2671 &format!(r#"{{"NextToken": "{long_token}"}}"#),
2672 );
2673 match svc.handle(req).await {
2674 Err(e) => assert!(e.to_string().contains("ValidationException")),
2675 Ok(_) => panic!("expected ValidationException"),
2676 }
2677 }
2678
2679 #[tokio::test]
2680 async fn test_client_request_token_length_validation() {
2681 let state = make_state();
2682 let svc = SecretsManagerService::new(state);
2683
2684 let req = make_request(
2686 "CreateSecret",
2687 r#"{"Name": "test", "SecretString": "val", "ClientRequestToken": "short"}"#,
2688 );
2689 match svc.handle(req).await {
2690 Err(e) => assert!(e.to_string().contains("ValidationException")),
2691 Ok(_) => panic!("expected ValidationException"),
2692 }
2693 }
2694
2695 #[tokio::test]
2696 async fn test_rotate_secret_with_lambda_creates_pending_version() {
2697 let state = make_state();
2698 let svc = SecretsManagerService::new(state.clone());
2699
2700 let req = make_request(
2702 "CreateSecret",
2703 r#"{"Name": "rotate-me", "SecretString": "old-password"}"#,
2704 );
2705 svc.handle(req).await.unwrap();
2706
2707 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2709 let body = serde_json::json!({
2710 "SecretId": "rotate-me",
2711 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:rotator",
2712 "ClientRequestToken": token,
2713 });
2714 let req = make_request("RotateSecret", &body.to_string());
2715 let resp = svc.handle(req).await.unwrap();
2716 assert_eq!(resp.status, StatusCode::OK);
2717 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2718 assert_eq!(resp_body["VersionId"], token);
2719
2720 let _accts = state.read();
2724 let s = _accts.default_ref();
2725 let secret = s.secrets.get("rotate-me").unwrap();
2726 assert!(
2727 !secret.versions.contains_key(token),
2728 "AWSPENDING version must not be pre-created; the rotation Lambda creates it"
2729 );
2730
2731 assert_eq!(
2733 secret.rotation_lambda_arn.as_deref(),
2734 Some("arn:aws:lambda:us-east-1:123456789012:function:rotator")
2735 );
2736 assert_eq!(secret.rotation_enabled, Some(true));
2737 }
2738
2739 #[tokio::test]
2740 async fn test_rotate_secret_without_lambda_promotes_directly() {
2741 let state = make_state();
2742 let svc = SecretsManagerService::new(state.clone());
2743
2744 let req = make_request(
2746 "CreateSecret",
2747 r#"{"Name": "rotate-no-lambda", "SecretString": "value1"}"#,
2748 );
2749 svc.handle(req).await.unwrap();
2750
2751 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2753 let body = serde_json::json!({
2754 "SecretId": "rotate-no-lambda",
2755 "ClientRequestToken": token,
2756 });
2757 let req = make_request("RotateSecret", &body.to_string());
2758 svc.handle(req).await.unwrap();
2759
2760 let _accts = state.read();
2762 let s = _accts.default_ref();
2763 let secret = s.secrets.get("rotate-no-lambda").unwrap();
2764 let new_ver = secret.versions.get(token).unwrap();
2765 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2766 assert_eq!(secret.current_version_id.as_deref(), Some(token));
2767 }
2768
2769 #[tokio::test]
2770 async fn test_rotate_secret_stores_rotation_config() {
2771 let state = make_state();
2772 let svc = SecretsManagerService::new(state.clone());
2773
2774 let req = make_request(
2775 "CreateSecret",
2776 r#"{"Name": "rot-cfg", "SecretString": "pw"}"#,
2777 );
2778 svc.handle(req).await.unwrap();
2779
2780 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2781 let body = serde_json::json!({
2782 "SecretId": "rot-cfg",
2783 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:my-rotator",
2784 "RotationRules": { "AutomaticallyAfterDays": 30 },
2785 "ClientRequestToken": token,
2786 });
2787 let req = make_request("RotateSecret", &body.to_string());
2788 let resp = svc.handle(req).await.unwrap();
2789 assert_eq!(resp.status, StatusCode::OK);
2790
2791 let _accts = state.read();
2792 let s = _accts.default_ref();
2793 let secret = s.secrets.get("rot-cfg").unwrap();
2794 assert_eq!(secret.rotation_enabled, Some(true));
2795 assert_eq!(
2796 secret.rotation_lambda_arn.as_deref(),
2797 Some("arn:aws:lambda:us-east-1:123456789012:function:my-rotator")
2798 );
2799 assert!(secret.last_rotated_at.is_some());
2800 let rules = secret.rotation_rules.as_ref().unwrap();
2801 assert_eq!(rules.automatically_after_days, Some(30));
2802
2803 assert!(!secret.versions.contains_key(token));
2807 }
2808
2809 #[tokio::test]
2810 async fn test_rotate_secret_version_stages_change() {
2811 let state = make_state();
2812 let svc = SecretsManagerService::new(state.clone());
2813
2814 let req = make_request(
2815 "CreateSecret",
2816 r#"{"Name": "rot-stages", "SecretString": "original"}"#,
2817 );
2818 svc.handle(req).await.unwrap();
2819
2820 let original_vid = {
2822 let _accts = state.read();
2823 let s = _accts.default_ref();
2824 let secret = s.secrets.get("rot-stages").unwrap();
2825 secret.current_version_id.clone().unwrap()
2826 };
2827
2828 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2830 let body = serde_json::json!({
2831 "SecretId": "rot-stages",
2832 "ClientRequestToken": token,
2833 });
2834 let req = make_request("RotateSecret", &body.to_string());
2835 svc.handle(req).await.unwrap();
2836
2837 let _accts = state.read();
2838 let s = _accts.default_ref();
2839 let secret = s.secrets.get("rot-stages").unwrap();
2840
2841 let new_ver = secret.versions.get(token).unwrap();
2843 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2844
2845 let old_ver = secret.versions.get(&original_vid).unwrap();
2847 assert!(old_ver.stages.contains(&"AWSPREVIOUS".to_string()));
2848 assert!(!old_ver.stages.contains(&"AWSCURRENT".to_string()));
2849 }
2850
2851 #[tokio::test]
2852 async fn test_cancel_rotate_secret() {
2853 let state = make_state();
2854 let svc = SecretsManagerService::new(state.clone());
2855
2856 let req = make_request(
2857 "CreateSecret",
2858 r#"{"Name": "cancel-rot", "SecretString": "pw"}"#,
2859 );
2860 svc.handle(req).await.unwrap();
2861
2862 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2864 let body = serde_json::json!({
2865 "SecretId": "cancel-rot",
2866 "ClientRequestToken": token,
2867 });
2868 let req = make_request("RotateSecret", &body.to_string());
2869 svc.handle(req).await.unwrap();
2870
2871 {
2873 let _accts = state.read();
2874 let s = _accts.default_ref();
2875 let secret = s.secrets.get("cancel-rot").unwrap();
2876 assert_eq!(secret.rotation_enabled, Some(true));
2877 }
2878
2879 let req = make_request("CancelRotateSecret", r#"{"SecretId": "cancel-rot"}"#);
2881 let resp = svc.handle(req).await.unwrap();
2882 assert_eq!(resp.status, StatusCode::OK);
2883 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2884 assert_eq!(body["Name"], "cancel-rot");
2885
2886 let _accts = state.read();
2888 let s = _accts.default_ref();
2889 let secret = s.secrets.get("cancel-rot").unwrap();
2890 assert_eq!(secret.rotation_enabled, Some(false));
2891 }
2892
2893 #[tokio::test]
2894 async fn test_cancel_rotate_secret_fails_when_not_enabled() {
2895 let state = make_state();
2896 let svc = SecretsManagerService::new(state);
2897
2898 let req = make_request(
2899 "CreateSecret",
2900 r#"{"Name": "no-rot", "SecretString": "pw"}"#,
2901 );
2902 svc.handle(req).await.unwrap();
2903
2904 let req = make_request("CancelRotateSecret", r#"{"SecretId": "no-rot"}"#);
2905 let result = svc.handle(req).await;
2906 assert!(result.is_err());
2907 }
2908
2909 #[tokio::test]
2910 async fn test_batch_get_secret_value_multiple() {
2911 let state = make_state();
2912 let svc = SecretsManagerService::new(state);
2913
2914 for (name, val) in &[("batch-a", "va"), ("batch-b", "vb"), ("batch-c", "vc")] {
2915 let req = make_request(
2916 "CreateSecret",
2917 &format!(r#"{{"Name": "{name}", "SecretString": "{val}"}}"#),
2918 );
2919 svc.handle(req).await.unwrap();
2920 }
2921
2922 let body = serde_json::json!({
2923 "SecretIdList": ["batch-a", "batch-b", "batch-c"]
2924 });
2925 let req = make_request("BatchGetSecretValue", &body.to_string());
2926 let resp = svc.handle(req).await.unwrap();
2927 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2928
2929 let values = resp_body["SecretValues"].as_array().unwrap();
2930 assert_eq!(values.len(), 3);
2931
2932 let names: Vec<&str> = values.iter().map(|v| v["Name"].as_str().unwrap()).collect();
2934 assert!(names.contains(&"batch-a"));
2935 assert!(names.contains(&"batch-b"));
2936 assert!(names.contains(&"batch-c"));
2937
2938 assert!(resp_body.get("Errors").is_none());
2940 }
2941
2942 #[tokio::test]
2943 async fn test_batch_get_secret_value_with_missing() {
2944 let state = make_state();
2945 let svc = SecretsManagerService::new(state);
2946
2947 let req = make_request(
2948 "CreateSecret",
2949 r#"{"Name": "exists", "SecretString": "val"}"#,
2950 );
2951 svc.handle(req).await.unwrap();
2952
2953 let body = serde_json::json!({
2954 "SecretIdList": ["exists", "nonexistent"]
2955 });
2956 let req = make_request("BatchGetSecretValue", &body.to_string());
2957 let resp = svc.handle(req).await.unwrap();
2958 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2959
2960 let values = resp_body["SecretValues"].as_array().unwrap();
2961 assert_eq!(values.len(), 1);
2962 assert_eq!(values[0]["Name"], "exists");
2963
2964 let errors = resp_body["Errors"].as_array().unwrap();
2965 assert_eq!(errors.len(), 1);
2966 assert_eq!(errors[0]["SecretId"], "nonexistent");
2967 assert_eq!(errors[0]["ErrorCode"], "ResourceNotFoundException");
2968 }
2969
2970 #[tokio::test]
2971 async fn test_update_secret_changes_description_and_kms() {
2972 let state = make_state();
2973 let svc = SecretsManagerService::new(state);
2974
2975 let req = make_request(
2976 "CreateSecret",
2977 r#"{"Name": "updatable", "SecretString": "val", "Description": "old desc"}"#,
2978 );
2979 svc.handle(req).await.unwrap();
2980
2981 let body = serde_json::json!({
2983 "SecretId": "updatable",
2984 "Description": "new desc",
2985 "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/my-key"
2986 });
2987 let req = make_request("UpdateSecret", &body.to_string());
2988 let resp = svc.handle(req).await.unwrap();
2989 assert_eq!(resp.status, StatusCode::OK);
2990 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2991 assert_eq!(resp_body["Name"], "updatable");
2992 assert!(resp_body.get("VersionId").is_none());
2994
2995 let req = make_request("DescribeSecret", r#"{"SecretId": "updatable"}"#);
2997 let resp = svc.handle(req).await.unwrap();
2998 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2999 assert_eq!(body["Description"], "new desc");
3000 assert_eq!(
3001 body["KmsKeyId"],
3002 "arn:aws:kms:us-east-1:123456789012:key/my-key"
3003 );
3004 }
3005
3006 #[tokio::test]
3007 async fn test_update_secret_with_new_value() {
3008 let state = make_state();
3009 let svc = SecretsManagerService::new(state);
3010
3011 let req = make_request(
3012 "CreateSecret",
3013 r#"{"Name": "upd-val", "SecretString": "old"}"#,
3014 );
3015 svc.handle(req).await.unwrap();
3016
3017 let body = serde_json::json!({
3019 "SecretId": "upd-val",
3020 "SecretString": "new-value"
3021 });
3022 let req = make_request("UpdateSecret", &body.to_string());
3023 let resp = svc.handle(req).await.unwrap();
3024 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3025 assert!(resp_body["VersionId"].as_str().is_some());
3026
3027 let req = make_request("GetSecretValue", r#"{"SecretId": "upd-val"}"#);
3029 let resp = svc.handle(req).await.unwrap();
3030 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3031 assert_eq!(body["SecretString"], "new-value");
3032 }
3033
3034 #[tokio::test]
3035 async fn test_get_random_password_custom_length() {
3036 let state = make_state();
3037 let svc = SecretsManagerService::new(state);
3038
3039 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 64}"#);
3040 let resp = svc.handle(req).await.unwrap();
3041 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3042 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 64);
3043 }
3044
3045 #[tokio::test]
3046 async fn test_get_random_password_exclude_chars() {
3047 let state = make_state();
3048 let svc = SecretsManagerService::new(state);
3049
3050 let req = make_request(
3051 "GetRandomPassword",
3052 r#"{"PasswordLength": 100, "ExcludeCharacters": "abc123"}"#,
3053 );
3054 let resp = svc.handle(req).await.unwrap();
3055 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3056 let password = body["RandomPassword"].as_str().unwrap();
3057 assert_eq!(password.len(), 100);
3058 assert!(!password.contains('a'));
3059 assert!(!password.contains('b'));
3060 assert!(!password.contains('c'));
3061 assert!(!password.contains('1'));
3062 assert!(!password.contains('2'));
3063 assert!(!password.contains('3'));
3064 }
3065
3066 #[tokio::test]
3067 async fn test_get_random_password_exclude_types() {
3068 let state = make_state();
3069 let svc = SecretsManagerService::new(state);
3070
3071 let body = serde_json::json!({
3073 "PasswordLength": 50,
3074 "ExcludeUppercase": true,
3075 "ExcludeNumbers": true,
3076 "ExcludePunctuation": true,
3077 "RequireEachIncludedType": false,
3078 });
3079 let req = make_request("GetRandomPassword", &body.to_string());
3080 let resp = svc.handle(req).await.unwrap();
3081 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3082 let password = resp_body["RandomPassword"].as_str().unwrap();
3083 assert_eq!(password.len(), 50);
3084 assert!(password.chars().all(|c| c.is_ascii_lowercase()));
3085 }
3086
3087 #[tokio::test]
3088 async fn test_get_random_password_too_short() {
3089 let state = make_state();
3090 let svc = SecretsManagerService::new(state);
3091
3092 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 3}"#);
3093 assert!(svc.handle(req).await.is_err());
3094 }
3095
3096 #[tokio::test]
3097 async fn test_get_random_password_too_long() {
3098 let state = make_state();
3099 let svc = SecretsManagerService::new(state);
3100
3101 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 4097}"#);
3102 assert!(svc.handle(req).await.is_err());
3103 }
3104
3105 #[tokio::test]
3106 async fn test_update_secret_version_stage_move_current() {
3107 let state = make_state();
3108 let svc = SecretsManagerService::new(state.clone());
3109
3110 let req = make_request(
3111 "CreateSecret",
3112 r#"{"Name": "stage-test", "SecretString": "v1"}"#,
3113 );
3114 svc.handle(req).await.unwrap();
3115
3116 let req = make_request(
3118 "PutSecretValue",
3119 r#"{"SecretId": "stage-test", "SecretString": "v2"}"#,
3120 );
3121 svc.handle(req).await.unwrap();
3122
3123 let (v1_id, v2_id) = {
3125 let _accts = state.read();
3126 let s = _accts.default_ref();
3127 let secret = s.secrets.get("stage-test").unwrap();
3128 let current = secret.current_version_id.clone().unwrap();
3129 let previous = secret
3130 .versions
3131 .iter()
3132 .find(|(id, _)| **id != current)
3133 .map(|(id, _)| id.clone())
3134 .unwrap();
3135 (previous, current)
3136 };
3137
3138 let body = serde_json::json!({
3140 "SecretId": "stage-test",
3141 "VersionStage": "AWSCURRENT",
3142 "MoveToVersionId": v1_id,
3143 "RemoveFromVersionId": v2_id,
3144 });
3145 let req = make_request("UpdateSecretVersionStage", &body.to_string());
3146 let resp = svc.handle(req).await.unwrap();
3147 assert_eq!(resp.status, StatusCode::OK);
3148
3149 let _accts = state.read();
3151 let s = _accts.default_ref();
3152 let secret = s.secrets.get("stage-test").unwrap();
3153 let v1 = secret.versions.get(&v1_id).unwrap();
3154 assert!(v1.stages.contains(&"AWSCURRENT".to_string()));
3155
3156 let v2 = secret.versions.get(&v2_id).unwrap();
3158 assert!(v2.stages.contains(&"AWSPREVIOUS".to_string()));
3159 assert!(!v2.stages.contains(&"AWSCURRENT".to_string()));
3160
3161 assert_eq!(secret.current_version_id.as_deref(), Some(v1_id.as_str()));
3162 }
3163
3164 #[tokio::test]
3165 async fn test_update_secret_version_stage_custom_label() {
3166 let state = make_state();
3167 let svc = SecretsManagerService::new(state.clone());
3168
3169 let req = make_request(
3170 "CreateSecret",
3171 r#"{"Name": "custom-stage", "SecretString": "v1"}"#,
3172 );
3173 svc.handle(req).await.unwrap();
3174
3175 let vid = {
3176 let _accts = state.read();
3177 let s = _accts.default_ref();
3178 s.secrets
3179 .get("custom-stage")
3180 .unwrap()
3181 .current_version_id
3182 .clone()
3183 .unwrap()
3184 };
3185
3186 let body = serde_json::json!({
3188 "SecretId": "custom-stage",
3189 "VersionStage": "MYAPP_LIVE",
3190 "MoveToVersionId": vid,
3191 });
3192 let req = make_request("UpdateSecretVersionStage", &body.to_string());
3193 svc.handle(req).await.unwrap();
3194
3195 let _accts = state.read();
3196 let s = _accts.default_ref();
3197 let secret = s.secrets.get("custom-stage").unwrap();
3198 let ver = secret.versions.get(&vid).unwrap();
3199 assert!(ver.stages.contains(&"MYAPP_LIVE".to_string()));
3200 assert!(ver.stages.contains(&"AWSCURRENT".to_string()));
3201 }
3202
3203 #[tokio::test]
3204 async fn test_validate_resource_policy() {
3205 let state = make_state();
3206 let svc = SecretsManagerService::new(state);
3207
3208 let policy = serde_json::json!({
3209 "Version": "2012-10-17",
3210 "Statement": [{
3211 "Effect": "Allow",
3212 "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
3213 "Action": "secretsmanager:GetSecretValue",
3214 "Resource": "*"
3215 }]
3216 });
3217
3218 let body = serde_json::json!({
3219 "ResourcePolicy": policy.to_string(),
3220 });
3221 let req = make_request("ValidateResourcePolicy", &body.to_string());
3222 let resp = svc.handle(req).await.unwrap();
3223 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3224 assert_eq!(resp_body["PolicyValidationPassed"], true);
3225 assert_eq!(resp_body["ValidationErrors"].as_array().unwrap().len(), 0);
3226 }
3227
3228 #[tokio::test]
3229 async fn test_validate_resource_policy_requires_policy() {
3230 let state = make_state();
3231 let svc = SecretsManagerService::new(state);
3232
3233 let req = make_request("ValidateResourcePolicy", r#"{}"#);
3234 assert!(svc.handle(req).await.is_err());
3235 }
3236
3237 #[tokio::test]
3238 async fn test_put_get_delete_resource_policy() {
3239 let state = make_state();
3240 let svc = SecretsManagerService::new(state);
3241
3242 let req = make_request(
3243 "CreateSecret",
3244 r#"{"Name": "policy-secret", "SecretString": "val"}"#,
3245 );
3246 svc.handle(req).await.unwrap();
3247
3248 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3250 let resp = svc.handle(req).await.unwrap();
3251 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3252 assert_eq!(body["Name"], "policy-secret");
3253 assert!(body.get("ResourcePolicy").is_none());
3254
3255 let policy = r#"{"Version":"2012-10-17","Statement":[]}"#;
3257 let put_body = serde_json::json!({
3258 "SecretId": "policy-secret",
3259 "ResourcePolicy": policy,
3260 });
3261 let req = make_request("PutResourcePolicy", &put_body.to_string());
3262 let resp = svc.handle(req).await.unwrap();
3263 assert_eq!(resp.status, StatusCode::OK);
3264
3265 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3267 let resp = svc.handle(req).await.unwrap();
3268 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3269 assert_eq!(body["ResourcePolicy"], policy);
3270
3271 let req = make_request("DeleteResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3273 let resp = svc.handle(req).await.unwrap();
3274 assert_eq!(resp.status, StatusCode::OK);
3275
3276 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3278 let resp = svc.handle(req).await.unwrap();
3279 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3280 assert!(body.get("ResourcePolicy").is_none());
3281 }
3282
3283 #[tokio::test]
3284 async fn test_batch_get_secret_value_with_deleted() {
3285 let state = make_state();
3286 let svc = SecretsManagerService::new(state);
3287
3288 let req = make_request(
3289 "CreateSecret",
3290 r#"{"Name": "batch-del", "SecretString": "val"}"#,
3291 );
3292 svc.handle(req).await.unwrap();
3293
3294 let req = make_request("DeleteSecret", r#"{"SecretId": "batch-del"}"#);
3296 svc.handle(req).await.unwrap();
3297
3298 let body = serde_json::json!({
3299 "SecretIdList": ["batch-del"]
3300 });
3301 let req = make_request("BatchGetSecretValue", &body.to_string());
3302 let resp = svc.handle(req).await.unwrap();
3303 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3304
3305 assert_eq!(resp_body["SecretValues"].as_array().unwrap().len(), 0);
3307 let errors = resp_body["Errors"].as_array().unwrap();
3308 assert_eq!(errors.len(), 1);
3309 assert_eq!(errors[0]["ErrorCode"], "InvalidRequestException");
3310 }
3311
3312 #[tokio::test]
3315 async fn create_secret_idempotent_same_value() {
3316 let state = make_state();
3317 let svc = SecretsManagerService::new(state);
3318
3319 let token = "a".repeat(32);
3320 let body = serde_json::json!({
3321 "Name": "idem",
3322 "SecretString": "val",
3323 "ClientRequestToken": token,
3324 });
3325 let req = make_request("CreateSecret", &body.to_string());
3326 svc.handle(req).await.unwrap();
3327
3328 let req = make_request("CreateSecret", &body.to_string());
3330 let resp = svc.handle(req).await.unwrap();
3331 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3332 assert_eq!(b["Name"], "idem");
3333 assert_eq!(b["VersionId"], token);
3334 }
3335
3336 #[tokio::test]
3337 async fn create_secret_idempotent_conflict() {
3338 let state = make_state();
3339 let svc = SecretsManagerService::new(state);
3340
3341 let token = "a".repeat(32);
3342 let body = serde_json::json!({
3343 "Name": "conflict",
3344 "SecretString": "val1",
3345 "ClientRequestToken": token,
3346 });
3347 let req = make_request("CreateSecret", &body.to_string());
3348 svc.handle(req).await.unwrap();
3349
3350 let body2 = serde_json::json!({
3352 "Name": "conflict",
3353 "SecretString": "val2",
3354 "ClientRequestToken": token,
3355 });
3356 let req = make_request("CreateSecret", &body2.to_string());
3357 let err = expect_err(svc.handle(req).await);
3358 assert!(err.to_string().contains("ResourceExistsException"));
3359 }
3360
3361 #[tokio::test]
3362 async fn create_secret_duplicate_name_no_token() {
3363 let state = make_state();
3364 let svc = SecretsManagerService::new(state);
3365
3366 let req = make_request("CreateSecret", r#"{"Name": "dup", "SecretString": "v1"}"#);
3367 svc.handle(req).await.unwrap();
3368
3369 let req = make_request("CreateSecret", r#"{"Name": "dup", "SecretString": "v2"}"#);
3370 let err = expect_err(svc.handle(req).await);
3371 assert!(err.to_string().contains("ResourceExistsException"));
3372 }
3373
3374 #[tokio::test]
3375 async fn create_secret_with_tags_and_description() {
3376 let state = make_state();
3377 let svc = SecretsManagerService::new(state);
3378
3379 let body = serde_json::json!({
3380 "Name": "full-secret",
3381 "SecretString": "v",
3382 "Description": "my secret desc",
3383 "KmsKeyId": "alias/my-key",
3384 "Tags": [{"Key": "env", "Value": "staging"}],
3385 });
3386 let req = make_request("CreateSecret", &body.to_string());
3387 svc.handle(req).await.unwrap();
3388
3389 let req = make_request("DescribeSecret", r#"{"SecretId": "full-secret"}"#);
3390 let resp = svc.handle(req).await.unwrap();
3391 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3392 assert_eq!(b["Description"], "my secret desc");
3393 assert_eq!(b["KmsKeyId"], "alias/my-key");
3394 assert_eq!(b["Tags"][0]["Key"], "env");
3395 }
3396
3397 #[tokio::test]
3400 async fn put_secret_value_requires_value() {
3401 let state = make_state();
3402 let svc = SecretsManagerService::new(state);
3403
3404 let req = make_request(
3405 "CreateSecret",
3406 r#"{"Name": "novalue", "SecretString": "v"}"#,
3407 );
3408 svc.handle(req).await.unwrap();
3409
3410 let req = make_request("PutSecretValue", r#"{"SecretId": "novalue"}"#);
3411 let err = expect_err(svc.handle(req).await);
3412 assert!(err.to_string().contains("InvalidRequestException"));
3413 }
3414
3415 #[tokio::test]
3416 async fn put_secret_value_not_found() {
3417 let state = make_state();
3418 let svc = SecretsManagerService::new(state);
3419
3420 let req = make_request(
3421 "PutSecretValue",
3422 r#"{"SecretId": "ghost", "SecretString": "v"}"#,
3423 );
3424 let err = expect_err(svc.handle(req).await);
3425 assert!(err.to_string().contains("ResourceNotFoundException"));
3426 }
3427
3428 #[tokio::test]
3429 async fn put_secret_value_on_deleted_secret() {
3430 let state = make_state();
3431 let svc = SecretsManagerService::new(state);
3432
3433 let req = make_request(
3434 "CreateSecret",
3435 r#"{"Name": "del-put", "SecretString": "v"}"#,
3436 );
3437 svc.handle(req).await.unwrap();
3438 let req = make_request("DeleteSecret", r#"{"SecretId": "del-put"}"#);
3439 svc.handle(req).await.unwrap();
3440
3441 let req = make_request(
3442 "PutSecretValue",
3443 r#"{"SecretId": "del-put", "SecretString": "v2"}"#,
3444 );
3445 let err = expect_err(svc.handle(req).await);
3446 assert!(err.to_string().contains("InvalidRequestException"));
3447 }
3448
3449 #[tokio::test]
3450 async fn put_secret_value_idempotent_match() {
3451 let state = make_state();
3452 let svc = SecretsManagerService::new(state);
3453
3454 let req = make_request(
3455 "CreateSecret",
3456 r#"{"Name": "put-idem", "SecretString": "original"}"#,
3457 );
3458 svc.handle(req).await.unwrap();
3459
3460 let token = "b".repeat(32);
3461 let body = serde_json::json!({
3462 "SecretId": "put-idem",
3463 "SecretString": "new-val",
3464 "ClientRequestToken": token,
3465 });
3466 let req = make_request("PutSecretValue", &body.to_string());
3467 svc.handle(req).await.unwrap();
3468
3469 let req = make_request("PutSecretValue", &body.to_string());
3471 let resp = svc.handle(req).await.unwrap();
3472 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3473 assert_eq!(b["VersionId"], token);
3474 }
3475
3476 #[tokio::test]
3477 async fn put_secret_value_idempotent_conflict() {
3478 let state = make_state();
3479 let svc = SecretsManagerService::new(state);
3480
3481 let req = make_request(
3482 "CreateSecret",
3483 r#"{"Name": "put-conflict", "SecretString": "original"}"#,
3484 );
3485 svc.handle(req).await.unwrap();
3486
3487 let token = "c".repeat(32);
3488 let body = serde_json::json!({
3489 "SecretId": "put-conflict",
3490 "SecretString": "val-a",
3491 "ClientRequestToken": token,
3492 });
3493 let req = make_request("PutSecretValue", &body.to_string());
3494 svc.handle(req).await.unwrap();
3495
3496 let body2 = serde_json::json!({
3498 "SecretId": "put-conflict",
3499 "SecretString": "val-b",
3500 "ClientRequestToken": token,
3501 });
3502 let req = make_request("PutSecretValue", &body2.to_string());
3503 let err = expect_err(svc.handle(req).await);
3504 assert!(err.to_string().contains("ResourceExistsException"));
3505 }
3506
3507 #[tokio::test]
3508 async fn put_secret_value_with_custom_stages() {
3509 let state = make_state();
3510 let svc = SecretsManagerService::new(state.clone());
3511
3512 let req = make_request(
3513 "CreateSecret",
3514 r#"{"Name": "staged", "SecretString": "v1"}"#,
3515 );
3516 svc.handle(req).await.unwrap();
3517
3518 let body = serde_json::json!({
3519 "SecretId": "staged",
3520 "SecretString": "v2",
3521 "VersionStages": ["AWSCURRENT", "MYAPP_V2"],
3522 });
3523 let req = make_request("PutSecretValue", &body.to_string());
3524 let resp = svc.handle(req).await.unwrap();
3525 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3526 let stages = b["VersionStages"].as_array().unwrap();
3527 assert!(stages.iter().any(|s| s == "MYAPP_V2"));
3528 }
3529
3530 #[tokio::test]
3533 async fn update_secret_not_found() {
3534 let state = make_state();
3535 let svc = SecretsManagerService::new(state);
3536
3537 let body = serde_json::json!({
3538 "SecretId": "ghost",
3539 "Description": "new",
3540 });
3541 let req = make_request("UpdateSecret", &body.to_string());
3542 let err = expect_err(svc.handle(req).await);
3543 assert!(err.to_string().contains("ResourceNotFoundException"));
3544 }
3545
3546 #[tokio::test]
3547 async fn update_secret_on_deleted() {
3548 let state = make_state();
3549 let svc = SecretsManagerService::new(state);
3550
3551 let req = make_request(
3552 "CreateSecret",
3553 r#"{"Name": "upd-del", "SecretString": "v"}"#,
3554 );
3555 svc.handle(req).await.unwrap();
3556 let req = make_request("DeleteSecret", r#"{"SecretId": "upd-del"}"#);
3557 svc.handle(req).await.unwrap();
3558
3559 let body = serde_json::json!({
3560 "SecretId": "upd-del",
3561 "Description": "new",
3562 });
3563 let req = make_request("UpdateSecret", &body.to_string());
3564 let err = expect_err(svc.handle(req).await);
3565 assert!(err.to_string().contains("InvalidRequestException"));
3566 }
3567
3568 #[tokio::test]
3569 async fn update_secret_idempotent_match() {
3570 let state = make_state();
3571 let svc = SecretsManagerService::new(state);
3572
3573 let req = make_request(
3574 "CreateSecret",
3575 r#"{"Name": "upd-idem", "SecretString": "orig"}"#,
3576 );
3577 svc.handle(req).await.unwrap();
3578
3579 let token = "d".repeat(32);
3580 let body = serde_json::json!({
3581 "SecretId": "upd-idem",
3582 "SecretString": "new-val",
3583 "ClientRequestToken": token,
3584 });
3585 let req = make_request("UpdateSecret", &body.to_string());
3586 svc.handle(req).await.unwrap();
3587
3588 let req = make_request("UpdateSecret", &body.to_string());
3590 let resp = svc.handle(req).await.unwrap();
3591 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3592 assert_eq!(b["VersionId"], token);
3593 }
3594
3595 #[tokio::test]
3598 async fn delete_secret_force() {
3599 let state = make_state();
3600 let svc = SecretsManagerService::new(state.clone());
3601
3602 let req = make_request(
3603 "CreateSecret",
3604 r#"{"Name": "force-del", "SecretString": "v"}"#,
3605 );
3606 svc.handle(req).await.unwrap();
3607
3608 let body = serde_json::json!({
3609 "SecretId": "force-del",
3610 "ForceDeleteWithoutRecovery": true,
3611 });
3612 let req = make_request("DeleteSecret", &body.to_string());
3613 let resp = svc.handle(req).await.unwrap();
3614 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3615 assert_eq!(b["Name"], "force-del");
3616
3617 let _accts = state.read();
3619 let s = _accts.default_ref();
3620 assert!(!s.secrets.contains_key("force-del"));
3621 }
3622
3623 #[tokio::test]
3624 async fn delete_secret_force_nonexistent() {
3625 let state = make_state();
3626 let svc = SecretsManagerService::new(state);
3627
3628 let body = serde_json::json!({
3629 "SecretId": "not-here",
3630 "ForceDeleteWithoutRecovery": true,
3631 });
3632 let req = make_request("DeleteSecret", &body.to_string());
3633 let resp = svc.handle(req).await.unwrap();
3634 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3635 assert_eq!(b["Name"], "not-here");
3636 }
3637
3638 #[tokio::test]
3639 async fn delete_secret_recovery_window() {
3640 let state = make_state();
3641 let svc = SecretsManagerService::new(state);
3642
3643 let req = make_request(
3644 "CreateSecret",
3645 r#"{"Name": "rec-win", "SecretString": "v"}"#,
3646 );
3647 svc.handle(req).await.unwrap();
3648
3649 let body = serde_json::json!({
3650 "SecretId": "rec-win",
3651 "RecoveryWindowInDays": 7,
3652 });
3653 let req = make_request("DeleteSecret", &body.to_string());
3654 let resp = svc.handle(req).await.unwrap();
3655 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3656 assert!(b["DeletionDate"].as_f64().is_some());
3657 }
3658
3659 #[tokio::test]
3660 async fn delete_secret_invalid_recovery_window() {
3661 let state = make_state();
3662 let svc = SecretsManagerService::new(state);
3663
3664 let req = make_request(
3665 "CreateSecret",
3666 r#"{"Name": "bad-win", "SecretString": "v"}"#,
3667 );
3668 svc.handle(req).await.unwrap();
3669
3670 let body = serde_json::json!({
3672 "SecretId": "bad-win",
3673 "RecoveryWindowInDays": 3,
3674 });
3675 let req = make_request("DeleteSecret", &body.to_string());
3676 let err = expect_err(svc.handle(req).await);
3677 assert!(err.to_string().contains("InvalidParameterException"));
3678
3679 let body = serde_json::json!({
3681 "SecretId": "bad-win",
3682 "RecoveryWindowInDays": 31,
3683 });
3684 let req = make_request("DeleteSecret", &body.to_string());
3685 let err = expect_err(svc.handle(req).await);
3686 assert!(err.to_string().contains("InvalidParameterException"));
3687 }
3688
3689 #[tokio::test]
3690 async fn delete_secret_force_and_recovery_conflict() {
3691 let state = make_state();
3692 let svc = SecretsManagerService::new(state);
3693
3694 let req = make_request("CreateSecret", r#"{"Name": "both", "SecretString": "v"}"#);
3695 svc.handle(req).await.unwrap();
3696
3697 let body = serde_json::json!({
3698 "SecretId": "both",
3699 "ForceDeleteWithoutRecovery": true,
3700 "RecoveryWindowInDays": 7,
3701 });
3702 let req = make_request("DeleteSecret", &body.to_string());
3703 let err = expect_err(svc.handle(req).await);
3704 assert!(err.to_string().contains("InvalidParameterException"));
3705 }
3706
3707 #[tokio::test]
3708 async fn delete_already_deleted_secret() {
3709 let state = make_state();
3710 let svc = SecretsManagerService::new(state);
3711
3712 let req = make_request(
3713 "CreateSecret",
3714 r#"{"Name": "dbl-del", "SecretString": "v"}"#,
3715 );
3716 svc.handle(req).await.unwrap();
3717
3718 let req = make_request("DeleteSecret", r#"{"SecretId": "dbl-del"}"#);
3719 svc.handle(req).await.unwrap();
3720
3721 let req = make_request("DeleteSecret", r#"{"SecretId": "dbl-del"}"#);
3722 let err = expect_err(svc.handle(req).await);
3723 assert!(err.to_string().contains("InvalidRequestException"));
3724 }
3725
3726 #[tokio::test]
3729 async fn get_secret_value_by_version_id() {
3730 let state = make_state();
3731 let svc = SecretsManagerService::new(state.clone());
3732
3733 let req = make_request(
3734 "CreateSecret",
3735 r#"{"Name": "ver-get", "SecretString": "v1"}"#,
3736 );
3737 svc.handle(req).await.unwrap();
3738
3739 let v1_id = {
3740 let _accts = state.read();
3741 let s = _accts.default_ref();
3742 s.secrets
3743 .get("ver-get")
3744 .unwrap()
3745 .current_version_id
3746 .clone()
3747 .unwrap()
3748 };
3749
3750 let req = make_request(
3751 "PutSecretValue",
3752 r#"{"SecretId": "ver-get", "SecretString": "v2"}"#,
3753 );
3754 svc.handle(req).await.unwrap();
3755
3756 let body = serde_json::json!({
3758 "SecretId": "ver-get",
3759 "VersionId": v1_id,
3760 "VersionStage": "AWSPREVIOUS",
3761 });
3762 let req = make_request("GetSecretValue", &body.to_string());
3763 let resp = svc.handle(req).await.unwrap();
3764 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3765 assert_eq!(b["SecretString"], "v1");
3766 }
3767
3768 #[tokio::test]
3769 async fn get_secret_value_version_stage_mismatch() {
3770 let state = make_state();
3771 let svc = SecretsManagerService::new(state.clone());
3772
3773 let req = make_request(
3774 "CreateSecret",
3775 r#"{"Name": "mismatch", "SecretString": "v1"}"#,
3776 );
3777 svc.handle(req).await.unwrap();
3778
3779 let vid = {
3780 let _accts = state.read();
3781 let s = _accts.default_ref();
3782 s.secrets
3783 .get("mismatch")
3784 .unwrap()
3785 .current_version_id
3786 .clone()
3787 .unwrap()
3788 };
3789
3790 let body = serde_json::json!({
3792 "SecretId": "mismatch",
3793 "VersionId": vid,
3794 "VersionStage": "AWSPREVIOUS",
3795 });
3796 let req = make_request("GetSecretValue", &body.to_string());
3797 let err = expect_err(svc.handle(req).await);
3798 assert!(err.to_string().contains("ResourceNotFoundException"));
3799 }
3800
3801 #[tokio::test]
3802 async fn get_secret_value_not_found() {
3803 let state = make_state();
3804 let svc = SecretsManagerService::new(state);
3805
3806 let req = make_request("GetSecretValue", r#"{"SecretId": "nope"}"#);
3807 let err = expect_err(svc.handle(req).await);
3808 assert!(err.to_string().contains("ResourceNotFoundException"));
3809 }
3810
3811 #[tokio::test]
3812 async fn get_secret_value_no_versions() {
3813 let state = make_state();
3814 let svc = SecretsManagerService::new(state);
3815
3816 let req = make_request("CreateSecret", r#"{"Name": "empty-ver"}"#);
3817 svc.handle(req).await.unwrap();
3818
3819 let req = make_request("GetSecretValue", r#"{"SecretId": "empty-ver"}"#);
3820 let err = expect_err(svc.handle(req).await);
3821 assert!(err.to_string().contains("ResourceNotFoundException"));
3822 }
3823
3824 #[tokio::test]
3825 async fn get_secret_value_with_binary() {
3826 let state = make_state();
3827 let svc = SecretsManagerService::new(state);
3828
3829 let body = serde_json::json!({
3831 "Name": "bin-secret",
3832 "SecretBinary": "SGVsbG8=", });
3834 let req = make_request("CreateSecret", &body.to_string());
3835 svc.handle(req).await.unwrap();
3836
3837 let req = make_request("GetSecretValue", r#"{"SecretId": "bin-secret"}"#);
3838 let resp = svc.handle(req).await.unwrap();
3839 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3840 assert!(b.get("SecretBinary").is_some());
3841 assert!(b.get("SecretString").is_none());
3842 }
3843
3844 #[tokio::test]
3847 async fn list_secrets_filter_by_name() {
3848 let state = make_state();
3849 let svc = SecretsManagerService::new(state);
3850
3851 for name in &["prod/db", "prod/api", "staging/db"] {
3852 let body = serde_json::json!({"Name": name, "SecretString": "v"});
3853 let req = make_request("CreateSecret", &body.to_string());
3854 svc.handle(req).await.unwrap();
3855 }
3856
3857 let body = serde_json::json!({
3858 "Filters": [{"Key": "name", "Values": ["prod/"]}]
3859 });
3860 let req = make_request("ListSecrets", &body.to_string());
3861 let resp = svc.handle(req).await.unwrap();
3862 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3863 assert_eq!(b["SecretList"].as_array().unwrap().len(), 2);
3864 }
3865
3866 #[tokio::test]
3867 async fn list_secrets_filter_by_tag_key() {
3868 let state = make_state();
3869 let svc = SecretsManagerService::new(state);
3870
3871 let body = serde_json::json!({
3872 "Name": "tagged-s",
3873 "SecretString": "v",
3874 "Tags": [{"Key": "team", "Value": "backend"}],
3875 });
3876 let req = make_request("CreateSecret", &body.to_string());
3877 svc.handle(req).await.unwrap();
3878
3879 let body = serde_json::json!({"Name": "untagged-s", "SecretString": "v"});
3880 let req = make_request("CreateSecret", &body.to_string());
3881 svc.handle(req).await.unwrap();
3882
3883 let body = serde_json::json!({
3884 "Filters": [{"Key": "tag-key", "Values": ["team"]}]
3885 });
3886 let req = make_request("ListSecrets", &body.to_string());
3887 let resp = svc.handle(req).await.unwrap();
3888 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3889 assert_eq!(b["SecretList"].as_array().unwrap().len(), 1);
3890 assert_eq!(b["SecretList"][0]["Name"], "tagged-s");
3891 }
3892
3893 #[tokio::test]
3894 async fn list_secrets_filter_by_description() {
3895 let state = make_state();
3896 let svc = SecretsManagerService::new(state);
3897
3898 let body = serde_json::json!({
3899 "Name": "desc-match",
3900 "SecretString": "v",
3901 "Description": "Database credentials for production",
3902 });
3903 let req = make_request("CreateSecret", &body.to_string());
3904 svc.handle(req).await.unwrap();
3905
3906 let body = serde_json::json!({"Name": "no-desc", "SecretString": "v"});
3907 let req = make_request("CreateSecret", &body.to_string());
3908 svc.handle(req).await.unwrap();
3909
3910 let body = serde_json::json!({
3911 "Filters": [{"Key": "description", "Values": ["Database"]}]
3912 });
3913 let req = make_request("ListSecrets", &body.to_string());
3914 let resp = svc.handle(req).await.unwrap();
3915 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3916 assert_eq!(b["SecretList"].as_array().unwrap().len(), 1);
3917 }
3918
3919 #[tokio::test]
3920 async fn list_secrets_include_planned_deletion() {
3921 let state = make_state();
3922 let svc = SecretsManagerService::new(state);
3923
3924 let req = make_request("CreateSecret", r#"{"Name": "alive", "SecretString": "v"}"#);
3925 svc.handle(req).await.unwrap();
3926
3927 let req = make_request("CreateSecret", r#"{"Name": "doomed", "SecretString": "v"}"#);
3928 svc.handle(req).await.unwrap();
3929 let req = make_request("DeleteSecret", r#"{"SecretId": "doomed"}"#);
3930 svc.handle(req).await.unwrap();
3931
3932 let req = make_request("ListSecrets", "{}");
3934 let resp = svc.handle(req).await.unwrap();
3935 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3936 assert_eq!(b["SecretList"].as_array().unwrap().len(), 1);
3937
3938 let body = serde_json::json!({"IncludePlannedDeletion": true});
3940 let req = make_request("ListSecrets", &body.to_string());
3941 let resp = svc.handle(req).await.unwrap();
3942 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3943 assert_eq!(b["SecretList"].as_array().unwrap().len(), 2);
3944 }
3945
3946 #[tokio::test]
3947 async fn list_secrets_pagination() {
3948 let state = make_state();
3949 let svc = SecretsManagerService::new(state);
3950
3951 for i in 0..5 {
3952 let body = serde_json::json!({
3953 "Name": format!("page-{i}"),
3954 "SecretString": "v",
3955 });
3956 let req = make_request("CreateSecret", &body.to_string());
3957 svc.handle(req).await.unwrap();
3958 }
3959
3960 let body = serde_json::json!({"MaxResults": 2});
3961 let req = make_request("ListSecrets", &body.to_string());
3962 let resp = svc.handle(req).await.unwrap();
3963 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3964 assert_eq!(b["SecretList"].as_array().unwrap().len(), 2);
3965 assert!(b["NextToken"].as_str().is_some());
3966 }
3967
3968 #[tokio::test]
3969 async fn list_secrets_invalid_filter_key() {
3970 let state = make_state();
3971 let svc = SecretsManagerService::new(state);
3972
3973 let body = serde_json::json!({
3974 "Filters": [{"Key": "bogus", "Values": ["x"]}]
3975 });
3976 let req = make_request("ListSecrets", &body.to_string());
3977 let err = expect_err(svc.handle(req).await);
3978 assert!(err.to_string().contains("ValidationException"));
3979 }
3980
3981 #[tokio::test]
3982 async fn list_secrets_empty_filter_values() {
3983 let state = make_state();
3984 let svc = SecretsManagerService::new(state);
3985
3986 let body = serde_json::json!({
3987 "Filters": [{"Key": "name", "Values": []}]
3988 });
3989 let req = make_request("ListSecrets", &body.to_string());
3990 let err = expect_err(svc.handle(req).await);
3991 assert!(err.to_string().contains("InvalidParameterException"));
3992 }
3993
3994 #[tokio::test]
3997 async fn list_secret_version_ids() {
3998 let state = make_state();
3999 let svc = SecretsManagerService::new(state);
4000
4001 let req = make_request(
4002 "CreateSecret",
4003 r#"{"Name": "multi-ver", "SecretString": "v1"}"#,
4004 );
4005 svc.handle(req).await.unwrap();
4006
4007 let req = make_request(
4008 "PutSecretValue",
4009 r#"{"SecretId": "multi-ver", "SecretString": "v2"}"#,
4010 );
4011 svc.handle(req).await.unwrap();
4012
4013 let req = make_request("ListSecretVersionIds", r#"{"SecretId": "multi-ver"}"#);
4014 let resp = svc.handle(req).await.unwrap();
4015 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4016 assert_eq!(b["Name"], "multi-ver");
4017 assert_eq!(b["Versions"].as_array().unwrap().len(), 2);
4018 }
4019
4020 #[tokio::test]
4023 async fn describe_secret_with_rotation_and_next_date() {
4024 let state = make_state();
4025 let svc = SecretsManagerService::new(state);
4026
4027 let req = make_request(
4028 "CreateSecret",
4029 r#"{"Name": "rot-desc", "SecretString": "pw"}"#,
4030 );
4031 svc.handle(req).await.unwrap();
4032
4033 let token = "e".repeat(32);
4034 let body = serde_json::json!({
4035 "SecretId": "rot-desc",
4036 "RotationRules": {"AutomaticallyAfterDays": 14},
4037 "ClientRequestToken": token,
4038 });
4039 let req = make_request("RotateSecret", &body.to_string());
4040 svc.handle(req).await.unwrap();
4041
4042 let req = make_request("DescribeSecret", r#"{"SecretId": "rot-desc"}"#);
4043 let resp = svc.handle(req).await.unwrap();
4044 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4045 assert_eq!(b["RotationEnabled"], true);
4046 assert!(b["LastRotatedDate"].as_f64().is_some());
4047 assert!(b["NextRotationDate"].as_f64().is_some());
4048 assert_eq!(b["RotationRules"]["AutomaticallyAfterDays"], 14);
4049 }
4050
4051 #[tokio::test]
4052 async fn describe_secret_deleted_shows_deletion_date() {
4053 let state = make_state();
4054 let svc = SecretsManagerService::new(state);
4055
4056 let req = make_request(
4057 "CreateSecret",
4058 r#"{"Name": "del-desc", "SecretString": "v"}"#,
4059 );
4060 svc.handle(req).await.unwrap();
4061 let req = make_request("DeleteSecret", r#"{"SecretId": "del-desc"}"#);
4062 svc.handle(req).await.unwrap();
4063
4064 let req = make_request("DescribeSecret", r#"{"SecretId": "del-desc"}"#);
4065 let resp = svc.handle(req).await.unwrap();
4066 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4067 assert!(b["DeletedDate"].as_f64().is_some());
4068 }
4069
4070 #[tokio::test]
4073 async fn batch_get_secret_value_both_list_and_filters() {
4074 let state = make_state();
4075 let svc = SecretsManagerService::new(state);
4076
4077 let body = serde_json::json!({
4078 "SecretIdList": ["a"],
4079 "Filters": [{"Key": "name", "Values": ["a"]}],
4080 });
4081 let req = make_request("BatchGetSecretValue", &body.to_string());
4082 let err = expect_err(svc.handle(req).await);
4083 assert!(err.to_string().contains("InvalidParameterException"));
4084 }
4085
4086 #[tokio::test]
4087 async fn batch_get_secret_value_max_results_without_filters() {
4088 let state = make_state();
4089 let svc = SecretsManagerService::new(state);
4090
4091 let body = serde_json::json!({
4092 "SecretIdList": ["a"],
4093 "MaxResults": 10,
4094 });
4095 let req = make_request("BatchGetSecretValue", &body.to_string());
4096 let err = expect_err(svc.handle(req).await);
4097 assert!(err.to_string().contains("InvalidParameterException"));
4098 }
4099
4100 #[tokio::test]
4101 async fn batch_get_secret_value_with_filters() {
4102 let state = make_state();
4103 let svc = SecretsManagerService::new(state);
4104
4105 for name in &["batch-f-a", "batch-f-b", "other-c"] {
4106 let body = serde_json::json!({"Name": name, "SecretString": "v"});
4107 let req = make_request("CreateSecret", &body.to_string());
4108 svc.handle(req).await.unwrap();
4109 }
4110
4111 let body = serde_json::json!({
4112 "Filters": [{"Key": "name", "Values": ["batch-f"]}],
4113 });
4114 let req = make_request("BatchGetSecretValue", &body.to_string());
4115 let resp = svc.handle(req).await.unwrap();
4116 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4117 assert_eq!(b["SecretValues"].as_array().unwrap().len(), 2);
4118 }
4119
4120 #[tokio::test]
4123 async fn rotate_secret_invalid_token_length() {
4124 let state = make_state();
4125 let svc = SecretsManagerService::new(state);
4126
4127 let req = make_request(
4128 "CreateSecret",
4129 r#"{"Name": "rot-val", "SecretString": "v"}"#,
4130 );
4131 svc.handle(req).await.unwrap();
4132
4133 let body = serde_json::json!({
4134 "SecretId": "rot-val",
4135 "ClientRequestToken": "short",
4136 });
4137 let req = make_request("RotateSecret", &body.to_string());
4138 let err = expect_err(svc.handle(req).await);
4139 assert!(err.to_string().contains("InvalidParameterException"));
4140 }
4141
4142 #[tokio::test]
4143 async fn rotate_secret_invalid_rules() {
4144 let state = make_state();
4145 let svc = SecretsManagerService::new(state);
4146
4147 let req = make_request(
4148 "CreateSecret",
4149 r#"{"Name": "rot-rules", "SecretString": "v"}"#,
4150 );
4151 svc.handle(req).await.unwrap();
4152
4153 let body = serde_json::json!({
4154 "SecretId": "rot-rules",
4155 "RotationRules": {"AutomaticallyAfterDays": 0},
4156 });
4157 let req = make_request("RotateSecret", &body.to_string());
4158 let err = expect_err(svc.handle(req).await);
4159 assert!(err.to_string().contains("InvalidParameterException"));
4160 }
4161
4162 #[tokio::test]
4163 async fn rotate_secret_on_deleted() {
4164 let state = make_state();
4165 let svc = SecretsManagerService::new(state);
4166
4167 let req = make_request(
4168 "CreateSecret",
4169 r#"{"Name": "rot-del", "SecretString": "v"}"#,
4170 );
4171 svc.handle(req).await.unwrap();
4172 let req = make_request("DeleteSecret", r#"{"SecretId": "rot-del"}"#);
4173 svc.handle(req).await.unwrap();
4174
4175 let body = serde_json::json!({"SecretId": "rot-del"});
4176 let req = make_request("RotateSecret", &body.to_string());
4177 let err = expect_err(svc.handle(req).await);
4178 assert!(err.to_string().contains("InvalidRequestException"));
4179 }
4180
4181 #[tokio::test]
4184 async fn cancel_rotate_on_deleted() {
4185 let state = make_state();
4186 let svc = SecretsManagerService::new(state);
4187
4188 let req = make_request("CreateSecret", r#"{"Name": "cr-del", "SecretString": "v"}"#);
4189 svc.handle(req).await.unwrap();
4190 let req = make_request("DeleteSecret", r#"{"SecretId": "cr-del"}"#);
4191 svc.handle(req).await.unwrap();
4192
4193 let req = make_request("CancelRotateSecret", r#"{"SecretId": "cr-del"}"#);
4194 let err = expect_err(svc.handle(req).await);
4195 assert!(err.to_string().contains("InvalidRequestException"));
4196 }
4197
4198 #[tokio::test]
4201 async fn update_version_stage_missing_remove_from() {
4202 let state = make_state();
4203 let svc = SecretsManagerService::new(state.clone());
4204
4205 let req = make_request(
4206 "CreateSecret",
4207 r#"{"Name": "stage-err", "SecretString": "v1"}"#,
4208 );
4209 svc.handle(req).await.unwrap();
4210
4211 let req = make_request(
4212 "PutSecretValue",
4213 r#"{"SecretId": "stage-err", "SecretString": "v2"}"#,
4214 );
4215 svc.handle(req).await.unwrap();
4216
4217 let new_vid = {
4218 let _accts = state.read();
4219 let s = _accts.default_ref();
4220 let secret = s.secrets.get("stage-err").unwrap();
4221 secret
4222 .versions
4223 .iter()
4224 .find(|(_, v)| v.stages.contains(&"AWSPREVIOUS".to_string()))
4225 .map(|(id, _)| id.clone())
4226 .unwrap()
4227 };
4228
4229 let body = serde_json::json!({
4231 "SecretId": "stage-err",
4232 "VersionStage": "AWSCURRENT",
4233 "MoveToVersionId": new_vid,
4234 });
4235 let req = make_request("UpdateSecretVersionStage", &body.to_string());
4236 let err = expect_err(svc.handle(req).await);
4237 assert!(err.to_string().contains("InvalidParameterException"));
4238 }
4239
4240 #[tokio::test]
4243 async fn find_secret_by_arn() {
4244 let state = make_state();
4245 let svc = SecretsManagerService::new(state);
4246
4247 let req = make_request(
4248 "CreateSecret",
4249 r#"{"Name": "arn-lookup", "SecretString": "v"}"#,
4250 );
4251 let resp = svc.handle(req).await.unwrap();
4252 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4253 let arn = b["ARN"].as_str().unwrap();
4254
4255 let body = serde_json::json!({"SecretId": arn});
4257 let req = make_request("GetSecretValue", &body.to_string());
4258 let resp = svc.handle(req).await.unwrap();
4259 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4260 assert_eq!(b["SecretString"], "v");
4261 }
4262
4263 #[tokio::test]
4264 async fn find_secret_by_partial_arn() {
4265 let state = make_state();
4266 let svc = SecretsManagerService::new(state);
4267
4268 let req = make_request(
4269 "CreateSecret",
4270 r#"{"Name": "partial-arn", "SecretString": "v"}"#,
4271 );
4272 svc.handle(req).await.unwrap();
4273
4274 let partial = "arn:aws:secretsmanager:us-east-1:123456789012:secret:partial-arn";
4276 let body = serde_json::json!({"SecretId": partial});
4277 let req = make_request("GetSecretValue", &body.to_string());
4278 let resp = svc.handle(req).await.unwrap();
4279 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4280 assert_eq!(b["SecretString"], "v");
4281 }
4282
4283 #[tokio::test]
4286 async fn validate_resource_policy_with_secret_id() {
4287 let state = make_state();
4288 let svc = SecretsManagerService::new(state);
4289
4290 let req = make_request(
4291 "CreateSecret",
4292 r#"{"Name": "pol-val", "SecretString": "v"}"#,
4293 );
4294 svc.handle(req).await.unwrap();
4295
4296 let body = serde_json::json!({
4297 "SecretId": "pol-val",
4298 "ResourcePolicy": r#"{"Version":"2012-10-17","Statement":[]}"#,
4299 });
4300 let req = make_request("ValidateResourcePolicy", &body.to_string());
4301 let resp = svc.handle(req).await.unwrap();
4302 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4303 assert_eq!(b["PolicyValidationPassed"], true);
4304 }
4305
4306 #[tokio::test]
4307 async fn validate_resource_policy_nonexistent_secret() {
4308 let state = make_state();
4309 let svc = SecretsManagerService::new(state);
4310
4311 let body = serde_json::json!({
4312 "SecretId": "ghost",
4313 "ResourcePolicy": r#"{"Version":"2012-10-17","Statement":[]}"#,
4314 });
4315 let req = make_request("ValidateResourcePolicy", &body.to_string());
4316 let err = expect_err(svc.handle(req).await);
4317 assert!(err.to_string().contains("ResourceNotFoundException"));
4318 }
4319
4320 #[tokio::test]
4323 async fn tag_resource_updates_existing_tag() {
4324 let state = make_state();
4325 let svc = SecretsManagerService::new(state);
4326
4327 let body = serde_json::json!({
4328 "Name": "tag-upd",
4329 "SecretString": "v",
4330 "Tags": [{"Key": "env", "Value": "dev"}],
4331 });
4332 let req = make_request("CreateSecret", &body.to_string());
4333 svc.handle(req).await.unwrap();
4334
4335 let body = serde_json::json!({
4337 "SecretId": "tag-upd",
4338 "Tags": [{"Key": "env", "Value": "prod"}],
4339 });
4340 let req = make_request("TagResource", &body.to_string());
4341 svc.handle(req).await.unwrap();
4342
4343 let req = make_request("DescribeSecret", r#"{"SecretId": "tag-upd"}"#);
4344 let resp = svc.handle(req).await.unwrap();
4345 let b: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
4346 let tags = b["Tags"].as_array().unwrap();
4347 assert_eq!(tags.len(), 1);
4348 assert_eq!(tags[0]["Value"], "prod");
4349 }
4350
4351 #[tokio::test]
4354 async fn unsupported_action_returns_error() {
4355 let state = make_state();
4356 let svc = SecretsManagerService::new(state);
4357
4358 let req = make_request("BogusAction", "{}");
4359 let err = expect_err(svc.handle(req).await);
4360 assert!(err.to_string().contains("BogusAction"));
4361 }
4362
4363 #[test]
4366 fn test_split_words_basic() {
4367 assert_eq!(split_words("hello"), vec!["hello"]);
4368 assert_eq!(split_words("HelloWorld"), vec!["Hello", "World"]);
4369 assert_eq!(split_words("my/secret/name"), vec!["my", "secret", "name"]);
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 }
4373
4374 #[test]
4375 fn test_split_words_multiple_delimiters() {
4376 assert_eq!(split_words("my/secret-name"), vec!["my/secret-name"]);
4378 }
4379
4380 #[test]
4381 fn test_split_words_with_spaces() {
4382 let words = split_words("hello world");
4383 assert_eq!(words, vec!["hello", "world"]);
4384 }
4385
4386 #[test]
4387 fn test_match_pattern_prefix() {
4388 assert!(match_pattern("prod", "production", true, true));
4389 assert!(!match_pattern("Prod", "production", true, true));
4390 assert!(match_pattern("Prod", "production", true, false));
4391 }
4392
4393 #[test]
4394 fn test_match_pattern_word() {
4395 assert!(match_pattern("hello", "HelloWorld", false, false));
4396 assert!(match_pattern("world", "HelloWorld", false, false));
4397 }
4398
4399 #[test]
4400 fn test_matcher_negation() {
4401 assert!(matcher(&["!prod"], &["staging"], true, true));
4403 }
4404
4405 #[test]
4406 fn test_base64_roundtrip() {
4407 let data = b"Hello, World!";
4408 let encoded = base64_encode(data);
4409 let decoded = base64_decode(&encoded).unwrap();
4410 assert_eq!(&decoded, data);
4411 }
4412
4413 #[test]
4414 fn test_base64_decode_invalid() {
4415 assert!(base64_decode("!!!").is_none());
4417 }
4418
4419 #[test]
4420 fn test_check_version_idempotency() {
4421 let mut versions = HashMap::new();
4422 versions.insert(
4423 "v1".to_string(),
4424 SecretVersion {
4425 version_id: "v1".to_string(),
4426 secret_string: Some("hello".to_string()),
4427 secret_binary: None,
4428 stages: vec!["AWSCURRENT".to_string()],
4429 created_at: Utc::now(),
4430 },
4431 );
4432
4433 assert!(matches!(
4435 check_secret_version_idempotency(&versions, "v2", None, &Some("x".to_string()), &None),
4436 VersionIdempotency::NotFound
4437 ));
4438
4439 assert!(matches!(
4441 check_secret_version_idempotency(
4442 &versions,
4443 "v1",
4444 Some("hello".to_string()),
4445 &Some("hello".to_string()),
4446 &None
4447 ),
4448 VersionIdempotency::Match
4449 ));
4450
4451 assert!(matches!(
4453 check_secret_version_idempotency(
4454 &versions,
4455 "v1",
4456 Some("hello".to_string()),
4457 &Some("different".to_string()),
4458 &None
4459 ),
4460 VersionIdempotency::Conflict
4461 ));
4462 }
4463
4464 #[test]
4465 fn test_is_mutating_action() {
4466 assert!(is_mutating_action("CreateSecret"));
4467 assert!(is_mutating_action("DeleteSecret"));
4468 assert!(is_mutating_action("TagResource"));
4469 assert!(!is_mutating_action("GetSecretValue"));
4470 assert!(!is_mutating_action("ListSecrets"));
4471 assert!(!is_mutating_action("DescribeSecret"));
4472 }
4473
4474 #[test]
4475 fn test_parse_tags_empty() {
4476 let val = serde_json::json!(null);
4477 assert_eq!(parse_tags(&val), vec![]);
4478 }
4479
4480 #[test]
4481 fn test_tags_to_json_roundtrip() {
4482 let tags = vec![
4483 ("k1".to_string(), "v1".to_string()),
4484 ("k2".to_string(), "v2".to_string()),
4485 ];
4486 let json = tags_to_json(&tags);
4487 assert_eq!(json.len(), 2);
4488 assert_eq!(json[0]["Key"], "k1");
4489 assert_eq!(json[1]["Value"], "v2");
4490 }
4491
4492 #[test]
4493 fn test_filter_name_prefix() {
4494 let secret = Secret {
4495 name: "prod/database".to_string(),
4496 arn: "arn".to_string(),
4497 description: None,
4498 kms_key_id: None,
4499 versions: HashMap::new(),
4500 current_version_id: None,
4501 tags: vec![],
4502 tags_ever_set: false,
4503 deleted: false,
4504 deletion_date: None,
4505 created_at: Utc::now(),
4506 last_changed_at: Utc::now(),
4507 last_accessed_at: None,
4508 rotation_enabled: None,
4509 rotation_lambda_arn: None,
4510 rotation_rules: None,
4511 last_rotated_at: None,
4512 resource_policy: None,
4513 };
4514 assert!(filter_name(&secret, &["prod/"]));
4515 assert!(!filter_name(&secret, &["staging/"]));
4516 }
4517
4518 #[test]
4519 fn test_filter_tag_value() {
4520 let secret = Secret {
4521 name: "s".to_string(),
4522 arn: "arn".to_string(),
4523 description: None,
4524 kms_key_id: None,
4525 versions: HashMap::new(),
4526 current_version_id: None,
4527 tags: vec![("env".to_string(), "production".to_string())],
4528 tags_ever_set: true,
4529 deleted: false,
4530 deletion_date: None,
4531 created_at: Utc::now(),
4532 last_changed_at: Utc::now(),
4533 last_accessed_at: None,
4534 rotation_enabled: None,
4535 rotation_lambda_arn: None,
4536 rotation_rules: None,
4537 last_rotated_at: None,
4538 resource_policy: None,
4539 };
4540 assert!(filter_tag_value(&secret, &["prod"]));
4541 assert!(!filter_tag_value(&secret, &["staging"]));
4542 }
4543
4544 #[test]
4545 fn test_filter_all_searches_name_desc_tags() {
4546 let secret = Secret {
4547 name: "my-secret".to_string(),
4548 arn: "arn".to_string(),
4549 description: Some("important database".to_string()),
4550 kms_key_id: None,
4551 versions: HashMap::new(),
4552 current_version_id: None,
4553 tags: vec![("team".to_string(), "backend".to_string())],
4554 tags_ever_set: true,
4555 deleted: false,
4556 deletion_date: None,
4557 created_at: Utc::now(),
4558 last_changed_at: Utc::now(),
4559 last_accessed_at: None,
4560 rotation_enabled: None,
4561 rotation_lambda_arn: None,
4562 rotation_rules: None,
4563 last_rotated_at: None,
4564 resource_policy: None,
4565 };
4566 assert!(filter_all(&secret, &["my"]));
4568 assert!(filter_all(&secret, &["database"]));
4570 assert!(filter_all(&secret, &["team"]));
4572 assert!(filter_all(&secret, &["backend"]));
4574 assert!(!filter_all(&secret, &["zzzz"]));
4576 }
4577}