1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use chrono::Utc;
6use http::{Method, StatusCode};
7use serde_json::{json, Value};
8use sha2::{Digest, Sha256};
9use tokio::sync::Mutex as AsyncMutex;
10
11use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
12use fakecloud_persistence::SnapshotStore;
13
14use crate::runtime::ContainerRuntime;
15use crate::state::{
16 EventSourceMapping, LambdaFunction, LambdaSnapshot, LambdaState, SharedLambdaState,
17 LAMBDA_SNAPSHOT_SCHEMA_VERSION,
18};
19
20fn invalid_param(msg: impl Into<String>) -> AwsServiceError {
21 AwsServiceError::aws_error(
22 StatusCode::BAD_REQUEST,
23 "InvalidParameterValueException",
24 msg,
25 )
26}
27
28fn check_len(field: &str, v: &str, min: usize, max: usize) -> Result<(), AwsServiceError> {
29 if v.len() < min || v.len() > max {
30 return Err(invalid_param(format!(
31 "{field} length must be in [{min},{max}], got {}",
32 v.len()
33 )));
34 }
35 Ok(())
36}
37
38fn check_optional_len(
39 field: &str,
40 v: Option<&str>,
41 min: usize,
42 max: usize,
43) -> Result<(), AwsServiceError> {
44 if let Some(s) = v {
45 check_len(field, s, min, max)?;
46 }
47 Ok(())
48}
49
50fn check_optional_int_range(
51 field: &str,
52 v: Option<i64>,
53 min: i64,
54 max: i64,
55) -> Result<(), AwsServiceError> {
56 if let Some(n) = v {
57 if n < min || n > max {
58 return Err(invalid_param(format!(
59 "{field} must be in [{min},{max}], got {n}"
60 )));
61 }
62 }
63 Ok(())
64}
65
66const LAMBDA_PUBLISH_TO_VALUES: &[&str] = &["LATEST_PUBLISHED"];
67
68const LAMBDA_RUNTIMES: &[&str] = &[
74 "nodejs",
75 "nodejs4.3",
76 "nodejs4.3-edge",
77 "nodejs6.10",
78 "nodejs8.10",
79 "nodejs10.x",
80 "nodejs12.x",
81 "nodejs14.x",
82 "nodejs16.x",
83 "nodejs18.x",
84 "nodejs20.x",
85 "nodejs22.x",
86 "nodejs24.x",
87 "java8",
88 "java8.al2",
89 "java11",
90 "java17",
91 "java21",
92 "java25",
93 "python2.7",
94 "python3.6",
95 "python3.7",
96 "python3.8",
97 "python3.9",
98 "python3.10",
99 "python3.11",
100 "python3.12",
101 "python3.13",
102 "python3.14",
103 "dotnetcore1.0",
104 "dotnetcore2.0",
105 "dotnetcore2.1",
106 "dotnetcore3.1",
107 "dotnet6",
108 "dotnet8",
109 "dotnet10",
110 "go1.x",
111 "ruby2.5",
112 "ruby2.7",
113 "ruby3.2",
114 "ruby3.3",
115 "ruby3.4",
116 "provided",
117 "provided.al2",
118 "provided.al2023",
119];
120
121fn check_optional_enum(
122 field: &str,
123 v: Option<&str>,
124 allowed: &[&str],
125) -> Result<(), AwsServiceError> {
126 if let Some(s) = v {
127 if !allowed.contains(&s) {
128 return Err(invalid_param(format!(
129 "{field} must be one of the enum values, got '{s}'"
130 )));
131 }
132 }
133 Ok(())
134}
135
136fn prevalidate_lambda(action: &str, req: &AwsRequest) -> Result<(), AwsServiceError> {
137 let body: Value = serde_json::from_slice(&req.body).unwrap_or(Value::Null);
138 match action {
139 "PublishVersion" => {
140 check_optional_len("Description", body["Description"].as_str(), 0, 256)?;
141 check_optional_enum(
142 "PublishTo",
143 body["PublishTo"].as_str(),
144 LAMBDA_PUBLISH_TO_VALUES,
145 )?;
146 }
147 "UpdateFunctionCode" => {
148 check_optional_enum(
149 "PublishTo",
150 body["PublishTo"].as_str(),
151 LAMBDA_PUBLISH_TO_VALUES,
152 )?;
153 check_optional_len("S3Bucket", body["S3Bucket"].as_str(), 3, 63)?;
154 check_optional_len("S3Key", body["S3Key"].as_str(), 1, 1024)?;
155 check_optional_len("S3ObjectVersion", body["S3ObjectVersion"].as_str(), 1, 1024)?;
156 }
157 "UpdateFunctionConfiguration" => {
158 check_optional_len("Description", body["Description"].as_str(), 0, 256)?;
159 check_optional_len("Handler", body["Handler"].as_str(), 0, 128)?;
160 check_optional_int_range("MemorySize", body["MemorySize"].as_i64(), 128, 32768)?;
161 check_optional_int_range("Timeout", body["Timeout"].as_i64(), 1, i64::MAX)?;
162 check_optional_enum("Runtime", body["Runtime"].as_str(), LAMBDA_RUNTIMES)?;
163 }
164 _ => {}
165 }
166 Ok(())
167}
168
169pub(crate) fn action_takes_function_name(action: &str) -> bool {
174 matches!(
175 action,
176 "GetFunction"
177 | "DeleteFunction"
178 | "Invoke"
179 | "InvokeAsync"
180 | "InvokeWithResponseStream"
181 | "PublishVersion"
182 | "ListVersionsByFunction"
183 | "AddPermission"
184 | "RemovePermission"
185 | "GetPolicy"
186 | "GetFunctionConfiguration"
187 | "UpdateFunctionConfiguration"
188 | "UpdateFunctionCode"
189 | "GetFunctionConcurrency"
190 | "PutFunctionConcurrency"
191 | "DeleteFunctionConcurrency"
192 | "PutProvisionedConcurrencyConfig"
193 | "GetProvisionedConcurrencyConfig"
194 | "DeleteProvisionedConcurrencyConfig"
195 | "ListProvisionedConcurrencyConfigs"
196 | "PutFunctionEventInvokeConfig"
197 | "UpdateFunctionEventInvokeConfig"
198 | "GetFunctionEventInvokeConfig"
199 | "DeleteFunctionEventInvokeConfig"
200 | "ListFunctionEventInvokeConfigs"
201 | "CreateFunctionUrlConfig"
202 | "UpdateFunctionUrlConfig"
203 | "GetFunctionUrlConfig"
204 | "DeleteFunctionUrlConfig"
205 | "ListFunctionUrlConfigs"
206 | "PutFunctionCodeSigningConfig"
207 | "GetFunctionCodeSigningConfig"
208 | "DeleteFunctionCodeSigningConfig"
209 | "GetFunctionScalingConfig"
210 | "PutFunctionScalingConfig"
211 | "PutFunctionRecursionConfig"
212 | "GetFunctionRecursionConfig"
213 | "CreateAlias"
214 | "GetAlias"
215 | "ListAliases"
216 | "UpdateAlias"
217 | "DeleteAlias"
218 | "PutRuntimeManagementConfig"
219 | "GetRuntimeManagementConfig"
220 )
221}
222
223pub(crate) fn iam_action_name_for(op: &str) -> Option<&'static str> {
253 let action = match op {
254 "Invoke" => "InvokeFunction",
256 "InvokeWithResponseStream" => "InvokeFunction",
257 "GetLayerVersionByArn" => "GetLayerVersion",
258
259 "CreateFunction" => "CreateFunction",
261 "ListFunctions" => "ListFunctions",
262 "GetFunction" => "GetFunction",
263 "DeleteFunction" => "DeleteFunction",
264 "InvokeAsync" => "InvokeAsync",
265 "UpdateFunctionCode" => "UpdateFunctionCode",
266 "UpdateFunctionConfiguration" => "UpdateFunctionConfiguration",
267 "GetFunctionConfiguration" => "GetFunctionConfiguration",
268 "PublishVersion" => "PublishVersion",
269 "ListVersionsByFunction" => "ListVersionsByFunction",
270 "GetAccountSettings" => "GetAccountSettings",
271
272 "AddPermission" => "AddPermission",
274 "RemovePermission" => "RemovePermission",
275 "GetPolicy" => "GetPolicy",
276
277 "CreateAlias" => "CreateAlias",
279 "GetAlias" => "GetAlias",
280 "UpdateAlias" => "UpdateAlias",
281 "DeleteAlias" => "DeleteAlias",
282 "ListAliases" => "ListAliases",
283
284 "PutFunctionConcurrency" => "PutFunctionConcurrency",
286 "GetFunctionConcurrency" => "GetFunctionConcurrency",
287 "DeleteFunctionConcurrency" => "DeleteFunctionConcurrency",
288 "PutProvisionedConcurrencyConfig" => "PutProvisionedConcurrencyConfig",
289 "GetProvisionedConcurrencyConfig" => "GetProvisionedConcurrencyConfig",
290 "DeleteProvisionedConcurrencyConfig" => "DeleteProvisionedConcurrencyConfig",
291 "ListProvisionedConcurrencyConfigs" => "ListProvisionedConcurrencyConfigs",
292
293 "PutFunctionEventInvokeConfig" => "PutFunctionEventInvokeConfig",
295 "GetFunctionEventInvokeConfig" => "GetFunctionEventInvokeConfig",
296 "UpdateFunctionEventInvokeConfig" => "UpdateFunctionEventInvokeConfig",
297 "DeleteFunctionEventInvokeConfig" => "DeleteFunctionEventInvokeConfig",
298 "ListFunctionEventInvokeConfigs" => "ListFunctionEventInvokeConfigs",
299
300 "PutRuntimeManagementConfig" => "PutRuntimeManagementConfig",
302 "GetRuntimeManagementConfig" => "GetRuntimeManagementConfig",
303 "PutFunctionScalingConfig" => "PutFunctionScalingConfig",
304 "GetFunctionScalingConfig" => "GetFunctionScalingConfig",
305 "PutFunctionRecursionConfig" => "PutFunctionRecursionConfig",
306 "GetFunctionRecursionConfig" => "GetFunctionRecursionConfig",
307
308 "CreateFunctionUrlConfig" => "CreateFunctionUrlConfig",
310 "GetFunctionUrlConfig" => "GetFunctionUrlConfig",
311 "UpdateFunctionUrlConfig" => "UpdateFunctionUrlConfig",
312 "DeleteFunctionUrlConfig" => "DeleteFunctionUrlConfig",
313 "ListFunctionUrlConfigs" => "ListFunctionUrlConfigs",
314
315 "CreateEventSourceMapping" => "CreateEventSourceMapping",
317 "ListEventSourceMappings" => "ListEventSourceMappings",
318 "GetEventSourceMapping" => "GetEventSourceMapping",
319 "UpdateEventSourceMapping" => "UpdateEventSourceMapping",
320 "DeleteEventSourceMapping" => "DeleteEventSourceMapping",
321
322 "PublishLayerVersion" => "PublishLayerVersion",
324 "ListLayers" => "ListLayers",
325 "ListLayerVersions" => "ListLayerVersions",
326 "GetLayerVersion" => "GetLayerVersion",
327 "DeleteLayerVersion" => "DeleteLayerVersion",
328 "GetLayerVersionPolicy" => "GetLayerVersionPolicy",
329 "AddLayerVersionPermission" => "AddLayerVersionPermission",
330 "RemoveLayerVersionPermission" => "RemoveLayerVersionPermission",
331
332 "CreateCodeSigningConfig" => "CreateCodeSigningConfig",
334 "GetCodeSigningConfig" => "GetCodeSigningConfig",
335 "UpdateCodeSigningConfig" => "UpdateCodeSigningConfig",
336 "DeleteCodeSigningConfig" => "DeleteCodeSigningConfig",
337 "ListCodeSigningConfigs" => "ListCodeSigningConfigs",
338 "PutFunctionCodeSigningConfig" => "PutFunctionCodeSigningConfig",
339 "GetFunctionCodeSigningConfig" => "GetFunctionCodeSigningConfig",
340 "DeleteFunctionCodeSigningConfig" => "DeleteFunctionCodeSigningConfig",
341 "ListFunctionsByCodeSigningConfig" => "ListFunctionsByCodeSigningConfig",
342
343 "TagResource" => "TagResource",
345 "UntagResource" => "UntagResource",
346 "ListTags" => "ListTags",
347
348 "CreateCapacityProvider" => "CreateCapacityProvider",
350 "GetCapacityProvider" => "GetCapacityProvider",
351 "ListCapacityProviders" => "ListCapacityProviders",
352 "UpdateCapacityProvider" => "UpdateCapacityProvider",
353 "DeleteCapacityProvider" => "DeleteCapacityProvider",
354 "ListFunctionVersionsByCapacityProvider" => "ListFunctionVersionsByCapacityProvider",
355
356 "GetDurableExecution" => "GetDurableExecution",
361 "GetDurableExecutionHistory" => "GetDurableExecutionHistory",
362 "GetDurableExecutionState" => "GetDurableExecutionState",
363 "ListDurableExecutionsByFunction" => "ListDurableExecutionsByFunction",
364 "CheckpointDurableExecution" => "CheckpointDurableExecution",
365 "StopDurableExecution" => "StopDurableExecution",
366 "SendDurableExecutionCallbackSuccess" => "SendDurableExecutionCallbackSuccess",
367 "SendDurableExecutionCallbackFailure" => "SendDurableExecutionCallbackFailure",
368 "SendDurableExecutionCallbackHeartbeat" => "SendDurableExecutionCallbackHeartbeat",
369
370 _ => return None,
371 };
372 Some(action)
373}
374
375pub(crate) fn normalize_function_name(input: &str) -> String {
376 if input.is_empty() {
377 return String::new();
378 }
379
380 let decoded = percent_encoding::percent_decode_str(input)
385 .decode_utf8_lossy()
386 .into_owned();
387 let input = decoded.as_str();
388
389 if let Some(rest) = input.strip_prefix("arn:aws:lambda:") {
391 let parts: Vec<&str> = rest.splitn(5, ':').collect();
392 if parts.len() >= 4 && parts[2] == "function" && !parts[3].is_empty() {
394 return parts[3].to_string();
395 }
396 return input.to_string();
397 }
398
399 let parts: Vec<&str> = input.splitn(4, ':').collect();
401 if parts.len() >= 3 && parts[1] == "function" && parts[0].chars().all(|c| c.is_ascii_digit()) {
402 if !parts[2].is_empty() {
403 return parts[2].to_string();
404 }
405 return input.to_string();
406 }
407
408 if input.matches(':').count() == 1 {
414 if let Some((name, _qualifier)) = input.split_once(':') {
415 if !name.is_empty() && name.chars().all(is_function_name_char) {
416 return name.to_string();
417 }
418 }
419 }
420
421 input.to_string()
422}
423
424fn is_function_name_char(c: char) -> bool {
425 c.is_ascii_alphanumeric() || c == '-' || c == '_'
426}
427
428pub(crate) fn paginate_marker<T, F>(
438 mut items: Vec<T>,
439 marker: Option<&str>,
440 max_items: Option<usize>,
441 marker_of: F,
442) -> (Vec<T>, String)
443where
444 F: Fn(&T) -> String,
445{
446 items.sort_by_key(&marker_of);
447
448 let start = match marker {
449 Some(m) if !m.is_empty() => items
450 .iter()
451 .position(|it| marker_of(it) == m)
452 .map(|p| p + 1)
453 .unwrap_or(items.len()),
454 _ => 0,
455 };
456 if start >= items.len() {
457 return (Vec::new(), String::new());
458 }
459
460 let limit = max_items.filter(|&n| n > 0).unwrap_or(usize::MAX);
461 let end = start.saturating_add(limit).min(items.len());
462 let next_marker = if end < items.len() {
463 marker_of(&items[end - 1])
464 } else {
465 String::new()
466 };
467 let page: Vec<T> = items.drain(start..end).collect();
468 (page, next_marker)
469}
470
471pub(crate) fn marker_page_size(req: &AwsRequest) -> Option<usize> {
474 req.query_params
475 .get("MaxItems")
476 .and_then(|s| s.parse::<usize>().ok())
477}
478
479pub(crate) fn qualifier_from_function_ref(input: &str) -> Option<String> {
486 if input.is_empty() {
487 return None;
488 }
489 let decoded = percent_encoding::percent_decode_str(input)
490 .decode_utf8_lossy()
491 .into_owned();
492 let input = decoded.as_str();
493
494 if let Some(rest) = input.strip_prefix("arn:aws:lambda:") {
495 let parts: Vec<&str> = rest.splitn(5, ':').collect();
497 if parts.len() == 5 && parts[2] == "function" && !parts[4].is_empty() {
498 return Some(parts[4].to_string());
499 }
500 return None;
501 }
502 let parts: Vec<&str> = input.splitn(4, ':').collect();
504 if parts.len() == 4
505 && parts[1] == "function"
506 && parts[0].chars().all(|c| c.is_ascii_digit())
507 && !parts[3].is_empty()
508 {
509 return Some(parts[3].to_string());
510 }
511 if input.matches(':').count() == 1 {
513 if let Some((name, qualifier)) = input.split_once(':') {
514 if !name.is_empty() && name.chars().all(is_function_name_char) && !qualifier.is_empty()
515 {
516 return Some(qualifier.to_string());
517 }
518 }
519 }
520 None
521}
522
523pub(crate) fn validate_ephemeral_storage(size: i64) -> Result<i64, AwsServiceError> {
528 if !(512..=10240).contains(&size) {
529 return Err(AwsServiceError::aws_error(
530 StatusCode::BAD_REQUEST,
531 "InvalidParameterValueException",
532 format!(
533 "Value {size} at 'ephemeralStorage.size' failed to satisfy constraint: \
534 Member must satisfy constraint: [Member must have value less than or equal to 10240, \
535 Member must have value greater than or equal to 512]"
536 ),
537 ));
538 }
539 Ok(size)
540}
541
542struct CreateFunctionInput {
546 function_name: String,
547 runtime: String,
548 role: String,
549 handler: String,
550 description: String,
551 timeout: i64,
552 memory_size: i64,
553 package_type: String,
554 tags: BTreeMap<String, String>,
555 environment: BTreeMap<String, String>,
556 architectures: Vec<String>,
557 code_zip: Option<Vec<u8>>,
558 code_fallback: Vec<u8>,
559 image_uri: Option<String>,
560 layer_arns: Vec<String>,
561 tracing_mode: Option<String>,
562 kms_key_arn: Option<String>,
563 ephemeral_storage_size: Option<i64>,
564 vpc_config: Option<serde_json::Value>,
565 snap_start: Option<serde_json::Value>,
566 dead_letter_config_arn: Option<String>,
567 file_system_configs: Vec<serde_json::Value>,
568 logging_config: Option<serde_json::Value>,
569 image_config: Option<serde_json::Value>,
570 durable_config: Option<serde_json::Value>,
571}
572
573impl CreateFunctionInput {
574 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
575 let function_name = body["FunctionName"]
576 .as_str()
577 .ok_or_else(|| {
578 AwsServiceError::aws_error(
579 StatusCode::BAD_REQUEST,
580 "InvalidParameterValueException",
581 "FunctionName is required",
582 )
583 })?
584 .to_string();
585
586 let tags: BTreeMap<String, String> = body["Tags"]
587 .as_object()
588 .map(|m| {
589 m.iter()
590 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
591 .collect()
592 })
593 .unwrap_or_default();
594
595 let environment: BTreeMap<String, String> = body["Environment"]["Variables"]
596 .as_object()
597 .map(|m| {
598 m.iter()
599 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
600 .collect()
601 })
602 .unwrap_or_default();
603
604 let architectures = body["Architectures"]
605 .as_array()
606 .map(|a| {
607 a.iter()
608 .filter_map(|v| v.as_str().map(|s| s.to_string()))
609 .collect()
610 })
611 .unwrap_or_else(|| vec!["x86_64".to_string()]);
612
613 let code_zip: Option<Vec<u8>> = match body["Code"]["ZipFile"].as_str() {
614 Some(b64) => Some(
615 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64).map_err(
616 |_| {
617 AwsServiceError::aws_error(
618 StatusCode::BAD_REQUEST,
619 "InvalidParameterValueException",
620 "Could not decode Code.ZipFile: invalid base64",
621 )
622 },
623 )?,
624 ),
625 None => None,
626 };
627
628 let code_fallback = serde_json::to_vec(&body["Code"]).unwrap_or_default();
629
630 let package_type = body["PackageType"].as_str().unwrap_or("Zip").to_string();
631 let image_uri = if package_type == "Image" {
636 body["Code"]["ImageUri"].as_str().map(String::from)
637 } else {
638 None
639 };
640
641 if package_type == "Image" && image_uri.is_none() {
645 return Err(AwsServiceError::aws_error(
646 StatusCode::BAD_REQUEST,
647 "InvalidParameterValueException",
648 "Code.ImageUri is required when PackageType is Image",
649 ));
650 }
651
652 let layer_arns: Vec<String> = body["Layers"]
653 .as_array()
654 .map(|arr| {
655 arr.iter()
656 .filter_map(|v| v.as_str().map(String::from))
657 .collect()
658 })
659 .unwrap_or_default();
660
661 let tracing_mode = body["TracingConfig"]["Mode"].as_str().map(String::from);
662 let kms_key_arn = body["KMSKeyArn"].as_str().map(String::from);
663 let ephemeral_storage_size = match body["EphemeralStorage"]["Size"].as_i64() {
664 Some(size) => Some(validate_ephemeral_storage(size)?),
665 None => None,
666 };
667 let vpc_config = body["VpcConfig"]
668 .is_object()
669 .then(|| body["VpcConfig"].clone());
670 let snap_start = body["SnapStart"]
671 .is_object()
672 .then(|| body["SnapStart"].clone());
673 let dead_letter_config_arn = body["DeadLetterConfig"]["TargetArn"]
674 .as_str()
675 .map(String::from);
676 let file_system_configs = body["FileSystemConfigs"]
677 .as_array()
678 .cloned()
679 .unwrap_or_default();
680 let logging_config = body["LoggingConfig"]
681 .is_object()
682 .then(|| body["LoggingConfig"].clone());
683 let image_config = body["ImageConfig"]
684 .is_object()
685 .then(|| body["ImageConfig"].clone());
686 let durable_config = body["DurableConfig"]
687 .is_object()
688 .then(|| body["DurableConfig"].clone());
689
690 Ok(Self {
691 function_name,
692 runtime: body["Runtime"].as_str().unwrap_or("python3.12").to_string(),
693 role: body["Role"].as_str().unwrap_or("").to_string(),
694 handler: body["Handler"]
695 .as_str()
696 .unwrap_or("index.handler")
697 .to_string(),
698 description: body["Description"].as_str().unwrap_or("").to_string(),
699 timeout: body["Timeout"].as_i64().unwrap_or(3),
700 memory_size: body["MemorySize"].as_i64().unwrap_or(128),
701 package_type,
702 tags,
703 environment,
704 architectures,
705 code_zip,
706 code_fallback,
707 image_uri,
708 layer_arns,
709 tracing_mode,
710 kms_key_arn,
711 ephemeral_storage_size,
712 vpc_config,
713 snap_start,
714 dead_letter_config_arn,
715 file_system_configs,
716 logging_config,
717 image_config,
718 durable_config,
719 })
720 }
721}
722
723#[derive(Debug, Clone, Copy, PartialEq, Eq)]
725pub enum InvocationType {
726 RequestResponse,
727 Event,
728 DryRun,
729}
730
731impl InvocationType {
732 pub fn from_header(value: Option<&str>) -> Self {
733 match value {
734 Some("Event") => Self::Event,
735 Some("DryRun") => Self::DryRun,
736 _ => Self::RequestResponse,
737 }
738 }
739}
740
741fn route_to_destination(
745 bus: Arc<fakecloud_core::delivery::DeliveryBus>,
746 function_arn: &str,
747 request_payload: &[u8],
748 result: &Result<Vec<u8>, String>,
749 destination_config: Option<&serde_json::Value>,
750) {
751 let Some(cfg) = destination_config else {
752 return;
753 };
754 let (key, condition, response_value): (&str, &str, serde_json::Value) = match result {
755 Ok(bytes) => (
756 "OnSuccess",
757 "Success",
758 serde_json::from_slice(bytes).unwrap_or(serde_json::Value::Null),
759 ),
760 Err(err) => (
761 "OnFailure",
762 "RetriesExhausted",
763 serde_json::json!({ "errorMessage": err }),
764 ),
765 };
766 let Some(dest) = cfg
767 .get(key)
768 .and_then(|v| v.get("Destination"))
769 .and_then(|v| v.as_str())
770 else {
771 return;
772 };
773 let request_payload_v: serde_json::Value =
774 serde_json::from_slice(request_payload).unwrap_or(serde_json::Value::Null);
775 let record = serde_json::json!({
776 "version": "1.0",
777 "timestamp": chrono::Utc::now().to_rfc3339(),
778 "requestContext": {
779 "requestId": uuid::Uuid::new_v4().to_string(),
780 "functionArn": format!("{function_arn}:$LATEST"),
781 "condition": condition,
782 "approximateInvokeCount": 1,
783 },
784 "requestPayload": request_payload_v,
785 "responseContext": {
786 "statusCode": 200,
787 "executedVersion": "$LATEST",
788 },
789 "responsePayload": response_value,
790 });
791 let body = record.to_string();
792 if dest.contains(":sqs:") {
793 bus.send_to_sqs(dest, &body, &std::collections::HashMap::new());
794 } else if dest.contains(":sns:") {
795 bus.publish_to_sns(dest, &body, None);
796 } else if dest.contains(":lambda:") {
797 let dest = dest.to_string();
798 let payload = body.clone();
799 tokio::spawn(async move {
800 let _ = bus.invoke_lambda(&dest, &payload).await;
801 });
802 } else if dest.contains(":events:") || dest.contains(":eventbridge:") {
803 let detail_type = if result.is_ok() {
804 "Lambda Function Invocation Result - Success"
805 } else {
806 "Lambda Function Invocation Result - Failure"
807 };
808 bus.put_event_to_eventbridge("lambda", detail_type, &body, "default");
809 }
810}
811
812pub(crate) struct ConcurrencyGuard {
818 pub(crate) map: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
819 pub(crate) key: String,
820}
821
822impl Drop for ConcurrencyGuard {
823 fn drop(&mut self) {
824 let mut m = self.map.write();
825 let n = m.get(&self.key).copied().unwrap_or(0);
826 if n <= 1 {
827 m.remove(&self.key);
828 } else {
829 m.insert(self.key.clone(), n - 1);
830 }
831 }
832}
833
834fn function_config_unchanged_for_publish(
850 prev: &LambdaFunction,
851 live: &LambdaFunction,
852 effective_description: &str,
853) -> bool {
854 prev.code_sha256 == live.code_sha256
855 && prev.code_size == live.code_size
856 && prev.image_uri == live.image_uri
857 && prev.package_type == live.package_type
858 && prev.runtime == live.runtime
859 && prev.role == live.role
860 && prev.handler == live.handler
861 && prev.description == effective_description
862 && prev.timeout == live.timeout
863 && prev.memory_size == live.memory_size
864 && prev.environment == live.environment
865 && prev.architectures == live.architectures
866 && prev.layers.len() == live.layers.len()
867 && prev
868 .layers
869 .iter()
870 .zip(live.layers.iter())
871 .all(|(a, b)| a.arn == b.arn && a.code_size == b.code_size)
872 && prev.tracing_mode == live.tracing_mode
873 && prev.kms_key_arn == live.kms_key_arn
874 && prev.ephemeral_storage_size == live.ephemeral_storage_size
875 && prev.vpc_config == live.vpc_config
876 && prev.dead_letter_config_arn == live.dead_letter_config_arn
877 && prev.file_system_configs == live.file_system_configs
878 && prev.logging_config == live.logging_config
879 && prev.image_config == live.image_config
880 && prev.signing_profile_version_arn == live.signing_profile_version_arn
881 && prev.signing_job_arn == live.signing_job_arn
882 && prev.runtime_version_config == live.runtime_version_config
883 && snap_start_apply_on_eq(prev.snap_start.as_ref(), live.snap_start.as_ref())
884}
885
886fn snap_start_apply_on_eq(prev: Option<&Value>, live: Option<&Value>) -> bool {
895 let prev_apply = prev
896 .and_then(|v| v.get("ApplyOn"))
897 .and_then(|v| v.as_str())
898 .unwrap_or("None");
899 let live_apply = live
900 .and_then(|v| v.get("ApplyOn"))
901 .and_then(|v| v.as_str())
902 .unwrap_or("None");
903 prev_apply == live_apply
904}
905
906pub(crate) fn resolve_qualifier_to_version(
909 state: &LambdaState,
910 function_name: &str,
911 qualifier: Option<&str>,
912) -> Option<String> {
913 let q = qualifier?;
914 if q == "$LATEST" {
915 return None;
916 }
917 if q.chars().all(|c| c.is_ascii_digit()) {
918 return Some(q.to_string());
919 }
920 let alias_key = format!("{function_name}:{q}");
921 let alias = state.aliases.get(&alias_key)?;
922 let primary = alias.function_version.clone();
923 let routing = alias
924 .routing_config
925 .as_ref()
926 .and_then(|rc| rc.get("AdditionalVersionWeights"))
927 .and_then(|m| m.as_object());
928 let Some(weights) = routing else {
929 return Some(primary);
930 };
931 let mut additional: Vec<(String, f64)> = Vec::with_capacity(weights.len());
934 let mut sum: f64 = 0.0;
935 for (ver, w) in weights {
936 let weight = w.as_f64().unwrap_or(0.0).clamp(0.0, 1.0);
937 sum += weight;
938 additional.push((ver.clone(), weight));
939 }
940 let primary_weight = (1.0 - sum).max(0.0);
941 let pick: f64 = {
942 use std::cell::Cell;
947 thread_local! {
948 static RNG: Cell<u64> = const { Cell::new(0x9E37_79B9_7F4A_7C15) };
949 }
950 let now_nanos = std::time::SystemTime::now()
951 .duration_since(std::time::UNIX_EPOCH)
952 .map(|d| d.as_nanos() as u64)
953 .unwrap_or(0);
954 RNG.with(|cell| {
955 let mut s = cell.get() ^ now_nanos;
956 s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
958 let mut z = s;
959 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
960 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
961 z ^= z >> 31;
962 cell.set(s);
963 (z >> 11) as f64 / ((1u64 << 53) as f64)
964 })
965 };
966 let mut acc = primary_weight;
967 if pick < acc {
968 return Some(primary);
969 }
970 for (ver, w) in &additional {
971 acc += w;
972 if pick < acc {
973 return Some(ver.clone());
974 }
975 }
976 Some(primary)
977}
978
979pub struct LambdaService {
980 pub(crate) state: SharedLambdaState,
981 pub(crate) runtime: Option<Arc<ContainerRuntime>>,
982 snapshot_store: Option<Arc<dyn SnapshotStore>>,
983 snapshot_lock: Arc<AsyncMutex<()>>,
984 pub(crate) delivery_bus: Option<Arc<fakecloud_core::delivery::DeliveryBus>>,
985 pub(crate) role_trust_validator: Option<Arc<dyn fakecloud_core::auth::RoleTrustValidator>>,
986 pub(crate) s3_delivery: Option<Arc<dyn fakecloud_core::delivery::S3Delivery>>,
987 pub(crate) inflight_invocations: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
994}
995
996mod functions;
997mod init;
998mod invoke;
999mod publish;
1000
1001impl LambdaService {
1002 pub fn new(state: SharedLambdaState) -> Self {
1003 Self {
1004 state,
1005 runtime: None,
1006 snapshot_store: None,
1007 snapshot_lock: Arc::new(AsyncMutex::new(())),
1008 delivery_bus: None,
1009 role_trust_validator: None,
1010 s3_delivery: None,
1011 inflight_invocations: Arc::new(parking_lot::RwLock::new(BTreeMap::new())),
1012 }
1013 }
1014
1015 pub fn with_s3_delivery(mut self, s3: Arc<dyn fakecloud_core::delivery::S3Delivery>) -> Self {
1016 self.s3_delivery = Some(s3);
1017 self
1018 }
1019
1020 pub fn with_runtime(mut self, runtime: Arc<ContainerRuntime>) -> Self {
1021 self.runtime = Some(runtime);
1022 self
1023 }
1024
1025 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
1026 self.snapshot_store = Some(store);
1027 self
1028 }
1029
1030 pub fn with_delivery_bus(mut self, bus: Arc<fakecloud_core::delivery::DeliveryBus>) -> Self {
1031 self.delivery_bus = Some(bus);
1032 self
1033 }
1034
1035 pub fn with_role_trust_validator(
1036 mut self,
1037 validator: Arc<dyn fakecloud_core::auth::RoleTrustValidator>,
1038 ) -> Self {
1039 self.role_trust_validator = Some(validator);
1040 self
1041 }
1042
1043 async fn save_snapshot(&self) {
1044 save_lambda_snapshot(
1045 &self.state,
1046 self.snapshot_store.clone(),
1047 &self.snapshot_lock,
1048 )
1049 .await;
1050 }
1051
1052 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
1057 let store = self.snapshot_store.clone()?;
1058 let state = self.state.clone();
1059 let lock = self.snapshot_lock.clone();
1060 Some(Arc::new(move || {
1061 let state = state.clone();
1062 let store = store.clone();
1063 let lock = lock.clone();
1064 Box::pin(async move {
1065 save_lambda_snapshot(&state, Some(store), &lock).await;
1066 })
1067 }))
1068 }
1069}
1070
1071pub async fn save_lambda_snapshot(
1077 state: &SharedLambdaState,
1078 store: Option<Arc<dyn SnapshotStore>>,
1079 lock: &AsyncMutex<()>,
1080) {
1081 let Some(store) = store else {
1082 return;
1083 };
1084 let _guard = lock.lock().await;
1085 let snapshot = LambdaSnapshot {
1086 schema_version: LAMBDA_SNAPSHOT_SCHEMA_VERSION,
1087 accounts: Some(state.read().clone()),
1088 state: None,
1089 };
1090 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
1091 let bytes = serde_json::to_vec(&snapshot)
1092 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
1093 store.save(&bytes)
1094 })
1095 .await;
1096 match join {
1097 Ok(Ok(())) => {}
1098 Ok(Err(err)) => tracing::error!(%err, "failed to write lambda snapshot"),
1099 Err(err) => tracing::error!(%err, "lambda snapshot task panicked"),
1100 }
1101}
1102
1103#[async_trait]
1104impl AwsService for LambdaService {
1105 fn service_name(&self) -> &str {
1106 "lambda"
1107 }
1108
1109 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1110 let (action, resource_name) = Self::resolve_action(&req).ok_or_else(|| {
1111 const KNOWN_COLLECTIONS: &[&str] = &[
1122 "functions",
1123 "layers",
1124 "layers-by-arn",
1125 "event-source-mappings",
1126 "tags",
1127 "account-settings",
1128 "code-signing-configs",
1129 ];
1130 let is_known_collection = req
1131 .path_segments
1132 .get(1)
1133 .map(|s| KNOWN_COLLECTIONS.contains(&s.as_str()))
1134 .unwrap_or(false);
1135 if is_known_collection {
1136 AwsServiceError::aws_error(
1137 StatusCode::BAD_REQUEST,
1138 "InvalidParameterValueException",
1139 format!(
1140 "Could not route request {} {} — missing or invalid identifier",
1141 req.method, req.raw_path
1142 ),
1143 )
1144 } else {
1145 AwsServiceError::aws_error(
1146 StatusCode::NOT_FOUND,
1147 "UnknownOperationException",
1148 format!("Unknown operation: {} {}", req.method, req.raw_path),
1149 )
1150 }
1151 })?;
1152
1153 let arn_embedded_qualifier = resource_name
1161 .as_deref()
1162 .and_then(qualifier_from_function_ref);
1163 let resource_name = if action_takes_function_name(action) {
1164 if let Some(raw) = resource_name.as_ref() {
1171 let decoded = crate::extras::percent_decode_for_length(raw);
1176 let len = decoded.chars().count();
1177 let limit = if decoded.starts_with("arn:") {
1188 200
1189 } else {
1190 140
1191 };
1192 if decoded.is_empty() || len > limit {
1193 let (code, msg) = if action == "InvokeAsync" {
1194 (
1195 "ResourceNotFoundException",
1196 format!("Function not found: {}", raw),
1197 )
1198 } else {
1199 (
1200 "InvalidParameterValueException",
1201 format!(
1202 "1 validation error detected: Value '{}' at 'functionName' failed to \
1203 satisfy constraint: Member must have length less than or equal to 140",
1204 raw
1205 ),
1206 )
1207 };
1208 return Err(AwsServiceError::aws_error(
1209 if action == "InvokeAsync" {
1210 StatusCode::NOT_FOUND
1211 } else {
1212 StatusCode::BAD_REQUEST
1213 },
1214 code,
1215 msg,
1216 ));
1217 }
1218 }
1219 resource_name.map(|s| normalize_function_name(&s))
1220 } else {
1221 resource_name
1222 };
1223
1224 if let Some(raw) = req.query_params.get("MaxItems") {
1230 let n = raw.parse::<i64>().map_err(|_| {
1234 AwsServiceError::aws_error(
1235 StatusCode::BAD_REQUEST,
1236 "InvalidParameterValueException",
1237 format!("MaxItems must be a number (got '{raw}')"),
1238 )
1239 })?;
1240 let max = match action {
1244 "ListLayers"
1245 | "ListLayerVersions"
1246 | "ListFunctionUrlConfigs"
1247 | "ListProvisionedConcurrencyConfigs"
1248 | "ListFunctionEventInvokeConfigs" => 50,
1249 _ => 10000,
1250 };
1251 if !(1..=max).contains(&n) {
1252 return Err(AwsServiceError::aws_error(
1253 StatusCode::BAD_REQUEST,
1254 "InvalidParameterValueException",
1255 format!("MaxItems must be between 1 and {} (got {})", max, n),
1256 ));
1257 }
1258 }
1259
1260 if let Some(q) = req.query_params.get("Qualifier") {
1265 let len = q.chars().count();
1266 if q.is_empty() || len > 128 {
1267 return Err(AwsServiceError::aws_error(
1268 StatusCode::BAD_REQUEST,
1269 "InvalidParameterValueException",
1270 format!("Qualifier must be 1..128 characters (got length {})", len),
1271 ));
1272 }
1273 }
1274 if let Some(fv) = req.query_params.get("FunctionVersion") {
1277 let len = fv.chars().count();
1278 if fv.is_empty() || len > 1024 {
1279 return Err(AwsServiceError::aws_error(
1280 StatusCode::BAD_REQUEST,
1281 "InvalidParameterValueException",
1282 format!(
1283 "FunctionVersion must be 1..1024 characters (got length {})",
1284 len
1285 ),
1286 ));
1287 }
1288 }
1289
1290 let mutates = matches!(
1291 action,
1292 "CreateFunction"
1293 | "DeleteFunction"
1294 | "PublishVersion"
1295 | "AddPermission"
1296 | "RemovePermission"
1297 | "CreateEventSourceMapping"
1298 | "DeleteEventSourceMapping"
1299 | "UpdateEventSourceMapping"
1300 | "UpdateFunctionCode"
1301 | "UpdateFunctionConfiguration"
1302 | "CreateAlias"
1303 | "DeleteAlias"
1304 | "UpdateAlias"
1305 | "PublishLayerVersion"
1306 | "DeleteLayerVersion"
1307 | "AddLayerVersionPermission"
1308 | "RemoveLayerVersionPermission"
1309 | "CreateFunctionUrlConfig"
1310 | "DeleteFunctionUrlConfig"
1311 | "UpdateFunctionUrlConfig"
1312 | "PutFunctionConcurrency"
1313 | "DeleteFunctionConcurrency"
1314 | "PutProvisionedConcurrencyConfig"
1315 | "DeleteProvisionedConcurrencyConfig"
1316 | "CreateCodeSigningConfig"
1317 | "UpdateCodeSigningConfig"
1318 | "DeleteCodeSigningConfig"
1319 | "PutFunctionCodeSigningConfig"
1320 | "DeleteFunctionCodeSigningConfig"
1321 | "PutFunctionEventInvokeConfig"
1322 | "UpdateFunctionEventInvokeConfig"
1323 | "DeleteFunctionEventInvokeConfig"
1324 | "PutRuntimeManagementConfig"
1325 | "PutFunctionScalingConfig"
1326 | "PutFunctionRecursionConfig"
1327 | "TagResource"
1328 | "UntagResource"
1329 | "InvokeAsync"
1330 | "InvokeWithResponseStream"
1331 );
1332
1333 let aid = &req.account_id;
1334 prevalidate_lambda(action, &req)?;
1339 let result = match action {
1340 "CreateFunction" => self.create_function(&req),
1341 "ListFunctions" => self.list_functions(
1342 aid,
1343 req.query_params.get("FunctionVersion").map(String::as_str),
1344 req.query_params.get("Marker").map(String::as_str),
1345 marker_page_size(&req),
1346 ),
1347 "GetFunction" => self.get_function(
1348 &req,
1349 resource_name.as_deref().unwrap_or(""),
1350 aid,
1351 req.region.as_str(),
1352 req.query_params.get("Qualifier").map(String::as_str),
1353 ),
1354 "DeleteFunction" => self.delete_function(
1355 resource_name.as_deref().unwrap_or(""),
1356 aid,
1357 req.query_params.get("Qualifier").map(String::as_str),
1358 ),
1359 "Invoke" => {
1360 let invocation_type = InvocationType::from_header(
1361 req.headers
1362 .get("x-amz-invocation-type")
1363 .and_then(|v| v.to_str().ok()),
1364 );
1365 let log_tail = req
1366 .headers
1367 .get("x-amz-log-type")
1368 .and_then(|v| v.to_str().ok())
1369 == Some("Tail");
1370 let qualifier = req
1373 .query_params
1374 .get("Qualifier")
1375 .map(String::as_str)
1376 .or(arn_embedded_qualifier.as_deref());
1377 self.invoke(
1378 resource_name.as_deref().unwrap_or(""),
1379 &req.body,
1380 aid,
1381 invocation_type,
1382 qualifier,
1383 log_tail,
1384 )
1385 .await
1386 }
1387 "InvokeAsync" => {
1388 let name = resource_name.as_deref().unwrap_or("");
1394 let accounts = self.state.read();
1395 let exists = accounts
1396 .get(aid)
1397 .map(|s| s.functions.contains_key(name))
1398 .unwrap_or(false);
1399 if !exists {
1400 Err(AwsServiceError::aws_error(
1401 StatusCode::NOT_FOUND,
1402 "ResourceNotFoundException",
1403 format!("Function not found: {}", name),
1404 ))
1405 } else {
1406 Ok(AwsResponse::json(
1407 StatusCode::ACCEPTED,
1408 json!({ "Status": 202 }).to_string(),
1409 ))
1410 }
1411 }
1412 "PublishVersion" => {
1413 self.publish_version(resource_name.as_deref().unwrap_or(""), aid, &req)
1414 }
1415 "AddPermission" => self.add_permission(resource_name.as_deref().unwrap_or(""), &req),
1416 "GetPolicy" => self.get_policy(
1417 resource_name.as_deref().unwrap_or(""),
1418 aid,
1419 req.query_params.get("Qualifier").map(String::as_str),
1420 ),
1421 "RemovePermission" => {
1422 let sid = req.path_segments.get(4).cloned().unwrap_or_default();
1424 self.remove_permission(
1425 resource_name.as_deref().unwrap_or(""),
1426 &sid,
1427 aid,
1428 req.query_params.get("Qualifier").map(String::as_str),
1429 )
1430 }
1431 "CreateEventSourceMapping" => self.create_event_source_mapping(&req),
1432 "ListEventSourceMappings" => {
1433 if let Some(fn_name) = req.query_params.get("FunctionName") {
1437 let len = fn_name.chars().count();
1438 if fn_name.is_empty() || len > 140 {
1439 return Err(AwsServiceError::aws_error(
1440 StatusCode::BAD_REQUEST,
1441 "InvalidParameterValueException",
1442 "FunctionName must be 1..140 characters",
1443 ));
1444 }
1445 }
1446 self.list_event_source_mappings(aid, &req)
1447 }
1448 "GetEventSourceMapping" => {
1449 self.get_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1450 }
1451 "DeleteEventSourceMapping" => {
1452 self.delete_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1453 }
1454 "CreateCapacityProvider" => {
1455 crate::workflows::create_capacity_provider(&self.state, &req, &req.json_body())
1456 }
1457 "GetCapacityProvider" => crate::workflows::get_capacity_provider(
1458 &self.state,
1459 &req,
1460 resource_name.as_deref().unwrap_or(""),
1461 ),
1462 "ListCapacityProviders" => crate::workflows::list_capacity_providers(&self.state, &req),
1463 "UpdateCapacityProvider" => crate::workflows::update_capacity_provider(
1464 &self.state,
1465 &req,
1466 resource_name.as_deref().unwrap_or(""),
1467 &req.json_body(),
1468 ),
1469 "DeleteCapacityProvider" => crate::workflows::delete_capacity_provider(
1470 &self.state,
1471 &req,
1472 resource_name.as_deref().unwrap_or(""),
1473 ),
1474 "ListFunctionVersionsByCapacityProvider" => {
1475 crate::workflows::list_function_versions_by_capacity_provider(
1476 &self.state,
1477 &req,
1478 resource_name.as_deref().unwrap_or(""),
1479 )
1480 }
1481 "GetDurableExecution" => crate::workflows::get_durable_execution(
1482 &self.state,
1483 &req,
1484 resource_name.as_deref().unwrap_or(""),
1485 ),
1486 "GetDurableExecutionHistory" => crate::workflows::get_durable_execution_history(
1487 &self.state,
1488 &req,
1489 resource_name.as_deref().unwrap_or(""),
1490 ),
1491 "GetDurableExecutionState" => crate::workflows::get_durable_execution_state(
1492 &self.state,
1493 &req,
1494 resource_name.as_deref().unwrap_or(""),
1495 ),
1496 "ListDurableExecutionsByFunction" => {
1497 crate::workflows::list_durable_executions_by_function(
1498 &self.state,
1499 &req,
1500 resource_name.as_deref().unwrap_or(""),
1501 )
1502 }
1503 "CheckpointDurableExecution" => crate::workflows::checkpoint_durable_execution(
1504 &self.state,
1505 &req,
1506 resource_name.as_deref().unwrap_or(""),
1507 &req.json_body(),
1508 ),
1509 "StopDurableExecution" => crate::workflows::stop_durable_execution(
1510 &self.state,
1511 &req,
1512 resource_name.as_deref().unwrap_or(""),
1513 ),
1514 "SendDurableExecutionCallbackSuccess" => crate::workflows::send_callback_success(
1515 &self.state,
1516 &req,
1517 resource_name.as_deref().unwrap_or(""),
1518 ),
1519 "SendDurableExecutionCallbackFailure" => crate::workflows::send_callback_failure(
1520 &self.state,
1521 &req,
1522 resource_name.as_deref().unwrap_or(""),
1523 ),
1524 "SendDurableExecutionCallbackHeartbeat" => crate::workflows::send_callback_heartbeat(
1525 &self.state,
1526 &req,
1527 resource_name.as_deref().unwrap_or(""),
1528 ),
1529 other => {
1530 self.handle_extra(other, resource_name.as_deref(), &req)
1531 .await
1532 }
1533 };
1534 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
1535 self.save_snapshot().await;
1536 }
1537 result
1538 }
1539
1540 fn supported_actions(&self) -> &[&str] {
1541 &[
1542 "CreateFunction",
1543 "GetFunction",
1544 "DeleteFunction",
1545 "ListFunctions",
1546 "Invoke",
1547 "InvokeAsync",
1548 "InvokeWithResponseStream",
1549 "PublishVersion",
1550 "ListVersionsByFunction",
1551 "AddPermission",
1552 "RemovePermission",
1553 "GetPolicy",
1554 "CreateEventSourceMapping",
1555 "ListEventSourceMappings",
1556 "GetEventSourceMapping",
1557 "UpdateEventSourceMapping",
1558 "DeleteEventSourceMapping",
1559 "GetFunctionConfiguration",
1560 "UpdateFunctionConfiguration",
1561 "UpdateFunctionCode",
1562 "GetAccountSettings",
1563 "CreateAlias",
1564 "GetAlias",
1565 "ListAliases",
1566 "UpdateAlias",
1567 "DeleteAlias",
1568 "PublishLayerVersion",
1569 "GetLayerVersion",
1570 "GetLayerVersionByArn",
1571 "DeleteLayerVersion",
1572 "ListLayerVersions",
1573 "ListLayers",
1574 "GetLayerVersionPolicy",
1575 "AddLayerVersionPermission",
1576 "RemoveLayerVersionPermission",
1577 "CreateFunctionUrlConfig",
1578 "GetFunctionUrlConfig",
1579 "UpdateFunctionUrlConfig",
1580 "DeleteFunctionUrlConfig",
1581 "ListFunctionUrlConfigs",
1582 "PutFunctionConcurrency",
1583 "GetFunctionConcurrency",
1584 "DeleteFunctionConcurrency",
1585 "PutProvisionedConcurrencyConfig",
1586 "GetProvisionedConcurrencyConfig",
1587 "DeleteProvisionedConcurrencyConfig",
1588 "ListProvisionedConcurrencyConfigs",
1589 "CreateCodeSigningConfig",
1590 "GetCodeSigningConfig",
1591 "UpdateCodeSigningConfig",
1592 "DeleteCodeSigningConfig",
1593 "ListCodeSigningConfigs",
1594 "PutFunctionCodeSigningConfig",
1595 "GetFunctionCodeSigningConfig",
1596 "DeleteFunctionCodeSigningConfig",
1597 "ListFunctionsByCodeSigningConfig",
1598 "PutFunctionEventInvokeConfig",
1599 "GetFunctionEventInvokeConfig",
1600 "UpdateFunctionEventInvokeConfig",
1601 "DeleteFunctionEventInvokeConfig",
1602 "ListFunctionEventInvokeConfigs",
1603 "PutRuntimeManagementConfig",
1604 "GetRuntimeManagementConfig",
1605 "PutFunctionScalingConfig",
1606 "GetFunctionScalingConfig",
1607 "PutFunctionRecursionConfig",
1608 "GetFunctionRecursionConfig",
1609 "TagResource",
1610 "UntagResource",
1611 "ListTags",
1612 "CreateCapacityProvider",
1613 "GetCapacityProvider",
1614 "ListCapacityProviders",
1615 "UpdateCapacityProvider",
1616 "DeleteCapacityProvider",
1617 "ListFunctionVersionsByCapacityProvider",
1618 "GetDurableExecution",
1619 "GetDurableExecutionHistory",
1620 "GetDurableExecutionState",
1621 "ListDurableExecutionsByFunction",
1622 "CheckpointDurableExecution",
1623 "StopDurableExecution",
1624 "SendDurableExecutionCallbackSuccess",
1625 "SendDurableExecutionCallbackFailure",
1626 "SendDurableExecutionCallbackHeartbeat",
1627 ]
1628 }
1629
1630 fn iam_enforceable(&self) -> bool {
1631 true
1632 }
1633
1634 fn iam_action_for(&self, request: &AwsRequest) -> Option<fakecloud_core::auth::IamAction> {
1638 let (action_str, resource_name) = Self::resolve_action(request)?;
1643 let action = iam_action_name_for(action_str)?;
1651 let accounts = self.state.read();
1652 let empty = LambdaState::new(&request.account_id, &request.region);
1653 let state = accounts.get(&request.account_id).unwrap_or(&empty);
1654 let resource = match action_str {
1655 "GetFunction"
1660 | "DeleteFunction"
1661 | "Invoke"
1662 | "InvokeAsync"
1663 | "InvokeWithResponseStream"
1664 | "PublishVersion"
1665 | "ListVersionsByFunction"
1666 | "AddPermission"
1667 | "RemovePermission"
1668 | "GetPolicy"
1669 | "GetFunctionConfiguration"
1670 | "UpdateFunctionConfiguration"
1671 | "UpdateFunctionCode"
1672 | "CreateAlias"
1673 | "GetAlias"
1674 | "UpdateAlias"
1675 | "DeleteAlias"
1676 | "ListAliases"
1677 | "PutFunctionConcurrency"
1678 | "GetFunctionConcurrency"
1679 | "DeleteFunctionConcurrency"
1680 | "PutProvisionedConcurrencyConfig"
1681 | "GetProvisionedConcurrencyConfig"
1682 | "DeleteProvisionedConcurrencyConfig"
1683 | "ListProvisionedConcurrencyConfigs"
1684 | "PutFunctionEventInvokeConfig"
1685 | "GetFunctionEventInvokeConfig"
1686 | "UpdateFunctionEventInvokeConfig"
1687 | "DeleteFunctionEventInvokeConfig"
1688 | "ListFunctionEventInvokeConfigs"
1689 | "PutRuntimeManagementConfig"
1690 | "GetRuntimeManagementConfig"
1691 | "PutFunctionScalingConfig"
1692 | "GetFunctionScalingConfig"
1693 | "PutFunctionRecursionConfig"
1694 | "GetFunctionRecursionConfig"
1695 | "PutFunctionCodeSigningConfig"
1696 | "GetFunctionCodeSigningConfig"
1697 | "DeleteFunctionCodeSigningConfig"
1698 | "CreateFunctionUrlConfig"
1699 | "GetFunctionUrlConfig"
1700 | "UpdateFunctionUrlConfig"
1701 | "DeleteFunctionUrlConfig"
1702 | "ListFunctionUrlConfigs"
1703 | "ListDurableExecutionsByFunction" => {
1704 let raw = resource_name.unwrap_or_default();
1705 if raw.is_empty() {
1706 "*".to_string()
1707 } else {
1708 let name = normalize_function_name(&raw);
1714 format!(
1715 "arn:aws:lambda:{}:{}:function:{}",
1716 state.region, state.account_id, name
1717 )
1718 }
1719 }
1720 "CreateFunction" => {
1721 serde_json::from_slice::<Value>(&request.body)
1726 .ok()
1727 .and_then(|v| {
1728 v.get("FunctionName").and_then(|f| f.as_str()).map(|n| {
1729 format!(
1730 "arn:aws:lambda:{}:{}:function:{}",
1731 state.region, state.account_id, n
1732 )
1733 })
1734 })
1735 .unwrap_or_else(|| "*".to_string())
1736 }
1737 _ => "*".to_string(),
1738 };
1739 Some(fakecloud_core::auth::IamAction {
1740 service: "lambda",
1741 action,
1742 resource,
1743 })
1744 }
1745
1746 fn iam_condition_keys_for(
1747 &self,
1748 request: &AwsRequest,
1749 action: &fakecloud_core::auth::IamAction,
1750 ) -> std::collections::BTreeMap<String, Vec<String>> {
1751 let mut out = std::collections::BTreeMap::new();
1752 if action.action == "AddPermission" {
1753 if action.resource != "*" {
1754 out.insert(
1755 "lambda:functionarn".to_string(),
1756 vec![action.resource.clone()],
1757 );
1758 }
1759 if let Ok(body) = serde_json::from_slice::<Value>(&request.body) {
1760 if let Some(principal) = body.get("Principal").and_then(|p| p.as_str()) {
1761 out.insert("lambda:principal".to_string(), vec![principal.to_string()]);
1762 }
1763 }
1764 }
1765 out
1766 }
1767}
1768
1769#[path = "../service_event_sources.rs"]
1770mod service_event_sources;
1771#[path = "../service_permissions.rs"]
1772mod service_permissions;
1773
1774#[cfg(test)]
1775#[path = "../service_tests.rs"]
1776mod tests;