1pub mod authz;
2mod error;
3mod executor;
4mod operations;
5pub mod state;
6mod util;
7
8pub use authz::LambdaResourcePolicyLookup;
9
10use std::path::Path;
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use awsim_core::{
15 AccountRegionStore, AwsError, BlobInventory, Body, BodyStore, Protocol, RequestContext,
16 RouteDefinition, ServiceHandler,
17};
18use serde_json::Value;
19use tracing::debug;
20
21use state::{LambdaState, LambdaStateSnapshot};
22
23pub struct LambdaService {
24 store: AccountRegionStore<LambdaState>,
25 body_store: Option<Arc<BodyStore>>,
26}
27
28impl LambdaService {
29 pub fn new() -> Self {
30 Self {
31 store: AccountRegionStore::new(),
32 body_store: None,
33 }
34 }
35
36 pub fn with_data_dir(dir: impl AsRef<Path>) -> Self {
37 Self {
38 store: AccountRegionStore::new(),
39 body_store: Some(Arc::new(BodyStore::new(dir.as_ref().to_path_buf()))),
40 }
41 }
42
43 pub fn with_max_blob_bytes(mut self, bytes: u64) -> Self {
44 if let Some(bs) = self.body_store.take() {
45 let root = bs.root().to_path_buf();
46 self.body_store = Some(Arc::new(BodyStore::new(root).with_max_size(bytes)));
47 }
48 self
49 }
50
51 fn get_state(&self, ctx: &RequestContext) -> Arc<LambdaState> {
52 let state = self.store.get(&ctx.account_id, &ctx.region);
53 if let Some(bs) = &self.body_store {
54 state.set_body_store(Arc::clone(bs));
55 }
56 state
57 }
58
59 pub fn store(&self) -> AccountRegionStore<LambdaState> {
60 self.store.clone()
61 }
62
63 pub fn body_store(&self) -> Option<&Arc<BodyStore>> {
64 self.body_store.as_ref()
65 }
66
67 pub const GROUPS: &'static [&'static str] = &["lambda"];
68
69 fn rebind_bodies(&self) {
70 let Some(bs) = &self.body_store else {
71 return;
72 };
73 for (_, state) in self.store.iter_all() {
74 state.set_body_store(Arc::clone(bs));
75 for mut entry in state.functions.iter_mut() {
76 let name = entry.key().clone();
77 let func = entry.value_mut();
78 if let Ok(path) = bs.blob_path("lambda", &name, "$LATEST") {
79 func.code = Some(Body::OnDisk(path));
80 }
81 for v in func.versions.iter_mut() {
82 if let Ok(path) = bs.blob_path("lambda", &name, &v.version) {
83 v.code = Some(Body::OnDisk(path));
84 }
85 }
86 }
87 }
88 }
89}
90
91impl Default for LambdaService {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl BlobInventory for LambdaService {
98 fn known_blobs(&self) -> Vec<(String, String, String)> {
99 let mut out = Vec::new();
100 for (_, state) in self.store.iter_all() {
101 for func_entry in state.functions.iter() {
102 let name = func_entry.key().clone();
103 out.push(("lambda".to_string(), name.clone(), "$LATEST".to_string()));
104 for v in func_entry.value().versions.iter() {
105 out.push(("lambda".to_string(), name.clone(), v.version.clone()));
106 }
107 }
108 }
109 out
110 }
111}
112
113#[async_trait]
114impl ServiceHandler for LambdaService {
115 fn service_name(&self) -> &str {
116 "lambda"
117 }
118
119 fn signing_name(&self) -> &str {
120 "lambda"
121 }
122
123 fn protocol(&self) -> Protocol {
124 Protocol::RestJson1
125 }
126
127 fn routes(&self) -> Vec<RouteDefinition> {
128 vec![
129 RouteDefinition {
131 method: "POST",
132 path_pattern: "/2015-03-31/functions",
133 operation: "CreateFunction",
134 required_query_param: None,
135 },
136 RouteDefinition {
137 method: "GET",
138 path_pattern: "/2015-03-31/functions",
139 operation: "ListFunctions",
140 required_query_param: None,
141 },
142 RouteDefinition {
143 method: "GET",
144 path_pattern: "/2015-03-31/functions/{FunctionName}",
145 operation: "GetFunction",
146 required_query_param: None,
147 },
148 RouteDefinition {
149 method: "DELETE",
150 path_pattern: "/2015-03-31/functions/{FunctionName}",
151 operation: "DeleteFunction",
152 required_query_param: None,
153 },
154 RouteDefinition {
155 method: "GET",
156 path_pattern: "/2015-03-31/functions/{FunctionName}/configuration",
157 operation: "GetFunctionConfiguration",
158 required_query_param: None,
159 },
160 RouteDefinition {
161 method: "PUT",
162 path_pattern: "/2015-03-31/functions/{FunctionName}/code",
163 operation: "UpdateFunctionCode",
164 required_query_param: None,
165 },
166 RouteDefinition {
167 method: "PUT",
168 path_pattern: "/2015-03-31/functions/{FunctionName}/configuration",
169 operation: "UpdateFunctionConfiguration",
170 required_query_param: None,
171 },
172 RouteDefinition {
173 method: "POST",
174 path_pattern: "/2015-03-31/functions/{FunctionName}/invocations",
175 operation: "Invoke",
176 required_query_param: None,
177 },
178 RouteDefinition {
180 method: "POST",
181 path_pattern: "/2015-03-31/functions/{FunctionName}/versions",
182 operation: "PublishVersion",
183 required_query_param: None,
184 },
185 RouteDefinition {
186 method: "GET",
187 path_pattern: "/2015-03-31/functions/{FunctionName}/versions",
188 operation: "ListVersionsByFunction",
189 required_query_param: None,
190 },
191 RouteDefinition {
193 method: "POST",
194 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases",
195 operation: "CreateAlias",
196 required_query_param: None,
197 },
198 RouteDefinition {
199 method: "GET",
200 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases",
201 operation: "ListAliases",
202 required_query_param: None,
203 },
204 RouteDefinition {
205 method: "GET",
206 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases/{Name}",
207 operation: "GetAlias",
208 required_query_param: None,
209 },
210 RouteDefinition {
211 method: "DELETE",
212 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases/{Name}",
213 operation: "DeleteAlias",
214 required_query_param: None,
215 },
216 RouteDefinition {
218 method: "POST",
219 path_pattern: "/2015-03-31/event-source-mappings",
220 operation: "CreateEventSourceMapping",
221 required_query_param: None,
222 },
223 RouteDefinition {
224 method: "GET",
225 path_pattern: "/2015-03-31/event-source-mappings",
226 operation: "ListEventSourceMappings",
227 required_query_param: None,
228 },
229 RouteDefinition {
230 method: "GET",
231 path_pattern: "/2015-03-31/event-source-mappings/{UUID}",
232 operation: "GetEventSourceMapping",
233 required_query_param: None,
234 },
235 RouteDefinition {
236 method: "DELETE",
237 path_pattern: "/2015-03-31/event-source-mappings/{UUID}",
238 operation: "DeleteEventSourceMapping",
239 required_query_param: None,
240 },
241 RouteDefinition {
242 method: "PUT",
243 path_pattern: "/2015-03-31/event-source-mappings/{UUID}",
244 operation: "UpdateEventSourceMapping",
245 required_query_param: None,
246 },
247 RouteDefinition {
249 method: "POST",
250 path_pattern: "/2018-10-31/layers/{LayerName}/versions",
251 operation: "PublishLayerVersion",
252 required_query_param: None,
253 },
254 RouteDefinition {
255 method: "GET",
256 path_pattern: "/2018-10-31/layers",
257 operation: "ListLayers",
258 required_query_param: None,
259 },
260 RouteDefinition {
261 method: "GET",
262 path_pattern: "/2018-10-31/layers/{LayerName}/versions",
263 operation: "ListLayerVersions",
264 required_query_param: None,
265 },
266 RouteDefinition {
267 method: "DELETE",
268 path_pattern: "/2018-10-31/layers/{LayerName}/versions/{VersionNumber}",
269 operation: "DeleteLayerVersion",
270 required_query_param: None,
271 },
272 RouteDefinition {
274 method: "POST",
275 path_pattern: "/2021-10-31/functions/{FunctionName}/url",
276 operation: "CreateFunctionUrlConfig",
277 required_query_param: None,
278 },
279 RouteDefinition {
280 method: "GET",
281 path_pattern: "/2021-10-31/functions/{FunctionName}/url",
282 operation: "GetFunctionUrlConfig",
283 required_query_param: None,
284 },
285 RouteDefinition {
286 method: "DELETE",
287 path_pattern: "/2021-10-31/functions/{FunctionName}/url",
288 operation: "DeleteFunctionUrlConfig",
289 required_query_param: None,
290 },
291 RouteDefinition {
292 method: "GET",
293 path_pattern: "/2021-10-31/functions/{FunctionName}/urls",
294 operation: "ListFunctionUrlConfigs",
295 required_query_param: None,
296 },
297 RouteDefinition {
299 method: "PUT",
300 path_pattern: "/2017-10-31/functions/{FunctionName}/concurrency",
301 operation: "PutFunctionConcurrency",
302 required_query_param: None,
303 },
304 RouteDefinition {
305 method: "GET",
306 path_pattern: "/2019-09-30/functions/{FunctionName}/concurrency",
307 operation: "GetFunctionConcurrency",
308 required_query_param: None,
309 },
310 RouteDefinition {
311 method: "DELETE",
312 path_pattern: "/2017-10-31/functions/{FunctionName}/concurrency",
313 operation: "DeleteFunctionConcurrency",
314 required_query_param: None,
315 },
316 RouteDefinition {
319 method: "PUT",
320 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
321 operation: "PutProvisionedConcurrencyConfig",
322 required_query_param: Some("Qualifier"),
323 },
324 RouteDefinition {
325 method: "GET",
326 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
327 operation: "GetProvisionedConcurrencyConfig",
328 required_query_param: Some("Qualifier"),
329 },
330 RouteDefinition {
331 method: "DELETE",
332 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
333 operation: "DeleteProvisionedConcurrencyConfig",
334 required_query_param: Some("Qualifier"),
335 },
336 RouteDefinition {
337 method: "GET",
338 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
339 operation: "ListProvisionedConcurrencyConfigs",
340 required_query_param: None,
341 },
342 RouteDefinition {
344 method: "GET",
345 path_pattern: "/2017-03-31/tags/{Resource}",
346 operation: "ListTags",
347 required_query_param: None,
348 },
349 RouteDefinition {
350 method: "POST",
351 path_pattern: "/2017-03-31/tags/{Resource}",
352 operation: "TagResource",
353 required_query_param: None,
354 },
355 RouteDefinition {
356 method: "DELETE",
357 path_pattern: "/2017-03-31/tags/{Resource}",
358 operation: "UntagResource",
359 required_query_param: None,
360 },
361 RouteDefinition {
363 method: "GET",
364 path_pattern: "/2015-03-31/functions/{FunctionName}/policy",
365 operation: "GetPolicy",
366 required_query_param: None,
367 },
368 RouteDefinition {
369 method: "POST",
370 path_pattern: "/2015-03-31/functions/{FunctionName}/policy",
371 operation: "AddPermission",
372 required_query_param: None,
373 },
374 RouteDefinition {
375 method: "DELETE",
376 path_pattern: "/2015-03-31/functions/{FunctionName}/policy/{StatementId}",
377 operation: "RemovePermission",
378 required_query_param: None,
379 },
380 RouteDefinition {
382 method: "GET",
383 path_pattern: "/2016-08-19/account-settings",
384 operation: "GetAccountSettings",
385 required_query_param: None,
386 },
387 RouteDefinition {
389 method: "PUT",
390 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
391 operation: "PutFunctionEventInvokeConfig",
392 required_query_param: None,
393 },
394 RouteDefinition {
395 method: "GET",
396 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
397 operation: "GetFunctionEventInvokeConfig",
398 required_query_param: None,
399 },
400 RouteDefinition {
401 method: "DELETE",
402 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
403 operation: "DeleteFunctionEventInvokeConfig",
404 required_query_param: None,
405 },
406 RouteDefinition {
407 method: "POST",
408 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
409 operation: "UpdateFunctionEventInvokeConfig",
410 required_query_param: None,
411 },
412 RouteDefinition {
413 method: "GET",
414 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config/list",
415 operation: "ListFunctionEventInvokeConfigs",
416 required_query_param: None,
417 },
418 ]
419 }
420
421 async fn handle(
422 &self,
423 operation: &str,
424 input: Value,
425 ctx: &RequestContext,
426 ) -> Result<Value, AwsError> {
427 debug!(operation, "Lambda request");
428 let state = self.get_state(ctx);
429
430 match operation {
431 "CreateFunction" => operations::functions::create_function(&state, &input, ctx),
433 "GetFunction" => operations::functions::get_function(&state, &input, ctx),
434 "GetFunctionConfiguration" => {
435 operations::functions::get_function_configuration(&state, &input, ctx)
436 }
437 "DeleteFunction" => operations::functions::delete_function(&state, &input),
438 "ListFunctions" => operations::functions::list_functions(&state, &input, ctx),
439 "UpdateFunctionCode" => {
440 operations::functions::update_function_code(&state, &input, ctx)
441 }
442 "UpdateFunctionConfiguration" => {
443 operations::functions::update_function_configuration(&state, &input, ctx)
444 }
445
446 "Invoke" => operations::invocations::invoke(&state, &input, ctx),
448
449 "PublishVersion" => operations::versions::publish_version(&state, &input, ctx),
451 "ListVersionsByFunction" => {
452 operations::versions::list_versions_by_function(&state, &input, ctx)
453 }
454
455 "CreateAlias" => operations::aliases::create_alias(&state, &input, ctx),
457 "GetAlias" => operations::aliases::get_alias(&state, &input, ctx),
458 "DeleteAlias" => operations::aliases::delete_alias(&state, &input, ctx),
459 "ListAliases" => operations::aliases::list_aliases(&state, &input, ctx),
460
461 "CreateEventSourceMapping" => {
463 operations::event_source_mappings::create_event_source_mapping(&state, &input, ctx)
464 }
465 "GetEventSourceMapping" => {
466 operations::event_source_mappings::get_event_source_mapping(&state, &input, ctx)
467 }
468 "DeleteEventSourceMapping" => {
469 operations::event_source_mappings::delete_event_source_mapping(&state, &input, ctx)
470 }
471 "ListEventSourceMappings" => {
472 operations::event_source_mappings::list_event_source_mappings(&state, &input, ctx)
473 }
474 "UpdateEventSourceMapping" => {
475 operations::event_source_mappings::update_event_source_mapping(&state, &input, ctx)
476 }
477
478 "PublishLayerVersion" => operations::layers::publish_layer_version(&state, &input, ctx),
480 "ListLayers" => operations::layers::list_layers(&state, &input, ctx),
481 "ListLayerVersions" => operations::layers::list_layer_versions(&state, &input, ctx),
482 "DeleteLayerVersion" => operations::layers::delete_layer_version(&state, &input, ctx),
483
484 "CreateFunctionUrlConfig" => {
486 operations::url_configs::create_function_url_config(&state, &input, ctx)
487 }
488 "GetFunctionUrlConfig" => {
489 operations::url_configs::get_function_url_config(&state, &input, ctx)
490 }
491 "DeleteFunctionUrlConfig" => {
492 operations::url_configs::delete_function_url_config(&state, &input, ctx)
493 }
494 "ListFunctionUrlConfigs" => {
495 operations::url_configs::list_function_url_configs(&state, &input, ctx)
496 }
497
498 "TagResource" => operations::tags::tag_resource(&state, &input, ctx),
500 "UntagResource" => operations::tags::untag_resource(&state, &input, ctx),
501 "ListTags" => operations::tags::list_tags(&state, &input, ctx),
502
503 "GetPolicy" => operations::permissions::get_policy(&state, &input, ctx),
505 "AddPermission" => operations::permissions::add_permission(&state, &input, ctx),
506 "RemovePermission" => operations::permissions::remove_permission(&state, &input, ctx),
507
508 "GetAccountSettings" => {
510 operations::permissions::get_account_settings(&state, &input, ctx)
511 }
512
513 "PutFunctionEventInvokeConfig" => {
515 operations::event_invoke_configs::put_function_event_invoke_config(
516 &state, &input, ctx,
517 )
518 }
519 "GetFunctionEventInvokeConfig" => {
520 operations::event_invoke_configs::get_function_event_invoke_config(
521 &state, &input, ctx,
522 )
523 }
524 "UpdateFunctionEventInvokeConfig" => {
525 operations::event_invoke_configs::update_function_event_invoke_config(
526 &state, &input, ctx,
527 )
528 }
529 "DeleteFunctionEventInvokeConfig" => {
530 operations::event_invoke_configs::delete_function_event_invoke_config(
531 &state, &input, ctx,
532 )
533 }
534 "ListFunctionEventInvokeConfigs" => {
535 operations::event_invoke_configs::list_function_event_invoke_configs(
536 &state, &input, ctx,
537 )
538 }
539
540 "PutFunctionConcurrency" => {
542 operations::concurrency::put_function_concurrency(&state, &input, ctx)
543 }
544 "GetFunctionConcurrency" => {
545 operations::concurrency::get_function_concurrency(&state, &input, ctx)
546 }
547 "DeleteFunctionConcurrency" => {
548 operations::concurrency::delete_function_concurrency(&state, &input, ctx)
549 }
550 "PutProvisionedConcurrencyConfig" => {
551 operations::concurrency::put_provisioned_concurrency_config(&state, &input, ctx)
552 }
553 "GetProvisionedConcurrencyConfig" => {
554 operations::concurrency::get_provisioned_concurrency_config(&state, &input, ctx)
555 }
556 "DeleteProvisionedConcurrencyConfig" => {
557 operations::concurrency::delete_provisioned_concurrency_config(&state, &input, ctx)
558 }
559 "ListProvisionedConcurrencyConfigs" => {
560 operations::concurrency::list_provisioned_concurrency_configs(&state, &input, ctx)
561 }
562
563 _ => Err(AwsError::unknown_operation(operation)),
564 }
565 }
566
567 fn snapshot(&self) -> Option<Vec<u8>> {
568 self.store.snapshot_to_bytes()
569 }
570
571 fn restore(&self, data: &[u8]) -> Result<(), String> {
572 use awsim_core::Snapshottable;
573 use state::LambdaRegionSnapshot;
574
575 if let Ok(()) = self.store.restore_from_bytes(data) {
576 self.rebind_bodies();
577 return Ok(());
578 }
579
580 let legacy: LambdaStateSnapshot =
581 serde_json::from_slice(data).map_err(|e| e.to_string())?;
582 let mut by_region: std::collections::HashMap<(String, String), Vec<_>> =
583 std::collections::HashMap::new();
584 for fs in legacy.functions {
585 by_region
586 .entry((fs.account_id.clone(), fs.region.clone()))
587 .or_default()
588 .push(fs);
589 }
590 self.store.clear();
591 for ((account_id, region), functions) in by_region {
592 let snap = LambdaRegionSnapshot {
593 account_id: account_id.clone(),
594 region: region.clone(),
595 functions,
596 };
597 let (acct, reg, state) = LambdaState::from_snapshot(snap);
598 self.store.set(&acct, ®, state);
599 }
600 self.rebind_bodies();
601 Ok(())
602 }
603
604 fn iam_action(&self, operation: &str) -> Option<String> {
605 match operation {
606 "CreateFunction"
607 | "GetFunction"
608 | "GetFunctionConfiguration"
609 | "DeleteFunction"
610 | "ListFunctions"
611 | "UpdateFunctionCode"
612 | "UpdateFunctionConfiguration"
613 | "Invoke"
614 | "InvokeFunction"
615 | "InvokeAsync"
616 | "PublishVersion"
617 | "ListVersionsByFunction"
618 | "CreateAlias"
619 | "GetAlias"
620 | "DeleteAlias"
621 | "ListAliases"
622 | "UpdateAlias"
623 | "CreateEventSourceMapping"
624 | "GetEventSourceMapping"
625 | "DeleteEventSourceMapping"
626 | "ListEventSourceMappings"
627 | "UpdateEventSourceMapping"
628 | "PublishLayerVersion"
629 | "ListLayers"
630 | "ListLayerVersions"
631 | "DeleteLayerVersion"
632 | "GetLayerVersion"
633 | "CreateFunctionUrlConfig"
634 | "GetFunctionUrlConfig"
635 | "DeleteFunctionUrlConfig"
636 | "ListFunctionUrlConfigs"
637 | "UpdateFunctionUrlConfig"
638 | "TagResource"
639 | "UntagResource"
640 | "ListTags"
641 | "GetPolicy"
642 | "AddPermission"
643 | "RemovePermission"
644 | "GetAccountSettings"
645 | "PutFunctionEventInvokeConfig"
646 | "GetFunctionEventInvokeConfig"
647 | "UpdateFunctionEventInvokeConfig"
648 | "DeleteFunctionEventInvokeConfig"
649 | "ListFunctionEventInvokeConfigs"
650 | "PutFunctionConcurrency"
651 | "GetFunctionConcurrency"
652 | "DeleteFunctionConcurrency"
653 | "PutProvisionedConcurrencyConfig"
654 | "GetProvisionedConcurrencyConfig"
655 | "DeleteProvisionedConcurrencyConfig"
656 | "ListProvisionedConcurrencyConfigs" => Some(format!("lambda:{operation}")),
657 _ => None,
658 }
659 }
660
661 fn iam_resource(&self, operation: &str, input: &Value, ctx: &RequestContext) -> Option<String> {
662 let prefix = format!("arn:aws:lambda:{}:{}", ctx.region, ctx.account_id);
663 match operation {
664 "ListFunctions"
665 | "ListEventSourceMappings"
666 | "ListLayers"
667 | "GetAccountSettings"
668 | "CreateFunction"
669 | "CreateEventSourceMapping" => Some("*".to_string()),
670 "TagResource" | "UntagResource" | "ListTags" => input
671 .get("Resource")
672 .and_then(|v| v.as_str())
673 .map(|s| s.to_string()),
674 "GetEventSourceMapping" | "DeleteEventSourceMapping" | "UpdateEventSourceMapping" => {
675 input
676 .get("UUID")
677 .and_then(|v| v.as_str())
678 .map(|uuid| format!("{prefix}:event-source-mapping:{uuid}"))
679 }
680 "PublishLayerVersion" | "ListLayerVersions" => input
681 .get("LayerName")
682 .and_then(|v| v.as_str())
683 .map(|name| format!("{prefix}:layer:{name}")),
684 "GetLayerVersion" | "DeleteLayerVersion" => {
685 let name = input.get("LayerName").and_then(|v| v.as_str())?;
686 let version = input
687 .get("VersionNumber")
688 .and_then(|v| v.as_i64())
689 .unwrap_or(0);
690 Some(format!("{prefix}:layer:{name}:{version}"))
691 }
692 _ => {
693 let name = input.get("FunctionName").and_then(|v| v.as_str())?;
694 if name.starts_with("arn:") {
695 Some(name.to_string())
696 } else {
697 Some(format!("{prefix}:function:{name}"))
698 }
699 }
700 }
701 }
702}