feat(awslambda): enrich Function model with inventory fields and add 3 security checks#10381
Open
sandiyochristan wants to merge 1 commit intoprowler-cloud:masterfrom
Open
Conversation
Add event source mapping inventory and three new checks to the Lambda service that surface attack-path-relevant findings: Service model (awslambda_service.py): - Add EventSourceMapping dataclass with uuid, event_source_arn, state, batch_size, starting_position fields - Add Layer dataclass with account_id property for cross-account detection - Add DeadLetterConfig dataclass with target_arn field - Add kms_key_arn, layers, dead_letter_config, event_source_mappings fields to Function model - Add _list_event_source_mappings() that paginates the full region mapping list and associates each mapping to its function by ARN New checks: - awslambda_function_no_dead_letter_queue (medium): flags async functions with no DLQ; silent failures can mask security events - awslambda_function_using_cross_account_layers (high): flags functions using layers owned by external accounts — supply chain attack surface - awslambda_function_env_vars_not_encrypted_with_cmk (medium): flags functions with env vars but no customer-managed KMS key Tests: 11 new tests across the 3 checks; all 64 Lambda tests pass
Contributor
|
✅ Conflict Markers Resolved All conflict markers have been successfully resolved in this pull request. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10381 +/- ##
===========================================
- Coverage 56.85% 16.68% -40.17%
===========================================
Files 87 837 +750
Lines 2846 23806 +20960
===========================================
+ Hits 1618 3973 +2355
- Misses 1228 19833 +18605
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this PR exists
The `Lambda` service model collected only basic function configuration (runtime, VPC, environment variables, policy, URL config). It had no visibility into:
These four gaps map directly to real attack paths and compliance requirements. This PR closes all four with inventory enrichment + checks.
What changed
1. Service model — `awslambda_service.py`
New dataclasses added:
```python
class EventSourceMapping(BaseModel):
uuid: str
event_source_arn: str # SQS / DynamoDB stream / Kinesis / MSK ARN
state: str # Enabled, Disabled, Creating, …
batch_size: Optional[int]
starting_position: Optional[str]
class Layer(BaseModel):
arn: str
# account_id extracted from ARN field [4] — used for cross-account detection
class DeadLetterConfig(BaseModel):
target_arn: str # SQS queue ARN or SNS topic ARN
```
New fields on `Function`:
New service method — `_list_event_source_mappings(regional_client)`:
Why read `DeadLetterConfig`, `KMSKeyArn`, and `Layers` from `list_functions` rather than a separate per-function call?
All three fields are already returned by the `list_functions` paginator — adding them costs zero additional API calls. Only `event_source_mappings` requires a separate call because the data lives in a different resource type.
Backward compatibility:
All new fields have defaults (`[]` or `None`), so existing checks that do not use them are unaffected.
2. New check — `awslambda_function_no_dead_letter_queue` (severity: medium)
What it checks:
Whether the Lambda function has a Dead Letter Queue (SQS or SNS) configured for failed async invocations.
Pass/Fail logic:
```
PASS function.dead_letter_config is not None → DLQ configured, target ARN shown
PASS function has no async invocations context → still reported; DLQ is always recommended
FAIL function.dead_letter_config is None → no DLQ configured
```
Why medium and not low?
A DLQ gap in a security-relevant function (alert handler, CloudTrail processor, SIEM forwarder) means a processing failure becomes invisible — there is no way to know a security event was dropped. The blast radius is bounded (it does not by itself expose data), hence medium rather than high.
AWS API used: `list_functions` → `DeadLetterConfig.TargetArn`
Remediation: `aws lambda update-function-configuration --function-name --dead-letter-config TargetArn=`
3. New check — `awslambda_function_using_cross_account_layers` (severity: high)
What it checks:
Whether any layer attached to the function is owned by a different AWS account than the one being audited.
How cross-account is detected:
Lambda layer ARN format: `arn:aws:lambda:{region}:{account-id}:layer:{name}:{version}`
The `account_id` property on `Layer` splits on `:` and returns index `[4]`. If this differs from `awslambda_client.audited_account`, the layer is external.
Pass/Fail logic:
```
PASS function has no layers
PASS all layer ARNs have account_id == audited_account
FAIL any layer ARN has account_id != audited_account → lists all offending ARNs
```
Why high?
A Lambda layer is code injected into the function execution environment at runtime — it runs with the same IAM role, same VPC placement, same environment variables. If the external account is compromised or the layer version is silently updated, every function using it executes attacker-controlled code. This is a supply chain attack path that leads directly to IAM privilege escalation.
Attack chain: compromise external layer account → publish new layer version → all consumer functions execute attacker code with their own IAM roles → lateral movement across all attached services (RDS, S3, DynamoDB, …)
Note on AWS-published layers: AWS-managed layers (e.g., `arn:aws:lambda:…:017000801446:layer:AWSLambdaPowertoolsPythonV3…`) will also trigger this check. Prowler's mute / exception mechanism can be used to suppress known-good external layers per function.
AWS API used: `list_functions` → `Layers[].Arn`
4. New check — `awslambda_function_env_vars_not_encrypted_with_cmk` (severity: medium)
What it checks:
Whether a function that has environment variables is using a customer-managed KMS key (CMK) to encrypt them at rest.
Pass/Fail logic:
```
PASS function.environment is None or empty dict → no env vars, nothing to encrypt
PASS function.kms_key_arn is not None → CMK active, key ARN shown
FAIL function.environment is set AND kms_key_arn is None → env vars present, no CMK
```
Why not flag functions with no env vars?
An empty `environment` means there is nothing to protect. Generating a finding for a function with no env vars would be noise.
Why medium and not low?
Environment variables commonly hold connection strings, feature-flag secrets, and API endpoint configurations. Without a CMK there is no customer-controlled key rotation, no granular access auditing via KMS CloudTrail events, and no key-revocation capability. PCI-DSS 3.5, HIPAA §164.312(a)(2)(iv), and FedRAMP require customer-controlled encryption for data at rest. Medium reflects the real compliance risk while acknowledging that env vars ideally should not hold actual secrets (Secrets Manager is the right tool for that).
AWS API used: `list_functions` → `KMSKeyArn`
Remediation: `aws lambda update-function-configuration --function-name --kms-key-arn `
How tests are structured
All tests follow the existing Prowler Lambda check test pattern:
Note on moto limitations: moto 5.x does not return `DeadLetterConfig`, `KMSKeyArn`, or `Layers` in its `list_functions` response. For FAIL-branch tests (the field is absent) this is fine — the service naturally produces `None`. For PASS-branch tests, the field is set directly on the `Function` object after service initialisation. This is intentional and consistent with how other Prowler tests handle moto gaps (the check logic is still fully exercised; only the collection path for that specific field is not covered by moto).
Test counts:
Files changed
```
prowler/providers/aws/services/awslambda/awslambda_service.py ← enriched
prowler/providers/aws/services/awslambda/awslambda_function_no_dead_letter_queue/
init.py
awslambda_function_no_dead_letter_queue.py
awslambda_function_no_dead_letter_queue.metadata.json
prowler/providers/aws/services/awslambda/awslambda_function_using_cross_account_layers/
init.py
awslambda_function_using_cross_account_layers.py
awslambda_function_using_cross_account_layers.metadata.json
prowler/providers/aws/services/awslambda/awslambda_function_env_vars_not_encrypted_with_cmk/
init.py
awslambda_function_env_vars_not_encrypted_with_cmk.py
awslambda_function_env_vars_not_encrypted_with_cmk.metadata.json
tests/providers/aws/services/awslambda/awslambda_function_no_dead_letter_queue/
init.py
awslambda_function_no_dead_letter_queue_test.py
tests/providers/aws/services/awslambda/awslambda_function_using_cross_account_layers/
init.py
awslambda_function_using_cross_account_layers_test.py
tests/providers/aws/services/awslambda/awslambda_function_env_vars_not_encrypted_with_cmk/
init.py
awslambda_function_env_vars_not_encrypted_with_cmk_test.py
```
No other service, check, or file is modified.
Checklist