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