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 principal: None,
2202 }
2203 }
2204
2205 #[tokio::test]
2206 async fn test_create_and_get_secret() {
2207 let state = make_state();
2208 let svc = SecretsManagerService::new(state);
2209
2210 let req = make_request(
2211 "CreateSecret",
2212 r#"{"Name": "test/secret", "SecretString": "mysecretvalue"}"#,
2213 );
2214 let resp = svc.handle(req).await.unwrap();
2215 assert_eq!(resp.status, StatusCode::OK);
2216 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2217 assert_eq!(body["Name"], "test/secret");
2218 assert!(body["ARN"].as_str().unwrap().contains("test/secret"));
2219
2220 let req = make_request("GetSecretValue", r#"{"SecretId": "test/secret"}"#);
2221 let resp = svc.handle(req).await.unwrap();
2222 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2223 assert_eq!(body["SecretString"], "mysecretvalue");
2224 }
2225
2226 #[tokio::test]
2227 async fn test_create_secret_without_value() {
2228 let state = make_state();
2229 let svc = SecretsManagerService::new(state);
2230
2231 let req = make_request("CreateSecret", r#"{"Name": "empty-secret"}"#);
2232 let resp = svc.handle(req).await.unwrap();
2233 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2234 assert_eq!(body["Name"], "empty-secret");
2235 assert!(body.get("VersionId").is_none());
2236 }
2237
2238 #[tokio::test]
2239 async fn test_put_secret_value_creates_version() {
2240 let state = make_state();
2241 let svc = SecretsManagerService::new(state);
2242
2243 let req = make_request(
2244 "CreateSecret",
2245 r#"{"Name": "versioned", "SecretString": "v1"}"#,
2246 );
2247 svc.handle(req).await.unwrap();
2248
2249 let req = make_request(
2250 "PutSecretValue",
2251 r#"{"SecretId": "versioned", "SecretString": "v2"}"#,
2252 );
2253 let resp = svc.handle(req).await.unwrap();
2254 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2255 assert_eq!(body["Name"], "versioned");
2256
2257 let req = make_request("GetSecretValue", r#"{"SecretId": "versioned"}"#);
2259 let resp = svc.handle(req).await.unwrap();
2260 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2261 assert_eq!(body["SecretString"], "v2");
2262 }
2263
2264 #[tokio::test]
2265 async fn test_delete_and_restore_secret() {
2266 let state = make_state();
2267 let svc = SecretsManagerService::new(state);
2268
2269 let req = make_request(
2270 "CreateSecret",
2271 r#"{"Name": "deleteme", "SecretString": "value"}"#,
2272 );
2273 svc.handle(req).await.unwrap();
2274
2275 let req = make_request("DeleteSecret", r#"{"SecretId": "deleteme"}"#);
2277 let resp = svc.handle(req).await.unwrap();
2278 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2279 assert!(body["DeletionDate"].as_f64().is_some());
2280
2281 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2283 assert!(svc.handle(req).await.is_err());
2284
2285 let req = make_request("RestoreSecret", r#"{"SecretId": "deleteme"}"#);
2287 let resp = svc.handle(req).await.unwrap();
2288 assert_eq!(resp.status, StatusCode::OK);
2289
2290 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2292 let resp = svc.handle(req).await.unwrap();
2293 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2294 assert_eq!(body["SecretString"], "value");
2295 }
2296
2297 #[tokio::test]
2298 async fn test_list_secrets() {
2299 let state = make_state();
2300 let svc = SecretsManagerService::new(state);
2301
2302 for name in &["alpha", "beta", "gamma"] {
2303 let req = make_request(
2304 "CreateSecret",
2305 &format!(r#"{{"Name": "{name}", "SecretString": "val"}}"#),
2306 );
2307 svc.handle(req).await.unwrap();
2308 }
2309
2310 let req = make_request("ListSecrets", "{}");
2311 let resp = svc.handle(req).await.unwrap();
2312 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2313 assert_eq!(body["SecretList"].as_array().unwrap().len(), 3);
2314 }
2315
2316 #[tokio::test]
2317 async fn test_tags() {
2318 let state = make_state();
2319 let svc = SecretsManagerService::new(state);
2320
2321 let req = make_request(
2322 "CreateSecret",
2323 r#"{"Name": "tagged", "SecretString": "val"}"#,
2324 );
2325 svc.handle(req).await.unwrap();
2326
2327 let req = make_request(
2328 "TagResource",
2329 r#"{"SecretId": "tagged", "Tags": [{"Key": "env", "Value": "prod"}]}"#,
2330 );
2331 svc.handle(req).await.unwrap();
2332
2333 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2334 let resp = svc.handle(req).await.unwrap();
2335 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2336 let tags = body["Tags"].as_array().unwrap();
2337 assert!(tags
2338 .iter()
2339 .any(|t| t["Key"] == "env" && t["Value"] == "prod"));
2340
2341 let req = make_request(
2342 "UntagResource",
2343 r#"{"SecretId": "tagged", "TagKeys": ["env"]}"#,
2344 );
2345 svc.handle(req).await.unwrap();
2346
2347 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2348 let resp = svc.handle(req).await.unwrap();
2349 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2350 assert_eq!(body["Tags"].as_array().unwrap().len(), 0);
2352 }
2353
2354 #[tokio::test]
2355 async fn test_get_random_password() {
2356 let state = make_state();
2357 let svc = SecretsManagerService::new(state);
2358
2359 let req = make_request("GetRandomPassword", "{}");
2360 let resp = svc.handle(req).await.unwrap();
2361 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2362 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 32);
2363 }
2364
2365 #[tokio::test]
2366 async fn test_replication_ops_return_arn() {
2367 let state = make_state();
2368 let svc = SecretsManagerService::new(state);
2369
2370 let req = make_request(
2371 "CreateSecret",
2372 r#"{"Name": "repl-secret", "SecretString": "val"}"#,
2373 );
2374 let resp = svc.handle(req).await.unwrap();
2375 let create_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2376 let expected_arn = create_body["ARN"].as_str().unwrap();
2377
2378 for action in &[
2379 "ReplicateSecretToRegions",
2380 "RemoveRegionsFromReplication",
2381 "StopReplicationToReplica",
2382 ] {
2383 let req = make_request(action, r#"{"SecretId": "repl-secret"}"#);
2384 let resp = svc.handle(req).await.unwrap();
2385 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2386 assert_eq!(
2387 body["ARN"].as_str().unwrap(),
2388 expected_arn,
2389 "{action} should return the secret's actual ARN"
2390 );
2391 }
2392 }
2393
2394 #[tokio::test]
2395 async fn test_secret_id_length_validation() {
2396 let state = make_state();
2397 let svc = SecretsManagerService::new(state);
2398
2399 let long_id = "x".repeat(2049);
2401 let req = make_request("GetSecretValue", &format!(r#"{{"SecretId": "{long_id}"}}"#));
2402 match svc.handle(req).await {
2403 Err(e) => assert!(e.to_string().contains("ValidationException")),
2404 Ok(_) => panic!("expected ValidationException"),
2405 }
2406 }
2407
2408 #[tokio::test]
2409 async fn test_name_length_validation() {
2410 let state = make_state();
2411 let svc = SecretsManagerService::new(state);
2412
2413 let long_name = "x".repeat(513);
2415 let req = make_request(
2416 "CreateSecret",
2417 &format!(r#"{{"Name": "{long_name}", "SecretString": "val"}}"#),
2418 );
2419 match svc.handle(req).await {
2420 Err(e) => assert!(e.to_string().contains("ValidationException")),
2421 Ok(_) => panic!("expected ValidationException"),
2422 }
2423 }
2424
2425 #[tokio::test]
2426 async fn test_next_token_length_validation() {
2427 let state = make_state();
2428 let svc = SecretsManagerService::new(state);
2429
2430 let long_token = "x".repeat(4097);
2432 let req = make_request(
2433 "ListSecrets",
2434 &format!(r#"{{"NextToken": "{long_token}"}}"#),
2435 );
2436 match svc.handle(req).await {
2437 Err(e) => assert!(e.to_string().contains("ValidationException")),
2438 Ok(_) => panic!("expected ValidationException"),
2439 }
2440 }
2441
2442 #[tokio::test]
2443 async fn test_client_request_token_length_validation() {
2444 let state = make_state();
2445 let svc = SecretsManagerService::new(state);
2446
2447 let req = make_request(
2449 "CreateSecret",
2450 r#"{"Name": "test", "SecretString": "val", "ClientRequestToken": "short"}"#,
2451 );
2452 match svc.handle(req).await {
2453 Err(e) => assert!(e.to_string().contains("ValidationException")),
2454 Ok(_) => panic!("expected ValidationException"),
2455 }
2456 }
2457
2458 #[tokio::test]
2459 async fn test_rotate_secret_with_lambda_creates_pending_version() {
2460 let state = make_state();
2461 let svc = SecretsManagerService::new(state.clone());
2462
2463 let req = make_request(
2465 "CreateSecret",
2466 r#"{"Name": "rotate-me", "SecretString": "old-password"}"#,
2467 );
2468 svc.handle(req).await.unwrap();
2469
2470 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2472 let body = serde_json::json!({
2473 "SecretId": "rotate-me",
2474 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:rotator",
2475 "ClientRequestToken": token,
2476 });
2477 let req = make_request("RotateSecret", &body.to_string());
2478 let resp = svc.handle(req).await.unwrap();
2479 assert_eq!(resp.status, StatusCode::OK);
2480 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2481 assert_eq!(resp_body["VersionId"], token);
2482
2483 let s = state.read();
2487 let secret = s.secrets.get("rotate-me").unwrap();
2488 assert!(
2489 !secret.versions.contains_key(token),
2490 "AWSPENDING version must not be pre-created; the rotation Lambda creates it"
2491 );
2492
2493 assert_eq!(
2495 secret.rotation_lambda_arn.as_deref(),
2496 Some("arn:aws:lambda:us-east-1:123456789012:function:rotator")
2497 );
2498 assert_eq!(secret.rotation_enabled, Some(true));
2499 }
2500
2501 #[tokio::test]
2502 async fn test_rotate_secret_without_lambda_promotes_directly() {
2503 let state = make_state();
2504 let svc = SecretsManagerService::new(state.clone());
2505
2506 let req = make_request(
2508 "CreateSecret",
2509 r#"{"Name": "rotate-no-lambda", "SecretString": "value1"}"#,
2510 );
2511 svc.handle(req).await.unwrap();
2512
2513 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2515 let body = serde_json::json!({
2516 "SecretId": "rotate-no-lambda",
2517 "ClientRequestToken": token,
2518 });
2519 let req = make_request("RotateSecret", &body.to_string());
2520 svc.handle(req).await.unwrap();
2521
2522 let s = state.read();
2524 let secret = s.secrets.get("rotate-no-lambda").unwrap();
2525 let new_ver = secret.versions.get(token).unwrap();
2526 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2527 assert_eq!(secret.current_version_id.as_deref(), Some(token));
2528 }
2529
2530 #[tokio::test]
2531 async fn test_rotate_secret_stores_rotation_config() {
2532 let state = make_state();
2533 let svc = SecretsManagerService::new(state.clone());
2534
2535 let req = make_request(
2536 "CreateSecret",
2537 r#"{"Name": "rot-cfg", "SecretString": "pw"}"#,
2538 );
2539 svc.handle(req).await.unwrap();
2540
2541 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2542 let body = serde_json::json!({
2543 "SecretId": "rot-cfg",
2544 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:my-rotator",
2545 "RotationRules": { "AutomaticallyAfterDays": 30 },
2546 "ClientRequestToken": token,
2547 });
2548 let req = make_request("RotateSecret", &body.to_string());
2549 let resp = svc.handle(req).await.unwrap();
2550 assert_eq!(resp.status, StatusCode::OK);
2551
2552 let s = state.read();
2553 let secret = s.secrets.get("rot-cfg").unwrap();
2554 assert_eq!(secret.rotation_enabled, Some(true));
2555 assert_eq!(
2556 secret.rotation_lambda_arn.as_deref(),
2557 Some("arn:aws:lambda:us-east-1:123456789012:function:my-rotator")
2558 );
2559 assert!(secret.last_rotated_at.is_some());
2560 let rules = secret.rotation_rules.as_ref().unwrap();
2561 assert_eq!(rules.automatically_after_days, Some(30));
2562
2563 assert!(!secret.versions.contains_key(token));
2567 }
2568
2569 #[tokio::test]
2570 async fn test_rotate_secret_version_stages_change() {
2571 let state = make_state();
2572 let svc = SecretsManagerService::new(state.clone());
2573
2574 let req = make_request(
2575 "CreateSecret",
2576 r#"{"Name": "rot-stages", "SecretString": "original"}"#,
2577 );
2578 svc.handle(req).await.unwrap();
2579
2580 let original_vid = {
2582 let s = state.read();
2583 let secret = s.secrets.get("rot-stages").unwrap();
2584 secret.current_version_id.clone().unwrap()
2585 };
2586
2587 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2589 let body = serde_json::json!({
2590 "SecretId": "rot-stages",
2591 "ClientRequestToken": token,
2592 });
2593 let req = make_request("RotateSecret", &body.to_string());
2594 svc.handle(req).await.unwrap();
2595
2596 let s = state.read();
2597 let secret = s.secrets.get("rot-stages").unwrap();
2598
2599 let new_ver = secret.versions.get(token).unwrap();
2601 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2602
2603 let old_ver = secret.versions.get(&original_vid).unwrap();
2605 assert!(old_ver.stages.contains(&"AWSPREVIOUS".to_string()));
2606 assert!(!old_ver.stages.contains(&"AWSCURRENT".to_string()));
2607 }
2608
2609 #[tokio::test]
2610 async fn test_cancel_rotate_secret() {
2611 let state = make_state();
2612 let svc = SecretsManagerService::new(state.clone());
2613
2614 let req = make_request(
2615 "CreateSecret",
2616 r#"{"Name": "cancel-rot", "SecretString": "pw"}"#,
2617 );
2618 svc.handle(req).await.unwrap();
2619
2620 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2622 let body = serde_json::json!({
2623 "SecretId": "cancel-rot",
2624 "ClientRequestToken": token,
2625 });
2626 let req = make_request("RotateSecret", &body.to_string());
2627 svc.handle(req).await.unwrap();
2628
2629 {
2631 let s = state.read();
2632 let secret = s.secrets.get("cancel-rot").unwrap();
2633 assert_eq!(secret.rotation_enabled, Some(true));
2634 }
2635
2636 let req = make_request("CancelRotateSecret", r#"{"SecretId": "cancel-rot"}"#);
2638 let resp = svc.handle(req).await.unwrap();
2639 assert_eq!(resp.status, StatusCode::OK);
2640 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2641 assert_eq!(body["Name"], "cancel-rot");
2642
2643 let s = state.read();
2645 let secret = s.secrets.get("cancel-rot").unwrap();
2646 assert_eq!(secret.rotation_enabled, Some(false));
2647 }
2648
2649 #[tokio::test]
2650 async fn test_cancel_rotate_secret_fails_when_not_enabled() {
2651 let state = make_state();
2652 let svc = SecretsManagerService::new(state);
2653
2654 let req = make_request(
2655 "CreateSecret",
2656 r#"{"Name": "no-rot", "SecretString": "pw"}"#,
2657 );
2658 svc.handle(req).await.unwrap();
2659
2660 let req = make_request("CancelRotateSecret", r#"{"SecretId": "no-rot"}"#);
2661 let result = svc.handle(req).await;
2662 assert!(result.is_err());
2663 }
2664
2665 #[tokio::test]
2666 async fn test_batch_get_secret_value_multiple() {
2667 let state = make_state();
2668 let svc = SecretsManagerService::new(state);
2669
2670 for (name, val) in &[("batch-a", "va"), ("batch-b", "vb"), ("batch-c", "vc")] {
2671 let req = make_request(
2672 "CreateSecret",
2673 &format!(r#"{{"Name": "{name}", "SecretString": "{val}"}}"#),
2674 );
2675 svc.handle(req).await.unwrap();
2676 }
2677
2678 let body = serde_json::json!({
2679 "SecretIdList": ["batch-a", "batch-b", "batch-c"]
2680 });
2681 let req = make_request("BatchGetSecretValue", &body.to_string());
2682 let resp = svc.handle(req).await.unwrap();
2683 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2684
2685 let values = resp_body["SecretValues"].as_array().unwrap();
2686 assert_eq!(values.len(), 3);
2687
2688 let names: Vec<&str> = values.iter().map(|v| v["Name"].as_str().unwrap()).collect();
2690 assert!(names.contains(&"batch-a"));
2691 assert!(names.contains(&"batch-b"));
2692 assert!(names.contains(&"batch-c"));
2693
2694 assert!(resp_body.get("Errors").is_none());
2696 }
2697
2698 #[tokio::test]
2699 async fn test_batch_get_secret_value_with_missing() {
2700 let state = make_state();
2701 let svc = SecretsManagerService::new(state);
2702
2703 let req = make_request(
2704 "CreateSecret",
2705 r#"{"Name": "exists", "SecretString": "val"}"#,
2706 );
2707 svc.handle(req).await.unwrap();
2708
2709 let body = serde_json::json!({
2710 "SecretIdList": ["exists", "nonexistent"]
2711 });
2712 let req = make_request("BatchGetSecretValue", &body.to_string());
2713 let resp = svc.handle(req).await.unwrap();
2714 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2715
2716 let values = resp_body["SecretValues"].as_array().unwrap();
2717 assert_eq!(values.len(), 1);
2718 assert_eq!(values[0]["Name"], "exists");
2719
2720 let errors = resp_body["Errors"].as_array().unwrap();
2721 assert_eq!(errors.len(), 1);
2722 assert_eq!(errors[0]["SecretId"], "nonexistent");
2723 assert_eq!(errors[0]["ErrorCode"], "ResourceNotFoundException");
2724 }
2725
2726 #[tokio::test]
2727 async fn test_update_secret_changes_description_and_kms() {
2728 let state = make_state();
2729 let svc = SecretsManagerService::new(state);
2730
2731 let req = make_request(
2732 "CreateSecret",
2733 r#"{"Name": "updatable", "SecretString": "val", "Description": "old desc"}"#,
2734 );
2735 svc.handle(req).await.unwrap();
2736
2737 let body = serde_json::json!({
2739 "SecretId": "updatable",
2740 "Description": "new desc",
2741 "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/my-key"
2742 });
2743 let req = make_request("UpdateSecret", &body.to_string());
2744 let resp = svc.handle(req).await.unwrap();
2745 assert_eq!(resp.status, StatusCode::OK);
2746 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2747 assert_eq!(resp_body["Name"], "updatable");
2748 assert!(resp_body.get("VersionId").is_none());
2750
2751 let req = make_request("DescribeSecret", r#"{"SecretId": "updatable"}"#);
2753 let resp = svc.handle(req).await.unwrap();
2754 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2755 assert_eq!(body["Description"], "new desc");
2756 assert_eq!(
2757 body["KmsKeyId"],
2758 "arn:aws:kms:us-east-1:123456789012:key/my-key"
2759 );
2760 }
2761
2762 #[tokio::test]
2763 async fn test_update_secret_with_new_value() {
2764 let state = make_state();
2765 let svc = SecretsManagerService::new(state);
2766
2767 let req = make_request(
2768 "CreateSecret",
2769 r#"{"Name": "upd-val", "SecretString": "old"}"#,
2770 );
2771 svc.handle(req).await.unwrap();
2772
2773 let body = serde_json::json!({
2775 "SecretId": "upd-val",
2776 "SecretString": "new-value"
2777 });
2778 let req = make_request("UpdateSecret", &body.to_string());
2779 let resp = svc.handle(req).await.unwrap();
2780 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2781 assert!(resp_body["VersionId"].as_str().is_some());
2782
2783 let req = make_request("GetSecretValue", r#"{"SecretId": "upd-val"}"#);
2785 let resp = svc.handle(req).await.unwrap();
2786 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2787 assert_eq!(body["SecretString"], "new-value");
2788 }
2789
2790 #[tokio::test]
2791 async fn test_get_random_password_custom_length() {
2792 let state = make_state();
2793 let svc = SecretsManagerService::new(state);
2794
2795 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 64}"#);
2796 let resp = svc.handle(req).await.unwrap();
2797 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2798 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 64);
2799 }
2800
2801 #[tokio::test]
2802 async fn test_get_random_password_exclude_chars() {
2803 let state = make_state();
2804 let svc = SecretsManagerService::new(state);
2805
2806 let req = make_request(
2807 "GetRandomPassword",
2808 r#"{"PasswordLength": 100, "ExcludeCharacters": "abc123"}"#,
2809 );
2810 let resp = svc.handle(req).await.unwrap();
2811 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2812 let password = body["RandomPassword"].as_str().unwrap();
2813 assert_eq!(password.len(), 100);
2814 assert!(!password.contains('a'));
2815 assert!(!password.contains('b'));
2816 assert!(!password.contains('c'));
2817 assert!(!password.contains('1'));
2818 assert!(!password.contains('2'));
2819 assert!(!password.contains('3'));
2820 }
2821
2822 #[tokio::test]
2823 async fn test_get_random_password_exclude_types() {
2824 let state = make_state();
2825 let svc = SecretsManagerService::new(state);
2826
2827 let body = serde_json::json!({
2829 "PasswordLength": 50,
2830 "ExcludeUppercase": true,
2831 "ExcludeNumbers": true,
2832 "ExcludePunctuation": true,
2833 "RequireEachIncludedType": false,
2834 });
2835 let req = make_request("GetRandomPassword", &body.to_string());
2836 let resp = svc.handle(req).await.unwrap();
2837 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2838 let password = resp_body["RandomPassword"].as_str().unwrap();
2839 assert_eq!(password.len(), 50);
2840 assert!(password.chars().all(|c| c.is_ascii_lowercase()));
2841 }
2842
2843 #[tokio::test]
2844 async fn test_get_random_password_too_short() {
2845 let state = make_state();
2846 let svc = SecretsManagerService::new(state);
2847
2848 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 3}"#);
2849 assert!(svc.handle(req).await.is_err());
2850 }
2851
2852 #[tokio::test]
2853 async fn test_get_random_password_too_long() {
2854 let state = make_state();
2855 let svc = SecretsManagerService::new(state);
2856
2857 let req = make_request("GetRandomPassword", r#"{"PasswordLength": 4097}"#);
2858 assert!(svc.handle(req).await.is_err());
2859 }
2860
2861 #[tokio::test]
2862 async fn test_update_secret_version_stage_move_current() {
2863 let state = make_state();
2864 let svc = SecretsManagerService::new(state.clone());
2865
2866 let req = make_request(
2867 "CreateSecret",
2868 r#"{"Name": "stage-test", "SecretString": "v1"}"#,
2869 );
2870 svc.handle(req).await.unwrap();
2871
2872 let req = make_request(
2874 "PutSecretValue",
2875 r#"{"SecretId": "stage-test", "SecretString": "v2"}"#,
2876 );
2877 svc.handle(req).await.unwrap();
2878
2879 let (v1_id, v2_id) = {
2881 let s = state.read();
2882 let secret = s.secrets.get("stage-test").unwrap();
2883 let current = secret.current_version_id.clone().unwrap();
2884 let previous = secret
2885 .versions
2886 .iter()
2887 .find(|(id, _)| **id != current)
2888 .map(|(id, _)| id.clone())
2889 .unwrap();
2890 (previous, current)
2891 };
2892
2893 let body = serde_json::json!({
2895 "SecretId": "stage-test",
2896 "VersionStage": "AWSCURRENT",
2897 "MoveToVersionId": v1_id,
2898 "RemoveFromVersionId": v2_id,
2899 });
2900 let req = make_request("UpdateSecretVersionStage", &body.to_string());
2901 let resp = svc.handle(req).await.unwrap();
2902 assert_eq!(resp.status, StatusCode::OK);
2903
2904 let s = state.read();
2906 let secret = s.secrets.get("stage-test").unwrap();
2907 let v1 = secret.versions.get(&v1_id).unwrap();
2908 assert!(v1.stages.contains(&"AWSCURRENT".to_string()));
2909
2910 let v2 = secret.versions.get(&v2_id).unwrap();
2912 assert!(v2.stages.contains(&"AWSPREVIOUS".to_string()));
2913 assert!(!v2.stages.contains(&"AWSCURRENT".to_string()));
2914
2915 assert_eq!(secret.current_version_id.as_deref(), Some(v1_id.as_str()));
2916 }
2917
2918 #[tokio::test]
2919 async fn test_update_secret_version_stage_custom_label() {
2920 let state = make_state();
2921 let svc = SecretsManagerService::new(state.clone());
2922
2923 let req = make_request(
2924 "CreateSecret",
2925 r#"{"Name": "custom-stage", "SecretString": "v1"}"#,
2926 );
2927 svc.handle(req).await.unwrap();
2928
2929 let vid = {
2930 let s = state.read();
2931 s.secrets
2932 .get("custom-stage")
2933 .unwrap()
2934 .current_version_id
2935 .clone()
2936 .unwrap()
2937 };
2938
2939 let body = serde_json::json!({
2941 "SecretId": "custom-stage",
2942 "VersionStage": "MYAPP_LIVE",
2943 "MoveToVersionId": vid,
2944 });
2945 let req = make_request("UpdateSecretVersionStage", &body.to_string());
2946 svc.handle(req).await.unwrap();
2947
2948 let s = state.read();
2949 let secret = s.secrets.get("custom-stage").unwrap();
2950 let ver = secret.versions.get(&vid).unwrap();
2951 assert!(ver.stages.contains(&"MYAPP_LIVE".to_string()));
2952 assert!(ver.stages.contains(&"AWSCURRENT".to_string()));
2953 }
2954
2955 #[tokio::test]
2956 async fn test_validate_resource_policy() {
2957 let state = make_state();
2958 let svc = SecretsManagerService::new(state);
2959
2960 let policy = serde_json::json!({
2961 "Version": "2012-10-17",
2962 "Statement": [{
2963 "Effect": "Allow",
2964 "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
2965 "Action": "secretsmanager:GetSecretValue",
2966 "Resource": "*"
2967 }]
2968 });
2969
2970 let body = serde_json::json!({
2971 "ResourcePolicy": policy.to_string(),
2972 });
2973 let req = make_request("ValidateResourcePolicy", &body.to_string());
2974 let resp = svc.handle(req).await.unwrap();
2975 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2976 assert_eq!(resp_body["PolicyValidationPassed"], true);
2977 assert_eq!(resp_body["ValidationErrors"].as_array().unwrap().len(), 0);
2978 }
2979
2980 #[tokio::test]
2981 async fn test_validate_resource_policy_requires_policy() {
2982 let state = make_state();
2983 let svc = SecretsManagerService::new(state);
2984
2985 let req = make_request("ValidateResourcePolicy", r#"{}"#);
2986 assert!(svc.handle(req).await.is_err());
2987 }
2988
2989 #[tokio::test]
2990 async fn test_put_get_delete_resource_policy() {
2991 let state = make_state();
2992 let svc = SecretsManagerService::new(state);
2993
2994 let req = make_request(
2995 "CreateSecret",
2996 r#"{"Name": "policy-secret", "SecretString": "val"}"#,
2997 );
2998 svc.handle(req).await.unwrap();
2999
3000 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3002 let resp = svc.handle(req).await.unwrap();
3003 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3004 assert_eq!(body["Name"], "policy-secret");
3005 assert!(body.get("ResourcePolicy").is_none());
3006
3007 let policy = r#"{"Version":"2012-10-17","Statement":[]}"#;
3009 let put_body = serde_json::json!({
3010 "SecretId": "policy-secret",
3011 "ResourcePolicy": policy,
3012 });
3013 let req = make_request("PutResourcePolicy", &put_body.to_string());
3014 let resp = svc.handle(req).await.unwrap();
3015 assert_eq!(resp.status, StatusCode::OK);
3016
3017 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3019 let resp = svc.handle(req).await.unwrap();
3020 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3021 assert_eq!(body["ResourcePolicy"], policy);
3022
3023 let req = make_request("DeleteResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3025 let resp = svc.handle(req).await.unwrap();
3026 assert_eq!(resp.status, StatusCode::OK);
3027
3028 let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
3030 let resp = svc.handle(req).await.unwrap();
3031 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3032 assert!(body.get("ResourcePolicy").is_none());
3033 }
3034
3035 #[tokio::test]
3036 async fn test_batch_get_secret_value_with_deleted() {
3037 let state = make_state();
3038 let svc = SecretsManagerService::new(state);
3039
3040 let req = make_request(
3041 "CreateSecret",
3042 r#"{"Name": "batch-del", "SecretString": "val"}"#,
3043 );
3044 svc.handle(req).await.unwrap();
3045
3046 let req = make_request("DeleteSecret", r#"{"SecretId": "batch-del"}"#);
3048 svc.handle(req).await.unwrap();
3049
3050 let body = serde_json::json!({
3051 "SecretIdList": ["batch-del"]
3052 });
3053 let req = make_request("BatchGetSecretValue", &body.to_string());
3054 let resp = svc.handle(req).await.unwrap();
3055 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
3056
3057 assert_eq!(resp_body["SecretValues"].as_array().unwrap().len(), 0);
3059 let errors = resp_body["Errors"].as_array().unwrap();
3060 assert_eq!(errors.len(), 1);
3061 assert_eq!(errors[0]["ErrorCode"], "InvalidRequestException");
3062 }
3063}