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