Overview
AWS WAF (Web Application Firewall) helps protect your web applications from common web exploits and bots that could affect availability, compromise security, or consume excessive resources.Core Concepts
WAF Components
Rule Types
Creating a Web ACL
Basic Setup
Copy
# Create IP set
aws wafv2 create-ip-set \
--name blocked-ips \
--scope REGIONAL \
--ip-address-version IPV4 \
--addresses 192.0.2.0/24 203.0.113.0/24
# Create Web ACL
aws wafv2 create-web-acl \
--name my-web-acl \
--scope REGIONAL \
--default-action Allow={} \
--rules file://rules.json \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=MyWebACL
# Associate with ALB
aws wafv2 associate-web-acl \
--web-acl-arn arn:aws:wafv2:us-east-1:123456789012:regional/webacl/my-web-acl/... \
--resource-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/...
Terraform Configuration
Copy
# IP Set for blocking
resource "aws_wafv2_ip_set" "blocked_ips" {
name = "blocked-ips"
description = "IP addresses to block"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = [
"192.0.2.0/24",
"203.0.113.0/24"
]
tags = {
Environment = "production"
}
}
# Regex pattern set for SQL injection
resource "aws_wafv2_regex_pattern_set" "sql_patterns" {
name = "sql-injection-patterns"
scope = "REGIONAL"
regular_expression {
regex_string = "(?i)(union.*select|insert.*into|delete.*from)"
}
regular_expression {
regex_string = "(?i)(exec.*xp_|execute.*sp_)"
}
}
# Web ACL
resource "aws_wafv2_web_acl" "main" {
name = "production-web-acl"
scope = "REGIONAL"
default_action {
allow {}
}
# Rule 1: Rate limiting
rule {
name = "rate-limit-rule"
priority = 0
action {
block {}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimitRule"
sampled_requests_enabled = true
}
}
# Rule 2: Block specific IPs
rule {
name = "block-bad-ips"
priority = 1
action {
block {
custom_response {
response_code = 403
}
}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.blocked_ips.arn
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlockBadIPs"
sampled_requests_enabled = true
}
}
# Rule 3: Geo blocking
rule {
name = "geo-block"
priority = 2
action {
block {}
}
statement {
geo_match_statement {
country_codes = ["KP", "IR"] # North Korea, Iran
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "GeoBlock"
sampled_requests_enabled = true
}
}
# Rule 4: AWS Managed Rules - Core Rule Set
rule {
name = "aws-managed-core-rule-set"
priority = 3
override_action {
none {}
}
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesCommonRuleSet"
# Exclude specific rules if needed
rule_action_override {
action_to_use {
count {}
}
name = "SizeRestrictions_BODY"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCommonRuleSet"
sampled_requests_enabled = true
}
}
# Rule 5: SQL Injection protection
rule {
name = "sql-injection-protection"
priority = 4
action {
block {}
}
statement {
or_statement {
statement {
sqli_match_statement {
field_to_match {
query_string {}
}
text_transformation {
priority = 0
type = "URL_DECODE"
}
text_transformation {
priority = 1
type = "HTML_ENTITY_DECODE"
}
}
}
statement {
sqli_match_statement {
field_to_match {
body {
oversize_handling = "CONTINUE"
}
}
text_transformation {
priority = 0
type = "URL_DECODE"
}
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "SQLInjectionProtection"
sampled_requests_enabled = true
}
}
# Rule 6: XSS protection
rule {
name = "xss-protection"
priority = 5
action {
block {}
}
statement {
xss_match_statement {
field_to_match {
body {
oversize_handling = "CONTINUE"
}
}
text_transformation {
priority = 0
type = "URL_DECODE"
}
text_transformation {
priority = 1
type = "HTML_ENTITY_DECODE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "XSSProtection"
sampled_requests_enabled = true
}
}
# Rule 7: Bot Control
rule {
name = "bot-control"
priority = 6
override_action {
none {}
}
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesBotControlRuleSet"
managed_rule_group_configs {
aws_managed_rules_bot_control_rule_set {
inspection_level = "COMMON" # or "TARGETED"
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BotControl"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "ProductionWebACL"
sampled_requests_enabled = true
}
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
# Associate with ALB
resource "aws_wafv2_web_acl_association" "alb" {
resource_arn = aws_lb.main.arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}
# Associate with CloudFront (requires global scope)
resource "aws_wafv2_web_acl" "cloudfront" {
provider = aws.us-east-1 # CloudFront requires us-east-1
name = "cloudfront-web-acl"
scope = "CLOUDFRONT"
default_action {
allow {}
}
# Similar rules as above...
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CloudFrontWebACL"
sampled_requests_enabled = true
}
}
Advanced Features
Bot Control
Copy
Bot Control Use Cases:
E-commerce:
- Prevent inventory hoarding
- Stop price scraping
- Block scalper bots
APIs:
- Rate limit automated requests
- Verify API consumers
- Prevent abuse
Content Sites:
- Allow search engine crawlers
- Block content scrapers
- Protect copyrighted material
Authentication:
- Prevent credential stuffing
- Block brute force attempts
- Protect login endpoints
CAPTCHA Challenge
Copy
# CAPTCHA configuration
resource "aws_wafv2_web_acl" "with_captcha" {
name = "captcha-enabled-acl"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "captcha-for-suspicious-requests"
priority = 0
action {
captcha {
custom_request_handling {
insert_header {
name = "x-waf-captcha"
value = "challenged"
}
}
}
}
statement {
rate_based_statement {
limit = 100
aggregate_key_type = "IP"
}
}
captcha_config {
immunity_time_property {
immunity_time = 300 # 5 minutes immunity after solving
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CAPTCHARule"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CAPTCHAWebACL"
sampled_requests_enabled = true
}
}
Custom Response
Copy
resource "aws_wafv2_web_acl" "custom_response" {
name = "custom-response-acl"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "block-with-custom-response"
priority = 0
action {
block {
custom_response {
response_code = 403
custom_response_body_key = "blocked_message"
response_header {
name = "x-block-reason"
value = "rate-limit-exceeded"
}
}
}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CustomResponseRule"
sampled_requests_enabled = true
}
}
custom_response_body {
key = "blocked_message"
content = jsonencode({
error = "Rate limit exceeded"
message = "Too many requests. Please try again later."
code = 403
})
content_type = "APPLICATION_JSON"
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CustomResponseWebACL"
sampled_requests_enabled = true
}
}
Logging and Monitoring
Enable Logging
Copy
# S3 bucket for WAF logs
resource "aws_s3_bucket" "waf_logs" {
bucket = "my-waf-logs-${data.aws_caller_identity.current.account_id}"
}
resource "aws_s3_bucket_public_access_block" "waf_logs" {
bucket = aws_s3_bucket.waf_logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Kinesis Data Firehose for WAF logs
resource "aws_kinesis_firehose_delivery_stream" "waf_logs" {
name = "aws-waf-logs-delivery-stream"
destination = "extended_s3"
extended_s3_configuration {
role_arn = aws_iam_role.firehose.arn
bucket_arn = aws_s3_bucket.waf_logs.arn
prefix = "waf-logs/"
compression_format = "GZIP"
cloudwatch_logging_options {
enabled = true
log_group_name = aws_cloudwatch_log_group.waf_logs.name
log_stream_name = "S3Delivery"
}
}
}
# Enable WAF logging
resource "aws_wafv2_web_acl_logging_configuration" "main" {
resource_arn = aws_wafv2_web_acl.main.arn
log_destination_configs = [aws_kinesis_firehose_delivery_stream.waf_logs.arn]
redacted_fields {
single_header {
name = "authorization"
}
}
redacted_fields {
single_header {
name = "cookie"
}
}
logging_filter {
default_behavior = "KEEP"
filter {
behavior = "KEEP"
condition {
action_condition {
action = "BLOCK"
}
}
requirement = "MEETS_ANY"
}
}
}
# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "waf_logs" {
name = "/aws/waf/logs"
retention_in_days = 30
}
CloudWatch Metrics and Alarms
Copy
# Alarm for high block rate
resource "aws_cloudwatch_metric_alarm" "waf_high_block_rate" {
alarm_name = "waf-high-block-rate"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "BlockedRequests"
namespace = "AWS/WAFV2"
period = "300"
statistic = "Sum"
threshold = "1000"
alarm_description = "This metric monitors WAF blocked requests"
alarm_actions = [aws_sns_topic.security_alerts.arn]
dimensions = {
WebACL = aws_wafv2_web_acl.main.name
Region = data.aws_region.current.name
Rule = "ALL"
}
}
# Alarm for rate limit rule triggers
resource "aws_cloudwatch_metric_alarm" "rate_limit_triggers" {
alarm_name = "waf-rate-limit-triggers"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "BlockedRequests"
namespace = "AWS/WAFV2"
period = "60"
statistic = "Sum"
threshold = "100"
alarm_description = "Rate limiting is being triggered frequently"
alarm_actions = [aws_sns_topic.security_alerts.arn]
dimensions = {
WebACL = aws_wafv2_web_acl.main.name
Region = data.aws_region.current.name
Rule = "rate-limit-rule"
}
}
Best Practices
Security Checklist
Copy
WAF Implementation Checklist:
Initial Setup:
☐ Enable AWS Managed Rules Core Rule Set
☐ Configure rate limiting
☐ Set up geo-blocking if needed
☐ Enable logging to S3/CloudWatch
☐ Create CloudWatch alarms
Testing Phase:
☐ Test all rules in Count mode first
☐ Review sampled requests
☐ Analyze false positives
☐ Tune rule sensitivity
☐ Document exceptions
Production:
☐ Switch rules to Block mode
☐ Enable bot control
☐ Configure custom responses
☐ Set up CAPTCHA for suspicious traffic
☐ Integrate with Security Hub
Ongoing:
☐ Weekly review of blocked requests
☐ Monthly rule effectiveness review
☐ Update IP sets as needed
☐ Monitor for new threat patterns
Cost Optimization
Copy
Pricing Structure:
Web ACL:
- $5.00 per month per Web ACL
Rules:
- $1.00 per month per rule
Requests:
- $0.60 per million requests
Bot Control:
- COMMON: $10.00 per million requests
- TARGETED: $16.00 per million requests
CAPTCHA:
- $0.40 per 1,000 challenge attempts
Optimization Tips:
- Consolidate rules where possible
- Use rule groups efficiently
- Start with Common bot control level
- Monitor request patterns
- Use count mode for testing
- Review unused rules monthly