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 "UpdateAlias" => operations::aliases::update_alias(&state, &input, ctx),
459 "DeleteAlias" => operations::aliases::delete_alias(&state, &input, ctx),
460 "ListAliases" => operations::aliases::list_aliases(&state, &input, ctx),
461
462 "CreateEventSourceMapping" => {
464 operations::event_source_mappings::create_event_source_mapping(&state, &input, ctx)
465 }
466 "GetEventSourceMapping" => {
467 operations::event_source_mappings::get_event_source_mapping(&state, &input, ctx)
468 }
469 "DeleteEventSourceMapping" => {
470 operations::event_source_mappings::delete_event_source_mapping(&state, &input, ctx)
471 }
472 "ListEventSourceMappings" => {
473 operations::event_source_mappings::list_event_source_mappings(&state, &input, ctx)
474 }
475 "UpdateEventSourceMapping" => {
476 operations::event_source_mappings::update_event_source_mapping(&state, &input, ctx)
477 }
478
479 "PublishLayerVersion" => operations::layers::publish_layer_version(&state, &input, ctx),
481 "ListLayers" => operations::layers::list_layers(&state, &input, ctx),
482 "ListLayerVersions" => operations::layers::list_layer_versions(&state, &input, ctx),
483 "GetLayerVersion" => operations::layers::get_layer_version(&state, &input, ctx),
484 "DeleteLayerVersion" => operations::layers::delete_layer_version(&state, &input, ctx),
485
486 "CreateFunctionUrlConfig" => {
488 operations::url_configs::create_function_url_config(&state, &input, ctx)
489 }
490 "GetFunctionUrlConfig" => {
491 operations::url_configs::get_function_url_config(&state, &input, ctx)
492 }
493 "DeleteFunctionUrlConfig" => {
494 operations::url_configs::delete_function_url_config(&state, &input, ctx)
495 }
496 "ListFunctionUrlConfigs" => {
497 operations::url_configs::list_function_url_configs(&state, &input, ctx)
498 }
499
500 "TagResource" => operations::tags::tag_resource(&state, &input, ctx),
502 "UntagResource" => operations::tags::untag_resource(&state, &input, ctx),
503 "ListTags" => operations::tags::list_tags(&state, &input, ctx),
504
505 "GetPolicy" => operations::permissions::get_policy(&state, &input, ctx),
507 "AddPermission" => operations::permissions::add_permission(&state, &input, ctx),
508 "RemovePermission" => operations::permissions::remove_permission(&state, &input, ctx),
509
510 "GetAccountSettings" => {
512 operations::permissions::get_account_settings(&state, &input, ctx)
513 }
514
515 "PutFunctionEventInvokeConfig" => {
517 operations::event_invoke_configs::put_function_event_invoke_config(
518 &state, &input, ctx,
519 )
520 }
521 "GetFunctionEventInvokeConfig" => {
522 operations::event_invoke_configs::get_function_event_invoke_config(
523 &state, &input, ctx,
524 )
525 }
526 "UpdateFunctionEventInvokeConfig" => {
527 operations::event_invoke_configs::update_function_event_invoke_config(
528 &state, &input, ctx,
529 )
530 }
531 "DeleteFunctionEventInvokeConfig" => {
532 operations::event_invoke_configs::delete_function_event_invoke_config(
533 &state, &input, ctx,
534 )
535 }
536 "ListFunctionEventInvokeConfigs" => {
537 operations::event_invoke_configs::list_function_event_invoke_configs(
538 &state, &input, ctx,
539 )
540 }
541
542 "PutFunctionConcurrency" => {
544 operations::concurrency::put_function_concurrency(&state, &input, ctx)
545 }
546 "GetFunctionConcurrency" => {
547 operations::concurrency::get_function_concurrency(&state, &input, ctx)
548 }
549 "DeleteFunctionConcurrency" => {
550 operations::concurrency::delete_function_concurrency(&state, &input, ctx)
551 }
552 "PutProvisionedConcurrencyConfig" => {
553 operations::concurrency::put_provisioned_concurrency_config(&state, &input, ctx)
554 }
555 "GetProvisionedConcurrencyConfig" => {
556 operations::concurrency::get_provisioned_concurrency_config(&state, &input, ctx)
557 }
558 "DeleteProvisionedConcurrencyConfig" => {
559 operations::concurrency::delete_provisioned_concurrency_config(&state, &input, ctx)
560 }
561 "ListProvisionedConcurrencyConfigs" => {
562 operations::concurrency::list_provisioned_concurrency_configs(&state, &input, ctx)
563 }
564
565 _ => Err(AwsError::unknown_operation(operation)),
566 }
567 }
568
569 fn snapshot(&self) -> Option<Vec<u8>> {
570 self.store.snapshot_to_bytes()
571 }
572
573 fn restore(&self, data: &[u8]) -> Result<(), String> {
574 use awsim_core::Snapshottable;
575 use state::LambdaRegionSnapshot;
576
577 if let Ok(()) = self.store.restore_from_bytes(data) {
578 self.rebind_bodies();
579 return Ok(());
580 }
581
582 let legacy: LambdaStateSnapshot =
583 serde_json::from_slice(data).map_err(|e| e.to_string())?;
584 let mut by_region: std::collections::HashMap<(String, String), Vec<_>> =
585 std::collections::HashMap::new();
586 for fs in legacy.functions {
587 by_region
588 .entry((fs.account_id.clone(), fs.region.clone()))
589 .or_default()
590 .push(fs);
591 }
592 self.store.clear();
593 for ((account_id, region), functions) in by_region {
594 let snap = LambdaRegionSnapshot {
595 account_id: account_id.clone(),
596 region: region.clone(),
597 functions,
598 };
599 let (acct, reg, state) = LambdaState::from_snapshot(snap);
600 self.store.set(&acct, ®, state);
601 }
602 self.rebind_bodies();
603 Ok(())
604 }
605
606 fn iam_action(&self, operation: &str) -> Option<String> {
607 match operation {
608 "CreateFunction"
609 | "GetFunction"
610 | "GetFunctionConfiguration"
611 | "DeleteFunction"
612 | "ListFunctions"
613 | "UpdateFunctionCode"
614 | "UpdateFunctionConfiguration"
615 | "Invoke"
616 | "InvokeFunction"
617 | "InvokeAsync"
618 | "PublishVersion"
619 | "ListVersionsByFunction"
620 | "CreateAlias"
621 | "GetAlias"
622 | "DeleteAlias"
623 | "ListAliases"
624 | "UpdateAlias"
625 | "CreateEventSourceMapping"
626 | "GetEventSourceMapping"
627 | "DeleteEventSourceMapping"
628 | "ListEventSourceMappings"
629 | "UpdateEventSourceMapping"
630 | "PublishLayerVersion"
631 | "ListLayers"
632 | "ListLayerVersions"
633 | "DeleteLayerVersion"
634 | "GetLayerVersion"
635 | "CreateFunctionUrlConfig"
636 | "GetFunctionUrlConfig"
637 | "DeleteFunctionUrlConfig"
638 | "ListFunctionUrlConfigs"
639 | "UpdateFunctionUrlConfig"
640 | "TagResource"
641 | "UntagResource"
642 | "ListTags"
643 | "GetPolicy"
644 | "AddPermission"
645 | "RemovePermission"
646 | "GetAccountSettings"
647 | "PutFunctionEventInvokeConfig"
648 | "GetFunctionEventInvokeConfig"
649 | "UpdateFunctionEventInvokeConfig"
650 | "DeleteFunctionEventInvokeConfig"
651 | "ListFunctionEventInvokeConfigs"
652 | "PutFunctionConcurrency"
653 | "GetFunctionConcurrency"
654 | "DeleteFunctionConcurrency"
655 | "PutProvisionedConcurrencyConfig"
656 | "GetProvisionedConcurrencyConfig"
657 | "DeleteProvisionedConcurrencyConfig"
658 | "ListProvisionedConcurrencyConfigs" => Some(format!("lambda:{operation}")),
659 _ => None,
660 }
661 }
662
663 fn iam_resource(&self, operation: &str, input: &Value, ctx: &RequestContext) -> Option<String> {
664 let prefix = format!("arn:aws:lambda:{}:{}", ctx.region, ctx.account_id);
665 match operation {
666 "ListFunctions"
667 | "ListEventSourceMappings"
668 | "ListLayers"
669 | "GetAccountSettings"
670 | "CreateFunction"
671 | "CreateEventSourceMapping" => Some("*".to_string()),
672 "TagResource" | "UntagResource" | "ListTags" => input
673 .get("Resource")
674 .and_then(|v| v.as_str())
675 .map(|s| s.to_string()),
676 "GetEventSourceMapping" | "DeleteEventSourceMapping" | "UpdateEventSourceMapping" => {
677 input
678 .get("UUID")
679 .and_then(|v| v.as_str())
680 .map(|uuid| format!("{prefix}:event-source-mapping:{uuid}"))
681 }
682 "PublishLayerVersion" | "ListLayerVersions" => input
683 .get("LayerName")
684 .and_then(|v| v.as_str())
685 .map(|name| format!("{prefix}:layer:{name}")),
686 "GetLayerVersion" | "DeleteLayerVersion" => {
687 let name = input.get("LayerName").and_then(|v| v.as_str())?;
688 let version = input
689 .get("VersionNumber")
690 .and_then(|v| v.as_i64())
691 .unwrap_or(0);
692 Some(format!("{prefix}:layer:{name}:{version}"))
693 }
694 _ => {
695 let name = input.get("FunctionName").and_then(|v| v.as_str())?;
696 if name.starts_with("arn:") {
697 Some(name.to_string())
698 } else {
699 Some(format!("{prefix}:function:{name}"))
700 }
701 }
702 }
703 }
704}