A Terraform module for deploying AWS WAF (Web Application Firewall) with automated Lambda-based threat detection and IP reputation management.
- Overview
- Quick Start: Update Lambda
- Features
- How It Works
- Prerequisites
- Usage
- Architecture
- WAF Rules
- Inputs
- Outputs
- Lambda Build Process
- Versioning
- Documentation
- File Structure
- Contributing
- License
- References
This module deploys a complete WAF solution including:
- Web ACL with OWASP Top 10 protection rules
- Log Parser Lambda - Analyzes WAF logs and blocks malicious IPs
- Reputation Lists Parser Lambda - Syncs external IP threat intelligence
flowchart LR
subgraph Module["This Module"]
WAF[AWS WAF]
Lambda[Lambda Functions]
end
subgraph External["Consumer Creates"]
Protected[CloudFront/ALB/API GW]
end
Internet((Internet)) --> Protected
Protected -.->|Associate| WAF
WAF -->|Logs| Lambda
Lambda -->|Block IPs| WAF
style Module fill:#d4edda,stroke:#28a745,color:#155724
style External fill:#e2e3e5,stroke:#6c757d,color:#383d41
classDef wafNode fill:#FF9900,stroke:#232F3E,color:white
classDef extNode fill:#6c757d,stroke:#495057,color:white
class WAF,Lambda wafNode
class Protected extNode
linkStyle default stroke:#333,stroke-width:2px
Note: This module creates the WAF and outputs its ARN. You must create your own CloudFront/ALB/API Gateway and associate them using
aws_wafv2_web_acl_association.
To update WAF Lambda packages to a new upstream version:
gh workflow run "Build WAF Lambda Packages" \
-f upstream_ref=v4.1.2 \
-f version_bump=patchSee docs/QUICKSTART.md for the full step-by-step guide.
| Feature | Description |
|---|---|
| OWASP Protection | SQL injection, XSS, path traversal, and more |
| Automated Blocking | Lambda functions automatically block malicious IPs |
| Reputation Lists | Integration with external IP blocklists |
| Multi-Scope | Works with CloudFront (edge) and regional resources |
The log_parser Lambda function provides reactive threat detection:
- Trigger: WAF logs are written to S3, which sends a notification to SNS
- Analysis: Lambda parses the logs looking for:
- HTTP Flood attacks - IPs making excessive requests (DDoS patterns)
- Scanners/Probes - IPs triggering 4xx errors (vulnerability scanning)
- Action: Malicious IPs are added to WAF IP Sets, blocking future requests
S3 (WAF Logs) → SNS → log_parser Lambda → Updates IP Sets → WAF blocks IP
The reputation_lists_parser Lambda provides proactive threat prevention:
- Trigger: CloudWatch Events runs the Lambda hourly
- Fetch: Downloads known-bad IP lists from threat intelligence sources:
- Spamhaus DROP - Known spammer networks
- Spamhaus EDROP - Extended drop list
- Tor Exit Nodes - Anonymous proxy exits
- Emerging Threats - Community blocklist
- Action: Updates WAF IP Sets to block these IPs before they attack
CloudWatch (hourly) → reputation_lists_parser Lambda → Fetches lists → Updates IP Sets
| IP Set | Updated By | Purpose |
|---|---|---|
HTTPFloodSetIPV4/V6 |
log_parser | Blocks flood attackers |
ScannersProbesSetIPV4/V6 |
log_parser | Blocks vulnerability scanners |
IPReputationListsSetIPV4/V6 |
reputation_lists_parser | Blocks known-bad IPs |
Before using this module, you must create:
resource "aws_s3_bucket" "waf_logs" {
bucket = "my-app-waf-logs"
}# SQS Queue for failed Lambda invocations
resource "aws_sqs_queue" "waf_dlq" {
name = "my-app-waf-dlq"
# Optional: encrypt with KMS
# kms_master_key_id = aws_kms_key.main.id
}
# IAM Policy allowing Lambda to send to DLQ
resource "aws_iam_policy" "waf_dlq" {
name = "my-app-waf-dlq-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["sqs:SendMessage"]
Resource = [aws_sqs_queue.waf_dlq.arn]
}]
})
}resource "aws_kms_key" "waf" {
description = "KMS key for WAF encryption"
}module "waf" {
source = "git@github.com:datastreamapp/terraform-waf-module?ref=v3.0.0"
scope = "REGIONAL" # or "CLOUDFRONT"
name = "my-app"
logging_bucket = aws_s3_bucket.waf_logs.id
dead_letter_arn = aws_sqs_queue.waf_dlq.arn
dead_letter_policy_arn = aws_iam_policy.waf_dlq.arn
# Optional KMS encryption
# kms_master_key_id = aws_kms_key.waf.id
# kms_master_key_arn = aws_kms_key.waf.arn
}See docs/ARCHITECTURE.md for detailed architecture diagrams.
flowchart TB
subgraph Module["This Module Creates"]
subgraph WAF["AWS WAF"]
ACL[Web ACL]
Rules[WAF Rules]
end
subgraph Lambda["Lambda Functions"]
LP[("log_parser")]
RP[("reputation_lists_parser")]
end
subgraph Storage["Storage"]
IPSet[(WAF IP Sets)]
end
subgraph Triggers["Triggers"]
SNS[SNS Topic]
CW[CloudWatch Events]
end
Output[/"Output: WAF ARN"/]
end
subgraph External["Consumer Responsibility"]
S3[(S3 Logs Bucket)]
CF[CloudFront]
ALB[ALB]
APIGW[API Gateway]
end
CF & ALB & APIGW -.->|"Associate WAF"| ACL
ACL --> Rules
S3 --> SNS
SNS --> LP
LP --> IPSet
CW -->|Hourly| RP
RP --> IPSet
IPSet --> Rules
ACL --> Output
style Module fill:#d4edda,stroke:#28a745,color:#155724
style External fill:#e2e3e5,stroke:#6c757d,color:#383d41
style WAF fill:#fff3cd,stroke:#ffc107,color:#856404
style Lambda fill:#fff3cd,stroke:#FF9900,color:#856404
style Storage fill:#cce5ff,stroke:#004085,color:#004085
style Triggers fill:#f8d7da,stroke:#721c24,color:#721c24
classDef wafNode fill:#FF9900,stroke:#232F3E,color:white
classDef lambdaNode fill:#FF9900,stroke:#232F3E,color:white
classDef storageNode fill:#3B48CC,stroke:#232F3E,color:white
classDef triggerNode fill:#E7157B,stroke:#232F3E,color:white
classDef externalNode fill:#6c757d,stroke:#495057,color:white
classDef outputNode fill:#28a745,stroke:#1e7e34,color:white
class ACL,Rules wafNode
class LP,RP lambdaNode
class IPSet,S3 storageNode
class SNS,CW triggerNode
class CF,ALB,APIGW externalNode
class Output outputNode
linkStyle default stroke:#333,stroke-width:2px
See docs/ARCHITECTURE.md for code references proving each element.
| Lambda | Trigger | Purpose |
|---|---|---|
log_parser |
SNS (from S3 logs) | Parses WAF logs, blocks suspicious IPs |
reputation_lists_parser |
CloudWatch (hourly) | Syncs external IP reputation lists |
Runtime: Python 3.12 on Amazon Linux 2023
The upstream aws-waf-security-automations
specifies python = ~3.12 in their pyproject.toml. Using the same Python version ensures Poetry
dependency resolution, pip installs, and runtime behavior all match upstream's tested configuration
exactly — no workarounds needed.
See docs/DECISIONS.md for detailed rationale.
Web ACL
|- Blacklist Group
| |- Bad Bot Rule
| |- Blacklist Rule
| |- HTTP Flood Rule
| |- Reputation List Rule
| |- Scanner Probes Rule
|- OWASP Group
| |- Admin URL Rule
| |- Auth Token Rule
| |- CSRF Rule
| |- Paths Rule
| |- Server Side Include Rule
| |- Size Restriction Rule
| |- SQL Injection Rule
| |- XSS Rule
|- Whitelist Rule
| Name | Description | Type | Default |
|---|---|---|---|
scope |
WAF scope (REGIONAL or CLOUDFRONT) |
string |
"CLOUDFRONT" |
name |
Application name | string |
required |
defaultAction |
Default action (ALLOW or DENY) |
string |
"DENY" |
logging_bucket |
S3 bucket for logs | string |
required |
See variables.tf for full list of inputs.
| Name | Description |
|---|---|
id |
WAF Web ACL ID |
arn |
WAF Web ACL ARN |
Lambda packages are built automatically from aws-solutions/aws-waf-security-automations.
Before triggering a build, check the upstream changelog for available versions:
- Upstream Changelog: https://github.com/aws-solutions/aws-waf-security-automations/blob/main/CHANGELOG.md
| Field | Description |
|---|---|
| Current Default | v4.1.2 (configured in workflow) |
| Where to Check | Upstream CHANGELOG.md |
flowchart LR
A[Trigger Workflow] --> B[Build in Docker]
B --> C[Tests - Positive and Negative]
C --> D[Security Scan]
D --> E[Commit zips to lambda/]
E --> F[Create PR]
F --> G{{"Review PR and Approve to Merge Packages"}}
G --> H[Tag Release]
linkStyle default stroke:#333,stroke-width:2px
To update Lambda packages with a new upstream version:
- Go to Actions tab in GitHub
- Select Build WAF Lambda Packages workflow
- Click Run workflow button
- Configure the workflow inputs:
| Input | Description | Example |
|---|---|---|
| upstream_ref | Tag from upstream repo to build from | v4.1.2 (default), v4.1.0, v4.0.3 |
| version_bump | How to bump this module's version | none, patch, minor, major |
- Click Run workflow to start the build
- Review and merge the generated PR
- Create release tag after merge (if version bump was requested)
| Bump Type | When to Use | Example |
|---|---|---|
none |
Testing build, no release planned | Local validation |
patch |
Security updates, bug fixes from upstream | v4.1.2 → v4.1.3 security patch |
minor |
New features, dependency updates | v4.0.x → v4.1.0 feature release |
major |
Breaking changes, Python runtime upgrade | Python 3.9 → 3.12 |
The workflow is defined in .github/workflows/build-lambda-packages.yml:
inputs:
upstream_ref:
description: 'Upstream repo tag (e.g., v4.1.2)'
default: 'v4.1.2' # Update this to change default version
version_bump:
description: 'Version bump type for this release'
options: ['none', 'patch', 'minor', 'major']To change the default upstream version, edit the default value in the workflow file.
# Clone upstream source
git clone --depth 1 --branch v4.1.2 \
https://github.com/aws-solutions/aws-waf-security-automations.git upstream
# Build Docker image
docker build -t lambda-builder -f scripts/Dockerfile.lambda-builder scripts/
# Build packages
docker run --rm \
-v $(pwd)/upstream:/upstream:ro \
-v $(pwd)/lambda:/output \
lambda-builder log_parser /upstream /output
docker run --rm \
-v $(pwd)/upstream:/upstream:ro \
-v $(pwd)/lambda:/output \
lambda-builder reputation_lists_parser /upstream /outputThe build process includes comprehensive tests:
Positive Tests:
- Zip file exists and not empty
- Handler file present
- Size under 50MB Lambda limit
- Required shared libraries included
- Security scan (pip-audit)
Negative Tests:
- No
__pycache__directories - No
.pycbytecode files - Zip integrity verified
- Import validation passes
This project follows Semantic Versioning.
| Version Type | When to Use |
|---|---|
| major | Breaking changes (Python upgrade, API changes) |
| minor | New features, dependency updates |
| patch | Bug fixes, security patches |
Current version: See docs/CHANGELOG.md
| Document | Description |
|---|---|
| README.md | This file |
| docs/QUICKSTART.md | How to update WAF Lambda packages |
| docs/CHANGELOG.md | Version history and decisions |
| docs/TODOLIST-801.md | Implementation task tracking |
| docs/RETROSPECTIVE.md | Lessons learned, checklists, and governance |
| docs/ARCHITECTURE.md | Architecture diagrams |
| docs/TESTING.md | Testing guide |
terraform-waf-module/
|- .github/
| |- workflows/
| |- build-lambda-packages.yml # Lambda build workflow
| |- test.yml # CI/CD test workflow
|- docs/
| |- ARCHITECTURE.md # Architecture diagrams
| |- CHANGELOG.md # Version history
| |- QUICKSTART.md # How to update Lambda packages
| |- RETROSPECTIVE.md # Lessons learned
| |- TESTING.md # Testing guide
| |- TODOLIST-801.md # Implementation task tracking
|- lambda/
| |- log_parser.zip # Built artifact
| |- reputation_lists_parser.zip # Built artifact
| |- LICENSE.txt
|- scripts/
| |- Dockerfile.lambda-builder # Build environment
| |- build-lambda.sh # Build script
|- lambda.log-parser.tf # Lambda TF config
|- lambda.reputation-list.tf # Lambda TF config
|- main.tf # WAF Web ACL
|- Makefile # Build and test automation
|- README.md # Project documentation
- Create feature branch from
master - Make changes
- Create PR for review
- Merge after approval
Apache 2.0 - See LICENSE