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 fakecloud_core::delivery::DeliveryBus;
10use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
11use fakecloud_core::validation::*;
12
13use crate::state::{RotationRules, Secret, SecretVersion, SharedSecretsManagerState};
14
15struct RotationInvocation {
17 lambda_arn: String,
18 secret_id: String,
19 client_request_token: String,
20}
21
22enum VersionIdempotency {
25 NotFound,
27 Match,
31 Conflict,
34}
35
36fn check_secret_version_idempotency(
41 versions: &HashMap<String, SecretVersion>,
42 version_id: &str,
43 secret_string: &Option<String>,
44 secret_binary: &Option<Vec<u8>>,
45) -> VersionIdempotency {
46 let Some(existing) = versions.get(version_id) else {
47 return VersionIdempotency::NotFound;
48 };
49 if &existing.secret_string == secret_string && &existing.secret_binary == secret_binary {
50 VersionIdempotency::Match
51 } else {
52 VersionIdempotency::Conflict
53 }
54}
55
56pub struct SecretsManagerService {
57 state: SharedSecretsManagerState,
58 delivery_bus: Option<Arc<DeliveryBus>>,
59}
60
61impl SecretsManagerService {
62 pub fn new(state: SharedSecretsManagerState) -> Self {
63 Self {
64 state,
65 delivery_bus: None,
66 }
67 }
68
69 pub fn with_delivery(mut self, delivery_bus: Arc<DeliveryBus>) -> Self {
70 self.delivery_bus = Some(delivery_bus);
71 self
72 }
73
74 fn create_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
75 let input = CreateSecretInput::from_body(&req.json_body())?;
76 let has_value = input.secret_string.is_some() || input.secret_binary.is_some();
77
78 let mut state = self.state.write();
79
80 if let Some(existing) = state.secrets.get(&input.name) {
81 if let Some(ref token) = input.client_request_token {
82 match check_secret_version_idempotency(
83 &existing.versions,
84 token,
85 &input.secret_string,
86 &input.secret_binary,
87 ) {
88 VersionIdempotency::Match => {
89 let mut response = json!({
90 "ARN": existing.arn,
91 "Name": existing.name,
92 "VersionId": token,
93 });
94 if !has_value {
95 response.as_object_mut().unwrap().remove("VersionId");
96 }
97 return Ok(AwsResponse::ok_json(response));
98 }
99 VersionIdempotency::Conflict => {
100 return Err(AwsServiceError::aws_error(
101 StatusCode::BAD_REQUEST,
102 "ResourceExistsException",
103 format!(
104 "You can't use ClientRequestToken {token} because that value is already in use for a version of secret {}.",
105 existing.arn
106 ),
107 ));
108 }
109 VersionIdempotency::NotFound => {}
110 }
111 }
112 return Err(AwsServiceError::aws_error(
113 StatusCode::BAD_REQUEST,
114 "ResourceExistsException",
115 format!(
116 "The operation failed because the secret {} already exists.",
117 input.name
118 ),
119 ));
120 }
121
122 let arn = format!(
123 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
124 req.region,
125 req.account_id,
126 input.name,
127 &uuid::Uuid::new_v4().to_string()[..6]
128 );
129
130 let now = Utc::now();
131
132 let (versions, current_version_id, version_id_for_response) = if has_value {
133 let vid = input
134 .client_request_token
135 .clone()
136 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
137 let version = SecretVersion {
138 version_id: vid.clone(),
139 secret_string: input.secret_string,
140 secret_binary: input.secret_binary,
141 stages: vec!["AWSCURRENT".to_string()],
142 created_at: now,
143 };
144 let mut versions = std::collections::HashMap::new();
145 versions.insert(vid.clone(), version);
146 (versions, Some(vid.clone()), Some(vid))
147 } else {
148 (std::collections::HashMap::new(), None, None)
149 };
150
151 let tags_ever_set = !input.tags.is_empty();
152 let secret = Secret {
153 name: input.name.clone(),
154 arn: arn.clone(),
155 description: input.description,
156 kms_key_id: input.kms_key_id,
157 versions,
158 current_version_id,
159 tags: input.tags,
160 tags_ever_set,
161 deleted: false,
162 deletion_date: None,
163 created_at: now,
164 last_changed_at: now,
165 last_accessed_at: None,
166 rotation_enabled: None,
167 rotation_lambda_arn: None,
168 rotation_rules: None,
169 last_rotated_at: None,
170 resource_policy: None,
171 };
172
173 state.secrets.insert(input.name.clone(), secret);
174
175 let mut response = json!({
176 "ARN": arn,
177 "Name": input.name,
178 });
179 if let Some(vid) = version_id_for_response {
180 response["VersionId"] = json!(vid);
181 }
182
183 Ok(AwsResponse::ok_json(response))
184 }
185
186 fn get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
187 let body = req.json_body();
188 let secret_id = require_secret_id(&body)?;
189 validate_optional_string_length("versionId", body["VersionId"].as_str(), 32, 64)?;
190 validate_optional_string_length("versionStage", body["VersionStage"].as_str(), 1, 256)?;
191
192 let mut state = self.state.write();
193 let secret = self.find_secret_mut(&mut state, &secret_id)?;
194
195 if secret.deleted {
196 return Err(AwsServiceError::aws_error(
197 StatusCode::BAD_REQUEST,
198 "InvalidRequestException",
199 "You can't perform this operation on the secret because it was marked for deletion.",
200 ));
201 }
202
203 let requested_stage = body["VersionStage"].as_str().unwrap_or("AWSCURRENT");
204
205 let version_id = body["VersionId"]
207 .as_str()
208 .map(|s| s.to_string())
209 .or_else(|| {
210 secret
211 .versions
212 .iter()
213 .find(|(_, v)| v.stages.contains(&requested_stage.to_string()))
214 .map(|(id, _)| id.clone())
215 });
216
217 let version_id = match version_id {
218 Some(vid) => vid,
219 None => {
220 return Err(AwsServiceError::aws_error(
222 StatusCode::NOT_FOUND,
223 "ResourceNotFoundException",
224 format!(
225 "Secrets Manager can't find the specified secret value for staging label: {requested_stage}"
226 ),
227 ));
228 }
229 };
230
231 let version = secret.versions.get(&version_id).ok_or_else(|| {
232 AwsServiceError::aws_error(
233 StatusCode::NOT_FOUND,
234 "ResourceNotFoundException",
235 format!(
236 "Secrets Manager can't find the specified secret value for VersionId: {version_id}"
237 ),
238 )
239 })?;
240
241 if body["VersionId"].as_str().is_some() {
243 if let Some(stage) = body["VersionStage"].as_str() {
244 if !version.stages.contains(&stage.to_string()) {
245 return Err(AwsServiceError::aws_error(
246 StatusCode::NOT_FOUND,
247 "ResourceNotFoundException",
248 "You provided a VersionStage that is not associated to the provided VersionId.",
249 ));
250 }
251 }
252 }
253
254 secret.last_accessed_at = Some(Utc::now());
256
257 let mut response = json!({
258 "ARN": secret.arn,
259 "Name": secret.name,
260 "VersionId": version.version_id,
261 "VersionStages": version.stages,
262 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
263 });
264
265 if let Some(ref s) = version.secret_string {
266 response["SecretString"] = json!(s);
267 }
268 if let Some(ref b) = version.secret_binary {
269 response["SecretBinary"] = json!(base64_encode(b));
270 }
271
272 Ok(AwsResponse::ok_json(response))
273 }
274
275 fn put_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
276 let body = req.json_body();
277 let secret_id = require_secret_id(&body)?;
278 validate_optional_string_length(
279 "clientRequestToken",
280 body["ClientRequestToken"].as_str(),
281 32,
282 64,
283 )?;
284 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
285
286 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
287 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
288
289 if secret_string.is_none() && secret_binary.is_none() {
291 return Err(AwsServiceError::aws_error(
292 StatusCode::BAD_REQUEST,
293 "InvalidRequestException",
294 "You must provide either SecretString or SecretBinary.",
295 ));
296 }
297
298 let mut state = self.state.write();
299 let secret = match self.find_secret_mut(&mut state, &secret_id) {
300 Ok(s) => s,
301 Err(_) => {
302 return Err(AwsServiceError::aws_error(
303 StatusCode::NOT_FOUND,
304 "ResourceNotFoundException",
305 "Secrets Manager can't find the specified secret.",
306 ));
307 }
308 };
309
310 if secret.deleted {
311 return Err(AwsServiceError::aws_error(
312 StatusCode::BAD_REQUEST,
313 "InvalidRequestException",
314 "You can't perform this operation on the secret because it was marked for deletion.",
315 ));
316 }
317
318 let now = Utc::now();
319 let version_id = body["ClientRequestToken"]
320 .as_str()
321 .map(|s| s.to_string())
322 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
323
324 match check_secret_version_idempotency(
325 &secret.versions,
326 &version_id,
327 &secret_string,
328 &secret_binary,
329 ) {
330 VersionIdempotency::Match => {
331 let existing_stages = secret.versions[&version_id].stages.clone();
332 return Ok(AwsResponse::ok_json(json!({
333 "ARN": secret.arn,
334 "Name": secret.name,
335 "VersionId": version_id,
336 "VersionStages": existing_stages,
337 })));
338 }
339 VersionIdempotency::Conflict => {
340 return Err(AwsServiceError::aws_error(
341 StatusCode::BAD_REQUEST,
342 "ResourceExistsException",
343 format!(
344 "You can't use ClientRequestToken {version_id} because that value is already in use for a version of secret {}.",
345 secret.arn
346 ),
347 ));
348 }
349 VersionIdempotency::NotFound => {}
350 }
351
352 let mut version_stages: Vec<String> = body["VersionStages"]
353 .as_array()
354 .map(|arr| {
355 arr.iter()
356 .filter_map(|v| v.as_str().map(|s| s.to_string()))
357 .collect()
358 })
359 .unwrap_or_else(|| vec!["AWSCURRENT".to_string()]);
360
361 let has_current = secret
363 .versions
364 .values()
365 .any(|v| v.stages.contains(&"AWSCURRENT".to_string()));
366 if !has_current && !version_stages.contains(&"AWSCURRENT".to_string()) {
367 version_stages.push("AWSCURRENT".to_string());
368 }
369
370 if version_stages.contains(&"AWSCURRENT".to_string()) {
372 if let Some(ref old_vid) = secret.current_version_id.clone() {
373 if let Some(old_version) = secret.versions.get_mut(old_vid) {
374 old_version.stages.retain(|s| s != "AWSCURRENT");
375 if !old_version.stages.contains(&"AWSPREVIOUS".to_string()) {
376 old_version.stages.push("AWSPREVIOUS".to_string());
377 }
378 }
379 for (id, v) in secret.versions.iter_mut() {
381 if id != old_vid {
382 v.stages.retain(|s| s != "AWSPREVIOUS");
383 }
384 }
385 }
386 secret.current_version_id = Some(version_id.clone());
387 }
388
389 for stage in &version_stages {
391 if stage == "AWSCURRENT" || stage == "AWSPREVIOUS" {
392 continue;
393 }
394 for v in secret.versions.values_mut() {
395 v.stages.retain(|s| s != stage);
396 }
397 }
398
399 secret.versions.retain(|_, v| !v.stages.is_empty());
401
402 let version = SecretVersion {
403 version_id: version_id.clone(),
404 secret_string,
405 secret_binary,
406 stages: version_stages.clone(),
407 created_at: now,
408 };
409
410 secret.versions.insert(version_id.clone(), version);
411 secret.last_changed_at = now;
412
413 let response = json!({
414 "ARN": secret.arn,
415 "Name": secret.name,
416 "VersionId": version_id,
417 "VersionStages": version_stages,
418 });
419
420 Ok(AwsResponse::ok_json(response))
421 }
422
423 fn update_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
424 let body = req.json_body();
425 let secret_id = require_secret_id(&body)?;
426 validate_optional_string_length(
427 "clientRequestToken",
428 body["ClientRequestToken"].as_str(),
429 32,
430 64,
431 )?;
432 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
433 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
434 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
435
436 let mut state = self.state.write();
437 let secret = match self.find_secret_mut(&mut state, &secret_id) {
438 Ok(s) => s,
439 Err(_) => {
440 return Err(AwsServiceError::aws_error(
441 StatusCode::NOT_FOUND,
442 "ResourceNotFoundException",
443 "Secrets Manager can't find the specified secret.",
444 ));
445 }
446 };
447
448 if secret.deleted {
449 return Err(AwsServiceError::aws_error(
450 StatusCode::BAD_REQUEST,
451 "InvalidRequestException",
452 "You can't perform this operation on the secret because it was marked for deletion.",
453 ));
454 }
455
456 if let Some(desc) = body["Description"].as_str() {
457 secret.description = Some(desc.to_string());
458 }
459 if let Some(kms) = body["KmsKeyId"].as_str() {
460 secret.kms_key_id = Some(kms.to_string());
461 }
462
463 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
465 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
466
467 let version_id = if secret_string.is_some() || secret_binary.is_some() {
468 let vid = body["ClientRequestToken"]
469 .as_str()
470 .map(|s| s.to_string())
471 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
472
473 match check_secret_version_idempotency(
474 &secret.versions,
475 &vid,
476 &secret_string,
477 &secret_binary,
478 ) {
479 VersionIdempotency::Match => {
480 return Ok(AwsResponse::ok_json(json!({
481 "ARN": secret.arn,
482 "Name": secret.name,
483 "VersionId": vid,
484 })));
485 }
486 VersionIdempotency::Conflict => {
487 return Err(AwsServiceError::aws_error(
488 StatusCode::BAD_REQUEST,
489 "ResourceExistsException",
490 format!(
491 "You can't use ClientRequestToken {vid} because that value is already in use for a version of secret {}.",
492 secret.arn
493 ),
494 ));
495 }
496 VersionIdempotency::NotFound => {}
497 }
498
499 let now = Utc::now();
500
501 if let Some(ref old_vid) = secret.current_version_id.clone() {
503 if let Some(old_v) = secret.versions.get_mut(old_vid) {
504 old_v.stages.retain(|s| s != "AWSCURRENT");
505 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
506 old_v.stages.push("AWSPREVIOUS".to_string());
507 }
508 }
509 }
510
511 let version = SecretVersion {
512 version_id: vid.clone(),
513 secret_string,
514 secret_binary,
515 stages: vec!["AWSCURRENT".to_string()],
516 created_at: now,
517 };
518 secret.versions.insert(vid.clone(), version);
519 secret.current_version_id = Some(vid.clone());
520 secret.last_changed_at = now;
521 Some(vid)
522 } else {
523 secret.last_changed_at = Utc::now();
524 None
525 };
526
527 let mut response = json!({
528 "ARN": secret.arn,
529 "Name": secret.name,
530 });
531 if let Some(vid) = version_id {
532 response["VersionId"] = json!(vid);
533 }
534
535 Ok(AwsResponse::ok_json(response))
536 }
537
538 fn delete_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
539 let body = req.json_body();
540 let secret_id = require_secret_id(&body)?;
541
542 let force_delete = body["ForceDeleteWithoutRecovery"]
543 .as_bool()
544 .unwrap_or(false);
545 let recovery_window = body.get("RecoveryWindowInDays").and_then(|v| v.as_i64());
546
547 if let Some(days) = recovery_window {
549 if !(7..=30).contains(&days) {
550 return Err(AwsServiceError::aws_error(
551 StatusCode::BAD_REQUEST,
552 "InvalidParameterException",
553 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: RecoveryWindowInDays value must be between 7 and 30 days (inclusive).",
554 ));
555 }
556 }
557
558 if force_delete && recovery_window.is_some() {
560 return Err(AwsServiceError::aws_error(
561 StatusCode::BAD_REQUEST,
562 "InvalidParameterException",
563 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: You can't use ForceDeleteWithoutRecovery in conjunction with RecoveryWindowInDays.",
564 ));
565 }
566
567 let mut state = self.state.write();
568
569 if force_delete {
570 match self.find_secret_mut(&mut state, &secret_id) {
572 Ok(secret) => {
573 let arn = secret.arn.clone();
574 let name = secret.name.clone();
575 let deletion_date = Utc::now();
576 state.secrets.remove(&name);
577 let response = json!({
578 "ARN": arn,
579 "Name": name,
580 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
581 });
582 return Ok(AwsResponse::ok_json(response));
583 }
584 Err(_) => {
585 let arn = format!(
587 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
588 req.region,
589 req.account_id,
590 secret_id,
591 &uuid::Uuid::new_v4().to_string()[..6]
592 );
593 let deletion_date = Utc::now();
594 let response = json!({
595 "ARN": arn,
596 "Name": secret_id,
597 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
598 });
599 return Ok(AwsResponse::ok_json(response));
600 }
601 }
602 }
603
604 let secret = self.find_secret_mut(&mut state, &secret_id)?;
605
606 if secret.deleted {
607 return Err(AwsServiceError::aws_error(
608 StatusCode::BAD_REQUEST,
609 "InvalidRequestException",
610 "You can't perform this operation on the secret because it was already scheduled for deletion.",
611 ));
612 }
613
614 let now = Utc::now();
615 let days = recovery_window.unwrap_or(30);
616 let deletion_date = now + chrono::Duration::days(days);
617 secret.deleted = true;
618 secret.deletion_date = Some(deletion_date);
619
620 let response = json!({
621 "ARN": secret.arn,
622 "Name": secret.name,
623 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
624 });
625
626 Ok(AwsResponse::ok_json(response))
627 }
628
629 fn restore_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
630 let body = req.json_body();
631 let secret_id = require_secret_id(&body)?;
632
633 let mut state = self.state.write();
634 let secret = self.find_secret_mut(&mut state, &secret_id)?;
635
636 secret.deleted = false;
638 secret.deletion_date = None;
639
640 let response = json!({
641 "ARN": secret.arn,
642 "Name": secret.name,
643 });
644
645 Ok(AwsResponse::ok_json(response))
646 }
647
648 fn describe_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
649 let body = req.json_body();
650 let secret_id = require_secret_id(&body)?;
651
652 let state = self.state.read();
653 let secret = self.find_secret_ref(&state, &secret_id)?;
654
655 let mut response = json!({
656 "ARN": secret.arn,
657 "Name": secret.name,
658 "CreatedDate": secret.created_at.timestamp_millis() as f64 / 1000.0,
659 "LastChangedDate": secret.last_changed_at.timestamp_millis() as f64 / 1000.0,
660 });
661
662 if !secret.versions.is_empty() {
663 let mut version_ids_to_stages: serde_json::Map<String, Value> = serde_json::Map::new();
664 for (vid, version) in &secret.versions {
665 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
666 }
667 response["VersionIdsToStages"] = Value::Object(version_ids_to_stages);
668 }
669
670 if let Some(ref desc) = secret.description {
671 if !desc.is_empty() {
672 response["Description"] = json!(desc);
673 }
674 }
675
676 if secret.tags_ever_set || !secret.tags.is_empty() {
677 response["Tags"] = json!(tags_to_json(&secret.tags));
678 }
679
680 if let Some(ref kms) = secret.kms_key_id {
681 response["KmsKeyId"] = json!(kms);
682 }
683 if secret.deleted {
684 response["DeletedDate"] = json!(secret
685 .deletion_date
686 .map(|d| d.timestamp_millis() as f64 / 1000.0));
687 }
688 if let Some(rotation_enabled) = secret.rotation_enabled {
689 response["RotationEnabled"] = json!(rotation_enabled);
690 }
691 if let Some(ref lambda_arn) = secret.rotation_lambda_arn {
692 response["RotationLambdaARN"] = json!(lambda_arn);
693 }
694 if let Some(ref rules) = secret.rotation_rules {
695 let mut rules_json = json!({});
696 if let Some(days) = rules.automatically_after_days {
697 rules_json["AutomaticallyAfterDays"] = json!(days);
698 }
699 response["RotationRules"] = rules_json;
700 }
701 if let Some(last_rotated) = secret.last_rotated_at {
702 response["LastRotatedDate"] = json!(last_rotated.timestamp_millis() as f64 / 1000.0);
703 }
704 if secret.rotation_enabled == Some(true) {
706 if let Some(ref rules) = secret.rotation_rules {
707 if let Some(days) = rules.automatically_after_days {
708 let base = secret.last_rotated_at.unwrap_or(secret.created_at);
709 let next = base + chrono::Duration::days(days);
710 response["NextRotationDate"] = json!(next.timestamp_millis() as f64 / 1000.0);
711 }
712 }
713 }
714
715 Ok(AwsResponse::ok_json(response))
716 }
717
718 fn list_secrets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
719 let body = req.json_body();
720 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
721 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
722 validate_optional_enum("sortBy", body["SortBy"].as_str(), &["name", "created-date"])?;
723 validate_optional_enum("sortOrder", body["SortOrder"].as_str(), &["asc", "desc"])?;
724 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
725 let next_token = body["NextToken"].as_str();
726 let filters = body["Filters"].as_array();
727 let include_deleted = body["IncludePlannedDeletion"].as_bool().unwrap_or(false);
728
729 if let Some(filters) = filters {
731 for filter in filters {
732 let key = filter["Key"].as_str().unwrap_or("");
733 let values = filter["Values"].as_array();
734
735 if key.is_empty() {
736 return Err(AwsServiceError::aws_error(
737 StatusCode::BAD_REQUEST,
738 "InvalidParameterException",
739 "Invalid filter key",
740 ));
741 }
742
743 let valid_keys = [
744 "all",
745 "name",
746 "tag-key",
747 "description",
748 "tag-value",
749 "owning-service",
750 "primary-region",
751 ];
752 if !valid_keys.contains(&key) {
753 return Err(AwsServiceError::aws_error(
754 StatusCode::BAD_REQUEST,
755 "ValidationException",
756 format!(
757 "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]",
758 key
759 ),
760 ));
761 }
762
763 if values.is_none() || values.unwrap().is_empty() {
764 return Err(AwsServiceError::aws_error(
765 StatusCode::BAD_REQUEST,
766 "InvalidParameterException",
767 format!("Invalid filter values for key: {key}"),
768 ));
769 }
770 }
771 }
772
773 let state = self.state.read();
774
775 let mut secrets: Vec<&Secret> = state
776 .secrets
777 .values()
778 .filter(|s| {
779 if s.deleted && !include_deleted {
781 return false;
782 }
783
784 if let Some(filters) = filters {
785 for filter in filters {
786 let key = filter["Key"].as_str().unwrap_or("");
787 let values: Vec<&str> = filter["Values"]
788 .as_array()
789 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
790 .unwrap_or_default();
791
792 let matches = match key {
793 "name" => filter_name(s, &values),
794 "description" => filter_description(s, &values),
795 "tag-key" => filter_tag_key(s, &values),
796 "tag-value" => filter_tag_value(s, &values),
797 "all" => filter_all(s, &values),
798 "owning-service" => false,
799 "primary-region" => false,
800 _ => true,
801 };
802
803 if !matches {
804 return false;
805 }
806 }
807 }
808 true
809 })
810 .collect();
811 secrets.sort_by(|a, b| a.created_at.cmp(&b.created_at));
812
813 let start_idx = if let Some(token) = next_token {
815 secrets.iter().position(|s| s.name == token).unwrap_or(0)
816 } else {
817 0
818 };
819
820 let page: Vec<Value> = secrets
821 .iter()
822 .skip(start_idx)
823 .take(max_results)
824 .map(|s| {
825 let mut entry = json!({
826 "ARN": s.arn,
827 "Name": s.name,
828 "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
829 "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
830 });
831
832 if !s.versions.is_empty() {
833 let mut version_ids_to_stages: serde_json::Map<String, Value> =
834 serde_json::Map::new();
835 for (vid, version) in &s.versions {
836 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
837 }
838 entry["SecretVersionsToStages"] = Value::Object(version_ids_to_stages);
839 }
840
841 if let Some(ref desc) = s.description {
842 if !desc.is_empty() {
843 entry["Description"] = json!(desc);
844 }
845 }
846
847 if s.tags_ever_set || !s.tags.is_empty() {
848 entry["Tags"] = json!(tags_to_json(&s.tags));
849 }
850
851 if let Some(ref kms) = s.kms_key_id {
852 entry["KmsKeyId"] = json!(kms);
853 }
854 if s.deleted {
855 entry["DeletedDate"] = json!(s
856 .deletion_date
857 .map(|d| d.timestamp_millis() as f64 / 1000.0));
858 }
859 entry
860 })
861 .collect();
862
863 let has_more = start_idx + max_results < secrets.len();
864 let mut response = json!({
865 "SecretList": page,
866 });
867 if has_more {
868 if let Some(next) = secrets.get(start_idx + max_results) {
869 response["NextToken"] = json!(next.name);
870 }
871 }
872
873 Ok(AwsResponse::ok_json(response))
874 }
875
876 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
877 let body = req.json_body();
878 let secret_id = require_secret_id(&body)?;
879
880 let new_tags = parse_tags(&body["Tags"]);
881
882 let mut state = self.state.write();
883 let secret = self.find_secret_mut(&mut state, &secret_id)?;
884
885 if !new_tags.is_empty() {
886 secret.tags_ever_set = true;
887 }
888 for (k, v) in new_tags {
889 if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
891 existing.1 = v;
892 } else {
893 secret.tags.push((k, v));
894 }
895 }
896
897 Ok(AwsResponse::json(StatusCode::OK, "{}"))
898 }
899
900 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
901 let body = req.json_body();
902 let secret_id = require_secret_id(&body)?;
903
904 let tag_keys: Vec<String> = body["TagKeys"]
905 .as_array()
906 .map(|arr| {
907 arr.iter()
908 .filter_map(|v| v.as_str().map(|s| s.to_string()))
909 .collect()
910 })
911 .unwrap_or_default();
912
913 let mut state = self.state.write();
914 let secret = self.find_secret_mut(&mut state, &secret_id)?;
915
916 secret.tags.retain(|(k, _)| !tag_keys.contains(k));
917
918 Ok(AwsResponse::json(StatusCode::OK, "{}"))
919 }
920
921 fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
922 let body = req.json_body();
923 let secret_id = require_secret_id(&body)?;
924 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
925
926 let state = self.state.read();
927 let secret = self.find_secret_ref(&state, &secret_id)?;
928
929 let versions: Vec<Value> = secret
930 .versions
931 .values()
932 .map(|v| {
933 json!({
934 "VersionId": v.version_id,
935 "VersionStages": v.stages,
936 "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
937 })
938 })
939 .collect();
940
941 let response = json!({
942 "ARN": secret.arn,
943 "Name": secret.name,
944 "Versions": versions,
945 });
946
947 Ok(AwsResponse::ok_json(response))
948 }
949
950 fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
951 let body = req.json_body();
952 let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
953
954 if length < 4 {
955 return Err(AwsServiceError::aws_error(
956 StatusCode::BAD_REQUEST,
957 "InvalidParameterException",
958 "InvalidParameterException",
959 ));
960 }
961 if length > 4096 {
962 return Err(AwsServiceError::aws_error(
963 StatusCode::BAD_REQUEST,
964 "InvalidParameterValue",
965 "InvalidParameterValue",
966 ));
967 }
968
969 let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
970 let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
971 let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
972 let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
973 let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
974 let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
975 validate_optional_string_length(
976 "excludeCharacters",
977 body["ExcludeCharacters"].as_str(),
978 0,
979 4096,
980 )?;
981 let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
982
983 let lowercase = "abcdefghijklmnopqrstuvwxyz";
984 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
985 let digits = "0123456789";
986 let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
987
988 let mut char_pool = String::new();
989 let mut required_chars: Vec<String> = Vec::new();
990
991 if !exclude_lowercase {
992 let filtered: String = lowercase
993 .chars()
994 .filter(|c| !exclude_chars.contains(*c))
995 .collect();
996 if !filtered.is_empty() {
997 required_chars.push(filtered.clone());
998 char_pool.push_str(&filtered);
999 }
1000 }
1001 if !exclude_uppercase {
1002 let filtered: String = uppercase
1003 .chars()
1004 .filter(|c| !exclude_chars.contains(*c))
1005 .collect();
1006 if !filtered.is_empty() {
1007 required_chars.push(filtered.clone());
1008 char_pool.push_str(&filtered);
1009 }
1010 }
1011 if !exclude_numbers {
1012 let filtered: String = digits
1013 .chars()
1014 .filter(|c| !exclude_chars.contains(*c))
1015 .collect();
1016 if !filtered.is_empty() {
1017 required_chars.push(filtered.clone());
1018 char_pool.push_str(&filtered);
1019 }
1020 }
1021 if !exclude_punctuation {
1022 let filtered: String = punctuation
1023 .chars()
1024 .filter(|c| !exclude_chars.contains(*c))
1025 .collect();
1026 if !filtered.is_empty() {
1027 required_chars.push(filtered.clone());
1028 char_pool.push_str(&filtered);
1029 }
1030 }
1031 if include_space && !exclude_chars.contains(' ') {
1032 char_pool.push(' ');
1033 }
1034
1035 if char_pool.is_empty() {
1036 return Err(AwsServiceError::aws_error(
1037 StatusCode::BAD_REQUEST,
1038 "InvalidParameterException",
1039 "InvalidParameterException",
1040 ));
1041 }
1042
1043 let pool_bytes: Vec<char> = char_pool.chars().collect();
1044 let mut password = String::with_capacity(length);
1045
1046 if require_each {
1048 for category in &required_chars {
1050 let chars: Vec<char> = category.chars().collect();
1051 let idx = simple_random() % chars.len();
1052 password.push(chars[idx]);
1053 }
1054 if include_space && !exclude_chars.contains(' ') {
1055 password.push(' ');
1056 }
1057 }
1058
1059 while password.len() < length {
1061 let idx = simple_random() % pool_bytes.len();
1062 password.push(pool_bytes[idx]);
1063 }
1064
1065 let mut chars: Vec<char> = password.chars().collect();
1067 for i in (1..chars.len()).rev() {
1068 let j = simple_random() % (i + 1);
1069 chars.swap(i, j);
1070 }
1071 let password: String = chars.into_iter().take(length).collect();
1072
1073 let response = json!({
1074 "RandomPassword": password,
1075 });
1076
1077 Ok(AwsResponse::ok_json(response))
1078 }
1079
1080 fn rotate_secret(
1081 &self,
1082 req: &AwsRequest,
1083 ) -> Result<(AwsResponse, Option<RotationInvocation>), AwsServiceError> {
1084 let body = req.json_body();
1085 let secret_id = require_secret_id(&body)?;
1086
1087 if let Some(token) = body["ClientRequestToken"].as_str() {
1089 if token.len() < 32 || token.len() > 64 {
1090 return Err(AwsServiceError::aws_error(
1091 StatusCode::BAD_REQUEST,
1092 "InvalidParameterException",
1093 "ClientRequestToken must be 32-64 characters long.",
1094 ));
1095 }
1096 }
1097
1098 if let Some(arn) = body["RotationLambdaARN"].as_str() {
1100 if arn.len() > 2048 {
1101 return Err(AwsServiceError::aws_error(
1102 StatusCode::BAD_REQUEST,
1103 "InvalidParameterException",
1104 "RotationLambdaARN length must be less than or equal to 2048.",
1105 ));
1106 }
1107 }
1108
1109 if let Some(rules) = body["RotationRules"].as_object() {
1111 if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1112 if !(1..=1000).contains(&days) {
1113 return Err(AwsServiceError::aws_error(
1114 StatusCode::BAD_REQUEST,
1115 "InvalidParameterException",
1116 "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1117 ));
1118 }
1119 }
1120 }
1121
1122 let mut state = self.state.write();
1123 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1124
1125 if secret.deleted {
1126 return Err(AwsServiceError::aws_error(
1127 StatusCode::BAD_REQUEST,
1128 "InvalidRequestException",
1129 "You can't perform this operation on the secret because it was marked for deletion.",
1130 ));
1131 }
1132
1133 if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1135 secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1136 }
1137
1138 if let Some(rules) = body["RotationRules"].as_object() {
1139 let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1140 secret.rotation_rules = Some(RotationRules {
1141 automatically_after_days: days,
1142 });
1143 }
1144
1145 secret.rotation_enabled = Some(true);
1146 let now = Utc::now();
1147 secret.last_rotated_at = Some(now);
1148 secret.last_changed_at = now;
1149
1150 let version_id = body["ClientRequestToken"]
1151 .as_str()
1152 .map(|s| s.to_string())
1153 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1154
1155 let has_lambda =
1156 body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1157 let lambda_arn = secret.rotation_lambda_arn.clone();
1158
1159 let mut invocation = None;
1161 if let Some(current_vid) = secret.current_version_id.clone() {
1162 let current_value = secret.versions.get(¤t_vid).cloned();
1163
1164 if let Some(cv) = current_value {
1165 if has_lambda {
1166 if let Some(ref arn) = lambda_arn {
1173 invocation = Some(RotationInvocation {
1174 lambda_arn: arn.clone(),
1175 secret_id: secret.arn.clone(),
1176 client_request_token: version_id.clone(),
1177 });
1178 }
1179 } else {
1180 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1183 old_v.stages.retain(|s| s != "AWSCURRENT");
1184 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1185 old_v.stages.push("AWSPREVIOUS".to_string());
1186 }
1187 }
1188 let version = SecretVersion {
1189 version_id: version_id.clone(),
1190 secret_string: cv.secret_string.clone(),
1191 secret_binary: cv.secret_binary.clone(),
1192 stages: vec!["AWSCURRENT".to_string()],
1193 created_at: now,
1194 };
1195 secret.versions.insert(version_id.clone(), version);
1196 secret.current_version_id = Some(version_id.clone());
1197 }
1198 }
1199 }
1200
1201 let response = json!({
1202 "ARN": secret.arn,
1203 "Name": secret.name,
1204 "VersionId": version_id,
1205 });
1206
1207 Ok((AwsResponse::ok_json(response), invocation))
1208 }
1209
1210 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1211 let body = req.json_body();
1212 let secret_id = require_secret_id(&body)?;
1213
1214 let mut state = self.state.write();
1215 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1216
1217 if secret.deleted {
1218 return Err(AwsServiceError::aws_error(
1219 StatusCode::BAD_REQUEST,
1220 "InvalidRequestException",
1221 "You can't perform this operation on the secret because it was marked for deletion.",
1222 ));
1223 }
1224
1225 if secret.rotation_enabled != Some(true) {
1226 return Err(AwsServiceError::aws_error(
1227 StatusCode::BAD_REQUEST,
1228 "InvalidRequestException",
1229 "You can't cancel rotation for a secret that does not have rotation enabled.",
1230 ));
1231 }
1232
1233 secret.rotation_enabled = Some(false);
1234
1235 let response = json!({
1236 "ARN": secret.arn,
1237 "Name": secret.name,
1238 });
1239
1240 Ok(AwsResponse::ok_json(response))
1241 }
1242
1243 fn update_secret_version_stage(
1244 &self,
1245 req: &AwsRequest,
1246 ) -> Result<AwsResponse, AwsServiceError> {
1247 let body = req.json_body();
1248 let secret_id = require_secret_id(&body)?;
1249 let version_stage = body["VersionStage"]
1250 .as_str()
1251 .ok_or_else(|| {
1252 AwsServiceError::aws_error(
1253 StatusCode::BAD_REQUEST,
1254 "InvalidParameterException",
1255 "VersionStage is required",
1256 )
1257 })?
1258 .to_string();
1259 validate_string_length("versionStage", &version_stage, 1, 256)?;
1260 validate_optional_string_length(
1261 "removeFromVersionId",
1262 body["RemoveFromVersionId"].as_str(),
1263 32,
1264 64,
1265 )?;
1266 validate_optional_string_length(
1267 "moveToVersionId",
1268 body["MoveToVersionId"].as_str(),
1269 32,
1270 64,
1271 )?;
1272
1273 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1274 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1275
1276 let mut state = self.state.write();
1277 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1278
1279 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1281 let current_holder = secret
1283 .versions
1284 .iter()
1285 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1286 .map(|(id, _)| id.clone());
1287
1288 if let Some(current_vid) = current_holder {
1289 return Err(AwsServiceError::aws_error(
1290 StatusCode::BAD_REQUEST,
1291 "InvalidParameterException",
1292 format!(
1293 "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."
1294 ),
1295 ));
1296 }
1297 }
1298
1299 if let Some(ref remove_vid) = remove_from {
1301 if let Some(version) = secret.versions.get_mut(remove_vid) {
1302 version.stages.retain(|s| s != &version_stage);
1303 if version_stage == "AWSCURRENT" {
1305 for (id, v) in secret.versions.iter_mut() {
1307 if id != remove_vid {
1308 v.stages.retain(|s| s != "AWSPREVIOUS");
1309 }
1310 }
1311 if let Some(v) = secret.versions.get_mut(remove_vid) {
1313 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1314 v.stages.push("AWSPREVIOUS".to_string());
1315 }
1316 }
1317 }
1318 }
1319 }
1320
1321 if let Some(ref move_vid) = move_to {
1323 if let Some(version) = secret.versions.get_mut(move_vid) {
1324 if !version.stages.contains(&version_stage) {
1325 version.stages.push(version_stage.clone());
1326 }
1327 }
1328 if version_stage == "AWSCURRENT" {
1330 secret.current_version_id = Some(move_vid.clone());
1331 }
1332 }
1333
1334 let response = json!({
1335 "ARN": secret.arn,
1336 "Name": secret.name,
1337 });
1338
1339 Ok(AwsResponse::ok_json(response))
1340 }
1341
1342 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1343 let body = req.json_body();
1344 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1345 let secret_id_list = body["SecretIdList"].as_array();
1346 let filters = body["Filters"].as_array();
1347 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1348
1349 if secret_id_list.is_some() && filters.is_some() {
1351 return Err(AwsServiceError::aws_error(
1352 StatusCode::BAD_REQUEST,
1353 "InvalidParameterException",
1354 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1355 ));
1356 }
1357
1358 if max_results.is_some() && filters.is_none() {
1360 return Err(AwsServiceError::aws_error(
1361 StatusCode::BAD_REQUEST,
1362 "InvalidParameterException",
1363 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1364 ));
1365 }
1366
1367 let state = self.state.read();
1368 let mut secret_values: Vec<Value> = Vec::new();
1369 let mut errors: Vec<Value> = Vec::new();
1370
1371 if let Some(id_list) = secret_id_list {
1372 for id_val in id_list {
1373 let sid = id_val.as_str().unwrap_or("");
1374 match self.find_secret_ref(&state, sid) {
1375 Ok(secret) => {
1376 if secret.deleted {
1377 errors.push(json!({
1378 "SecretId": sid,
1379 "ErrorCode": "InvalidRequestException",
1380 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1381 }));
1382 } else if let Some(ref current_vid) = secret.current_version_id {
1383 if let Some(version) = secret.versions.get(current_vid) {
1384 let mut entry = json!({
1385 "ARN": secret.arn,
1386 "Name": secret.name,
1387 "VersionId": version.version_id,
1388 "VersionStages": version.stages,
1389 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1390 });
1391 if let Some(ref s) = version.secret_string {
1392 entry["SecretString"] = json!(s);
1393 }
1394 if let Some(ref b) = version.secret_binary {
1395 entry["SecretBinary"] = json!(base64_encode(b));
1396 }
1397 secret_values.push(entry);
1398 } else {
1399 errors.push(json!({
1400 "SecretId": sid,
1401 "ErrorCode": "ResourceNotFoundException",
1402 "Message": "Secrets Manager can't find the specified secret.",
1403 }));
1404 }
1405 } else {
1406 errors.push(json!({
1407 "SecretId": sid,
1408 "ErrorCode": "ResourceNotFoundException",
1409 "Message": "Secrets Manager can't find the specified secret.",
1410 }));
1411 }
1412 }
1413 Err(_) => {
1414 errors.push(json!({
1415 "SecretId": sid,
1416 "ErrorCode": "ResourceNotFoundException",
1417 "Message": "Secrets Manager can't find the specified secret.",
1418 }));
1419 }
1420 }
1421 }
1422 } else if let Some(filters) = filters {
1423 let matching: Vec<&Secret> = state
1425 .secrets
1426 .values()
1427 .filter(|s| {
1428 if s.deleted {
1429 return false;
1430 }
1431 for filter in filters {
1432 let key = filter["Key"].as_str().unwrap_or("");
1433 let values: Vec<&str> = filter["Values"]
1434 .as_array()
1435 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1436 .unwrap_or_default();
1437 let matches = match key {
1438 "name" => filter_name(s, &values),
1439 "description" => filter_description(s, &values),
1440 "tag-key" => filter_tag_key(s, &values),
1441 "tag-value" => filter_tag_value(s, &values),
1442 "all" => filter_all(s, &values),
1443 _ => true,
1444 };
1445 if !matches {
1446 return false;
1447 }
1448 }
1449 true
1450 })
1451 .collect();
1452
1453 let limit = max_results.unwrap_or(100) as usize;
1454 let mut no_value_found = false;
1455 let mut matching = matching;
1456 matching.sort_by(|a, b| a.name.cmp(&b.name));
1457
1458 for secret in matching.iter().take(limit) {
1459 if let Some(ref current_vid) = secret.current_version_id {
1460 if let Some(version) = secret.versions.get(current_vid) {
1461 let mut entry = json!({
1462 "ARN": secret.arn,
1463 "Name": secret.name,
1464 "VersionId": version.version_id,
1465 "VersionStages": version.stages,
1466 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1467 });
1468 if let Some(ref s) = version.secret_string {
1469 entry["SecretString"] = json!(s);
1470 }
1471 if let Some(ref b) = version.secret_binary {
1472 entry["SecretBinary"] = json!(base64_encode(b));
1473 }
1474 secret_values.push(entry);
1475 } else {
1476 no_value_found = true;
1477 }
1478 } else {
1479 no_value_found = true;
1480 }
1481 }
1482
1483 if no_value_found && secret_values.is_empty() {
1484 return Err(AwsServiceError::aws_error(
1485 StatusCode::NOT_FOUND,
1486 "ResourceNotFoundException",
1487 "Secrets Manager can't find the specified secret.",
1488 ));
1489 }
1490 }
1491
1492 let mut response = json!({
1493 "SecretValues": secret_values,
1494 "Errors": errors,
1495 });
1496
1497 if errors.is_empty() {
1499 response.as_object_mut().unwrap().remove("Errors");
1500 }
1501
1502 Ok(AwsResponse::ok_json(response))
1503 }
1504
1505 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1506 let body = req.json_body();
1507 let secret_id = require_secret_id(&body)?;
1508
1509 let state = self.state.read();
1510 let secret = self.find_secret_ref(&state, &secret_id)?;
1511
1512 let mut response = json!({
1513 "ARN": secret.arn,
1514 "Name": secret.name,
1515 });
1516
1517 if let Some(ref policy) = secret.resource_policy {
1518 response["ResourcePolicy"] = json!(policy);
1519 }
1520
1521 Ok(AwsResponse::ok_json(response))
1522 }
1523
1524 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1525 let body = req.json_body();
1526 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1527 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1528 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1529 AwsServiceError::aws_error(
1530 StatusCode::BAD_REQUEST,
1531 "InvalidParameterException",
1532 "ResourcePolicy must be a string",
1533 )
1534 })?;
1535 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1536
1537 if let Some(secret_id) = body["SecretId"].as_str() {
1539 let state = self.state.read();
1540 self.find_secret_key(&state, secret_id)?;
1541 }
1542
1543 let response = json!({
1544 "PolicyValidationPassed": true,
1545 "ValidationErrors": [],
1546 });
1547 Ok(AwsResponse::ok_json(response))
1548 }
1549
1550 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1551 let body = req.json_body();
1552 let secret_id = require_secret_id(&body)?;
1553 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1554 validate_optional_string_length(
1555 "resourcePolicy",
1556 body["ResourcePolicy"].as_str(),
1557 1,
1558 20480,
1559 )?;
1560 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1561
1562 let mut state = self.state.write();
1563 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1564 secret.resource_policy = policy;
1565
1566 let response = json!({
1567 "ARN": secret.arn,
1568 "Name": secret.name,
1569 });
1570
1571 Ok(AwsResponse::ok_json(response))
1572 }
1573
1574 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1575 let body = req.json_body();
1576 let secret_id = require_secret_id(&body)?;
1577
1578 let mut state = self.state.write();
1579 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1580 secret.resource_policy = None;
1581
1582 let response = json!({
1583 "ARN": secret.arn,
1584 "Name": secret.name,
1585 });
1586
1587 Ok(AwsResponse::ok_json(response))
1588 }
1589
1590 fn replicate_secret_to_regions(
1591 &self,
1592 req: &AwsRequest,
1593 ) -> Result<AwsResponse, AwsServiceError> {
1594 let body = req.json_body();
1595 let secret_id = require_secret_id(&body)?;
1596
1597 let state = self.state.read();
1598 let secret = self.find_secret_ref(&state, &secret_id)?;
1599
1600 let response = json!({
1601 "ARN": secret.arn,
1602 "ReplicationStatus": [],
1603 });
1604 Ok(AwsResponse::ok_json(response))
1605 }
1606
1607 fn remove_regions_from_replication(
1608 &self,
1609 req: &AwsRequest,
1610 ) -> Result<AwsResponse, AwsServiceError> {
1611 let body = req.json_body();
1612 let secret_id = require_secret_id(&body)?;
1613
1614 let state = self.state.read();
1615 let secret = self.find_secret_ref(&state, &secret_id)?;
1616
1617 let response = json!({
1618 "ARN": secret.arn,
1619 "ReplicationStatus": [],
1620 });
1621 Ok(AwsResponse::ok_json(response))
1622 }
1623
1624 fn stop_replication_to_replica(
1625 &self,
1626 req: &AwsRequest,
1627 ) -> Result<AwsResponse, AwsServiceError> {
1628 let body = req.json_body();
1629 let secret_id = require_secret_id(&body)?;
1630
1631 let state = self.state.read();
1632 let secret = self.find_secret_ref(&state, &secret_id)?;
1633
1634 let response = json!({
1635 "ARN": secret.arn,
1636 });
1637 Ok(AwsResponse::ok_json(response))
1638 }
1639
1640 fn find_secret_mut<'a>(
1642 &self,
1643 state: &'a mut crate::state::SecretsManagerState,
1644 secret_id: &str,
1645 ) -> Result<&'a mut Secret, AwsServiceError> {
1646 let key = self.find_secret_key(state, secret_id)?;
1647 Ok(state.secrets.get_mut(&key).unwrap())
1648 }
1649
1650 fn find_secret_key(
1651 &self,
1652 state: &crate::state::SecretsManagerState,
1653 secret_id: &str,
1654 ) -> Result<String, AwsServiceError> {
1655 if state.secrets.contains_key(secret_id) {
1656 return Ok(secret_id.to_string());
1657 }
1658
1659 for secret in state.secrets.values() {
1660 if secret.arn == secret_id {
1661 return Ok(secret.name.clone());
1662 }
1663 }
1664
1665 if secret_id.starts_with("arn:aws:secretsmanager:") {
1666 for secret in state.secrets.values() {
1667 if secret.arn.starts_with(secret_id) {
1668 return Ok(secret.name.clone());
1669 }
1670 }
1671 }
1672
1673 Err(AwsServiceError::aws_error(
1674 StatusCode::NOT_FOUND,
1675 "ResourceNotFoundException",
1676 "Secrets Manager can't find the specified secret.",
1677 ))
1678 }
1679
1680 fn find_secret_ref<'a>(
1682 &self,
1683 state: &'a crate::state::SecretsManagerState,
1684 secret_id: &str,
1685 ) -> Result<&'a Secret, AwsServiceError> {
1686 if let Some(secret) = state.secrets.get(secret_id) {
1687 return Ok(secret);
1688 }
1689
1690 for secret in state.secrets.values() {
1692 if secret.arn == secret_id {
1693 return Ok(secret);
1694 }
1695 }
1696
1697 if secret_id.starts_with("arn:aws:secretsmanager:") {
1699 for secret in state.secrets.values() {
1700 if secret.arn.starts_with(secret_id) {
1701 return Ok(secret);
1702 }
1703 }
1704 }
1705
1706 Err(AwsServiceError::aws_error(
1707 StatusCode::NOT_FOUND,
1708 "ResourceNotFoundException",
1709 "Secrets Manager can't find the specified secret.",
1710 ))
1711 }
1712}
1713
1714struct CreateSecretInput {
1716 name: String,
1717 client_request_token: Option<String>,
1718 description: Option<String>,
1719 kms_key_id: Option<String>,
1720 secret_string: Option<String>,
1721 secret_binary: Option<Vec<u8>>,
1722 tags: Vec<(String, String)>,
1723}
1724
1725impl CreateSecretInput {
1726 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
1727 validate_required("Name", &body["Name"])?;
1728 let name = body["Name"]
1729 .as_str()
1730 .ok_or_else(|| {
1731 AwsServiceError::aws_error(
1732 StatusCode::BAD_REQUEST,
1733 "InvalidParameterException",
1734 "Name is required",
1735 )
1736 })?
1737 .to_string();
1738 validate_string_length("name", &name, 1, 512)?;
1739 validate_optional_string_length(
1740 "clientRequestToken",
1741 body["ClientRequestToken"].as_str(),
1742 32,
1743 64,
1744 )?;
1745 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
1746 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
1747 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
1748
1749 Ok(Self {
1750 name,
1751 client_request_token: body["ClientRequestToken"].as_str().map(|s| s.to_string()),
1752 description: body["Description"].as_str().map(|s| s.to_string()),
1753 kms_key_id: body["KmsKeyId"].as_str().map(|s| s.to_string()),
1754 secret_string: body["SecretString"].as_str().map(|s| s.to_string()),
1755 secret_binary: body["SecretBinary"].as_str().and_then(base64_decode),
1756 tags: parse_tags(&body["Tags"]),
1757 })
1758 }
1759}
1760
1761fn require_secret_id(body: &Value) -> Result<String, AwsServiceError> {
1762 let id = body["SecretId"].as_str().ok_or_else(|| {
1763 AwsServiceError::aws_error(
1764 StatusCode::BAD_REQUEST,
1765 "InvalidParameterException",
1766 "SecretId is required",
1767 )
1768 })?;
1769 validate_string_length("secretId", id, 1, 2048)?;
1770 Ok(id.to_string())
1771}
1772
1773fn parse_tags(tags_val: &Value) -> Vec<(String, String)> {
1774 tags_val
1775 .as_array()
1776 .map(|arr| {
1777 arr.iter()
1778 .filter_map(|t| {
1779 let key = t["Key"].as_str()?;
1780 let value = t["Value"].as_str()?;
1781 Some((key.to_string(), value.to_string()))
1782 })
1783 .collect()
1784 })
1785 .unwrap_or_default()
1786}
1787
1788fn tags_to_json(tags: &[(String, String)]) -> Vec<Value> {
1789 tags.iter()
1790 .map(|(k, v)| json!({"Key": k, "Value": v}))
1791 .collect()
1792}
1793
1794fn split_words(text: &str) -> Vec<String> {
1799 let mut all_words = Vec::new();
1801 for space_part in text.split_whitespace() {
1802 all_words.extend(split_words_no_space(space_part));
1803 }
1804 all_words
1805}
1806
1807fn split_words_no_space(text: &str) -> Vec<String> {
1808 let special_chars = ['/', '-', '_', '+', '=', '.', '@'];
1809
1810 if text.len() == 1 && special_chars.contains(&text.chars().next().unwrap_or(' ')) {
1812 return vec![];
1813 }
1814
1815 let present: Vec<char> = special_chars
1817 .iter()
1818 .filter(|&&c| text.contains(c))
1819 .copied()
1820 .collect();
1821
1822 if present.len() > 1 {
1823 return vec![text.to_string()];
1825 }
1826
1827 if present.len() == 1 {
1828 let ch = present[0];
1829 let parts: Vec<&str> = text.split(ch).filter(|s| !s.is_empty()).collect();
1830 let mut result = Vec::new();
1831 for part in parts {
1832 result.extend(split_by_uppercase(part));
1833 }
1834 return result;
1835 }
1836
1837 split_by_uppercase(text)
1839}
1840
1841fn split_by_uppercase(text: &str) -> Vec<String> {
1844 let chars: Vec<char> = text.chars().collect();
1847 let mut words = Vec::new();
1848 let mut last_end = 0;
1849 let mut i = 0;
1850
1851 while i < chars.len() {
1852 if !chars[i].is_ascii_lowercase()
1854 && i + 1 < chars.len()
1855 && chars[i + 1].is_ascii_lowercase()
1856 {
1857 if i > last_end {
1859 let between: String = chars[last_end..i].iter().collect();
1860 let trimmed = between.trim().to_string();
1861 if !trimmed.is_empty() {
1862 words.push(trimmed);
1863 }
1864 }
1865
1866 let start = i;
1868 i += 2;
1869 while i < chars.len() && chars[i].is_ascii_lowercase() {
1870 i += 1;
1871 }
1872 let word: String = chars[start..i].iter().collect();
1873 let trimmed = word.trim().to_string();
1874 if !trimmed.is_empty() {
1875 words.push(trimmed);
1876 }
1877 last_end = i;
1878 } else {
1879 i += 1;
1880 }
1881 }
1882
1883 if last_end < chars.len() {
1885 let after: String = chars[last_end..].iter().collect();
1886 let trimmed = after.trim().to_string();
1887 if !trimmed.is_empty() {
1888 words.push(trimmed);
1889 }
1890 }
1891
1892 words
1893}
1894
1895fn match_pattern(pattern: &str, value: &str, match_prefix: bool, case_sensitive: bool) -> bool {
1899 if match_prefix {
1900 if case_sensitive {
1901 value.starts_with(pattern)
1902 } else {
1903 value.to_lowercase().starts_with(&pattern.to_lowercase())
1904 }
1905 } else {
1906 let mut pattern_words = split_words(pattern);
1907 if pattern_words.is_empty() {
1908 return false;
1909 }
1910 let mut value_words = split_words(value);
1911 if !case_sensitive {
1912 pattern_words = pattern_words.iter().map(|w| w.to_lowercase()).collect();
1913 value_words = value_words.iter().map(|w| w.to_lowercase()).collect();
1914 }
1915 for pw in &pattern_words {
1916 if !value_words.iter().any(|vw| vw.starts_with(pw.as_str())) {
1917 return false;
1918 }
1919 }
1920 true
1921 }
1922}
1923
1924fn matcher(patterns: &[&str], strings: &[&str], match_prefix: bool, case_sensitive: bool) -> bool {
1927 for pattern in patterns.iter().filter(|p| p.starts_with('!')) {
1929 let inner = &pattern[1..];
1930 for s in strings {
1931 if !match_pattern(inner, s, match_prefix, case_sensitive) {
1932 return true;
1933 }
1934 }
1935 }
1936
1937 for pattern in patterns.iter().filter(|p| !p.starts_with('!')) {
1939 for s in strings {
1940 if match_pattern(pattern, s, match_prefix, case_sensitive) {
1941 return true;
1942 }
1943 }
1944 }
1945 false
1946}
1947
1948fn filter_name(secret: &Secret, values: &[&str]) -> bool {
1950 matcher(values, &[secret.name.as_str()], true, true)
1951}
1952
1953fn filter_description(secret: &Secret, values: &[&str]) -> bool {
1955 match secret.description.as_deref() {
1956 Some(desc) if !desc.is_empty() => matcher(values, &[desc], false, false),
1957 _ => false,
1958 }
1959}
1960
1961fn filter_tag_key(secret: &Secret, values: &[&str]) -> bool {
1963 if secret.tags.is_empty() {
1964 return false;
1965 }
1966 let keys: Vec<&str> = secret.tags.iter().map(|(k, _)| k.as_str()).collect();
1967 matcher(values, &keys, true, true)
1968}
1969
1970fn filter_tag_value(secret: &Secret, values: &[&str]) -> bool {
1972 if secret.tags.is_empty() {
1973 return false;
1974 }
1975 let vals: Vec<&str> = secret.tags.iter().map(|(_, v)| v.as_str()).collect();
1976 matcher(values, &vals, true, true)
1977}
1978
1979fn filter_all(secret: &Secret, values: &[&str]) -> bool {
1981 let mut attributes: Vec<&str> = vec![secret.name.as_str()];
1982 if let Some(ref desc) = secret.description {
1983 if !desc.is_empty() {
1984 attributes.push(desc.as_str());
1985 }
1986 }
1987 for (k, v) in &secret.tags {
1988 attributes.push(k.as_str());
1989 attributes.push(v.as_str());
1990 }
1991 matcher(values, &attributes, false, false)
1992}
1993
1994fn simple_random() -> usize {
1995 use std::collections::hash_map::RandomState;
1996 use std::hash::{BuildHasher, Hasher};
1997 let s = RandomState::new();
1998 let mut hasher = s.build_hasher();
1999 hasher.write_usize(0);
2000 hasher.finish() as usize
2001}
2002
2003#[async_trait]
2004impl AwsService for SecretsManagerService {
2005 fn service_name(&self) -> &str {
2006 "secretsmanager"
2007 }
2008
2009 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2010 match req.action.as_str() {
2011 "CreateSecret" => self.create_secret(&req),
2012 "GetSecretValue" => self.get_secret_value(&req),
2013 "PutSecretValue" => self.put_secret_value(&req),
2014 "UpdateSecret" => self.update_secret(&req),
2015 "DeleteSecret" => self.delete_secret(&req),
2016 "RestoreSecret" => self.restore_secret(&req),
2017 "DescribeSecret" => self.describe_secret(&req),
2018 "ListSecrets" => self.list_secrets(&req),
2019 "TagResource" => self.tag_resource(&req),
2020 "UntagResource" => self.untag_resource(&req),
2021 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
2022 "GetRandomPassword" => self.get_random_password(&req),
2023 "RotateSecret" => {
2024 let (response, invocation) = self.rotate_secret(&req)?;
2025 if let Some(inv) = invocation {
2026 if let Some(ref bus) = self.delivery_bus {
2027 let bus = bus.clone();
2028 tokio::spawn(async move {
2030 for step in &["createSecret", "setSecret", "testSecret", "finishSecret"]
2031 {
2032 let payload = serde_json::json!({
2033 "SecretId": inv.secret_id,
2034 "ClientRequestToken": inv.client_request_token,
2035 "Step": step,
2036 });
2037 let payload_str = payload.to_string();
2038 match bus.invoke_lambda(&inv.lambda_arn, &payload_str).await {
2039 Some(Ok(_)) => {}
2040 Some(Err(e)) => {
2041 tracing::warn!(
2042 step = step,
2043 error = %e,
2044 "rotation Lambda invocation failed"
2045 );
2046 }
2047 None => {
2048 tracing::warn!(
2049 lambda_arn = %inv.lambda_arn,
2050 step = step,
2051 "rotation Lambda delivery not configured; \
2052 Lambda invocation skipped"
2053 );
2054 break;
2055 }
2056 }
2057 }
2058 });
2059 }
2060 }
2061 Ok(response)
2062 }
2063 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2064 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2065 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2066 "GetResourcePolicy" => self.get_resource_policy(&req),
2067 "PutResourcePolicy" => self.put_resource_policy(&req),
2068 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2069 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2070 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2071 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2072 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2073 _ => Err(AwsServiceError::action_not_implemented(
2074 "secretsmanager",
2075 &req.action,
2076 )),
2077 }
2078 }
2079
2080 fn supported_actions(&self) -> &[&str] {
2081 &[
2082 "CreateSecret",
2083 "GetSecretValue",
2084 "PutSecretValue",
2085 "UpdateSecret",
2086 "DeleteSecret",
2087 "RestoreSecret",
2088 "DescribeSecret",
2089 "ListSecrets",
2090 "TagResource",
2091 "UntagResource",
2092 "ListSecretVersionIds",
2093 "GetRandomPassword",
2094 "RotateSecret",
2095 "CancelRotateSecret",
2096 "UpdateSecretVersionStage",
2097 "BatchGetSecretValue",
2098 "GetResourcePolicy",
2099 "PutResourcePolicy",
2100 "DeleteResourcePolicy",
2101 "ValidateResourcePolicy",
2102 "ReplicateSecretToRegions",
2103 "RemoveRegionsFromReplication",
2104 "StopReplicationToReplica",
2105 ]
2106 }
2107}
2108
2109fn base64_decode(input: &str) -> Option<Vec<u8>> {
2110 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2111 let mut buf = Vec::new();
2112 let mut bits: u32 = 0;
2113 let mut count = 0;
2114 for &b in input.as_bytes() {
2115 if b == b'=' || b == b'\n' || b == b'\r' {
2116 continue;
2117 }
2118 let val = table.iter().position(|&c| c == b)? as u32;
2119 bits = (bits << 6) | val;
2120 count += 1;
2121 if count == 4 {
2122 buf.push((bits >> 16) as u8);
2123 buf.push((bits >> 8) as u8);
2124 buf.push(bits as u8);
2125 bits = 0;
2126 count = 0;
2127 }
2128 }
2129 match count {
2130 2 => {
2131 bits <<= 12;
2132 buf.push((bits >> 16) as u8);
2133 }
2134 3 => {
2135 bits <<= 6;
2136 buf.push((bits >> 16) as u8);
2137 buf.push((bits >> 8) as u8);
2138 }
2139 _ => {}
2140 }
2141 Some(buf)
2142}
2143
2144fn base64_encode(input: &[u8]) -> String {
2145 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2146 let mut result = String::new();
2147 for chunk in input.chunks(3) {
2148 let b0 = chunk[0] as u32;
2149 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
2150 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
2151 let triple = (b0 << 16) | (b1 << 8) | b2;
2152 result.push(table[((triple >> 18) & 0x3F) as usize] as char);
2153 result.push(table[((triple >> 12) & 0x3F) as usize] as char);
2154 if chunk.len() > 1 {
2155 result.push(table[((triple >> 6) & 0x3F) as usize] as char);
2156 } else {
2157 result.push('=');
2158 }
2159 if chunk.len() > 2 {
2160 result.push(table[(triple & 0x3F) as usize] as char);
2161 } else {
2162 result.push('=');
2163 }
2164 }
2165 result
2166}
2167
2168#[cfg(test)]
2169mod tests {
2170 use super::*;
2171 use crate::state::SecretsManagerState;
2172 use bytes::Bytes;
2173 use http::{HeaderMap, Method};
2174 use parking_lot::RwLock;
2175 use std::collections::HashMap;
2176 use std::sync::Arc;
2177
2178 fn make_state() -> SharedSecretsManagerState {
2179 Arc::new(RwLock::new(SecretsManagerState::new(
2180 "123456789012",
2181 "us-east-1",
2182 )))
2183 }
2184
2185 fn make_request(action: &str, body: &str) -> AwsRequest {
2186 AwsRequest {
2187 service: "secretsmanager".to_string(),
2188 action: action.to_string(),
2189 region: "us-east-1".to_string(),
2190 account_id: "123456789012".to_string(),
2191 request_id: "test-request-id".to_string(),
2192 headers: HeaderMap::new(),
2193 query_params: HashMap::new(),
2194 body: Bytes::from(body.to_string()),
2195 path_segments: vec![],
2196 raw_path: "/".to_string(),
2197 raw_query: String::new(),
2198 method: Method::POST,
2199 is_query_protocol: false,
2200 access_key_id: None,
2201 }
2202 }
2203
2204 #[tokio::test]
2205 async fn test_create_and_get_secret() {
2206 let state = make_state();
2207 let svc = SecretsManagerService::new(state);
2208
2209 let req = make_request(
2210 "CreateSecret",
2211 r#"{"Name": "test/secret", "SecretString": "mysecretvalue"}"#,
2212 );
2213 let resp = svc.handle(req).await.unwrap();
2214 assert_eq!(resp.status, StatusCode::OK);
2215 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2216 assert_eq!(body["Name"], "test/secret");
2217 assert!(body["ARN"].as_str().unwrap().contains("test/secret"));
2218
2219 let req = make_request("GetSecretValue", r#"{"SecretId": "test/secret"}"#);
2220 let resp = svc.handle(req).await.unwrap();
2221 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2222 assert_eq!(body["SecretString"], "mysecretvalue");
2223 }
2224
2225 #[tokio::test]
2226 async fn test_create_secret_without_value() {
2227 let state = make_state();
2228 let svc = SecretsManagerService::new(state);
2229
2230 let req = make_request("CreateSecret", r#"{"Name": "empty-secret"}"#);
2231 let resp = svc.handle(req).await.unwrap();
2232 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2233 assert_eq!(body["Name"], "empty-secret");
2234 assert!(body.get("VersionId").is_none());
2235 }
2236
2237 #[tokio::test]
2238 async fn test_put_secret_value_creates_version() {
2239 let state = make_state();
2240 let svc = SecretsManagerService::new(state);
2241
2242 let req = make_request(
2243 "CreateSecret",
2244 r#"{"Name": "versioned", "SecretString": "v1"}"#,
2245 );
2246 svc.handle(req).await.unwrap();
2247
2248 let req = make_request(
2249 "PutSecretValue",
2250 r#"{"SecretId": "versioned", "SecretString": "v2"}"#,
2251 );
2252 let resp = svc.handle(req).await.unwrap();
2253 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2254 assert_eq!(body["Name"], "versioned");
2255
2256 let req = make_request("GetSecretValue", r#"{"SecretId": "versioned"}"#);
2258 let resp = svc.handle(req).await.unwrap();
2259 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2260 assert_eq!(body["SecretString"], "v2");
2261 }
2262
2263 #[tokio::test]
2264 async fn test_delete_and_restore_secret() {
2265 let state = make_state();
2266 let svc = SecretsManagerService::new(state);
2267
2268 let req = make_request(
2269 "CreateSecret",
2270 r#"{"Name": "deleteme", "SecretString": "value"}"#,
2271 );
2272 svc.handle(req).await.unwrap();
2273
2274 let req = make_request("DeleteSecret", r#"{"SecretId": "deleteme"}"#);
2276 let resp = svc.handle(req).await.unwrap();
2277 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2278 assert!(body["DeletionDate"].as_f64().is_some());
2279
2280 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2282 assert!(svc.handle(req).await.is_err());
2283
2284 let req = make_request("RestoreSecret", r#"{"SecretId": "deleteme"}"#);
2286 let resp = svc.handle(req).await.unwrap();
2287 assert_eq!(resp.status, StatusCode::OK);
2288
2289 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2291 let resp = svc.handle(req).await.unwrap();
2292 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2293 assert_eq!(body["SecretString"], "value");
2294 }
2295
2296 #[tokio::test]
2297 async fn test_list_secrets() {
2298 let state = make_state();
2299 let svc = SecretsManagerService::new(state);
2300
2301 for name in &["alpha", "beta", "gamma"] {
2302 let req = make_request(
2303 "CreateSecret",
2304 &format!(r#"{{"Name": "{name}", "SecretString": "val"}}"#),
2305 );
2306 svc.handle(req).await.unwrap();
2307 }
2308
2309 let req = make_request("ListSecrets", "{}");
2310 let resp = svc.handle(req).await.unwrap();
2311 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2312 assert_eq!(body["SecretList"].as_array().unwrap().len(), 3);
2313 }
2314
2315 #[tokio::test]
2316 async fn test_tags() {
2317 let state = make_state();
2318 let svc = SecretsManagerService::new(state);
2319
2320 let req = make_request(
2321 "CreateSecret",
2322 r#"{"Name": "tagged", "SecretString": "val"}"#,
2323 );
2324 svc.handle(req).await.unwrap();
2325
2326 let req = make_request(
2327 "TagResource",
2328 r#"{"SecretId": "tagged", "Tags": [{"Key": "env", "Value": "prod"}]}"#,
2329 );
2330 svc.handle(req).await.unwrap();
2331
2332 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2333 let resp = svc.handle(req).await.unwrap();
2334 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2335 let tags = body["Tags"].as_array().unwrap();
2336 assert!(tags
2337 .iter()
2338 .any(|t| t["Key"] == "env" && t["Value"] == "prod"));
2339
2340 let req = make_request(
2341 "UntagResource",
2342 r#"{"SecretId": "tagged", "TagKeys": ["env"]}"#,
2343 );
2344 svc.handle(req).await.unwrap();
2345
2346 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2347 let resp = svc.handle(req).await.unwrap();
2348 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2349 assert_eq!(body["Tags"].as_array().unwrap().len(), 0);
2351 }
2352
2353 #[tokio::test]
2354 async fn test_get_random_password() {
2355 let state = make_state();
2356 let svc = SecretsManagerService::new(state);
2357
2358 let req = make_request("GetRandomPassword", "{}");
2359 let resp = svc.handle(req).await.unwrap();
2360 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2361 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 32);
2362 }
2363
2364 #[tokio::test]
2365 async fn test_replication_ops_return_arn() {
2366 let state = make_state();
2367 let svc = SecretsManagerService::new(state);
2368
2369 let req = make_request(
2370 "CreateSecret",
2371 r#"{"Name": "repl-secret", "SecretString": "val"}"#,
2372 );
2373 let resp = svc.handle(req).await.unwrap();
2374 let create_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2375 let expected_arn = create_body["ARN"].as_str().unwrap();
2376
2377 for action in &[
2378 "ReplicateSecretToRegions",
2379 "RemoveRegionsFromReplication",
2380 "StopReplicationToReplica",
2381 ] {
2382 let req = make_request(action, r#"{"SecretId": "repl-secret"}"#);
2383 let resp = svc.handle(req).await.unwrap();
2384 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2385 assert_eq!(
2386 body["ARN"].as_str().unwrap(),
2387 expected_arn,
2388 "{action} should return the secret's actual ARN"
2389 );
2390 }
2391 }
2392
2393 #[tokio::test]
2394 async fn test_secret_id_length_validation() {
2395 let state = make_state();
2396 let svc = SecretsManagerService::new(state);
2397
2398 let long_id = "x".repeat(2049);
2400 let req = make_request("GetSecretValue", &format!(r#"{{"SecretId": "{long_id}"}}"#));
2401 match svc.handle(req).await {
2402 Err(e) => assert!(e.to_string().contains("ValidationException")),
2403 Ok(_) => panic!("expected ValidationException"),
2404 }
2405 }
2406
2407 #[tokio::test]
2408 async fn test_name_length_validation() {
2409 let state = make_state();
2410 let svc = SecretsManagerService::new(state);
2411
2412 let long_name = "x".repeat(513);
2414 let req = make_request(
2415 "CreateSecret",
2416 &format!(r#"{{"Name": "{long_name}", "SecretString": "val"}}"#),
2417 );
2418 match svc.handle(req).await {
2419 Err(e) => assert!(e.to_string().contains("ValidationException")),
2420 Ok(_) => panic!("expected ValidationException"),
2421 }
2422 }
2423
2424 #[tokio::test]
2425 async fn test_next_token_length_validation() {
2426 let state = make_state();
2427 let svc = SecretsManagerService::new(state);
2428
2429 let long_token = "x".repeat(4097);
2431 let req = make_request(
2432 "ListSecrets",
2433 &format!(r#"{{"NextToken": "{long_token}"}}"#),
2434 );
2435 match svc.handle(req).await {
2436 Err(e) => assert!(e.to_string().contains("ValidationException")),
2437 Ok(_) => panic!("expected ValidationException"),
2438 }
2439 }
2440
2441 #[tokio::test]
2442 async fn test_client_request_token_length_validation() {
2443 let state = make_state();
2444 let svc = SecretsManagerService::new(state);
2445
2446 let req = make_request(
2448 "CreateSecret",
2449 r#"{"Name": "test", "SecretString": "val", "ClientRequestToken": "short"}"#,
2450 );
2451 match svc.handle(req).await {
2452 Err(e) => assert!(e.to_string().contains("ValidationException")),
2453 Ok(_) => panic!("expected ValidationException"),
2454 }
2455 }
2456
2457 #[tokio::test]
2458 async fn test_rotate_secret_with_lambda_creates_pending_version() {
2459 let state = make_state();
2460 let svc = SecretsManagerService::new(state.clone());
2461
2462 let req = make_request(
2464 "CreateSecret",
2465 r#"{"Name": "rotate-me", "SecretString": "old-password"}"#,
2466 );
2467 svc.handle(req).await.unwrap();
2468
2469 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2471 let body = serde_json::json!({
2472 "SecretId": "rotate-me",
2473 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:rotator",
2474 "ClientRequestToken": token,
2475 });
2476 let req = make_request("RotateSecret", &body.to_string());
2477 let resp = svc.handle(req).await.unwrap();
2478 assert_eq!(resp.status, StatusCode::OK);
2479 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2480 assert_eq!(resp_body["VersionId"], token);
2481
2482 let s = state.read();
2486 let secret = s.secrets.get("rotate-me").unwrap();
2487 assert!(
2488 !secret.versions.contains_key(token),
2489 "AWSPENDING version must not be pre-created; the rotation Lambda creates it"
2490 );
2491
2492 assert_eq!(
2494 secret.rotation_lambda_arn.as_deref(),
2495 Some("arn:aws:lambda:us-east-1:123456789012:function:rotator")
2496 );
2497 assert_eq!(secret.rotation_enabled, Some(true));
2498 }
2499
2500 #[tokio::test]
2501 async fn test_rotate_secret_without_lambda_promotes_directly() {
2502 let state = make_state();
2503 let svc = SecretsManagerService::new(state.clone());
2504
2505 let req = make_request(
2507 "CreateSecret",
2508 r#"{"Name": "rotate-no-lambda", "SecretString": "value1"}"#,
2509 );
2510 svc.handle(req).await.unwrap();
2511
2512 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2514 let body = serde_json::json!({
2515 "SecretId": "rotate-no-lambda",
2516 "ClientRequestToken": token,
2517 });
2518 let req = make_request("RotateSecret", &body.to_string());
2519 svc.handle(req).await.unwrap();
2520
2521 let s = state.read();
2523 let secret = s.secrets.get("rotate-no-lambda").unwrap();
2524 let new_ver = secret.versions.get(token).unwrap();
2525 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2526 assert_eq!(secret.current_version_id.as_deref(), Some(token));
2527 }
2528
2529 #[tokio::test]
2530 async fn test_rotate_secret_stores_rotation_config() {
2531 let state = make_state();
2532 let svc = SecretsManagerService::new(state.clone());
2533
2534 let req = make_request(
2535 "CreateSecret",
2536 r#"{"Name": "rot-cfg", "SecretString": "pw"}"#,
2537 );
2538 svc.handle(req).await.unwrap();
2539
2540 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2541 let body = serde_json::json!({
2542 "SecretId": "rot-cfg",
2543 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:my-rotator",
2544 "RotationRules": { "AutomaticallyAfterDays": 30 },
2545 "ClientRequestToken": token,
2546 });
2547 let req = make_request("RotateSecret", &body.to_string());
2548 let resp = svc.handle(req).await.unwrap();
2549 assert_eq!(resp.status, StatusCode::OK);
2550
2551 let s = state.read();
2552 let secret = s.secrets.get("rot-cfg").unwrap();
2553 assert_eq!(secret.rotation_enabled, Some(true));
2554 assert_eq!(
2555 secret.rotation_lambda_arn.as_deref(),
2556 Some("arn:aws:lambda:us-east-1:123456789012:function:my-rotator")
2557 );
2558 assert!(secret.last_rotated_at.is_some());
2559 let rules = secret.rotation_rules.as_ref().unwrap();
2560 assert_eq!(rules.automatically_after_days, Some(30));
2561
2562 assert!(!secret.versions.contains_key(token));
2566 }
2567
2568 #[tokio::test]
2569 async fn test_rotate_secret_version_stages_change() {
2570 let state = make_state();
2571 let svc = SecretsManagerService::new(state.clone());
2572
2573 let req = make_request(
2574 "CreateSecret",
2575 r#"{"Name": "rot-stages", "SecretString": "original"}"#,
2576 );
2577 svc.handle(req).await.unwrap();
2578
2579 let original_vid = {
2581 let s = state.read();
2582 let secret = s.secrets.get("rot-stages").unwrap();
2583 secret.current_version_id.clone().unwrap()
2584 };
2585
2586 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2588 let body = serde_json::json!({
2589 "SecretId": "rot-stages",
2590 "ClientRequestToken": token,
2591 });
2592 let req = make_request("RotateSecret", &body.to_string());
2593 svc.handle(req).await.unwrap();
2594
2595 let s = state.read();
2596 let secret = s.secrets.get("rot-stages").unwrap();
2597
2598 let new_ver = secret.versions.get(token).unwrap();
2600 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2601
2602 let old_ver = secret.versions.get(&original_vid).unwrap();
2604 assert!(old_ver.stages.contains(&"AWSPREVIOUS".to_string()));
2605 assert!(!old_ver.stages.contains(&"AWSCURRENT".to_string()));
2606 }
2607
2608 #[tokio::test]
2609 async fn test_cancel_rotate_secret() {
2610 let state = make_state();
2611 let svc = SecretsManagerService::new(state.clone());
2612
2613 let req = make_request(
2614 "CreateSecret",
2615 r#"{"Name": "cancel-rot", "SecretString": "pw"}"#,
2616 );
2617 svc.handle(req).await.unwrap();
2618
2619 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2621 let body = serde_json::json!({
2622 "SecretId": "cancel-rot",
2623 "ClientRequestToken": token,
2624 });
2625 let req = make_request("RotateSecret", &body.to_string());
2626 svc.handle(req).await.unwrap();
2627
2628 {
2630 let s = state.read();
2631 let secret = s.secrets.get("cancel-rot").unwrap();
2632 assert_eq!(secret.rotation_enabled, Some(true));
2633 }
2634
2635 let req = make_request("CancelRotateSecret", r#"{"SecretId": "cancel-rot"}"#);
2637 let resp = svc.handle(req).await.unwrap();
2638 assert_eq!(resp.status, StatusCode::OK);
2639 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2640 assert_eq!(body["Name"], "cancel-rot");
2641
2642 let s = state.read();
2644 let secret = s.secrets.get("cancel-rot").unwrap();
2645 assert_eq!(secret.rotation_enabled, Some(false));
2646 }
2647
2648 #[tokio::test]
2649 async fn test_cancel_rotate_secret_fails_when_not_enabled() {
2650 let state = make_state();
2651 let svc = SecretsManagerService::new(state);
2652
2653 let req = make_request(
2654 "CreateSecret",
2655 r#"{"Name": "no-rot", "SecretString": "pw"}"#,
2656 );
2657 svc.handle(req).await.unwrap();
2658
2659 let req = make_request("CancelRotateSecret", r#"{"SecretId": "no-rot"}"#);
2660 let result = svc.handle(req).await;
2661 assert!(result.is_err());
2662 }
2663
2664 #[tokio::test]
2665 async fn test_batch_get_secret_value_multiple() {
2666 let state = make_state();
2667 let svc = SecretsManagerService::new(state);
2668
2669 for (name, val) in &[("batch-a", "va"), ("batch-b", "vb"), ("batch-c", "vc")] {
2670 let req = make_request(
2671 "CreateSecret",
2672 &format!(r#"{{"Name": "{name}", "SecretString": "{val}"}}"#),
2673 );
2674 svc.handle(req).await.unwrap();
2675 }
2676
2677 let body = serde_json::json!({
2678 "SecretIdList": ["batch-a", "batch-b", "batch-c"]
2679 });
2680 let req = make_request("BatchGetSecretValue", &body.to_string());
2681 let resp = svc.handle(req).await.unwrap();
2682 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2683
2684 let values = resp_body["SecretValues"].as_array().unwrap();
2685 assert_eq!(values.len(), 3);
2686
2687 let names: Vec<&str> = values.iter().map(|v| v["Name"].as_str().unwrap()).collect();
2689 assert!(names.contains(&"batch-a"));
2690 assert!(names.contains(&"batch-b"));
2691 assert!(names.contains(&"batch-c"));
2692
2693 assert!(resp_body.get("Errors").is_none());
2695 }
2696
2697 #[tokio::test]
2698 async fn test_batch_get_secret_value_with_missing() {
2699 let state = make_state();
2700 let svc = SecretsManagerService::new(state);
2701
2702 let req = make_request(
2703 "CreateSecret",
2704 r#"{"Name": "exists", "SecretString": "val"}"#,
2705 );
2706 svc.handle(req).await.unwrap();
2707
2708 let body = serde_json::json!({
2709 "SecretIdList": ["exists", "nonexistent"]
2710 });
2711 let req = make_request("BatchGetSecretValue", &body.to_string());
2712 let resp = svc.handle(req).await.unwrap();
2713 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2714
2715 let values = resp_body["SecretValues"].as_array().unwrap();
2716 assert_eq!(values.len(), 1);
2717 assert_eq!(values[0]["Name"], "exists");
2718
2719 let errors = resp_body["Errors"].as_array().unwrap();
2720 assert_eq!(errors.len(), 1);
2721 assert_eq!(errors[0]["SecretId"], "nonexistent");
2722 assert_eq!(errors[0]["ErrorCode"], "ResourceNotFoundException");
2723 }
2724
2725 #[tokio::test]
2726 async fn test_update_secret_changes_description_and_kms() {
2727 let state = make_state();
2728 let svc = SecretsManagerService::new(state);
2729
2730 let req = make_request(
2731 "CreateSecret",
2732 r#"{"Name": "updatable", "SecretString": "val", "Description": "old desc"}"#,
2733 );
2734 svc.handle(req).await.unwrap();
2735
2736 let body = serde_json::json!({
2738 "SecretId": "updatable",
2739 "Description": "new desc",
2740 "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/my-key"
2741 });
2742 let req = make_request("UpdateSecret", &body.to_string());
2743 let resp = svc.handle(req).await.unwrap();
2744 assert_eq!(resp.status, StatusCode::OK);
2745 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2746 assert_eq!(resp_body["Name"], "updatable");
2747 assert!(resp_body.get("VersionId").is_none());
2749
2750 let req = make_request("DescribeSecret", r#"{"SecretId": "updatable"}"#);
2752 let resp = svc.handle(req).await.unwrap();
2753 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2754 assert_eq!(body["Description"], "new desc");
2755 assert_eq!(
2756 body["KmsKeyId"],
2757 "arn:aws:kms:us-east-1:123456789012:key/my-key"
2758 );
2759 }
2760
2761 #[tokio::test]
2762 async fn test_update_secret_with_new_value() {
2763 let state = make_state();
2764 let svc = SecretsManagerService::new(state);
2765
2766 let req = make_request(
2767 "CreateSecret",
2768 r#"{"Name": "upd-val", "SecretString": "old"}"#,
2769 );
2770 svc.handle(req).await.unwrap();
2771
2772 let body = serde_json::json!({
2774 "SecretId": "upd-val",
2775 "SecretString": "new-value"
2776 });
2777 let req = make_request("UpdateSecret", &body.to_string());
2778 let resp = svc.handle(req).await.unwrap();
2779 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2780 assert!(resp_body["VersionId"].as_str().is_some());
2781
2782 let req = make_request("GetSecretValue", r#"{"SecretId": "upd-val"}"#);
2784 let resp = svc.handle(req).await.unwrap();
2785 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2786 assert_eq!(body["SecretString"], "new-value");
2787 }
2788
2789 #[tokio::test]
2790 async fn test_get_random_password_custom_length() {
2791 let state = make_state();
2792 let svc = SecretsManagerService::new(state);
2793
2794 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 64}"#);
2795 let resp = svc.handle(req).await.unwrap();
2796 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2797 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 64);
2798 }
2799
2800 #[tokio::test]
2801 async fn test_get_random_password_exclude_chars() {
2802 let state = make_state();
2803 let svc = SecretsManagerService::new(state);
2804
2805 let req = make_request(
2806 "GetRandomPassword",
2807 r#"{"PasswordLength": 100, "ExcludeCharacters": "abc123"}"#,
2808 );
2809 let resp = svc.handle(req).await.unwrap();
2810 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2811 let password = body["RandomPassword"].as_str().unwrap();
2812 assert_eq!(password.len(), 100);
2813 assert!(!password.contains('a'));
2814 assert!(!password.contains('b'));
2815 assert!(!password.contains('c'));
2816 assert!(!password.contains('1'));
2817 assert!(!password.contains('2'));
2818 assert!(!password.contains('3'));
2819 }
2820
2821 #[tokio::test]
2822 async fn test_get_random_password_exclude_types() {
2823 let state = make_state();
2824 let svc = SecretsManagerService::new(state);
2825
2826 let body = serde_json::json!({
2828 "PasswordLength": 50,
2829 "ExcludeUppercase": true,
2830 "ExcludeNumbers": true,
2831 "ExcludePunctuation": true,
2832 "RequireEachIncludedType": false,
2833 });
2834 let req = make_request("GetRandomPassword", &body.to_string());
2835 let resp = svc.handle(req).await.unwrap();
2836 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2837 let password = resp_body["RandomPassword"].as_str().unwrap();
2838 assert_eq!(password.len(), 50);
2839 assert!(password.chars().all(|c| c.is_ascii_lowercase()));
2840 }
2841
2842 #[tokio::test]
2843 async fn test_get_random_password_too_short() {
2844 let state = make_state();
2845 let svc = SecretsManagerService::new(state);
2846
2847 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 3}"#);
2848 assert!(svc.handle(req).await.is_err());
2849 }
2850
2851 #[tokio::test]
2852 async fn test_get_random_password_too_long() {
2853 let state = make_state();
2854 let svc = SecretsManagerService::new(state);
2855
2856 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 4097}"#);
2857 assert!(svc.handle(req).await.is_err());
2858 }
2859
2860 #[tokio::test]
2861 async fn test_update_secret_version_stage_move_current() {
2862 let state = make_state();
2863 let svc = SecretsManagerService::new(state.clone());
2864
2865 let req = make_request(
2866 "CreateSecret",
2867 r#"{"Name": "stage-test", "SecretString": "v1"}"#,
2868 );
2869 svc.handle(req).await.unwrap();
2870
2871 let req = make_request(
2873 "PutSecretValue",
2874 r#"{"SecretId": "stage-test", "SecretString": "v2"}"#,
2875 );
2876 svc.handle(req).await.unwrap();
2877
2878 let (v1_id, v2_id) = {
2880 let s = state.read();
2881 let secret = s.secrets.get("stage-test").unwrap();
2882 let current = secret.current_version_id.clone().unwrap();
2883 let previous = secret
2884 .versions
2885 .iter()
2886 .find(|(id, _)| **id != current)
2887 .map(|(id, _)| id.clone())
2888 .unwrap();
2889 (previous, current)
2890 };
2891
2892 let body = serde_json::json!({
2894 "SecretId": "stage-test",
2895 "VersionStage": "AWSCURRENT",
2896 "MoveToVersionId": v1_id,
2897 "RemoveFromVersionId": v2_id,
2898 });
2899 let req = make_request("UpdateSecretVersionStage", &body.to_string());
2900 let resp = svc.handle(req).await.unwrap();
2901 assert_eq!(resp.status, StatusCode::OK);
2902
2903 let s = state.read();
2905 let secret = s.secrets.get("stage-test").unwrap();
2906 let v1 = secret.versions.get(&v1_id).unwrap();
2907 assert!(v1.stages.contains(&"AWSCURRENT".to_string()));
2908
2909 let v2 = secret.versions.get(&v2_id).unwrap();
2911 assert!(v2.stages.contains(&"AWSPREVIOUS".to_string()));
2912 assert!(!v2.stages.contains(&"AWSCURRENT".to_string()));
2913
2914 assert_eq!(secret.current_version_id.as_deref(), Some(v1_id.as_str()));
2915 }
2916
2917 #[tokio::test]
2918 async fn test_update_secret_version_stage_custom_label() {
2919 let state = make_state();
2920 let svc = SecretsManagerService::new(state.clone());
2921
2922 let req = make_request(
2923 "CreateSecret",
2924 r#"{"Name": "custom-stage", "SecretString": "v1"}"#,
2925 );
2926 svc.handle(req).await.unwrap();
2927
2928 let vid = {
2929 let s = state.read();
2930 s.secrets
2931 .get("custom-stage")
2932 .unwrap()
2933 .current_version_id
2934 .clone()
2935 .unwrap()
2936 };
2937
2938 let body = serde_json::json!({
2940 "SecretId": "custom-stage",
2941 "VersionStage": "MYAPP_LIVE",
2942 "MoveToVersionId": vid,
2943 });
2944 let req = make_request("UpdateSecretVersionStage", &body.to_string());
2945 svc.handle(req).await.unwrap();
2946
2947 let s = state.read();
2948 let secret = s.secrets.get("custom-stage").unwrap();
2949 let ver = secret.versions.get(&vid).unwrap();
2950 assert!(ver.stages.contains(&"MYAPP_LIVE".to_string()));
2951 assert!(ver.stages.contains(&"AWSCURRENT".to_string()));
2952 }
2953
2954 #[tokio::test]
2955 async fn test_validate_resource_policy() {
2956 let state = make_state();
2957 let svc = SecretsManagerService::new(state);
2958
2959 let policy = serde_json::json!({
2960 "Version": "2012-10-17",
2961 "Statement": [{
2962 "Effect": "Allow",
2963 "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
2964 "Action": "secretsmanager:GetSecretValue",
2965 "Resource": "*"
2966 }]
2967 });
2968
2969 let body = serde_json::json!({
2970 "ResourcePolicy": policy.to_string(),
2971 });
2972 let req = make_request("ValidateResourcePolicy", &body.to_string());
2973 let resp = svc.handle(req).await.unwrap();
2974 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2975 assert_eq!(resp_body["PolicyValidationPassed"], true);
2976 assert_eq!(resp_body["ValidationErrors"].as_array().unwrap().len(), 0);
2977 }
2978
2979 #[tokio::test]
2980 async fn test_validate_resource_policy_requires_policy() {
2981 let state = make_state();
2982 let svc = SecretsManagerService::new(state);
2983
2984 let req = make_request("ValidateResourcePolicy", r#"{}"#);
2985 assert!(svc.handle(req).await.is_err());
2986 }
2987
2988 #[tokio::test]
2989 async fn test_put_get_delete_resource_policy() {
2990 let state = make_state();
2991 let svc = SecretsManagerService::new(state);
2992
2993 let req = make_request(
2994 "CreateSecret",
2995 r#"{"Name": "policy-secret", "SecretString": "val"}"#,
2996 );
2997 svc.handle(req).await.unwrap();
2998
2999 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3001 let resp = svc.handle(req).await.unwrap();
3002 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3003 assert_eq!(body["Name"], "policy-secret");
3004 assert!(body.get("ResourcePolicy").is_none());
3005
3006 let policy = r#"{"Version":"2012-10-17","Statement":[]}"#;
3008 let put_body = serde_json::json!({
3009 "SecretId": "policy-secret",
3010 "ResourcePolicy": policy,
3011 });
3012 let req = make_request("PutResourcePolicy", &put_body.to_string());
3013 let resp = svc.handle(req).await.unwrap();
3014 assert_eq!(resp.status, StatusCode::OK);
3015
3016 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3018 let resp = svc.handle(req).await.unwrap();
3019 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3020 assert_eq!(body["ResourcePolicy"], policy);
3021
3022 let req = make_request("DeleteResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3024 let resp = svc.handle(req).await.unwrap();
3025 assert_eq!(resp.status, StatusCode::OK);
3026
3027 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3029 let resp = svc.handle(req).await.unwrap();
3030 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3031 assert!(body.get("ResourcePolicy").is_none());
3032 }
3033
3034 #[tokio::test]
3035 async fn test_batch_get_secret_value_with_deleted() {
3036 let state = make_state();
3037 let svc = SecretsManagerService::new(state);
3038
3039 let req = make_request(
3040 "CreateSecret",
3041 r#"{"Name": "batch-del", "SecretString": "val"}"#,
3042 );
3043 svc.handle(req).await.unwrap();
3044
3045 let req = make_request("DeleteSecret", r#"{"SecretId": "batch-del"}"#);
3047 svc.handle(req).await.unwrap();
3048
3049 let body = serde_json::json!({
3050 "SecretIdList": ["batch-del"]
3051 });
3052 let req = make_request("BatchGetSecretValue", &body.to_string());
3053 let resp = svc.handle(req).await.unwrap();
3054 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3055
3056 assert_eq!(resp_body["SecretValues"].as_array().unwrap().len(), 0);
3058 let errors = resp_body["Errors"].as_array().unwrap();
3059 assert_eq!(errors.len(), 1);
3060 assert_eq!(errors[0]["ErrorCode"], "InvalidRequestException");
3061 }
3062}