1use async_trait::async_trait;
2use chrono::Utc;
3use http::StatusCode;
4use serde_json::{json, Value};
5
6use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
7use fakecloud_core::validation::*;
8
9use crate::state::{RotationRules, Secret, SecretVersion, SharedSecretsManagerState};
10
11pub struct SecretsManagerService {
12 state: SharedSecretsManagerState,
13}
14
15impl SecretsManagerService {
16 pub fn new(state: SharedSecretsManagerState) -> Self {
17 Self { state }
18 }
19
20 fn create_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
21 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
22 validate_required("Name", &body["Name"])?;
23 let name = body["Name"]
24 .as_str()
25 .ok_or_else(|| {
26 AwsServiceError::aws_error(
27 StatusCode::BAD_REQUEST,
28 "InvalidParameterException",
29 "Name is required",
30 )
31 })?
32 .to_string();
33 validate_string_length("name", &name, 1, 512)?;
34 validate_optional_string_length(
35 "clientRequestToken",
36 body["ClientRequestToken"].as_str(),
37 32,
38 64,
39 )?;
40 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
41 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
42 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
43
44 let mut state = self.state.write();
45
46 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
47 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
48 let has_value = secret_string.is_some() || secret_binary.is_some();
49
50 let client_request_token = body["ClientRequestToken"].as_str().map(|s| s.to_string());
51
52 if let Some(existing) = state.secrets.get(&name) {
54 if let Some(ref token) = client_request_token {
55 if existing.versions.contains_key(token) {
57 let version = &existing.versions[token];
58 if version.secret_string == secret_string
60 && version.secret_binary == secret_binary
61 {
62 let mut response = json!({
64 "ARN": existing.arn,
65 "Name": existing.name,
66 "VersionId": token,
67 });
68 if !has_value {
69 response.as_object_mut().unwrap().remove("VersionId");
70 }
71 return Ok(AwsResponse::json(StatusCode::OK, response.to_string()));
72 } else {
73 return Err(AwsServiceError::aws_error(
74 StatusCode::BAD_REQUEST,
75 "ResourceExistsException",
76 format!(
77 "You can't use ClientRequestToken {token} because that value is already in use for a version of secret {}.",
78 existing.arn
79 ),
80 ));
81 }
82 }
83 }
84 return Err(AwsServiceError::aws_error(
85 StatusCode::BAD_REQUEST,
86 "ResourceExistsException",
87 format!("The operation failed because the secret {name} already exists."),
88 ));
89 }
90
91 let region = &req.region;
92 let account_id = &req.account_id;
93 let arn = format!(
94 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
95 region,
96 account_id,
97 name,
98 &uuid::Uuid::new_v4().to_string()[..6]
99 );
100
101 let now = Utc::now();
102
103 let description = body["Description"].as_str().map(|s| s.to_string());
104 let kms_key_id = body["KmsKeyId"].as_str().map(|s| s.to_string());
105
106 let tags = parse_tags(&body["Tags"]);
107
108 let (versions, current_version_id, version_id_for_response) = if has_value {
109 let vid = client_request_token.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
110 let version = SecretVersion {
111 version_id: vid.clone(),
112 secret_string,
113 secret_binary,
114 stages: vec!["AWSCURRENT".to_string()],
115 created_at: now,
116 };
117 let mut versions = std::collections::HashMap::new();
118 versions.insert(vid.clone(), version);
119 (versions, Some(vid.clone()), Some(vid))
120 } else {
121 (std::collections::HashMap::new(), None, None)
122 };
123
124 let tags_ever_set = !tags.is_empty();
125 let secret = Secret {
126 name: name.clone(),
127 arn: arn.clone(),
128 description,
129 kms_key_id,
130 versions,
131 current_version_id,
132 tags,
133 tags_ever_set,
134 deleted: false,
135 deletion_date: None,
136 created_at: now,
137 last_changed_at: now,
138 last_accessed_at: None,
139 rotation_enabled: None,
140 rotation_lambda_arn: None,
141 rotation_rules: None,
142 last_rotated_at: None,
143 resource_policy: None,
144 };
145
146 state.secrets.insert(name.clone(), secret);
147
148 let mut response = json!({
149 "ARN": arn,
150 "Name": name,
151 });
152 if let Some(vid) = version_id_for_response {
153 response["VersionId"] = json!(vid);
154 }
155
156 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
157 }
158
159 fn get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
160 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
161 let secret_id = require_secret_id(&body)?;
162 validate_optional_string_length("versionId", body["VersionId"].as_str(), 32, 64)?;
163 validate_optional_string_length("versionStage", body["VersionStage"].as_str(), 1, 256)?;
164
165 let mut state = self.state.write();
166 let secret = self.find_secret_mut(&mut state, &secret_id)?;
167
168 if secret.deleted {
169 return Err(AwsServiceError::aws_error(
170 StatusCode::BAD_REQUEST,
171 "InvalidRequestException",
172 "You can't perform this operation on the secret because it was marked for deletion.",
173 ));
174 }
175
176 let requested_stage = body["VersionStage"].as_str().unwrap_or("AWSCURRENT");
177
178 let version_id = body["VersionId"]
180 .as_str()
181 .map(|s| s.to_string())
182 .or_else(|| {
183 secret
184 .versions
185 .iter()
186 .find(|(_, v)| v.stages.contains(&requested_stage.to_string()))
187 .map(|(id, _)| id.clone())
188 });
189
190 let version_id = match version_id {
191 Some(vid) => vid,
192 None => {
193 return Err(AwsServiceError::aws_error(
195 StatusCode::NOT_FOUND,
196 "ResourceNotFoundException",
197 format!(
198 "Secrets Manager can't find the specified secret value for staging label: {requested_stage}"
199 ),
200 ));
201 }
202 };
203
204 let version = secret.versions.get(&version_id).ok_or_else(|| {
205 AwsServiceError::aws_error(
206 StatusCode::NOT_FOUND,
207 "ResourceNotFoundException",
208 format!(
209 "Secrets Manager can't find the specified secret value for VersionId: {version_id}"
210 ),
211 )
212 })?;
213
214 if body["VersionId"].as_str().is_some() {
216 if let Some(stage) = body["VersionStage"].as_str() {
217 if !version.stages.contains(&stage.to_string()) {
218 return Err(AwsServiceError::aws_error(
219 StatusCode::NOT_FOUND,
220 "ResourceNotFoundException",
221 "You provided a VersionStage that is not associated to the provided VersionId.",
222 ));
223 }
224 }
225 }
226
227 secret.last_accessed_at = Some(Utc::now());
229
230 let mut response = json!({
231 "ARN": secret.arn,
232 "Name": secret.name,
233 "VersionId": version.version_id,
234 "VersionStages": version.stages,
235 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
236 });
237
238 if let Some(ref s) = version.secret_string {
239 response["SecretString"] = json!(s);
240 }
241 if let Some(ref b) = version.secret_binary {
242 response["SecretBinary"] = json!(base64_encode(b));
243 }
244
245 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
246 }
247
248 fn put_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
249 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
250 let secret_id = require_secret_id(&body)?;
251 validate_optional_string_length(
252 "clientRequestToken",
253 body["ClientRequestToken"].as_str(),
254 32,
255 64,
256 )?;
257 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
258
259 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
260 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
261
262 if secret_string.is_none() && secret_binary.is_none() {
264 return Err(AwsServiceError::aws_error(
265 StatusCode::BAD_REQUEST,
266 "InvalidRequestException",
267 "You must provide either SecretString or SecretBinary.",
268 ));
269 }
270
271 let mut state = self.state.write();
272 let secret = match self.find_secret_mut(&mut state, &secret_id) {
273 Ok(s) => s,
274 Err(_) => {
275 return Err(AwsServiceError::aws_error(
276 StatusCode::NOT_FOUND,
277 "ResourceNotFoundException",
278 "Secrets Manager can't find the specified secret.",
279 ));
280 }
281 };
282
283 if secret.deleted {
284 return Err(AwsServiceError::aws_error(
285 StatusCode::BAD_REQUEST,
286 "InvalidRequestException",
287 "You can't perform this operation on the secret because it was marked for deletion.",
288 ));
289 }
290
291 let now = Utc::now();
292 let version_id = body["ClientRequestToken"]
293 .as_str()
294 .map(|s| s.to_string())
295 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
296
297 if let Some(existing_version) = secret.versions.get(&version_id) {
299 if existing_version.secret_string == secret_string
300 && existing_version.secret_binary == secret_binary
301 {
302 let response = json!({
304 "ARN": secret.arn,
305 "Name": secret.name,
306 "VersionId": version_id,
307 "VersionStages": existing_version.stages,
308 });
309 return Ok(AwsResponse::json(StatusCode::OK, response.to_string()));
310 } else {
311 return Err(AwsServiceError::aws_error(
312 StatusCode::BAD_REQUEST,
313 "ResourceExistsException",
314 format!(
315 "You can't use ClientRequestToken {version_id} because that value is already in use for a version of secret {}.",
316 secret.arn
317 ),
318 ));
319 }
320 }
321
322 let mut version_stages: Vec<String> = body["VersionStages"]
323 .as_array()
324 .map(|arr| {
325 arr.iter()
326 .filter_map(|v| v.as_str().map(|s| s.to_string()))
327 .collect()
328 })
329 .unwrap_or_else(|| vec!["AWSCURRENT".to_string()]);
330
331 let has_current = secret
333 .versions
334 .values()
335 .any(|v| v.stages.contains(&"AWSCURRENT".to_string()));
336 if !has_current && !version_stages.contains(&"AWSCURRENT".to_string()) {
337 version_stages.push("AWSCURRENT".to_string());
338 }
339
340 if version_stages.contains(&"AWSCURRENT".to_string()) {
342 if let Some(ref old_vid) = secret.current_version_id.clone() {
343 if let Some(old_version) = secret.versions.get_mut(old_vid) {
344 old_version.stages.retain(|s| s != "AWSCURRENT");
345 if !old_version.stages.contains(&"AWSPREVIOUS".to_string()) {
346 old_version.stages.push("AWSPREVIOUS".to_string());
347 }
348 }
349 for (id, v) in secret.versions.iter_mut() {
351 if id != old_vid {
352 v.stages.retain(|s| s != "AWSPREVIOUS");
353 }
354 }
355 }
356 secret.current_version_id = Some(version_id.clone());
357 }
358
359 for stage in &version_stages {
361 if stage == "AWSCURRENT" || stage == "AWSPREVIOUS" {
362 continue;
363 }
364 for v in secret.versions.values_mut() {
365 v.stages.retain(|s| s != stage);
366 }
367 }
368
369 secret.versions.retain(|_, v| !v.stages.is_empty());
371
372 let version = SecretVersion {
373 version_id: version_id.clone(),
374 secret_string,
375 secret_binary,
376 stages: version_stages.clone(),
377 created_at: now,
378 };
379
380 secret.versions.insert(version_id.clone(), version);
381 secret.last_changed_at = now;
382
383 let response = json!({
384 "ARN": secret.arn,
385 "Name": secret.name,
386 "VersionId": version_id,
387 "VersionStages": version_stages,
388 });
389
390 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
391 }
392
393 fn update_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
394 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
395 let secret_id = require_secret_id(&body)?;
396 validate_optional_string_length(
397 "clientRequestToken",
398 body["ClientRequestToken"].as_str(),
399 32,
400 64,
401 )?;
402 validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
403 validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
404 validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
405
406 let mut state = self.state.write();
407 let secret = match self.find_secret_mut(&mut state, &secret_id) {
408 Ok(s) => s,
409 Err(_) => {
410 return Err(AwsServiceError::aws_error(
411 StatusCode::NOT_FOUND,
412 "ResourceNotFoundException",
413 "Secrets Manager can't find the specified secret.",
414 ));
415 }
416 };
417
418 if secret.deleted {
419 return Err(AwsServiceError::aws_error(
420 StatusCode::BAD_REQUEST,
421 "InvalidRequestException",
422 "You can't perform this operation on the secret because it was marked for deletion.",
423 ));
424 }
425
426 if let Some(desc) = body["Description"].as_str() {
427 secret.description = Some(desc.to_string());
428 }
429 if let Some(kms) = body["KmsKeyId"].as_str() {
430 secret.kms_key_id = Some(kms.to_string());
431 }
432
433 let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
435 let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
436
437 let version_id = if secret_string.is_some() || secret_binary.is_some() {
438 let vid = body["ClientRequestToken"]
439 .as_str()
440 .map(|s| s.to_string())
441 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
442
443 if let Some(existing_version) = secret.versions.get(&vid) {
445 if existing_version.secret_string == secret_string
446 && existing_version.secret_binary == secret_binary
447 {
448 let response = json!({
450 "ARN": secret.arn,
451 "Name": secret.name,
452 "VersionId": vid,
453 });
454 return Ok(AwsResponse::json(StatusCode::OK, response.to_string()));
455 } else {
456 return Err(AwsServiceError::aws_error(
457 StatusCode::BAD_REQUEST,
458 "ResourceExistsException",
459 format!(
460 "You can't use ClientRequestToken {vid} because that value is already in use for a version of secret {}.",
461 secret.arn
462 ),
463 ));
464 }
465 }
466
467 let now = Utc::now();
468
469 if let Some(ref old_vid) = secret.current_version_id.clone() {
471 if let Some(old_v) = secret.versions.get_mut(old_vid) {
472 old_v.stages.retain(|s| s != "AWSCURRENT");
473 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
474 old_v.stages.push("AWSPREVIOUS".to_string());
475 }
476 }
477 }
478
479 let version = SecretVersion {
480 version_id: vid.clone(),
481 secret_string,
482 secret_binary,
483 stages: vec!["AWSCURRENT".to_string()],
484 created_at: now,
485 };
486 secret.versions.insert(vid.clone(), version);
487 secret.current_version_id = Some(vid.clone());
488 secret.last_changed_at = now;
489 Some(vid)
490 } else {
491 secret.last_changed_at = Utc::now();
492 None
493 };
494
495 let mut response = json!({
496 "ARN": secret.arn,
497 "Name": secret.name,
498 });
499 if let Some(vid) = version_id {
500 response["VersionId"] = json!(vid);
501 }
502
503 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
504 }
505
506 fn delete_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
507 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
508 let secret_id = require_secret_id(&body)?;
509
510 let force_delete = body["ForceDeleteWithoutRecovery"]
511 .as_bool()
512 .unwrap_or(false);
513 let recovery_window = body.get("RecoveryWindowInDays").and_then(|v| v.as_i64());
514
515 if let Some(days) = recovery_window {
517 if !(7..=30).contains(&days) {
518 return Err(AwsServiceError::aws_error(
519 StatusCode::BAD_REQUEST,
520 "InvalidParameterException",
521 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: RecoveryWindowInDays value must be between 7 and 30 days (inclusive).",
522 ));
523 }
524 }
525
526 if force_delete && recovery_window.is_some() {
528 return Err(AwsServiceError::aws_error(
529 StatusCode::BAD_REQUEST,
530 "InvalidParameterException",
531 "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: You can't use ForceDeleteWithoutRecovery in conjunction with RecoveryWindowInDays.",
532 ));
533 }
534
535 let mut state = self.state.write();
536
537 if force_delete {
538 match self.find_secret_mut(&mut state, &secret_id) {
540 Ok(secret) => {
541 let arn = secret.arn.clone();
542 let name = secret.name.clone();
543 let deletion_date = Utc::now();
544 state.secrets.remove(&name);
545 let response = json!({
546 "ARN": arn,
547 "Name": name,
548 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
549 });
550 return Ok(AwsResponse::json(StatusCode::OK, response.to_string()));
551 }
552 Err(_) => {
553 let arn = format!(
555 "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
556 req.region,
557 req.account_id,
558 secret_id,
559 &uuid::Uuid::new_v4().to_string()[..6]
560 );
561 let deletion_date = Utc::now();
562 let response = json!({
563 "ARN": arn,
564 "Name": secret_id,
565 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
566 });
567 return Ok(AwsResponse::json(StatusCode::OK, response.to_string()));
568 }
569 }
570 }
571
572 let secret = self.find_secret_mut(&mut state, &secret_id)?;
573
574 if secret.deleted {
575 return Err(AwsServiceError::aws_error(
576 StatusCode::BAD_REQUEST,
577 "InvalidRequestException",
578 "You can't perform this operation on the secret because it was already scheduled for deletion.",
579 ));
580 }
581
582 let now = Utc::now();
583 let days = recovery_window.unwrap_or(30);
584 let deletion_date = now + chrono::Duration::days(days);
585 secret.deleted = true;
586 secret.deletion_date = Some(deletion_date);
587
588 let response = json!({
589 "ARN": secret.arn,
590 "Name": secret.name,
591 "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
592 });
593
594 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
595 }
596
597 fn restore_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
598 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
599 let secret_id = require_secret_id(&body)?;
600
601 let mut state = self.state.write();
602 let secret = self.find_secret_mut(&mut state, &secret_id)?;
603
604 secret.deleted = false;
606 secret.deletion_date = None;
607
608 let response = json!({
609 "ARN": secret.arn,
610 "Name": secret.name,
611 });
612
613 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
614 }
615
616 fn describe_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
617 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
618 let secret_id = require_secret_id(&body)?;
619
620 let state = self.state.read();
621 let secret = self.find_secret_ref(&state, &secret_id)?;
622
623 let mut response = json!({
624 "ARN": secret.arn,
625 "Name": secret.name,
626 "CreatedDate": secret.created_at.timestamp_millis() as f64 / 1000.0,
627 "LastChangedDate": secret.last_changed_at.timestamp_millis() as f64 / 1000.0,
628 });
629
630 if !secret.versions.is_empty() {
631 let mut version_ids_to_stages: serde_json::Map<String, Value> = serde_json::Map::new();
632 for (vid, version) in &secret.versions {
633 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
634 }
635 response["VersionIdsToStages"] = Value::Object(version_ids_to_stages);
636 }
637
638 if let Some(ref desc) = secret.description {
639 if !desc.is_empty() {
640 response["Description"] = json!(desc);
641 }
642 }
643
644 if secret.tags_ever_set || !secret.tags.is_empty() {
645 response["Tags"] = json!(tags_to_json(&secret.tags));
646 }
647
648 if let Some(ref kms) = secret.kms_key_id {
649 response["KmsKeyId"] = json!(kms);
650 }
651 if secret.deleted {
652 response["DeletedDate"] = json!(secret
653 .deletion_date
654 .map(|d| d.timestamp_millis() as f64 / 1000.0));
655 }
656 if let Some(rotation_enabled) = secret.rotation_enabled {
657 response["RotationEnabled"] = json!(rotation_enabled);
658 }
659 if let Some(ref lambda_arn) = secret.rotation_lambda_arn {
660 response["RotationLambdaARN"] = json!(lambda_arn);
661 }
662 if let Some(ref rules) = secret.rotation_rules {
663 let mut rules_json = json!({});
664 if let Some(days) = rules.automatically_after_days {
665 rules_json["AutomaticallyAfterDays"] = json!(days);
666 }
667 response["RotationRules"] = rules_json;
668 }
669 if let Some(last_rotated) = secret.last_rotated_at {
670 response["LastRotatedDate"] = json!(last_rotated.timestamp_millis() as f64 / 1000.0);
671 }
672 if secret.rotation_enabled == Some(true) {
674 if let Some(ref rules) = secret.rotation_rules {
675 if let Some(days) = rules.automatically_after_days {
676 let base = secret.last_rotated_at.unwrap_or(secret.created_at);
677 let next = base + chrono::Duration::days(days);
678 response["NextRotationDate"] = json!(next.timestamp_millis() as f64 / 1000.0);
679 }
680 }
681 }
682
683 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
684 }
685
686 fn list_secrets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
687 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
688 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
689 validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
690 validate_optional_enum("sortBy", body["SortBy"].as_str(), &["name", "created-date"])?;
691 validate_optional_enum("sortOrder", body["SortOrder"].as_str(), &["asc", "desc"])?;
692 let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
693 let next_token = body["NextToken"].as_str();
694 let filters = body["Filters"].as_array();
695 let include_deleted = body["IncludePlannedDeletion"].as_bool().unwrap_or(false);
696
697 if let Some(filters) = filters {
699 for filter in filters {
700 let key = filter["Key"].as_str().unwrap_or("");
701 let values = filter["Values"].as_array();
702
703 if key.is_empty() {
704 return Err(AwsServiceError::aws_error(
705 StatusCode::BAD_REQUEST,
706 "InvalidParameterException",
707 "Invalid filter key",
708 ));
709 }
710
711 let valid_keys = [
712 "all",
713 "name",
714 "tag-key",
715 "description",
716 "tag-value",
717 "owning-service",
718 "primary-region",
719 ];
720 if !valid_keys.contains(&key) {
721 return Err(AwsServiceError::aws_error(
722 StatusCode::BAD_REQUEST,
723 "ValidationException",
724 format!(
725 "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]",
726 key
727 ),
728 ));
729 }
730
731 if values.is_none() || values.unwrap().is_empty() {
732 return Err(AwsServiceError::aws_error(
733 StatusCode::BAD_REQUEST,
734 "InvalidParameterException",
735 format!("Invalid filter values for key: {key}"),
736 ));
737 }
738 }
739 }
740
741 let state = self.state.read();
742
743 let mut secrets: Vec<&Secret> = state
744 .secrets
745 .values()
746 .filter(|s| {
747 if s.deleted && !include_deleted {
749 return false;
750 }
751
752 if let Some(filters) = filters {
753 for filter in filters {
754 let key = filter["Key"].as_str().unwrap_or("");
755 let values: Vec<&str> = filter["Values"]
756 .as_array()
757 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
758 .unwrap_or_default();
759
760 let matches = match key {
761 "name" => filter_name(s, &values),
762 "description" => filter_description(s, &values),
763 "tag-key" => filter_tag_key(s, &values),
764 "tag-value" => filter_tag_value(s, &values),
765 "all" => filter_all(s, &values),
766 "owning-service" => false,
767 "primary-region" => false,
768 _ => true,
769 };
770
771 if !matches {
772 return false;
773 }
774 }
775 }
776 true
777 })
778 .collect();
779 secrets.sort_by(|a, b| a.created_at.cmp(&b.created_at));
780
781 let start_idx = if let Some(token) = next_token {
783 secrets.iter().position(|s| s.name == token).unwrap_or(0)
784 } else {
785 0
786 };
787
788 let page: Vec<Value> = secrets
789 .iter()
790 .skip(start_idx)
791 .take(max_results)
792 .map(|s| {
793 let mut entry = json!({
794 "ARN": s.arn,
795 "Name": s.name,
796 "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
797 "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
798 });
799
800 if !s.versions.is_empty() {
801 let mut version_ids_to_stages: serde_json::Map<String, Value> =
802 serde_json::Map::new();
803 for (vid, version) in &s.versions {
804 version_ids_to_stages.insert(vid.clone(), json!(version.stages));
805 }
806 entry["SecretVersionsToStages"] = Value::Object(version_ids_to_stages);
807 }
808
809 if let Some(ref desc) = s.description {
810 if !desc.is_empty() {
811 entry["Description"] = json!(desc);
812 }
813 }
814
815 if s.tags_ever_set || !s.tags.is_empty() {
816 entry["Tags"] = json!(tags_to_json(&s.tags));
817 }
818
819 if let Some(ref kms) = s.kms_key_id {
820 entry["KmsKeyId"] = json!(kms);
821 }
822 if s.deleted {
823 entry["DeletedDate"] = json!(s
824 .deletion_date
825 .map(|d| d.timestamp_millis() as f64 / 1000.0));
826 }
827 entry
828 })
829 .collect();
830
831 let has_more = start_idx + max_results < secrets.len();
832 let mut response = json!({
833 "SecretList": page,
834 });
835 if has_more {
836 if let Some(next) = secrets.get(start_idx + max_results) {
837 response["NextToken"] = json!(next.name);
838 }
839 }
840
841 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
842 }
843
844 fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
845 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
846 let secret_id = require_secret_id(&body)?;
847
848 let new_tags = parse_tags(&body["Tags"]);
849
850 let mut state = self.state.write();
851 let secret = self.find_secret_mut(&mut state, &secret_id)?;
852
853 if !new_tags.is_empty() {
854 secret.tags_ever_set = true;
855 }
856 for (k, v) in new_tags {
857 if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
859 existing.1 = v;
860 } else {
861 secret.tags.push((k, v));
862 }
863 }
864
865 Ok(AwsResponse::json(StatusCode::OK, "{}"))
866 }
867
868 fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
869 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
870 let secret_id = require_secret_id(&body)?;
871
872 let tag_keys: Vec<String> = body["TagKeys"]
873 .as_array()
874 .map(|arr| {
875 arr.iter()
876 .filter_map(|v| v.as_str().map(|s| s.to_string()))
877 .collect()
878 })
879 .unwrap_or_default();
880
881 let mut state = self.state.write();
882 let secret = self.find_secret_mut(&mut state, &secret_id)?;
883
884 secret.tags.retain(|(k, _)| !tag_keys.contains(k));
885
886 Ok(AwsResponse::json(StatusCode::OK, "{}"))
887 }
888
889 fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
890 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
891 let secret_id = require_secret_id(&body)?;
892 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
893
894 let state = self.state.read();
895 let secret = self.find_secret_ref(&state, &secret_id)?;
896
897 let versions: Vec<Value> = secret
898 .versions
899 .values()
900 .map(|v| {
901 json!({
902 "VersionId": v.version_id,
903 "VersionStages": v.stages,
904 "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
905 })
906 })
907 .collect();
908
909 let response = json!({
910 "ARN": secret.arn,
911 "Name": secret.name,
912 "Versions": versions,
913 });
914
915 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
916 }
917
918 fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
919 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
920 let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
921
922 if length < 4 {
923 return Err(AwsServiceError::aws_error(
924 StatusCode::BAD_REQUEST,
925 "InvalidParameterException",
926 "InvalidParameterException",
927 ));
928 }
929 if length > 4096 {
930 return Err(AwsServiceError::aws_error(
931 StatusCode::BAD_REQUEST,
932 "InvalidParameterValue",
933 "InvalidParameterValue",
934 ));
935 }
936
937 let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
938 let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
939 let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
940 let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
941 let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
942 let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
943 validate_optional_string_length(
944 "excludeCharacters",
945 body["ExcludeCharacters"].as_str(),
946 0,
947 4096,
948 )?;
949 let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
950
951 let lowercase = "abcdefghijklmnopqrstuvwxyz";
952 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
953 let digits = "0123456789";
954 let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
955
956 let mut char_pool = String::new();
957 let mut required_chars: Vec<String> = Vec::new();
958
959 if !exclude_lowercase {
960 let filtered: String = lowercase
961 .chars()
962 .filter(|c| !exclude_chars.contains(*c))
963 .collect();
964 if !filtered.is_empty() {
965 required_chars.push(filtered.clone());
966 char_pool.push_str(&filtered);
967 }
968 }
969 if !exclude_uppercase {
970 let filtered: String = uppercase
971 .chars()
972 .filter(|c| !exclude_chars.contains(*c))
973 .collect();
974 if !filtered.is_empty() {
975 required_chars.push(filtered.clone());
976 char_pool.push_str(&filtered);
977 }
978 }
979 if !exclude_numbers {
980 let filtered: String = digits
981 .chars()
982 .filter(|c| !exclude_chars.contains(*c))
983 .collect();
984 if !filtered.is_empty() {
985 required_chars.push(filtered.clone());
986 char_pool.push_str(&filtered);
987 }
988 }
989 if !exclude_punctuation {
990 let filtered: String = punctuation
991 .chars()
992 .filter(|c| !exclude_chars.contains(*c))
993 .collect();
994 if !filtered.is_empty() {
995 required_chars.push(filtered.clone());
996 char_pool.push_str(&filtered);
997 }
998 }
999 if include_space && !exclude_chars.contains(' ') {
1000 char_pool.push(' ');
1001 }
1002
1003 if char_pool.is_empty() {
1004 return Err(AwsServiceError::aws_error(
1005 StatusCode::BAD_REQUEST,
1006 "InvalidParameterException",
1007 "InvalidParameterException",
1008 ));
1009 }
1010
1011 let pool_bytes: Vec<char> = char_pool.chars().collect();
1012 let mut password = String::with_capacity(length);
1013
1014 if require_each {
1016 for category in &required_chars {
1018 let chars: Vec<char> = category.chars().collect();
1019 let idx = simple_random() % chars.len();
1020 password.push(chars[idx]);
1021 }
1022 if include_space && !exclude_chars.contains(' ') {
1023 password.push(' ');
1024 }
1025 }
1026
1027 while password.len() < length {
1029 let idx = simple_random() % pool_bytes.len();
1030 password.push(pool_bytes[idx]);
1031 }
1032
1033 let mut chars: Vec<char> = password.chars().collect();
1035 for i in (1..chars.len()).rev() {
1036 let j = simple_random() % (i + 1);
1037 chars.swap(i, j);
1038 }
1039 let password: String = chars.into_iter().take(length).collect();
1040
1041 let response = json!({
1042 "RandomPassword": password,
1043 });
1044
1045 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1046 }
1047
1048 fn rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1049 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1050 let secret_id = require_secret_id(&body)?;
1051
1052 if let Some(token) = body["ClientRequestToken"].as_str() {
1054 if token.len() < 32 || token.len() > 64 {
1055 return Err(AwsServiceError::aws_error(
1056 StatusCode::BAD_REQUEST,
1057 "InvalidParameterException",
1058 "ClientRequestToken must be 32-64 characters long.",
1059 ));
1060 }
1061 }
1062
1063 if let Some(arn) = body["RotationLambdaARN"].as_str() {
1065 if arn.len() > 2048 {
1066 return Err(AwsServiceError::aws_error(
1067 StatusCode::BAD_REQUEST,
1068 "InvalidParameterException",
1069 "RotationLambdaARN length must be less than or equal to 2048.",
1070 ));
1071 }
1072 }
1073
1074 if let Some(rules) = body["RotationRules"].as_object() {
1076 if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1077 if !(1..=1000).contains(&days) {
1078 return Err(AwsServiceError::aws_error(
1079 StatusCode::BAD_REQUEST,
1080 "InvalidParameterException",
1081 "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1082 ));
1083 }
1084 }
1085 }
1086
1087 let mut state = self.state.write();
1088 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1089
1090 if secret.deleted {
1091 return Err(AwsServiceError::aws_error(
1092 StatusCode::BAD_REQUEST,
1093 "InvalidRequestException",
1094 "You can't perform this operation on the secret because it was marked for deletion.",
1095 ));
1096 }
1097
1098 if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1100 secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1101 }
1102
1103 if let Some(rules) = body["RotationRules"].as_object() {
1104 let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1105 secret.rotation_rules = Some(RotationRules {
1106 automatically_after_days: days,
1107 });
1108 }
1109
1110 secret.rotation_enabled = Some(true);
1111 let now = Utc::now();
1112 secret.last_rotated_at = Some(now);
1113 secret.last_changed_at = now;
1114
1115 let version_id = body["ClientRequestToken"]
1116 .as_str()
1117 .map(|s| s.to_string())
1118 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1119
1120 let has_lambda =
1121 body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1122
1123 if let Some(current_vid) = secret.current_version_id.clone() {
1125 let current_value = secret.versions.get(¤t_vid).cloned();
1126
1127 if let Some(cv) = current_value {
1128 if has_lambda {
1129 let version = SecretVersion {
1131 version_id: version_id.clone(),
1132 secret_string: cv.secret_string.clone(),
1133 secret_binary: cv.secret_binary.clone(),
1134 stages: vec!["AWSPENDING".to_string()],
1135 created_at: now,
1136 };
1137 secret.versions.insert(version_id.clone(), version);
1138 } else {
1139 if let Some(old_v) = secret.versions.get_mut(¤t_vid) {
1142 old_v.stages.retain(|s| s != "AWSCURRENT");
1143 if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1144 old_v.stages.push("AWSPREVIOUS".to_string());
1145 }
1146 }
1147 let version = SecretVersion {
1148 version_id: version_id.clone(),
1149 secret_string: cv.secret_string.clone(),
1150 secret_binary: cv.secret_binary.clone(),
1151 stages: vec!["AWSCURRENT".to_string()],
1152 created_at: now,
1153 };
1154 secret.versions.insert(version_id.clone(), version);
1155 secret.current_version_id = Some(version_id.clone());
1156 }
1157 }
1158 }
1159
1160 let response = json!({
1161 "ARN": secret.arn,
1162 "Name": secret.name,
1163 "VersionId": version_id,
1164 });
1165
1166 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1167 }
1168
1169 fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1170 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1171 let secret_id = require_secret_id(&body)?;
1172
1173 let mut state = self.state.write();
1174 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1175
1176 if secret.deleted {
1177 return Err(AwsServiceError::aws_error(
1178 StatusCode::BAD_REQUEST,
1179 "InvalidRequestException",
1180 "You can't perform this operation on the secret because it was marked for deletion.",
1181 ));
1182 }
1183
1184 if secret.rotation_enabled != Some(true) {
1185 return Err(AwsServiceError::aws_error(
1186 StatusCode::BAD_REQUEST,
1187 "InvalidRequestException",
1188 "You can't cancel rotation for a secret that does not have rotation enabled.",
1189 ));
1190 }
1191
1192 secret.rotation_enabled = Some(false);
1193
1194 let response = json!({
1195 "ARN": secret.arn,
1196 "Name": secret.name,
1197 });
1198
1199 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1200 }
1201
1202 fn update_secret_version_stage(
1203 &self,
1204 req: &AwsRequest,
1205 ) -> Result<AwsResponse, AwsServiceError> {
1206 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1207 let secret_id = require_secret_id(&body)?;
1208 let version_stage = body["VersionStage"]
1209 .as_str()
1210 .ok_or_else(|| {
1211 AwsServiceError::aws_error(
1212 StatusCode::BAD_REQUEST,
1213 "InvalidParameterException",
1214 "VersionStage is required",
1215 )
1216 })?
1217 .to_string();
1218 validate_string_length("versionStage", &version_stage, 1, 256)?;
1219 validate_optional_string_length(
1220 "removeFromVersionId",
1221 body["RemoveFromVersionId"].as_str(),
1222 32,
1223 64,
1224 )?;
1225 validate_optional_string_length(
1226 "moveToVersionId",
1227 body["MoveToVersionId"].as_str(),
1228 32,
1229 64,
1230 )?;
1231
1232 let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1233 let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1234
1235 let mut state = self.state.write();
1236 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1237
1238 if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1240 let current_holder = secret
1242 .versions
1243 .iter()
1244 .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1245 .map(|(id, _)| id.clone());
1246
1247 if let Some(current_vid) = current_holder {
1248 return Err(AwsServiceError::aws_error(
1249 StatusCode::BAD_REQUEST,
1250 "InvalidParameterException",
1251 format!(
1252 "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."
1253 ),
1254 ));
1255 }
1256 }
1257
1258 if let Some(ref remove_vid) = remove_from {
1260 if let Some(version) = secret.versions.get_mut(remove_vid) {
1261 version.stages.retain(|s| s != &version_stage);
1262 if version_stage == "AWSCURRENT" {
1264 for (id, v) in secret.versions.iter_mut() {
1266 if id != remove_vid {
1267 v.stages.retain(|s| s != "AWSPREVIOUS");
1268 }
1269 }
1270 if let Some(v) = secret.versions.get_mut(remove_vid) {
1272 if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1273 v.stages.push("AWSPREVIOUS".to_string());
1274 }
1275 }
1276 }
1277 }
1278 }
1279
1280 if let Some(ref move_vid) = move_to {
1282 if let Some(version) = secret.versions.get_mut(move_vid) {
1283 if !version.stages.contains(&version_stage) {
1284 version.stages.push(version_stage.clone());
1285 }
1286 }
1287 if version_stage == "AWSCURRENT" {
1289 secret.current_version_id = Some(move_vid.clone());
1290 }
1291 }
1292
1293 let response = json!({
1294 "ARN": secret.arn,
1295 "Name": secret.name,
1296 });
1297
1298 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1299 }
1300
1301 fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1302 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1303 validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1304 let secret_id_list = body["SecretIdList"].as_array();
1305 let filters = body["Filters"].as_array();
1306 let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1307
1308 if secret_id_list.is_some() && filters.is_some() {
1310 return Err(AwsServiceError::aws_error(
1311 StatusCode::BAD_REQUEST,
1312 "InvalidParameterException",
1313 "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1314 ));
1315 }
1316
1317 if max_results.is_some() && filters.is_none() {
1319 return Err(AwsServiceError::aws_error(
1320 StatusCode::BAD_REQUEST,
1321 "InvalidParameterException",
1322 "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1323 ));
1324 }
1325
1326 let state = self.state.read();
1327 let mut secret_values: Vec<Value> = Vec::new();
1328 let mut errors: Vec<Value> = Vec::new();
1329
1330 if let Some(id_list) = secret_id_list {
1331 for id_val in id_list {
1332 let sid = id_val.as_str().unwrap_or("");
1333 match self.find_secret_ref(&state, sid) {
1334 Ok(secret) => {
1335 if secret.deleted {
1336 errors.push(json!({
1337 "SecretId": sid,
1338 "ErrorCode": "InvalidRequestException",
1339 "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1340 }));
1341 } else if let Some(ref current_vid) = secret.current_version_id {
1342 if let Some(version) = secret.versions.get(current_vid) {
1343 let mut entry = json!({
1344 "ARN": secret.arn,
1345 "Name": secret.name,
1346 "VersionId": version.version_id,
1347 "VersionStages": version.stages,
1348 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1349 });
1350 if let Some(ref s) = version.secret_string {
1351 entry["SecretString"] = json!(s);
1352 }
1353 if let Some(ref b) = version.secret_binary {
1354 entry["SecretBinary"] = json!(base64_encode(b));
1355 }
1356 secret_values.push(entry);
1357 } else {
1358 errors.push(json!({
1359 "SecretId": sid,
1360 "ErrorCode": "ResourceNotFoundException",
1361 "Message": "Secrets Manager can't find the specified secret.",
1362 }));
1363 }
1364 } else {
1365 errors.push(json!({
1366 "SecretId": sid,
1367 "ErrorCode": "ResourceNotFoundException",
1368 "Message": "Secrets Manager can't find the specified secret.",
1369 }));
1370 }
1371 }
1372 Err(_) => {
1373 errors.push(json!({
1374 "SecretId": sid,
1375 "ErrorCode": "ResourceNotFoundException",
1376 "Message": "Secrets Manager can't find the specified secret.",
1377 }));
1378 }
1379 }
1380 }
1381 } else if let Some(filters) = filters {
1382 let matching: Vec<&Secret> = state
1384 .secrets
1385 .values()
1386 .filter(|s| {
1387 if s.deleted {
1388 return false;
1389 }
1390 for filter in filters {
1391 let key = filter["Key"].as_str().unwrap_or("");
1392 let values: Vec<&str> = filter["Values"]
1393 .as_array()
1394 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1395 .unwrap_or_default();
1396 let matches = match key {
1397 "name" => filter_name(s, &values),
1398 "description" => filter_description(s, &values),
1399 "tag-key" => filter_tag_key(s, &values),
1400 "tag-value" => filter_tag_value(s, &values),
1401 "all" => filter_all(s, &values),
1402 _ => true,
1403 };
1404 if !matches {
1405 return false;
1406 }
1407 }
1408 true
1409 })
1410 .collect();
1411
1412 let limit = max_results.unwrap_or(100) as usize;
1413 let mut no_value_found = false;
1414 let mut matching = matching;
1415 matching.sort_by(|a, b| a.name.cmp(&b.name));
1416
1417 for secret in matching.iter().take(limit) {
1418 if let Some(ref current_vid) = secret.current_version_id {
1419 if let Some(version) = secret.versions.get(current_vid) {
1420 let mut entry = json!({
1421 "ARN": secret.arn,
1422 "Name": secret.name,
1423 "VersionId": version.version_id,
1424 "VersionStages": version.stages,
1425 "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1426 });
1427 if let Some(ref s) = version.secret_string {
1428 entry["SecretString"] = json!(s);
1429 }
1430 if let Some(ref b) = version.secret_binary {
1431 entry["SecretBinary"] = json!(base64_encode(b));
1432 }
1433 secret_values.push(entry);
1434 } else {
1435 no_value_found = true;
1436 }
1437 } else {
1438 no_value_found = true;
1439 }
1440 }
1441
1442 if no_value_found && secret_values.is_empty() {
1443 return Err(AwsServiceError::aws_error(
1444 StatusCode::NOT_FOUND,
1445 "ResourceNotFoundException",
1446 "Secrets Manager can't find the specified secret.",
1447 ));
1448 }
1449 }
1450
1451 let mut response = json!({
1452 "SecretValues": secret_values,
1453 "Errors": errors,
1454 });
1455
1456 if errors.is_empty() {
1458 response.as_object_mut().unwrap().remove("Errors");
1459 }
1460
1461 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1462 }
1463
1464 fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1465 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1466 let secret_id = require_secret_id(&body)?;
1467
1468 let state = self.state.read();
1469 let secret = self.find_secret_ref(&state, &secret_id)?;
1470
1471 let mut response = json!({
1472 "ARN": secret.arn,
1473 "Name": secret.name,
1474 });
1475
1476 if let Some(ref policy) = secret.resource_policy {
1477 response["ResourcePolicy"] = json!(policy);
1478 }
1479
1480 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1481 }
1482
1483 fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1484 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1485 validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1486 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1487 let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1488 AwsServiceError::aws_error(
1489 StatusCode::BAD_REQUEST,
1490 "InvalidParameterException",
1491 "ResourcePolicy must be a string",
1492 )
1493 })?;
1494 validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1495
1496 if let Some(secret_id) = body["SecretId"].as_str() {
1498 let state = self.state.read();
1499 self.find_secret_key(&state, secret_id)?;
1500 }
1501
1502 let response = json!({
1503 "PolicyValidationPassed": true,
1504 "ValidationErrors": [],
1505 });
1506 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1507 }
1508
1509 fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1510 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1511 let secret_id = require_secret_id(&body)?;
1512 validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1513 validate_optional_string_length(
1514 "resourcePolicy",
1515 body["ResourcePolicy"].as_str(),
1516 1,
1517 20480,
1518 )?;
1519 let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1520
1521 let mut state = self.state.write();
1522 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1523 secret.resource_policy = policy;
1524
1525 let response = json!({
1526 "ARN": secret.arn,
1527 "Name": secret.name,
1528 });
1529
1530 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1531 }
1532
1533 fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1534 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1535 let secret_id = require_secret_id(&body)?;
1536
1537 let mut state = self.state.write();
1538 let secret = self.find_secret_mut(&mut state, &secret_id)?;
1539 secret.resource_policy = None;
1540
1541 let response = json!({
1542 "ARN": secret.arn,
1543 "Name": secret.name,
1544 });
1545
1546 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1547 }
1548
1549 fn replicate_secret_to_regions(
1550 &self,
1551 req: &AwsRequest,
1552 ) -> Result<AwsResponse, AwsServiceError> {
1553 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1554 let secret_id = require_secret_id(&body)?;
1555
1556 let state = self.state.read();
1557 let secret = self.find_secret_ref(&state, &secret_id)?;
1558
1559 let response = json!({
1560 "ARN": secret.arn,
1561 "ReplicationStatus": [],
1562 });
1563 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1564 }
1565
1566 fn remove_regions_from_replication(
1567 &self,
1568 req: &AwsRequest,
1569 ) -> Result<AwsResponse, AwsServiceError> {
1570 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1571 let secret_id = require_secret_id(&body)?;
1572
1573 let state = self.state.read();
1574 let secret = self.find_secret_ref(&state, &secret_id)?;
1575
1576 let response = json!({
1577 "ARN": secret.arn,
1578 "ReplicationStatus": [],
1579 });
1580 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1581 }
1582
1583 fn stop_replication_to_replica(
1584 &self,
1585 req: &AwsRequest,
1586 ) -> Result<AwsResponse, AwsServiceError> {
1587 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1588 let secret_id = require_secret_id(&body)?;
1589
1590 let state = self.state.read();
1591 let secret = self.find_secret_ref(&state, &secret_id)?;
1592
1593 let response = json!({
1594 "ARN": secret.arn,
1595 });
1596 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1597 }
1598
1599 fn find_secret_mut<'a>(
1601 &self,
1602 state: &'a mut crate::state::SecretsManagerState,
1603 secret_id: &str,
1604 ) -> Result<&'a mut Secret, AwsServiceError> {
1605 let key = self.find_secret_key(state, secret_id)?;
1606 Ok(state.secrets.get_mut(&key).unwrap())
1607 }
1608
1609 fn find_secret_key(
1610 &self,
1611 state: &crate::state::SecretsManagerState,
1612 secret_id: &str,
1613 ) -> Result<String, AwsServiceError> {
1614 if state.secrets.contains_key(secret_id) {
1615 return Ok(secret_id.to_string());
1616 }
1617
1618 for secret in state.secrets.values() {
1619 if secret.arn == secret_id {
1620 return Ok(secret.name.clone());
1621 }
1622 }
1623
1624 if secret_id.starts_with("arn:aws:secretsmanager:") {
1625 for secret in state.secrets.values() {
1626 if secret.arn.starts_with(secret_id) {
1627 return Ok(secret.name.clone());
1628 }
1629 }
1630 }
1631
1632 Err(AwsServiceError::aws_error(
1633 StatusCode::NOT_FOUND,
1634 "ResourceNotFoundException",
1635 "Secrets Manager can't find the specified secret.",
1636 ))
1637 }
1638
1639 fn find_secret_ref<'a>(
1641 &self,
1642 state: &'a crate::state::SecretsManagerState,
1643 secret_id: &str,
1644 ) -> Result<&'a Secret, AwsServiceError> {
1645 if let Some(secret) = state.secrets.get(secret_id) {
1646 return Ok(secret);
1647 }
1648
1649 for secret in state.secrets.values() {
1651 if secret.arn == secret_id {
1652 return Ok(secret);
1653 }
1654 }
1655
1656 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);
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
1673fn require_secret_id(body: &Value) -> Result<String, AwsServiceError> {
1674 let id = body["SecretId"].as_str().ok_or_else(|| {
1675 AwsServiceError::aws_error(
1676 StatusCode::BAD_REQUEST,
1677 "InvalidParameterException",
1678 "SecretId is required",
1679 )
1680 })?;
1681 validate_string_length("secretId", id, 1, 2048)?;
1682 Ok(id.to_string())
1683}
1684
1685fn parse_tags(tags_val: &Value) -> Vec<(String, String)> {
1686 tags_val
1687 .as_array()
1688 .map(|arr| {
1689 arr.iter()
1690 .filter_map(|t| {
1691 let key = t["Key"].as_str()?;
1692 let value = t["Value"].as_str()?;
1693 Some((key.to_string(), value.to_string()))
1694 })
1695 .collect()
1696 })
1697 .unwrap_or_default()
1698}
1699
1700fn tags_to_json(tags: &[(String, String)]) -> Vec<Value> {
1701 tags.iter()
1702 .map(|(k, v)| json!({"Key": k, "Value": v}))
1703 .collect()
1704}
1705
1706fn split_words(text: &str) -> Vec<String> {
1711 let mut all_words = Vec::new();
1713 for space_part in text.split_whitespace() {
1714 all_words.extend(split_words_no_space(space_part));
1715 }
1716 all_words
1717}
1718
1719fn split_words_no_space(text: &str) -> Vec<String> {
1720 let special_chars = ['/', '-', '_', '+', '=', '.', '@'];
1721
1722 if text.len() == 1 && special_chars.contains(&text.chars().next().unwrap_or(' ')) {
1724 return vec![];
1725 }
1726
1727 let present: Vec<char> = special_chars
1729 .iter()
1730 .filter(|&&c| text.contains(c))
1731 .copied()
1732 .collect();
1733
1734 if present.len() > 1 {
1735 return vec![text.to_string()];
1737 }
1738
1739 if present.len() == 1 {
1740 let ch = present[0];
1741 let parts: Vec<&str> = text.split(ch).filter(|s| !s.is_empty()).collect();
1742 let mut result = Vec::new();
1743 for part in parts {
1744 result.extend(split_by_uppercase(part));
1745 }
1746 return result;
1747 }
1748
1749 split_by_uppercase(text)
1751}
1752
1753fn split_by_uppercase(text: &str) -> Vec<String> {
1756 let chars: Vec<char> = text.chars().collect();
1759 let mut words = Vec::new();
1760 let mut last_end = 0;
1761 let mut i = 0;
1762
1763 while i < chars.len() {
1764 if !chars[i].is_ascii_lowercase()
1766 && i + 1 < chars.len()
1767 && chars[i + 1].is_ascii_lowercase()
1768 {
1769 if i > last_end {
1771 let between: String = chars[last_end..i].iter().collect();
1772 let trimmed = between.trim().to_string();
1773 if !trimmed.is_empty() {
1774 words.push(trimmed);
1775 }
1776 }
1777
1778 let start = i;
1780 i += 2;
1781 while i < chars.len() && chars[i].is_ascii_lowercase() {
1782 i += 1;
1783 }
1784 let word: String = chars[start..i].iter().collect();
1785 let trimmed = word.trim().to_string();
1786 if !trimmed.is_empty() {
1787 words.push(trimmed);
1788 }
1789 last_end = i;
1790 } else {
1791 i += 1;
1792 }
1793 }
1794
1795 if last_end < chars.len() {
1797 let after: String = chars[last_end..].iter().collect();
1798 let trimmed = after.trim().to_string();
1799 if !trimmed.is_empty() {
1800 words.push(trimmed);
1801 }
1802 }
1803
1804 words
1805}
1806
1807fn match_pattern(pattern: &str, value: &str, match_prefix: bool, case_sensitive: bool) -> bool {
1811 if match_prefix {
1812 if case_sensitive {
1813 value.starts_with(pattern)
1814 } else {
1815 value.to_lowercase().starts_with(&pattern.to_lowercase())
1816 }
1817 } else {
1818 let mut pattern_words = split_words(pattern);
1819 if pattern_words.is_empty() {
1820 return false;
1821 }
1822 let mut value_words = split_words(value);
1823 if !case_sensitive {
1824 pattern_words = pattern_words.iter().map(|w| w.to_lowercase()).collect();
1825 value_words = value_words.iter().map(|w| w.to_lowercase()).collect();
1826 }
1827 for pw in &pattern_words {
1828 if !value_words.iter().any(|vw| vw.starts_with(pw.as_str())) {
1829 return false;
1830 }
1831 }
1832 true
1833 }
1834}
1835
1836fn matcher(patterns: &[&str], strings: &[&str], match_prefix: bool, case_sensitive: bool) -> bool {
1839 for pattern in patterns.iter().filter(|p| p.starts_with('!')) {
1841 let inner = &pattern[1..];
1842 for s in strings {
1843 if !match_pattern(inner, s, match_prefix, case_sensitive) {
1844 return true;
1845 }
1846 }
1847 }
1848
1849 for pattern in patterns.iter().filter(|p| !p.starts_with('!')) {
1851 for s in strings {
1852 if match_pattern(pattern, s, match_prefix, case_sensitive) {
1853 return true;
1854 }
1855 }
1856 }
1857 false
1858}
1859
1860fn filter_name(secret: &Secret, values: &[&str]) -> bool {
1862 matcher(values, &[secret.name.as_str()], true, true)
1863}
1864
1865fn filter_description(secret: &Secret, values: &[&str]) -> bool {
1867 match secret.description.as_deref() {
1868 Some(desc) if !desc.is_empty() => matcher(values, &[desc], false, false),
1869 _ => false,
1870 }
1871}
1872
1873fn filter_tag_key(secret: &Secret, values: &[&str]) -> bool {
1875 if secret.tags.is_empty() {
1876 return false;
1877 }
1878 let keys: Vec<&str> = secret.tags.iter().map(|(k, _)| k.as_str()).collect();
1879 matcher(values, &keys, true, true)
1880}
1881
1882fn filter_tag_value(secret: &Secret, values: &[&str]) -> bool {
1884 if secret.tags.is_empty() {
1885 return false;
1886 }
1887 let vals: Vec<&str> = secret.tags.iter().map(|(_, v)| v.as_str()).collect();
1888 matcher(values, &vals, true, true)
1889}
1890
1891fn filter_all(secret: &Secret, values: &[&str]) -> bool {
1893 let mut attributes: Vec<&str> = vec![secret.name.as_str()];
1894 if let Some(ref desc) = secret.description {
1895 if !desc.is_empty() {
1896 attributes.push(desc.as_str());
1897 }
1898 }
1899 for (k, v) in &secret.tags {
1900 attributes.push(k.as_str());
1901 attributes.push(v.as_str());
1902 }
1903 matcher(values, &attributes, false, false)
1904}
1905
1906fn simple_random() -> usize {
1907 use std::collections::hash_map::RandomState;
1908 use std::hash::{BuildHasher, Hasher};
1909 let s = RandomState::new();
1910 let mut hasher = s.build_hasher();
1911 hasher.write_usize(0);
1912 hasher.finish() as usize
1913}
1914
1915#[async_trait]
1916impl AwsService for SecretsManagerService {
1917 fn service_name(&self) -> &str {
1918 "secretsmanager"
1919 }
1920
1921 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1922 match req.action.as_str() {
1923 "CreateSecret" => self.create_secret(&req),
1924 "GetSecretValue" => self.get_secret_value(&req),
1925 "PutSecretValue" => self.put_secret_value(&req),
1926 "UpdateSecret" => self.update_secret(&req),
1927 "DeleteSecret" => self.delete_secret(&req),
1928 "RestoreSecret" => self.restore_secret(&req),
1929 "DescribeSecret" => self.describe_secret(&req),
1930 "ListSecrets" => self.list_secrets(&req),
1931 "TagResource" => self.tag_resource(&req),
1932 "UntagResource" => self.untag_resource(&req),
1933 "ListSecretVersionIds" => self.list_secret_version_ids(&req),
1934 "GetRandomPassword" => self.get_random_password(&req),
1935 "RotateSecret" => self.rotate_secret(&req),
1936 "CancelRotateSecret" => self.cancel_rotate_secret(&req),
1937 "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
1938 "BatchGetSecretValue" => self.batch_get_secret_value(&req),
1939 "GetResourcePolicy" => self.get_resource_policy(&req),
1940 "PutResourcePolicy" => self.put_resource_policy(&req),
1941 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
1942 "ValidateResourcePolicy" => self.validate_resource_policy(&req),
1943 "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
1944 "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
1945 "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
1946 _ => Err(AwsServiceError::action_not_implemented(
1947 "secretsmanager",
1948 &req.action,
1949 )),
1950 }
1951 }
1952
1953 fn supported_actions(&self) -> &[&str] {
1954 &[
1955 "CreateSecret",
1956 "GetSecretValue",
1957 "PutSecretValue",
1958 "UpdateSecret",
1959 "DeleteSecret",
1960 "RestoreSecret",
1961 "DescribeSecret",
1962 "ListSecrets",
1963 "TagResource",
1964 "UntagResource",
1965 "ListSecretVersionIds",
1966 "GetRandomPassword",
1967 "RotateSecret",
1968 "CancelRotateSecret",
1969 "UpdateSecretVersionStage",
1970 "BatchGetSecretValue",
1971 "GetResourcePolicy",
1972 "PutResourcePolicy",
1973 "DeleteResourcePolicy",
1974 "ValidateResourcePolicy",
1975 "ReplicateSecretToRegions",
1976 "RemoveRegionsFromReplication",
1977 "StopReplicationToReplica",
1978 ]
1979 }
1980}
1981
1982fn base64_decode(input: &str) -> Option<Vec<u8>> {
1983 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1984 let mut buf = Vec::new();
1985 let mut bits: u32 = 0;
1986 let mut count = 0;
1987 for &b in input.as_bytes() {
1988 if b == b'=' || b == b'\n' || b == b'\r' {
1989 continue;
1990 }
1991 let val = table.iter().position(|&c| c == b)? as u32;
1992 bits = (bits << 6) | val;
1993 count += 1;
1994 if count == 4 {
1995 buf.push((bits >> 16) as u8);
1996 buf.push((bits >> 8) as u8);
1997 buf.push(bits as u8);
1998 bits = 0;
1999 count = 0;
2000 }
2001 }
2002 match count {
2003 2 => {
2004 bits <<= 12;
2005 buf.push((bits >> 16) as u8);
2006 }
2007 3 => {
2008 bits <<= 6;
2009 buf.push((bits >> 16) as u8);
2010 buf.push((bits >> 8) as u8);
2011 }
2012 _ => {}
2013 }
2014 Some(buf)
2015}
2016
2017fn base64_encode(input: &[u8]) -> String {
2018 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2019 let mut result = String::new();
2020 for chunk in input.chunks(3) {
2021 let b0 = chunk[0] as u32;
2022 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
2023 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
2024 let triple = (b0 << 16) | (b1 << 8) | b2;
2025 result.push(table[((triple >> 18) & 0x3F) as usize] as char);
2026 result.push(table[((triple >> 12) & 0x3F) as usize] as char);
2027 if chunk.len() > 1 {
2028 result.push(table[((triple >> 6) & 0x3F) as usize] as char);
2029 } else {
2030 result.push('=');
2031 }
2032 if chunk.len() > 2 {
2033 result.push(table[(triple & 0x3F) as usize] as char);
2034 } else {
2035 result.push('=');
2036 }
2037 }
2038 result
2039}
2040
2041#[cfg(test)]
2042mod tests {
2043 use super::*;
2044 use crate::state::SecretsManagerState;
2045 use bytes::Bytes;
2046 use http::{HeaderMap, Method};
2047 use parking_lot::RwLock;
2048 use std::collections::HashMap;
2049 use std::sync::Arc;
2050
2051 fn make_state() -> SharedSecretsManagerState {
2052 Arc::new(RwLock::new(SecretsManagerState::new(
2053 "123456789012",
2054 "us-east-1",
2055 )))
2056 }
2057
2058 fn make_request(action: &str, body: &str) -> AwsRequest {
2059 AwsRequest {
2060 service: "secretsmanager".to_string(),
2061 action: action.to_string(),
2062 region: "us-east-1".to_string(),
2063 account_id: "123456789012".to_string(),
2064 request_id: "test-request-id".to_string(),
2065 headers: HeaderMap::new(),
2066 query_params: HashMap::new(),
2067 body: Bytes::from(body.to_string()),
2068 path_segments: vec![],
2069 raw_path: "/".to_string(),
2070 method: Method::POST,
2071 is_query_protocol: false,
2072 access_key_id: None,
2073 }
2074 }
2075
2076 #[tokio::test]
2077 async fn test_create_and_get_secret() {
2078 let state = make_state();
2079 let svc = SecretsManagerService::new(state);
2080
2081 let req = make_request(
2082 "CreateSecret",
2083 r#"{"Name": "test/secret", "SecretString": "mysecretvalue"}"#,
2084 );
2085 let resp = svc.handle(req).await.unwrap();
2086 assert_eq!(resp.status, StatusCode::OK);
2087 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2088 assert_eq!(body["Name"], "test/secret");
2089 assert!(body["ARN"].as_str().unwrap().contains("test/secret"));
2090
2091 let req = make_request("GetSecretValue", r#"{"SecretId": "test/secret"}"#);
2092 let resp = svc.handle(req).await.unwrap();
2093 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2094 assert_eq!(body["SecretString"], "mysecretvalue");
2095 }
2096
2097 #[tokio::test]
2098 async fn test_create_secret_without_value() {
2099 let state = make_state();
2100 let svc = SecretsManagerService::new(state);
2101
2102 let req = make_request("CreateSecret", r#"{"Name": "empty-secret"}"#);
2103 let resp = svc.handle(req).await.unwrap();
2104 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2105 assert_eq!(body["Name"], "empty-secret");
2106 assert!(body.get("VersionId").is_none());
2107 }
2108
2109 #[tokio::test]
2110 async fn test_put_secret_value_creates_version() {
2111 let state = make_state();
2112 let svc = SecretsManagerService::new(state);
2113
2114 let req = make_request(
2115 "CreateSecret",
2116 r#"{"Name": "versioned", "SecretString": "v1"}"#,
2117 );
2118 svc.handle(req).await.unwrap();
2119
2120 let req = make_request(
2121 "PutSecretValue",
2122 r#"{"SecretId": "versioned", "SecretString": "v2"}"#,
2123 );
2124 let resp = svc.handle(req).await.unwrap();
2125 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2126 assert_eq!(body["Name"], "versioned");
2127
2128 let req = make_request("GetSecretValue", r#"{"SecretId": "versioned"}"#);
2130 let resp = svc.handle(req).await.unwrap();
2131 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2132 assert_eq!(body["SecretString"], "v2");
2133 }
2134
2135 #[tokio::test]
2136 async fn test_delete_and_restore_secret() {
2137 let state = make_state();
2138 let svc = SecretsManagerService::new(state);
2139
2140 let req = make_request(
2141 "CreateSecret",
2142 r#"{"Name": "deleteme", "SecretString": "value"}"#,
2143 );
2144 svc.handle(req).await.unwrap();
2145
2146 let req = make_request("DeleteSecret", r#"{"SecretId": "deleteme"}"#);
2148 let resp = svc.handle(req).await.unwrap();
2149 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2150 assert!(body["DeletionDate"].as_f64().is_some());
2151
2152 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2154 assert!(svc.handle(req).await.is_err());
2155
2156 let req = make_request("RestoreSecret", r#"{"SecretId": "deleteme"}"#);
2158 let resp = svc.handle(req).await.unwrap();
2159 assert_eq!(resp.status, StatusCode::OK);
2160
2161 let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2163 let resp = svc.handle(req).await.unwrap();
2164 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2165 assert_eq!(body["SecretString"], "value");
2166 }
2167
2168 #[tokio::test]
2169 async fn test_list_secrets() {
2170 let state = make_state();
2171 let svc = SecretsManagerService::new(state);
2172
2173 for name in &["alpha", "beta", "gamma"] {
2174 let req = make_request(
2175 "CreateSecret",
2176 &format!(r#"{{"Name": "{name}", "SecretString": "val"}}"#),
2177 );
2178 svc.handle(req).await.unwrap();
2179 }
2180
2181 let req = make_request("ListSecrets", "{}");
2182 let resp = svc.handle(req).await.unwrap();
2183 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2184 assert_eq!(body["SecretList"].as_array().unwrap().len(), 3);
2185 }
2186
2187 #[tokio::test]
2188 async fn test_tags() {
2189 let state = make_state();
2190 let svc = SecretsManagerService::new(state);
2191
2192 let req = make_request(
2193 "CreateSecret",
2194 r#"{"Name": "tagged", "SecretString": "val"}"#,
2195 );
2196 svc.handle(req).await.unwrap();
2197
2198 let req = make_request(
2199 "TagResource",
2200 r#"{"SecretId": "tagged", "Tags": [{"Key": "env", "Value": "prod"}]}"#,
2201 );
2202 svc.handle(req).await.unwrap();
2203
2204 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2205 let resp = svc.handle(req).await.unwrap();
2206 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2207 let tags = body["Tags"].as_array().unwrap();
2208 assert!(tags
2209 .iter()
2210 .any(|t| t["Key"] == "env" && t["Value"] == "prod"));
2211
2212 let req = make_request(
2213 "UntagResource",
2214 r#"{"SecretId": "tagged", "TagKeys": ["env"]}"#,
2215 );
2216 svc.handle(req).await.unwrap();
2217
2218 let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2219 let resp = svc.handle(req).await.unwrap();
2220 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2221 assert_eq!(body["Tags"].as_array().unwrap().len(), 0);
2223 }
2224
2225 #[tokio::test]
2226 async fn test_get_random_password() {
2227 let state = make_state();
2228 let svc = SecretsManagerService::new(state);
2229
2230 let req = make_request("GetRandomPassword", "{}");
2231 let resp = svc.handle(req).await.unwrap();
2232 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2233 assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 32);
2234 }
2235
2236 #[tokio::test]
2237 async fn test_replication_ops_return_arn() {
2238 let state = make_state();
2239 let svc = SecretsManagerService::new(state);
2240
2241 let req = make_request(
2242 "CreateSecret",
2243 r#"{"Name": "repl-secret", "SecretString": "val"}"#,
2244 );
2245 let resp = svc.handle(req).await.unwrap();
2246 let create_body: Value = serde_json::from_slice(&resp.body).unwrap();
2247 let expected_arn = create_body["ARN"].as_str().unwrap();
2248
2249 for action in &[
2250 "ReplicateSecretToRegions",
2251 "RemoveRegionsFromReplication",
2252 "StopReplicationToReplica",
2253 ] {
2254 let req = make_request(action, r#"{"SecretId": "repl-secret"}"#);
2255 let resp = svc.handle(req).await.unwrap();
2256 let body: Value = serde_json::from_slice(&resp.body).unwrap();
2257 assert_eq!(
2258 body["ARN"].as_str().unwrap(),
2259 expected_arn,
2260 "{action} should return the secret's actual ARN"
2261 );
2262 }
2263 }
2264
2265 #[tokio::test]
2266 async fn test_secret_id_length_validation() {
2267 let state = make_state();
2268 let svc = SecretsManagerService::new(state);
2269
2270 let long_id = "x".repeat(2049);
2272 let req = make_request("GetSecretValue", &format!(r#"{{"SecretId": "{long_id}"}}"#));
2273 match svc.handle(req).await {
2274 Err(e) => assert!(e.to_string().contains("ValidationException")),
2275 Ok(_) => panic!("expected ValidationException"),
2276 }
2277 }
2278
2279 #[tokio::test]
2280 async fn test_name_length_validation() {
2281 let state = make_state();
2282 let svc = SecretsManagerService::new(state);
2283
2284 let long_name = "x".repeat(513);
2286 let req = make_request(
2287 "CreateSecret",
2288 &format!(r#"{{"Name": "{long_name}", "SecretString": "val"}}"#),
2289 );
2290 match svc.handle(req).await {
2291 Err(e) => assert!(e.to_string().contains("ValidationException")),
2292 Ok(_) => panic!("expected ValidationException"),
2293 }
2294 }
2295
2296 #[tokio::test]
2297 async fn test_next_token_length_validation() {
2298 let state = make_state();
2299 let svc = SecretsManagerService::new(state);
2300
2301 let long_token = "x".repeat(4097);
2303 let req = make_request(
2304 "ListSecrets",
2305 &format!(r#"{{"NextToken": "{long_token}"}}"#),
2306 );
2307 match svc.handle(req).await {
2308 Err(e) => assert!(e.to_string().contains("ValidationException")),
2309 Ok(_) => panic!("expected ValidationException"),
2310 }
2311 }
2312
2313 #[tokio::test]
2314 async fn test_client_request_token_length_validation() {
2315 let state = make_state();
2316 let svc = SecretsManagerService::new(state);
2317
2318 let req = make_request(
2320 "CreateSecret",
2321 r#"{"Name": "test", "SecretString": "val", "ClientRequestToken": "short"}"#,
2322 );
2323 match svc.handle(req).await {
2324 Err(e) => assert!(e.to_string().contains("ValidationException")),
2325 Ok(_) => panic!("expected ValidationException"),
2326 }
2327 }
2328}