lambda-preflight 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lambda_preflight-0.1.0/LICENSE +21 -0
- lambda_preflight-0.1.0/PKG-INFO +143 -0
- lambda_preflight-0.1.0/README.md +112 -0
- lambda_preflight-0.1.0/pyproject.toml +55 -0
- lambda_preflight-0.1.0/src/lambda_preflight/__init__.py +58 -0
- lambda_preflight-0.1.0/src/lambda_preflight/events/__init__.py +21 -0
- lambda_preflight-0.1.0/src/lambda_preflight/events/builders.py +287 -0
- lambda_preflight-0.1.0/src/lambda_preflight/iam/__init__.py +32 -0
- lambda_preflight-0.1.0/src/lambda_preflight/iam/conditions.py +131 -0
- lambda_preflight-0.1.0/src/lambda_preflight/iam/evaluator.py +177 -0
- lambda_preflight-0.1.0/src/lambda_preflight/iam/intercept.py +101 -0
- lambda_preflight-0.1.0/src/lambda_preflight/iam/model.py +90 -0
- lambda_preflight-0.1.0/src/lambda_preflight/iam/puller.py +190 -0
- lambda_preflight-0.1.0/src/lambda_preflight/runtime/__init__.py +13 -0
- lambda_preflight-0.1.0/src/lambda_preflight/runtime/context.py +75 -0
- lambda_preflight-0.1.0/src/lambda_preflight/runtime/errors.py +34 -0
- lambda_preflight-0.1.0/src/lambda_preflight/runtime/harness.py +167 -0
- lambda_preflight-0.1.0/src/lambda_preflight/runtime/loader.py +94 -0
- lambda_preflight-0.1.0/src/lambda_preflight/testing/__init__.py +3 -0
- lambda_preflight-0.1.0/src/lambda_preflight/testing/fixtures.py +43 -0
- lambda_preflight-0.1.0/tests/sample_handlers.py +34 -0
- lambda_preflight-0.1.0/tests/test_events.py +85 -0
- lambda_preflight-0.1.0/tests/test_iam.py +138 -0
- lambda_preflight-0.1.0/tests/test_runtime.py +68 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Swar Borgaonkar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lambda-preflight
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Catch AWS Lambda IAM permission errors and event-shape bugs before you deploy — a local pre-flight linter and AWS-faithful runtime. No Docker, no SAM, no invocation charges.
|
|
5
|
+
Project-URL: Homepage, https://github.com/swaranshu-borgaonkar/lambda-preflight
|
|
6
|
+
Project-URL: Issues, https://github.com/swaranshu-borgaonkar/lambda-preflight/issues
|
|
7
|
+
Author: Swar Borgaonkar
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: aws,iam,lambda,linter,local,mock,preflight,pytest,serverless,testing
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Provides-Extra: aws
|
|
24
|
+
Requires-Dist: boto3>=1.26; extra == 'aws'
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: boto3>=1.26; extra == 'dev'
|
|
27
|
+
Requires-Dist: build; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: twine; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# lambda-preflight
|
|
33
|
+
|
|
34
|
+
**Catch AWS Lambda IAM permission errors and event-shape bugs before you deploy.**
|
|
35
|
+
|
|
36
|
+
Every "local lambda" tool runs your handler. `lambda-preflight` does something none of them do: it **checks your handler's AWS calls against your execution role's real IAM policies** and fails locally — so you find the missing `s3:PutObject` on your laptop, not in a CloudWatch log after a failed deploy.
|
|
37
|
+
|
|
38
|
+
No Docker. No SAM CLI. No cloud invocations, so no invocation charges.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install "lambda-preflight[aws]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## The headline: local IAM linting
|
|
47
|
+
|
|
48
|
+
Your Lambda runs as an execution *role*. When that role is missing a permission, you normally find out the slow way — deploy, invoke, read the `AccessDenied` in the logs, redeploy. `lambda-preflight` pulls your role's **real** policies once, caches them, and checks every AWS call your handler makes *before* anything leaves your machine.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from lambda_preflight import pull_role, PolicyEvaluator, intercept_boto3
|
|
52
|
+
from lambda_preflight import LambdaHarness, events
|
|
53
|
+
|
|
54
|
+
# Pull the execution role's real policies (managed + inline + boundary), cached after first call.
|
|
55
|
+
policies = pull_role("my-function-exec-role")
|
|
56
|
+
evaluator = PolicyEvaluator(policies, strict=True)
|
|
57
|
+
|
|
58
|
+
harness = LambdaHarness("app.handler", timeout=30)
|
|
59
|
+
|
|
60
|
+
# boto3 calls inside the handler are authorized against the real policy — locally.
|
|
61
|
+
with intercept_boto3(evaluator):
|
|
62
|
+
result = harness.invoke(events.sqs(body={"orderId": 123}))
|
|
63
|
+
# If app.handler calls s3:PutObject and the role can't → AccessDenied raised here,
|
|
64
|
+
# with the same reason AWS would give, before you ever deploy.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Three ways to load policies, so it works on a plane or in locked-down accounts:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from lambda_preflight import pull_role, load_role_policies, PolicySet
|
|
71
|
+
|
|
72
|
+
pull_role("my-exec-role") # 1. live pull — needs creds + IAM read, caches result
|
|
73
|
+
load_role_policies("my-exec-role") # 2. offline — reuse cache, no AWS call, no charge
|
|
74
|
+
PolicySet.from_policy_documents([{"Statement": [...]}]) # 3. BYO JSON — when you can't pull
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
It models the parts that actually decide real permissions: explicit-deny-wins, implicit deny, wildcard actions, resource matching, permission-boundary intersection, and common condition operators. What it **can't** faithfully evaluate (uncommon condition keys, SCPs, resource-based policies) it **flags** — in `strict=True` mode it raises `CannotEvaluate` rather than returning a false green. It tells you the edge of its own knowledge instead of guessing.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## The foundation: an AWS-faithful local runtime
|
|
82
|
+
|
|
83
|
+
The linter needs something real to run against, so the runtime half is built to match AWS's actual invocation contract — not a stubbed approximation.
|
|
84
|
+
|
|
85
|
+
### Schema-accurate event fixtures
|
|
86
|
+
|
|
87
|
+
The #1 cause of "worked locally, broke in AWS" is a test event whose *shape* is wrong. These builders emit payloads matching AWS's real schemas, so what you test is what you'll get. REST (v1) and HTTP (v2) API Gateway differ meaningfully — they're separate on purpose:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
events.api_gateway_v1(method="GET", path="/health")
|
|
91
|
+
events.api_gateway_v2(method="POST", path="/orders", body={"id": 1})
|
|
92
|
+
events.sqs(records=[{"a": 1}, {"b": 2}]) # real batch shape, messageAttributes, md5
|
|
93
|
+
events.sns(message={"x": 1}, subject="hi")
|
|
94
|
+
events.s3(bucket="b", key="k.txt", event_name="ObjectCreated:Put")
|
|
95
|
+
events.eventbridge(detail={"k": "v"}, detail_type="my.type", source="svc")
|
|
96
|
+
events.dynamodb_stream(keys={"id": {"S": "1"}}, event_name="INSERT")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Faithful invocation semantics
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
result = harness.invoke(event)
|
|
103
|
+
result.ok # success vs. error
|
|
104
|
+
result.payload # what the handler returned
|
|
105
|
+
result.error # {errorType, errorMessage, stackTrace} — AWS's exact envelope
|
|
106
|
+
result.cold_start # models warm-container reuse across invokes
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The `context` reproduces real fields (`aws_request_id`, `invoked_function_arn`, log names) and a live `get_remaining_time_in_millis()` countdown. Timeouts are enforced the way AWS enforces them. Lambda's environment variables are injected so handler code that reads them behaves the same.
|
|
110
|
+
|
|
111
|
+
### pytest, because CI is the point
|
|
112
|
+
|
|
113
|
+
Fixtures register automatically — fast, cloud-free, zero-charge tests:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
def test_orders(lambda_harness, lambda_events):
|
|
117
|
+
harness = lambda_harness("app.handler", timeout=5)
|
|
118
|
+
result = harness.invoke(lambda_events.sqs(body={"order": 42}))
|
|
119
|
+
assert result.ok
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## How this differs from other local-lambda tools
|
|
125
|
+
|
|
126
|
+
Tools like `python-lambda-local` and `local-aws-lambda` are **runners** — they execute your handler against an event dict you supply. That's the commodity half, and it's solved. `lambda-preflight` includes a competent runner too, but its reason to exist is what sits on top:
|
|
127
|
+
|
|
128
|
+
| | Typical local-lambda tools | lambda-preflight |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| Run a handler locally | ✅ | ✅ |
|
|
131
|
+
| Schema-accurate event fixtures | ✋ you write the dict | ✅ built-in, v1 ≠ v2 |
|
|
132
|
+
| Faithful context / timeout / cold-start / AWS-shape errors | partial | ✅ |
|
|
133
|
+
| **IAM permission linting against your real role** | ❌ | ✅ **unique** |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Honest scope
|
|
138
|
+
|
|
139
|
+
This kills the **shape / contract / runtime-semantics / identity-permission** class of "works locally, breaks in AWS" failures — which is most of the day-to-day pain. It does **not**, and a local tool cannot, reproduce cloud-only realities: cold-start latency, live concurrency, account quotas, **SCPs**, and **resource-based policies** (the last two are recorded in `PolicySet.unscoped`, never silently ignored). The IAM layer is a **pre-flight linter, not a parity guarantee**. For final confidence, run against real AWS credentials.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# lambda-preflight
|
|
2
|
+
|
|
3
|
+
**Catch AWS Lambda IAM permission errors and event-shape bugs before you deploy.**
|
|
4
|
+
|
|
5
|
+
Every "local lambda" tool runs your handler. `lambda-preflight` does something none of them do: it **checks your handler's AWS calls against your execution role's real IAM policies** and fails locally — so you find the missing `s3:PutObject` on your laptop, not in a CloudWatch log after a failed deploy.
|
|
6
|
+
|
|
7
|
+
No Docker. No SAM CLI. No cloud invocations, so no invocation charges.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install "lambda-preflight[aws]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The headline: local IAM linting
|
|
16
|
+
|
|
17
|
+
Your Lambda runs as an execution *role*. When that role is missing a permission, you normally find out the slow way — deploy, invoke, read the `AccessDenied` in the logs, redeploy. `lambda-preflight` pulls your role's **real** policies once, caches them, and checks every AWS call your handler makes *before* anything leaves your machine.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from lambda_preflight import pull_role, PolicyEvaluator, intercept_boto3
|
|
21
|
+
from lambda_preflight import LambdaHarness, events
|
|
22
|
+
|
|
23
|
+
# Pull the execution role's real policies (managed + inline + boundary), cached after first call.
|
|
24
|
+
policies = pull_role("my-function-exec-role")
|
|
25
|
+
evaluator = PolicyEvaluator(policies, strict=True)
|
|
26
|
+
|
|
27
|
+
harness = LambdaHarness("app.handler", timeout=30)
|
|
28
|
+
|
|
29
|
+
# boto3 calls inside the handler are authorized against the real policy — locally.
|
|
30
|
+
with intercept_boto3(evaluator):
|
|
31
|
+
result = harness.invoke(events.sqs(body={"orderId": 123}))
|
|
32
|
+
# If app.handler calls s3:PutObject and the role can't → AccessDenied raised here,
|
|
33
|
+
# with the same reason AWS would give, before you ever deploy.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Three ways to load policies, so it works on a plane or in locked-down accounts:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from lambda_preflight import pull_role, load_role_policies, PolicySet
|
|
40
|
+
|
|
41
|
+
pull_role("my-exec-role") # 1. live pull — needs creds + IAM read, caches result
|
|
42
|
+
load_role_policies("my-exec-role") # 2. offline — reuse cache, no AWS call, no charge
|
|
43
|
+
PolicySet.from_policy_documents([{"Statement": [...]}]) # 3. BYO JSON — when you can't pull
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It models the parts that actually decide real permissions: explicit-deny-wins, implicit deny, wildcard actions, resource matching, permission-boundary intersection, and common condition operators. What it **can't** faithfully evaluate (uncommon condition keys, SCPs, resource-based policies) it **flags** — in `strict=True` mode it raises `CannotEvaluate` rather than returning a false green. It tells you the edge of its own knowledge instead of guessing.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## The foundation: an AWS-faithful local runtime
|
|
51
|
+
|
|
52
|
+
The linter needs something real to run against, so the runtime half is built to match AWS's actual invocation contract — not a stubbed approximation.
|
|
53
|
+
|
|
54
|
+
### Schema-accurate event fixtures
|
|
55
|
+
|
|
56
|
+
The #1 cause of "worked locally, broke in AWS" is a test event whose *shape* is wrong. These builders emit payloads matching AWS's real schemas, so what you test is what you'll get. REST (v1) and HTTP (v2) API Gateway differ meaningfully — they're separate on purpose:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
events.api_gateway_v1(method="GET", path="/health")
|
|
60
|
+
events.api_gateway_v2(method="POST", path="/orders", body={"id": 1})
|
|
61
|
+
events.sqs(records=[{"a": 1}, {"b": 2}]) # real batch shape, messageAttributes, md5
|
|
62
|
+
events.sns(message={"x": 1}, subject="hi")
|
|
63
|
+
events.s3(bucket="b", key="k.txt", event_name="ObjectCreated:Put")
|
|
64
|
+
events.eventbridge(detail={"k": "v"}, detail_type="my.type", source="svc")
|
|
65
|
+
events.dynamodb_stream(keys={"id": {"S": "1"}}, event_name="INSERT")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Faithful invocation semantics
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
result = harness.invoke(event)
|
|
72
|
+
result.ok # success vs. error
|
|
73
|
+
result.payload # what the handler returned
|
|
74
|
+
result.error # {errorType, errorMessage, stackTrace} — AWS's exact envelope
|
|
75
|
+
result.cold_start # models warm-container reuse across invokes
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The `context` reproduces real fields (`aws_request_id`, `invoked_function_arn`, log names) and a live `get_remaining_time_in_millis()` countdown. Timeouts are enforced the way AWS enforces them. Lambda's environment variables are injected so handler code that reads them behaves the same.
|
|
79
|
+
|
|
80
|
+
### pytest, because CI is the point
|
|
81
|
+
|
|
82
|
+
Fixtures register automatically — fast, cloud-free, zero-charge tests:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
def test_orders(lambda_harness, lambda_events):
|
|
86
|
+
harness = lambda_harness("app.handler", timeout=5)
|
|
87
|
+
result = harness.invoke(lambda_events.sqs(body={"order": 42}))
|
|
88
|
+
assert result.ok
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## How this differs from other local-lambda tools
|
|
94
|
+
|
|
95
|
+
Tools like `python-lambda-local` and `local-aws-lambda` are **runners** — they execute your handler against an event dict you supply. That's the commodity half, and it's solved. `lambda-preflight` includes a competent runner too, but its reason to exist is what sits on top:
|
|
96
|
+
|
|
97
|
+
| | Typical local-lambda tools | lambda-preflight |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Run a handler locally | ✅ | ✅ |
|
|
100
|
+
| Schema-accurate event fixtures | ✋ you write the dict | ✅ built-in, v1 ≠ v2 |
|
|
101
|
+
| Faithful context / timeout / cold-start / AWS-shape errors | partial | ✅ |
|
|
102
|
+
| **IAM permission linting against your real role** | ❌ | ✅ **unique** |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Honest scope
|
|
107
|
+
|
|
108
|
+
This kills the **shape / contract / runtime-semantics / identity-permission** class of "works locally, breaks in AWS" failures — which is most of the day-to-day pain. It does **not**, and a local tool cannot, reproduce cloud-only realities: cold-start latency, live concurrency, account quotas, **SCPs**, and **resource-based policies** (the last two are recorded in `PolicySet.unscoped`, never silently ignored). The IAM layer is a **pre-flight linter, not a parity guarantee**. For final confidence, run against real AWS credentials.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lambda-preflight"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Catch AWS Lambda IAM permission errors and event-shape bugs before you deploy — a local pre-flight linter and AWS-faithful runtime. No Docker, no SAM, no invocation charges."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Swar Borgaonkar" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"iam",
|
|
15
|
+
"linter",
|
|
16
|
+
"preflight",
|
|
17
|
+
"aws",
|
|
18
|
+
"lambda",
|
|
19
|
+
"serverless",
|
|
20
|
+
"local",
|
|
21
|
+
"testing",
|
|
22
|
+
"mock",
|
|
23
|
+
"pytest",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.8",
|
|
31
|
+
"Programming Language :: Python :: 3.9",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Topic :: Software Development :: Testing",
|
|
36
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
37
|
+
]
|
|
38
|
+
dependencies = []
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
aws = ["boto3>=1.26"]
|
|
42
|
+
dev = ["pytest>=7.0", "boto3>=1.26", "build", "twine"]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/swaranshu-borgaonkar/lambda-preflight"
|
|
46
|
+
Issues = "https://github.com/swaranshu-borgaonkar/lambda-preflight/issues"
|
|
47
|
+
|
|
48
|
+
[project.entry-points.pytest11]
|
|
49
|
+
lambda_preflight = "lambda_preflight.testing.fixtures"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/lambda_preflight"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""lambda_preflight — catch AWS Lambda IAM and event-shape bugs before you deploy.
|
|
2
|
+
|
|
3
|
+
The headline feature is a local **IAM linter**: it evaluates the AWS calls your
|
|
4
|
+
handler makes against your execution role's *real, pulled* policies and raises
|
|
5
|
+
AccessDenied locally — catching the permission mistakes you'd otherwise only
|
|
6
|
+
discover after deploying. Nothing else in the ecosystem does this.
|
|
7
|
+
|
|
8
|
+
Around it sits an AWS-faithful local runtime so the linter has something real to
|
|
9
|
+
run against: schema-accurate event fixtures, a faithful context object, timeout
|
|
10
|
+
enforcement, cold/warm semantics, and AWS-shape error envelopes. No Docker, no
|
|
11
|
+
SAM CLI, no cloud invocations (no invocation charges).
|
|
12
|
+
|
|
13
|
+
Honest scope: this eliminates the shape/contract/runtime-semantics class of
|
|
14
|
+
"works locally, breaks in AWS" failures. It does not and cannot reproduce
|
|
15
|
+
cold-start latency, live concurrency, SCPs, resource-based policies, or
|
|
16
|
+
account quotas — those exist only in the cloud. The IAM layer is a fast
|
|
17
|
+
pre-flight linter, not a parity guarantee, and fails loud (strict mode) on
|
|
18
|
+
anything it cannot faithfully evaluate.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from . import events
|
|
22
|
+
from .iam import (
|
|
23
|
+
AccessDenied,
|
|
24
|
+
CannotEvaluate,
|
|
25
|
+
PolicyEvaluator,
|
|
26
|
+
PolicySet,
|
|
27
|
+
PullError,
|
|
28
|
+
intercept_boto3,
|
|
29
|
+
load_role_policies,
|
|
30
|
+
pull_role,
|
|
31
|
+
)
|
|
32
|
+
from .runtime import (
|
|
33
|
+
HandlerLoadError,
|
|
34
|
+
InvocationResult,
|
|
35
|
+
LambdaContext,
|
|
36
|
+
LambdaHarness,
|
|
37
|
+
LambdaTimeout,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__version__ = "0.1.0"
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"LambdaHarness",
|
|
44
|
+
"InvocationResult",
|
|
45
|
+
"LambdaContext",
|
|
46
|
+
"LambdaTimeout",
|
|
47
|
+
"HandlerLoadError",
|
|
48
|
+
"events",
|
|
49
|
+
"PolicyEvaluator",
|
|
50
|
+
"PolicySet",
|
|
51
|
+
"pull_role",
|
|
52
|
+
"load_role_policies",
|
|
53
|
+
"intercept_boto3",
|
|
54
|
+
"AccessDenied",
|
|
55
|
+
"CannotEvaluate",
|
|
56
|
+
"PullError",
|
|
57
|
+
"__version__",
|
|
58
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Schema-accurate event builders for common Lambda triggers."""
|
|
2
|
+
|
|
3
|
+
from .builders import (
|
|
4
|
+
api_gateway_v1,
|
|
5
|
+
api_gateway_v2,
|
|
6
|
+
dynamodb_stream,
|
|
7
|
+
eventbridge,
|
|
8
|
+
s3,
|
|
9
|
+
sns,
|
|
10
|
+
sqs,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"api_gateway_v1",
|
|
15
|
+
"api_gateway_v2",
|
|
16
|
+
"sqs",
|
|
17
|
+
"sns",
|
|
18
|
+
"s3",
|
|
19
|
+
"eventbridge",
|
|
20
|
+
"dynamodb_stream",
|
|
21
|
+
]
|