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 let version = SecretVersion {
1168 version_id: version_id.clone(),
1169 secret_string: cv.secret_string.clone(),
1170 secret_binary: cv.secret_binary.clone(),
1171 stages: vec!["AWSPENDING".to_string()],
1172 created_at: now,
1173 };
1174 secret.versions.insert(version_id.clone(), version);
1175
1176 if let Some(ref arn) = lambda_arn {
1178 invocation = Some(RotationInvocation {
1179 lambda_arn: arn.clone(),
1180 secret_id: secret.arn.clone(),
1181 client_request_token: version_id.clone(),
1182 });
1183 }
1184 } else {
1185 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1188 old_v.stages.retain(|s| s != "AWSCURRENT");
1189 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1190 old_v.stages.push("AWSPREVIOUS".to_string());
1191 }
1192 }
1193 let version = SecretVersion {
1194 version_id: version_id.clone(),
1195 secret_string: cv.secret_string.clone(),
1196 secret_binary: cv.secret_binary.clone(),
1197 stages: vec!["AWSCURRENT".to_string()],
1198 created_at: now,
1199 };
1200 secret.versions.insert(version_id.clone(), version);
1201 secret.current_version_id = Some(version_id.clone());
1202 }
1203 }
1204 }
1205
1206 let response = json!({
1207 "ARN": secret.arn,
1208 "Name": secret.name,
1209 "VersionId": version_id,
1210 });
1211
1212 Ok((AwsResponse::ok_json(response), invocation))
1213 }
1214
1215 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1216 let body = req.json_body();
1217 let secret_id = require_secret_id(&body)?;
1218
1219 let mut state = self.state.write();
1220 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1221
1222 if secret.deleted {
1223 return Err(AwsServiceError::aws_error(
1224 StatusCode::BAD_REQUEST,
1225 "InvalidRequestException",
1226 "You can't perform this operation on the secret because it was marked for deletion.",
1227 ));
1228 }
1229
1230 if secret.rotation_enabled != Some(true) {
1231 return Err(AwsServiceError::aws_error(
1232 StatusCode::BAD_REQUEST,
1233 "InvalidRequestException",
1234 "You can't cancel rotation for a secret that does not have rotation enabled.",
1235 ));
1236 }
1237
1238 secret.rotation_enabled = Some(false);
1239
1240 let response = json!({
1241 "ARN": secret.arn,
1242 "Name": secret.name,
1243 });
1244
1245 Ok(AwsResponse::ok_json(response))
1246 }
1247
1248 fn update_secret_version_stage(
1249 &self,
1250 req: &AwsRequest,
1251 ) -> Result<AwsResponse, AwsServiceError> {
1252 let body = req.json_body();
1253 let secret_id = require_secret_id(&body)?;
1254 let version_stage = body["VersionStage"]
1255 .as_str()
1256 .ok_or_else(|| {
1257 AwsServiceError::aws_error(
1258 StatusCode::BAD_REQUEST,
1259 "InvalidParameterException",
1260 "VersionStage is required",
1261 )
1262 })?
1263 .to_string();
1264 validate_string_length("versionStage", &version_stage, 1, 256)?;
1265 validate_optional_string_length(
1266 "removeFromVersionId",
1267 body["RemoveFromVersionId"].as_str(),
1268 32,
1269 64,
1270 )?;
1271 validate_optional_string_length(
1272 "moveToVersionId",
1273 body["MoveToVersionId"].as_str(),
1274 32,
1275 64,
1276 )?;
1277
1278 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1279 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1280
1281 let mut state = self.state.write();
1282 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1283
1284 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1286 let current_holder = secret
1288 .versions
1289 .iter()
1290 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1291 .map(|(id, _)| id.clone());
1292
1293 if let Some(current_vid) = current_holder {
1294 return Err(AwsServiceError::aws_error(
1295 StatusCode::BAD_REQUEST,
1296 "InvalidParameterException",
1297 format!(
1298 "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."
1299 ),
1300 ));
1301 }
1302 }
1303
1304 if let Some(ref remove_vid) = remove_from {
1306 if let Some(version) = secret.versions.get_mut(remove_vid) {
1307 version.stages.retain(|s| s != &version_stage);
1308 if version_stage == "AWSCURRENT" {
1310 for (id, v) in secret.versions.iter_mut() {
1312 if id != remove_vid {
1313 v.stages.retain(|s| s != "AWSPREVIOUS");
1314 }
1315 }
1316 if let Some(v) = secret.versions.get_mut(remove_vid) {
1318 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1319 v.stages.push("AWSPREVIOUS".to_string());
1320 }
1321 }
1322 }
1323 }
1324 }
1325
1326 if let Some(ref move_vid) = move_to {
1328 if let Some(version) = secret.versions.get_mut(move_vid) {
1329 if !version.stages.contains(&version_stage) {
1330 version.stages.push(version_stage.clone());
1331 }
1332 }
1333 if version_stage == "AWSCURRENT" {
1335 secret.current_version_id = Some(move_vid.clone());
1336 }
1337 }
1338
1339 let response = json!({
1340 "ARN": secret.arn,
1341 "Name": secret.name,
1342 });
1343
1344 Ok(AwsResponse::ok_json(response))
1345 }
1346
1347 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1348 let body = req.json_body();
1349 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1350 let secret_id_list = body["SecretIdList"].as_array();
1351 let filters = body["Filters"].as_array();
1352 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1353
1354 if secret_id_list.is_some() && filters.is_some() {
1356 return Err(AwsServiceError::aws_error(
1357 StatusCode::BAD_REQUEST,
1358 "InvalidParameterException",
1359 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1360 ));
1361 }
1362
1363 if max_results.is_some() && filters.is_none() {
1365 return Err(AwsServiceError::aws_error(
1366 StatusCode::BAD_REQUEST,
1367 "InvalidParameterException",
1368 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1369 ));
1370 }
1371
1372 let state = self.state.read();
1373 let mut secret_values: Vec<Value> = Vec::new();
1374 let mut errors: Vec<Value> = Vec::new();
1375
1376 if let Some(id_list) = secret_id_list {
1377 for id_val in id_list {
1378 let sid = id_val.as_str().unwrap_or("");
1379 match self.find_secret_ref(&state, sid) {
1380 Ok(secret) => {
1381 if secret.deleted {
1382 errors.push(json!({
1383 "SecretId": sid,
1384 "ErrorCode": "InvalidRequestException",
1385 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1386 }));
1387 } else if let Some(ref current_vid) = secret.current_version_id {
1388 if let Some(version) = secret.versions.get(current_vid) {
1389 let mut entry = json!({
1390 "ARN": secret.arn,
1391 "Name": secret.name,
1392 "VersionId": version.version_id,
1393 "VersionStages": version.stages,
1394 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1395 });
1396 if let Some(ref s) = version.secret_string {
1397 entry["SecretString"] = json!(s);
1398 }
1399 if let Some(ref b) = version.secret_binary {
1400 entry["SecretBinary"] = json!(base64_encode(b));
1401 }
1402 secret_values.push(entry);
1403 } else {
1404 errors.push(json!({
1405 "SecretId": sid,
1406 "ErrorCode": "ResourceNotFoundException",
1407 "Message": "Secrets Manager can't find the specified secret.",
1408 }));
1409 }
1410 } else {
1411 errors.push(json!({
1412 "SecretId": sid,
1413 "ErrorCode": "ResourceNotFoundException",
1414 "Message": "Secrets Manager can't find the specified secret.",
1415 }));
1416 }
1417 }
1418 Err(_) => {
1419 errors.push(json!({
1420 "SecretId": sid,
1421 "ErrorCode": "ResourceNotFoundException",
1422 "Message": "Secrets Manager can't find the specified secret.",
1423 }));
1424 }
1425 }
1426 }
1427 } else if let Some(filters) = filters {
1428 let matching: Vec<&Secret> = state
1430 .secrets
1431 .values()
1432 .filter(|s| {
1433 if s.deleted {
1434 return false;
1435 }
1436 for filter in filters {
1437 let key = filter["Key"].as_str().unwrap_or("");
1438 let values: Vec<&str> = filter["Values"]
1439 .as_array()
1440 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1441 .unwrap_or_default();
1442 let matches = match key {
1443 "name" => filter_name(s, &values),
1444 "description" => filter_description(s, &values),
1445 "tag-key" => filter_tag_key(s, &values),
1446 "tag-value" => filter_tag_value(s, &values),
1447 "all" => filter_all(s, &values),
1448 _ => true,
1449 };
1450 if !matches {
1451 return false;
1452 }
1453 }
1454 true
1455 })
1456 .collect();
1457
1458 let limit = max_results.unwrap_or(100) as usize;
1459 let mut no_value_found = false;
1460 let mut matching = matching;
1461 matching.sort_by(|a, b| a.name.cmp(&b.name));
1462
1463 for secret in matching.iter().take(limit) {
1464 if let Some(ref current_vid) = secret.current_version_id {
1465 if let Some(version) = secret.versions.get(current_vid) {
1466 let mut entry = json!({
1467 "ARN": secret.arn,
1468 "Name": secret.name,
1469 "VersionId": version.version_id,
1470 "VersionStages": version.stages,
1471 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1472 });
1473 if let Some(ref s) = version.secret_string {
1474 entry["SecretString"] = json!(s);
1475 }
1476 if let Some(ref b) = version.secret_binary {
1477 entry["SecretBinary"] = json!(base64_encode(b));
1478 }
1479 secret_values.push(entry);
1480 } else {
1481 no_value_found = true;
1482 }
1483 } else {
1484 no_value_found = true;
1485 }
1486 }
1487
1488 if no_value_found && secret_values.is_empty() {
1489 return Err(AwsServiceError::aws_error(
1490 StatusCode::NOT_FOUND,
1491 "ResourceNotFoundException",
1492 "Secrets Manager can't find the specified secret.",
1493 ));
1494 }
1495 }
1496
1497 let mut response = json!({
1498 "SecretValues": secret_values,
1499 "Errors": errors,
1500 });
1501
1502 if errors.is_empty() {
1504 response.as_object_mut().unwrap().remove("Errors");
1505 }
1506
1507 Ok(AwsResponse::ok_json(response))
1508 }
1509
1510 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1511 let body = req.json_body();
1512 let secret_id = require_secret_id(&body)?;
1513
1514 let state = self.state.read();
1515 let secret = self.find_secret_ref(&state, &secret_id)?;
1516
1517 let mut response = json!({
1518 "ARN": secret.arn,
1519 "Name": secret.name,
1520 });
1521
1522 if let Some(ref policy) = secret.resource_policy {
1523 response["ResourcePolicy"] = json!(policy);
1524 }
1525
1526 Ok(AwsResponse::ok_json(response))
1527 }
1528
1529 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1530 let body = req.json_body();
1531 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1532 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1533 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1534 AwsServiceError::aws_error(
1535 StatusCode::BAD_REQUEST,
1536 "InvalidParameterException",
1537 "ResourcePolicy must be a string",
1538 )
1539 })?;
1540 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1541
1542 if let Some(secret_id) = body["SecretId"].as_str() {
1544 let state = self.state.read();
1545 self.find_secret_key(&state, secret_id)?;
1546 }
1547
1548 let response = json!({
1549 "PolicyValidationPassed": true,
1550 "ValidationErrors": [],
1551 });
1552 Ok(AwsResponse::ok_json(response))
1553 }
1554
1555 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1556 let body = req.json_body();
1557 let secret_id = require_secret_id(&body)?;
1558 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1559 validate_optional_string_length(
1560 "resourcePolicy",
1561 body["ResourcePolicy"].as_str(),
1562 1,
1563 20480,
1564 )?;
1565 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1566
1567 let mut state = self.state.write();
1568 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1569 secret.resource_policy = policy;
1570
1571 let response = json!({
1572 "ARN": secret.arn,
1573 "Name": secret.name,
1574 });
1575
1576 Ok(AwsResponse::ok_json(response))
1577 }
1578
1579 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1580 let body = req.json_body();
1581 let secret_id = require_secret_id(&body)?;
1582
1583 let mut state = self.state.write();
1584 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1585 secret.resource_policy = None;
1586
1587 let response = json!({
1588 "ARN": secret.arn,
1589 "Name": secret.name,
1590 });
1591
1592 Ok(AwsResponse::ok_json(response))
1593 }
1594
1595 fn replicate_secret_to_regions(
1596 &self,
1597 req: &AwsRequest,
1598 ) -> Result<AwsResponse, AwsServiceError> {
1599 let body = req.json_body();
1600 let secret_id = require_secret_id(&body)?;
1601
1602 let state = self.state.read();
1603 let secret = self.find_secret_ref(&state, &secret_id)?;
1604
1605 let response = json!({
1606 "ARN": secret.arn,
1607 "ReplicationStatus": [],
1608 });
1609 Ok(AwsResponse::ok_json(response))
1610 }
1611
1612 fn remove_regions_from_replication(
1613 &self,
1614 req: &AwsRequest,
1615 ) -> Result<AwsResponse, AwsServiceError> {
1616 let body = req.json_body();
1617 let secret_id = require_secret_id(&body)?;
1618
1619 let state = self.state.read();
1620 let secret = self.find_secret_ref(&state, &secret_id)?;
1621
1622 let response = json!({
1623 "ARN": secret.arn,
1624 "ReplicationStatus": [],
1625 });
1626 Ok(AwsResponse::ok_json(response))
1627 }
1628
1629 fn stop_replication_to_replica(
1630 &self,
1631 req: &AwsRequest,
1632 ) -> Result<AwsResponse, AwsServiceError> {
1633 let body = req.json_body();
1634 let secret_id = require_secret_id(&body)?;
1635
1636 let state = self.state.read();
1637 let secret = self.find_secret_ref(&state, &secret_id)?;
1638
1639 let response = json!({
1640 "ARN": secret.arn,
1641 });
1642 Ok(AwsResponse::ok_json(response))
1643 }
1644
1645 fn find_secret_mut<'a>(
1647 &self,
1648 state: &'a mut crate::state::SecretsManagerState,
1649 secret_id: &str,
1650 ) -> Result<&'a mut Secret, AwsServiceError> {
1651 let key = self.find_secret_key(state, secret_id)?;
1652 Ok(state.secrets.get_mut(&key).unwrap())
1653 }
1654
1655 fn find_secret_key(
1656 &self,
1657 state: &crate::state::SecretsManagerState,
1658 secret_id: &str,
1659 ) -> Result<String, AwsServiceError> {
1660 if state.secrets.contains_key(secret_id) {
1661 return Ok(secret_id.to_string());
1662 }
1663
1664 for secret in state.secrets.values() {
1665 if secret.arn == secret_id {
1666 return Ok(secret.name.clone());
1667 }
1668 }
1669
1670 if secret_id.starts_with("arn:aws:secretsmanager:") {
1671 for secret in state.secrets.values() {
1672 if secret.arn.starts_with(secret_id) {
1673 return Ok(secret.name.clone());
1674 }
1675 }
1676 }
1677
1678 Err(AwsServiceError::aws_error(
1679 StatusCode::NOT_FOUND,
1680 "ResourceNotFoundException",
1681 "Secrets Manager can't find the specified secret.",
1682 ))
1683 }
1684
1685 fn find_secret_ref<'a>(
1687 &self,
1688 state: &'a crate::state::SecretsManagerState,
1689 secret_id: &str,
1690 ) -> Result<&'a Secret, AwsServiceError> {
1691 if let Some(secret) = state.secrets.get(secret_id) {
1692 return Ok(secret);
1693 }
1694
1695 for secret in state.secrets.values() {
1697 if secret.arn == secret_id {
1698 return Ok(secret);
1699 }
1700 }
1701
1702 if secret_id.starts_with("arn:aws:secretsmanager:") {
1704 for secret in state.secrets.values() {
1705 if secret.arn.starts_with(secret_id) {
1706 return Ok(secret);
1707 }
1708 }
1709 }
1710
1711 Err(AwsServiceError::aws_error(
1712 StatusCode::NOT_FOUND,
1713 "ResourceNotFoundException",
1714 "Secrets Manager can't find the specified secret.",
1715 ))
1716 }
1717}
1718
1719struct CreateSecretInput {
1721 name: String,
1722 client_request_token: Option<String>,
1723 description: Option<String>,
1724 kms_key_id: Option<String>,
1725 secret_string: Option<String>,
1726 secret_binary: Option<Vec<u8>>,
1727 tags: Vec<(String, String)>,
1728}
1729
1730impl CreateSecretInput {
1731 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
1732 validate_required("Name", &body["Name"])?;
1733 let name = body["Name"]
1734 .as_str()
1735 .ok_or_else(|| {
1736 AwsServiceError::aws_error(
1737 StatusCode::BAD_REQUEST,
1738 "InvalidParameterException",
1739 "Name is required",
1740 )
1741 })?
1742 .to_string();
1743 validate_string_length("name", &name, 1, 512)?;
1744 validate_optional_string_length(
1745 "clientRequestToken",
1746 body["ClientRequestToken"].as_str(),
1747 32,
1748 64,
1749 )?;
1750 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
1751 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
1752 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
1753
1754 Ok(Self {
1755 name,
1756 client_request_token: body["ClientRequestToken"].as_str().map(|s| s.to_string()),
1757 description: body["Description"].as_str().map(|s| s.to_string()),
1758 kms_key_id: body["KmsKeyId"].as_str().map(|s| s.to_string()),
1759 secret_string: body["SecretString"].as_str().map(|s| s.to_string()),
1760 secret_binary: body["SecretBinary"].as_str().and_then(base64_decode),
1761 tags: parse_tags(&body["Tags"]),
1762 })
1763 }
1764}
1765
1766fn require_secret_id(body: &Value) -> Result<String, AwsServiceError> {
1767 let id = body["SecretId"].as_str().ok_or_else(|| {
1768 AwsServiceError::aws_error(
1769 StatusCode::BAD_REQUEST,
1770 "InvalidParameterException",
1771 "SecretId is required",
1772 )
1773 })?;
1774 validate_string_length("secretId", id, 1, 2048)?;
1775 Ok(id.to_string())
1776}
1777
1778fn parse_tags(tags_val: &Value) -> Vec<(String, String)> {
1779 tags_val
1780 .as_array()
1781 .map(|arr| {
1782 arr.iter()
1783 .filter_map(|t| {
1784 let key = t["Key"].as_str()?;
1785 let value = t["Value"].as_str()?;
1786 Some((key.to_string(), value.to_string()))
1787 })
1788 .collect()
1789 })
1790 .unwrap_or_default()
1791}
1792
1793fn tags_to_json(tags: &[(String, String)]) -> Vec<Value> {
1794 tags.iter()
1795 .map(|(k, v)| json!({"Key": k, "Value": v}))
1796 .collect()
1797}
1798
1799fn split_words(text: &str) -> Vec<String> {
1804 let mut all_words = Vec::new();
1806 for space_part in text.split_whitespace() {
1807 all_words.extend(split_words_no_space(space_part));
1808 }
1809 all_words
1810}
1811
1812fn split_words_no_space(text: &str) -> Vec<String> {
1813 let special_chars = ['/', '-', '_', '+', '=', '.', '@'];
1814
1815 if text.len() == 1 && special_chars.contains(&text.chars().next().unwrap_or(' ')) {
1817 return vec![];
1818 }
1819
1820 let present: Vec<char> = special_chars
1822 .iter()
1823 .filter(|&&c| text.contains(c))
1824 .copied()
1825 .collect();
1826
1827 if present.len() > 1 {
1828 return vec![text.to_string()];
1830 }
1831
1832 if present.len() == 1 {
1833 let ch = present[0];
1834 let parts: Vec<&str> = text.split(ch).filter(|s| !s.is_empty()).collect();
1835 let mut result = Vec::new();
1836 for part in parts {
1837 result.extend(split_by_uppercase(part));
1838 }
1839 return result;
1840 }
1841
1842 split_by_uppercase(text)
1844}
1845
1846fn split_by_uppercase(text: &str) -> Vec<String> {
1849 let chars: Vec<char> = text.chars().collect();
1852 let mut words = Vec::new();
1853 let mut last_end = 0;
1854 let mut i = 0;
1855
1856 while i < chars.len() {
1857 if !chars[i].is_ascii_lowercase()
1859 && i + 1 < chars.len()
1860 && chars[i + 1].is_ascii_lowercase()
1861 {
1862 if i > last_end {
1864 let between: String = chars[last_end..i].iter().collect();
1865 let trimmed = between.trim().to_string();
1866 if !trimmed.is_empty() {
1867 words.push(trimmed);
1868 }
1869 }
1870
1871 let start = i;
1873 i += 2;
1874 while i < chars.len() && chars[i].is_ascii_lowercase() {
1875 i += 1;
1876 }
1877 let word: String = chars[start..i].iter().collect();
1878 let trimmed = word.trim().to_string();
1879 if !trimmed.is_empty() {
1880 words.push(trimmed);
1881 }
1882 last_end = i;
1883 } else {
1884 i += 1;
1885 }
1886 }
1887
1888 if last_end < chars.len() {
1890 let after: String = chars[last_end..].iter().collect();
1891 let trimmed = after.trim().to_string();
1892 if !trimmed.is_empty() {
1893 words.push(trimmed);
1894 }
1895 }
1896
1897 words
1898}
1899
1900fn match_pattern(pattern: &str, value: &str, match_prefix: bool, case_sensitive: bool) -> bool {
1904 if match_prefix {
1905 if case_sensitive {
1906 value.starts_with(pattern)
1907 } else {
1908 value.to_lowercase().starts_with(&pattern.to_lowercase())
1909 }
1910 } else {
1911 let mut pattern_words = split_words(pattern);
1912 if pattern_words.is_empty() {
1913 return false;
1914 }
1915 let mut value_words = split_words(value);
1916 if !case_sensitive {
1917 pattern_words = pattern_words.iter().map(|w| w.to_lowercase()).collect();
1918 value_words = value_words.iter().map(|w| w.to_lowercase()).collect();
1919 }
1920 for pw in &pattern_words {
1921 if !value_words.iter().any(|vw| vw.starts_with(pw.as_str())) {
1922 return false;
1923 }
1924 }
1925 true
1926 }
1927}
1928
1929fn matcher(patterns: &[&str], strings: &[&str], match_prefix: bool, case_sensitive: bool) -> bool {
1932 for pattern in patterns.iter().filter(|p| p.starts_with('!')) {
1934 let inner = &pattern[1..];
1935 for s in strings {
1936 if !match_pattern(inner, s, match_prefix, case_sensitive) {
1937 return true;
1938 }
1939 }
1940 }
1941
1942 for pattern in patterns.iter().filter(|p| !p.starts_with('!')) {
1944 for s in strings {
1945 if match_pattern(pattern, s, match_prefix, case_sensitive) {
1946 return true;
1947 }
1948 }
1949 }
1950 false
1951}
1952
1953fn filter_name(secret: &Secret, values: &[&str]) -> bool {
1955 matcher(values, &[secret.name.as_str()], true, true)
1956}
1957
1958fn filter_description(secret: &Secret, values: &[&str]) -> bool {
1960 match secret.description.as_deref() {
1961 Some(desc) if !desc.is_empty() => matcher(values, &[desc], false, false),
1962 _ => false,
1963 }
1964}
1965
1966fn filter_tag_key(secret: &Secret, values: &[&str]) -> bool {
1968 if secret.tags.is_empty() {
1969 return false;
1970 }
1971 let keys: Vec<&str> = secret.tags.iter().map(|(k, _)| k.as_str()).collect();
1972 matcher(values, &keys, true, true)
1973}
1974
1975fn filter_tag_value(secret: &Secret, values: &[&str]) -> bool {
1977 if secret.tags.is_empty() {
1978 return false;
1979 }
1980 let vals: Vec<&str> = secret.tags.iter().map(|(_, v)| v.as_str()).collect();
1981 matcher(values, &vals, true, true)
1982}
1983
1984fn filter_all(secret: &Secret, values: &[&str]) -> bool {
1986 let mut attributes: Vec<&str> = vec![secret.name.as_str()];
1987 if let Some(ref desc) = secret.description {
1988 if !desc.is_empty() {
1989 attributes.push(desc.as_str());
1990 }
1991 }
1992 for (k, v) in &secret.tags {
1993 attributes.push(k.as_str());
1994 attributes.push(v.as_str());
1995 }
1996 matcher(values, &attributes, false, false)
1997}
1998
1999fn simple_random() -> usize {
2000 use std::collections::hash_map::RandomState;
2001 use std::hash::{BuildHasher, Hasher};
2002 let s = RandomState::new();
2003 let mut hasher = s.build_hasher();
2004 hasher.write_usize(0);
2005 hasher.finish() as usize
2006}
2007
2008#[async_trait]
2009impl AwsService for SecretsManagerService {
2010 fn service_name(&self) -> &str {
2011 "secretsmanager"
2012 }
2013
2014 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2015 match req.action.as_str() {
2016 "CreateSecret" => self.create_secret(&req),
2017 "GetSecretValue" => self.get_secret_value(&req),
2018 "PutSecretValue" => self.put_secret_value(&req),
2019 "UpdateSecret" => self.update_secret(&req),
2020 "DeleteSecret" => self.delete_secret(&req),
2021 "RestoreSecret" => self.restore_secret(&req),
2022 "DescribeSecret" => self.describe_secret(&req),
2023 "ListSecrets" => self.list_secrets(&req),
2024 "TagResource" => self.tag_resource(&req),
2025 "UntagResource" => self.untag_resource(&req),
2026 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
2027 "GetRandomPassword" => self.get_random_password(&req),
2028 "RotateSecret" => {
2029 let (response, invocation) = self.rotate_secret(&req)?;
2030 if let Some(inv) = invocation {
2031 if let Some(ref bus) = self.delivery_bus {
2032 let bus = bus.clone();
2033 tokio::spawn(async move {
2035 for step in &["createSecret", "setSecret", "testSecret", "finishSecret"]
2036 {
2037 let payload = serde_json::json!({
2038 "SecretId": inv.secret_id,
2039 "ClientRequestToken": inv.client_request_token,
2040 "Step": step,
2041 });
2042 let payload_str = payload.to_string();
2043 match bus.invoke_lambda(&inv.lambda_arn, &payload_str).await {
2044 Some(Ok(_)) => {}
2045 Some(Err(e)) => {
2046 tracing::warn!(
2047 step = step,
2048 error = %e,
2049 "rotation Lambda invocation failed"
2050 );
2051 }
2052 None => {
2053 tracing::warn!(
2054 lambda_arn = %inv.lambda_arn,
2055 step = step,
2056 "rotation Lambda delivery not configured; \
2057 Lambda invocation skipped"
2058 );
2059 break;
2060 }
2061 }
2062 }
2063 });
2064 }
2065 }
2066 Ok(response)
2067 }
2068 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2069 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2070 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2071 "GetResourcePolicy" => self.get_resource_policy(&req),
2072 "PutResourcePolicy" => self.put_resource_policy(&req),
2073 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2074 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2075 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2076 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2077 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2078 _ => Err(AwsServiceError::action_not_implemented(
2079 "secretsmanager",
2080 &req.action,
2081 )),
2082 }
2083 }
2084
2085 fn supported_actions(&self) -> &[&str] {
2086 &[
2087 "CreateSecret",
2088 "GetSecretValue",
2089 "PutSecretValue",
2090 "UpdateSecret",
2091 "DeleteSecret",
2092 "RestoreSecret",
2093 "DescribeSecret",
2094 "ListSecrets",
2095 "TagResource",
2096 "UntagResource",
2097 "ListSecretVersionIds",
2098 "GetRandomPassword",
2099 "RotateSecret",
2100 "CancelRotateSecret",
2101 "UpdateSecretVersionStage",
2102 "BatchGetSecretValue",
2103 "GetResourcePolicy",
2104 "PutResourcePolicy",
2105 "DeleteResourcePolicy",
2106 "ValidateResourcePolicy",
2107 "ReplicateSecretToRegions",
2108 "RemoveRegionsFromReplication",
2109 "StopReplicationToReplica",
2110 ]
2111 }
2112}
2113
2114fn base64_decode(input: &str) -> Option<Vec<u8>> {
2115 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2116 let mut buf = Vec::new();
2117 let mut bits: u32 = 0;
2118 let mut count = 0;
2119 for &b in input.as_bytes() {
2120 if b == b'=' || b == b'\n' || b == b'\r' {
2121 continue;
2122 }
2123 let val = table.iter().position(|&c| c == b)? as u32;
2124 bits = (bits << 6) | val;
2125 count += 1;
2126 if count == 4 {
2127 buf.push((bits >> 16) as u8);
2128 buf.push((bits >> 8) as u8);
2129 buf.push(bits as u8);
2130 bits = 0;
2131 count = 0;
2132 }
2133 }
2134 match count {
2135 2 => {
2136 bits <<= 12;
2137 buf.push((bits >> 16) as u8);
2138 }
2139 3 => {
2140 bits <<= 6;
2141 buf.push((bits >> 16) as u8);
2142 buf.push((bits >> 8) as u8);
2143 }
2144 _ => {}
2145 }
2146 Some(buf)
2147}
2148
2149fn base64_encode(input: &[u8]) -> String {
2150 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2151 let mut result = String::new();
2152 for chunk in input.chunks(3) {
2153 let b0 = chunk[0] as u32;
2154 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
2155 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
2156 let triple = (b0 << 16) | (b1 << 8) | b2;
2157 result.push(table[((triple >> 18) & 0x3F) as usize] as char);
2158 result.push(table[((triple >> 12) & 0x3F) as usize] as char);
2159 if chunk.len() > 1 {
2160 result.push(table[((triple >> 6) & 0x3F) as usize] as char);
2161 } else {
2162 result.push('=');
2163 }
2164 if chunk.len() > 2 {
2165 result.push(table[(triple & 0x3F) as usize] as char);
2166 } else {
2167 result.push('=');
2168 }
2169 }
2170 result
2171}
2172
2173#[cfg(test)]
2174mod tests {
2175 use super::*;
2176 use crate::state::SecretsManagerState;
2177 use bytes::Bytes;
2178 use http::{HeaderMap, Method};
2179 use parking_lot::RwLock;
2180 use std::collections::HashMap;
2181 use std::sync::Arc;
2182
2183 fn make_state() -> SharedSecretsManagerState {
2184 Arc::new(RwLock::new(SecretsManagerState::new(
2185 "123456789012",
2186 "us-east-1",
2187 )))
2188 }
2189
2190 fn make_request(action: &str, body: &str) -> AwsRequest {
2191 AwsRequest {
2192 service: "secretsmanager".to_string(),
2193 action: action.to_string(),
2194 region: "us-east-1".to_string(),
2195 account_id: "123456789012".to_string(),
2196 request_id: "test-request-id".to_string(),
2197 headers: HeaderMap::new(),
2198 query_params: HashMap::new(),
2199 body: Bytes::from(body.to_string()),
2200 path_segments: vec![],
2201 raw_path: "/".to_string(),
2202 raw_query: String::new(),
2203 method: Method::POST,
2204 is_query_protocol: false,
2205 access_key_id: None,
2206 }
2207 }
2208
2209 #[tokio::test]
2210 async fn test_create_and_get_secret() {
2211 let state = make_state();
2212 let svc = SecretsManagerService::new(state);
2213
2214 let req = make_request(
2215 "CreateSecret",
2216 r#"{"Name": "test/secret", "SecretString": "mysecretvalue"}"#,
2217 );
2218 let resp = svc.handle(req).await.unwrap();
2219 assert_eq!(resp.status, StatusCode::OK);
2220 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2221 assert_eq!(body["Name"], "test/secret");
2222 assert!(body["ARN"].as_str().unwrap().contains("test/secret"));
2223
2224 let req = make_request("GetSecretValue", r#"{"SecretId": "test/secret"}"#);
2225 let resp = svc.handle(req).await.unwrap();
2226 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2227 assert_eq!(body["SecretString"], "mysecretvalue");
2228 }
2229
2230 #[tokio::test]
2231 async fn test_create_secret_without_value() {
2232 let state = make_state();
2233 let svc = SecretsManagerService::new(state);
2234
2235 let req = make_request("CreateSecret", r#"{"Name": "empty-secret"}"#);
2236 let resp = svc.handle(req).await.unwrap();
2237 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2238 assert_eq!(body["Name"], "empty-secret");
2239 assert!(body.get("VersionId").is_none());
2240 }
2241
2242 #[tokio::test]
2243 async fn test_put_secret_value_creates_version() {
2244 let state = make_state();
2245 let svc = SecretsManagerService::new(state);
2246
2247 let req = make_request(
2248 "CreateSecret",
2249 r#"{"Name": "versioned", "SecretString": "v1"}"#,
2250 );
2251 svc.handle(req).await.unwrap();
2252
2253 let req = make_request(
2254 "PutSecretValue",
2255 r#"{"SecretId": "versioned", "SecretString": "v2"}"#,
2256 );
2257 let resp = svc.handle(req).await.unwrap();
2258 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2259 assert_eq!(body["Name"], "versioned");
2260
2261 let req = make_request("GetSecretValue", r#"{"SecretId": "versioned"}"#);
2263 let resp = svc.handle(req).await.unwrap();
2264 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2265 assert_eq!(body["SecretString"], "v2");
2266 }
2267
2268 #[tokio::test]
2269 async fn test_delete_and_restore_secret() {
2270 let state = make_state();
2271 let svc = SecretsManagerService::new(state);
2272
2273 let req = make_request(
2274 "CreateSecret",
2275 r#"{"Name": "deleteme", "SecretString": "value"}"#,
2276 );
2277 svc.handle(req).await.unwrap();
2278
2279 let req = make_request("DeleteSecret", r#"{"SecretId": "deleteme"}"#);
2281 let resp = svc.handle(req).await.unwrap();
2282 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2283 assert!(body["DeletionDate"].as_f64().is_some());
2284
2285 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2287 assert!(svc.handle(req).await.is_err());
2288
2289 let req = make_request("RestoreSecret", r#"{"SecretId": "deleteme"}"#);
2291 let resp = svc.handle(req).await.unwrap();
2292 assert_eq!(resp.status, StatusCode::OK);
2293
2294 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2296 let resp = svc.handle(req).await.unwrap();
2297 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2298 assert_eq!(body["SecretString"], "value");
2299 }
2300
2301 #[tokio::test]
2302 async fn test_list_secrets() {
2303 let state = make_state();
2304 let svc = SecretsManagerService::new(state);
2305
2306 for name in &["alpha", "beta", "gamma"] {
2307 let req = make_request(
2308 "CreateSecret",
2309 &format!(r#"{{"Name": "{name}", "SecretString": "val"}}"#),
2310 );
2311 svc.handle(req).await.unwrap();
2312 }
2313
2314 let req = make_request("ListSecrets", "{}");
2315 let resp = svc.handle(req).await.unwrap();
2316 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2317 assert_eq!(body["SecretList"].as_array().unwrap().len(), 3);
2318 }
2319
2320 #[tokio::test]
2321 async fn test_tags() {
2322 let state = make_state();
2323 let svc = SecretsManagerService::new(state);
2324
2325 let req = make_request(
2326 "CreateSecret",
2327 r#"{"Name": "tagged", "SecretString": "val"}"#,
2328 );
2329 svc.handle(req).await.unwrap();
2330
2331 let req = make_request(
2332 "TagResource",
2333 r#"{"SecretId": "tagged", "Tags": [{"Key": "env", "Value": "prod"}]}"#,
2334 );
2335 svc.handle(req).await.unwrap();
2336
2337 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2338 let resp = svc.handle(req).await.unwrap();
2339 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2340 let tags = body["Tags"].as_array().unwrap();
2341 assert!(tags
2342 .iter()
2343 .any(|t| t["Key"] == "env" && t["Value"] == "prod"));
2344
2345 let req = make_request(
2346 "UntagResource",
2347 r#"{"SecretId": "tagged", "TagKeys": ["env"]}"#,
2348 );
2349 svc.handle(req).await.unwrap();
2350
2351 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2352 let resp = svc.handle(req).await.unwrap();
2353 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2354 assert_eq!(body["Tags"].as_array().unwrap().len(), 0);
2356 }
2357
2358 #[tokio::test]
2359 async fn test_get_random_password() {
2360 let state = make_state();
2361 let svc = SecretsManagerService::new(state);
2362
2363 let req = make_request("GetRandomPassword", "{}");
2364 let resp = svc.handle(req).await.unwrap();
2365 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2366 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 32);
2367 }
2368
2369 #[tokio::test]
2370 async fn test_replication_ops_return_arn() {
2371 let state = make_state();
2372 let svc = SecretsManagerService::new(state);
2373
2374 let req = make_request(
2375 "CreateSecret",
2376 r#"{"Name": "repl-secret", "SecretString": "val"}"#,
2377 );
2378 let resp = svc.handle(req).await.unwrap();
2379 let create_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2380 let expected_arn = create_body["ARN"].as_str().unwrap();
2381
2382 for action in &[
2383 "ReplicateSecretToRegions",
2384 "RemoveRegionsFromReplication",
2385 "StopReplicationToReplica",
2386 ] {
2387 let req = make_request(action, r#"{"SecretId": "repl-secret"}"#);
2388 let resp = svc.handle(req).await.unwrap();
2389 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2390 assert_eq!(
2391 body["ARN"].as_str().unwrap(),
2392 expected_arn,
2393 "{action} should return the secret's actual ARN"
2394 );
2395 }
2396 }
2397
2398 #[tokio::test]
2399 async fn test_secret_id_length_validation() {
2400 let state = make_state();
2401 let svc = SecretsManagerService::new(state);
2402
2403 let long_id = "x".repeat(2049);
2405 let req = make_request("GetSecretValue", &format!(r#"{{"SecretId": "{long_id}"}}"#));
2406 match svc.handle(req).await {
2407 Err(e) => assert!(e.to_string().contains("ValidationException")),
2408 Ok(_) => panic!("expected ValidationException"),
2409 }
2410 }
2411
2412 #[tokio::test]
2413 async fn test_name_length_validation() {
2414 let state = make_state();
2415 let svc = SecretsManagerService::new(state);
2416
2417 let long_name = "x".repeat(513);
2419 let req = make_request(
2420 "CreateSecret",
2421 &format!(r#"{{"Name": "{long_name}", "SecretString": "val"}}"#),
2422 );
2423 match svc.handle(req).await {
2424 Err(e) => assert!(e.to_string().contains("ValidationException")),
2425 Ok(_) => panic!("expected ValidationException"),
2426 }
2427 }
2428
2429 #[tokio::test]
2430 async fn test_next_token_length_validation() {
2431 let state = make_state();
2432 let svc = SecretsManagerService::new(state);
2433
2434 let long_token = "x".repeat(4097);
2436 let req = make_request(
2437 "ListSecrets",
2438 &format!(r#"{{"NextToken": "{long_token}"}}"#),
2439 );
2440 match svc.handle(req).await {
2441 Err(e) => assert!(e.to_string().contains("ValidationException")),
2442 Ok(_) => panic!("expected ValidationException"),
2443 }
2444 }
2445
2446 #[tokio::test]
2447 async fn test_client_request_token_length_validation() {
2448 let state = make_state();
2449 let svc = SecretsManagerService::new(state);
2450
2451 let req = make_request(
2453 "CreateSecret",
2454 r#"{"Name": "test", "SecretString": "val", "ClientRequestToken": "short"}"#,
2455 );
2456 match svc.handle(req).await {
2457 Err(e) => assert!(e.to_string().contains("ValidationException")),
2458 Ok(_) => panic!("expected ValidationException"),
2459 }
2460 }
2461
2462 #[tokio::test]
2463 async fn test_rotate_secret_with_lambda_creates_pending_version() {
2464 let state = make_state();
2465 let svc = SecretsManagerService::new(state.clone());
2466
2467 let req = make_request(
2469 "CreateSecret",
2470 r#"{"Name": "rotate-me", "SecretString": "old-password"}"#,
2471 );
2472 svc.handle(req).await.unwrap();
2473
2474 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2476 let body = serde_json::json!({
2477 "SecretId": "rotate-me",
2478 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:rotator",
2479 "ClientRequestToken": token,
2480 });
2481 let req = make_request("RotateSecret", &body.to_string());
2482 let resp = svc.handle(req).await.unwrap();
2483 assert_eq!(resp.status, StatusCode::OK);
2484 let resp_body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2485 assert_eq!(resp_body["VersionId"], token);
2486
2487 let s = state.read();
2489 let secret = s.secrets.get("rotate-me").unwrap();
2490 let pending = secret.versions.get(token).unwrap();
2491 assert!(pending.stages.contains(&"AWSPENDING".to_string()));
2492 assert_eq!(pending.secret_string.as_deref(), Some("old-password"));
2493
2494 assert_eq!(
2496 secret.rotation_lambda_arn.as_deref(),
2497 Some("arn:aws:lambda:us-east-1:123456789012:function:rotator")
2498 );
2499 assert_eq!(secret.rotation_enabled, Some(true));
2500 }
2501
2502 #[tokio::test]
2503 async fn test_rotate_secret_without_lambda_promotes_directly() {
2504 let state = make_state();
2505 let svc = SecretsManagerService::new(state.clone());
2506
2507 let req = make_request(
2509 "CreateSecret",
2510 r#"{"Name": "rotate-no-lambda", "SecretString": "value1"}"#,
2511 );
2512 svc.handle(req).await.unwrap();
2513
2514 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2516 let body = serde_json::json!({
2517 "SecretId": "rotate-no-lambda",
2518 "ClientRequestToken": token,
2519 });
2520 let req = make_request("RotateSecret", &body.to_string());
2521 svc.handle(req).await.unwrap();
2522
2523 let s = state.read();
2525 let secret = s.secrets.get("rotate-no-lambda").unwrap();
2526 let new_ver = secret.versions.get(token).unwrap();
2527 assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2528 assert_eq!(secret.current_version_id.as_deref(), Some(token));
2529 }
2530
2531 #[tokio::test]
2532 async fn test_rotate_secret_stores_rotation_config() {
2533 let state = make_state();
2534 let svc = SecretsManagerService::new(state.clone());
2535
2536 let req = make_request(
2537 "CreateSecret",
2538 r#"{"Name": "rot-cfg", "SecretString": "pw"}"#,
2539 );
2540 svc.handle(req).await.unwrap();
2541
2542 let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2543 let body = serde_json::json!({
2544 "SecretId": "rot-cfg",
2545 "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:my-rotator",
2546 "RotationRules": { "AutomaticallyAfterDays": 30 },
2547 "ClientRequestToken": token,
2548 });
2549 let req = make_request("RotateSecret", &body.to_string());
2550 let resp = svc.handle(req).await.unwrap();
2551 assert_eq!(resp.status, StatusCode::OK);
2552
2553 let s = state.read();
2554 let secret = s.secrets.get("rot-cfg").unwrap();
2555 assert_eq!(secret.rotation_enabled, Some(true));
2556 assert_eq!(
2557 secret.rotation_lambda_arn.as_deref(),
2558 Some("arn:aws:lambda:us-east-1:123456789012:function:my-rotator")
2559 );
2560 assert!(secret.last_rotated_at.is_some());
2561 let rules = secret.rotation_rules.as_ref().unwrap();
2562 assert_eq!(rules.automatically_after_days, Some(30));
2563
2564 let pending = secret.versions.get(token).unwrap();
2566 assert!(pending.stages.contains(&"AWSPENDING".to_string()));
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}