WAF on ALB and API Gateway
AWS managed rules, custom rate limiting, evaluation order, and attaching WAF to shared infrastructure with CDK.
- DATE:
- MAR.28.2026
- READ:
- 10 MIN
What WAF protects against
AWS WAF operates at Layer 7 — it inspects HTTP requests before they reach your application. It filters SQL injection, cross-site scripting (XSS), server-side request forgery (SSRF), local/remote file inclusion, and malformed inputs. Every rule evaluates against the request’s URI, headers, query string, and body.
WAF is not a replacement for application-level input validation. A WAF rule can block ' OR 1=1 -- in a query parameter, but it won’t catch a business logic flaw where a user modifies their own account ID in a JSON body to access someone else’s data. The principle is defense in depth: WAF stops the known attack patterns at the edge, your application validates the rest.
WAF attaches to regional resources — Application Load Balancers, API Gateway REST APIs, API Gateway HTTP APIs, AppSync, and Cognito user pools. One Web ACL can protect multiple resources, which matters when you’re sharing an ALB across services.
Managed rule groups
AWS maintains rule groups that cover common attack vectors. You add them to your Web ACL and AWS keeps the signatures current — no manual rule updates when a new CVE drops.
+--------------------+--------------------+----------+ | Rule group | Covers | Priority | +--------------------+--------------------+----------+ | AWSManagedRulesCom | OWASP Top 10 | 0 | | monRuleSet | (SQLi, XSS, SSRF, | | | | path traversal) | | +--------------------+--------------------+----------+ | AWSManagedRulesKno | Log4j/JNDI, host | 1 | | wnBadInputsRuleSet | header exploits | | +--------------------+--------------------+----------+ | AWSManagedRulesAma | Known bot/threat | 2 | | zonIpReputationLis | IPs | | | t | | | +--------------------+--------------------+----------+ | Rate-based rule | Per-IP request | 3 | | | threshold | | +--------------------+--------------------+----------+
The Common Rule Set is the workhorse — it covers the OWASP Top 10 patterns that account for the vast majority of web application attacks. The Known Bad Inputs set specifically targets payloads like ${jndi:ldap://...} that exploit deserialization vulnerabilities. The IP Reputation List blocks traffic from IPs that AWS threat intelligence has flagged as botnets, scanners, or known malicious actors.
Priority values determine evaluation order. Lower numbers evaluate first.
CDK implementation
Creating the Web ACL
The Web ACL is the container for all your rules. Set scope to REGIONAL for ALB and API Gateway resources (use CLOUDFRONT only for CloudFront distributions).
web_acl = wafv2.CfnWebACL(
self, "ServiceWebACL",
default_action=wafv2.CfnWebACL.DefaultActionProperty(allow={}),
scope="REGIONAL",
visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty(
cloud_watch_metrics_enabled=True,
metric_name="acmecorp-webacl",
sampled_requests_enabled=True,
),
rules=[
wafv2.CfnWebACL.RuleProperty(
name="AWS-AWSManagedRulesCommonRuleSet",
priority=0,
statement=wafv2.CfnWebACL.StatementProperty(
managed_rule_group_statement=wafv2.CfnWebACL.ManagedRuleGroupStatementProperty(
name="AWSManagedRulesCommonRuleSet",
vendor_name="AWS",
)
),
override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}),
visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty(
cloud_watch_metrics_enabled=True,
metric_name="acmecorp-common-rules",
sampled_requests_enabled=True,
),
),
wafv2.CfnWebACL.RuleProperty(
name="AWS-AWSManagedRulesKnownBadInputsRuleSet",
priority=1,
statement=wafv2.CfnWebACL.StatementProperty(
managed_rule_group_statement=wafv2.CfnWebACL.ManagedRuleGroupStatementProperty(
name="AWSManagedRulesKnownBadInputsRuleSet",
vendor_name="AWS",
)
),
override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}),
visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty(
cloud_watch_metrics_enabled=True,
metric_name="acmecorp-known-bad-inputs",
sampled_requests_enabled=True,
),
),
wafv2.CfnWebACL.RuleProperty(
name="AWS-AWSManagedRulesAmazonIpReputationList",
priority=2,
statement=wafv2.CfnWebACL.StatementProperty(
managed_rule_group_statement=wafv2.CfnWebACL.ManagedRuleGroupStatementProperty(
name="AWSManagedRulesAmazonIpReputationList",
vendor_name="AWS",
)
),
override_action=wafv2.CfnWebACL.OverrideActionProperty(none={}),
visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty(
cloud_watch_metrics_enabled=True,
metric_name="acmecorp-ip-reputation",
sampled_requests_enabled=True,
),
),
],
)Each managed rule group uses override_action instead of action. Setting none={} means the individual rules within the group take their default actions (Block for most). Setting count={} would override every rule in the group to Count mode — useful for testing.
Associating with ALB and API Gateway
A single Web ACL can attach to multiple resources. The association is a separate CloudFormation resource that references the Web ACL ARN and the target resource ARN.
wafv2.CfnWebACLAssociation(
self, "AlbWafAssociation",
resource_arn=shared_alb.load_balancer_arn,
web_acl_arn=web_acl.attr_arn,
)
wafv2.CfnWebACLAssociation(
self, "ApiGatewayWafAssociation",
resource_arn=f"arn:aws:apigateway:{self.region}::/restapis/{api.rest_api_id}/stages/{api.deployment_stage.stage_name}",
web_acl_arn=web_acl.attr_arn,
)For API Gateway, the resource ARN format is the stage ARN, not the API ARN. This catches people — attaching to the API ARN silently does nothing.
Evaluation order
Rules evaluate by priority, lowest number first. Each rule produces one of three outcomes:
- Allow — terminates evaluation. The request passes through.
- Block — terminates evaluation. WAF returns a 403.
- Count — non-terminating. WAF logs the match and continues evaluating the next rule.
If no rule matches, the Web ACL’s default_action applies. Setting it to allow means unmatched traffic passes through. Setting it to block creates a deny-by-default posture — only explicitly allowed traffic gets through.
The practical implication: put allow-list rules at low priority numbers. If your health check endpoint or internal monitoring IP is trusted, let it pass at priority 0 before WAF burns cycles evaluating every managed rule group against it. This reduces both latency and cost (you pay per request evaluated).
wafv2.CfnWebACL.RuleProperty(
name="AllowHealthChecks",
priority=0,
statement=wafv2.CfnWebACL.StatementProperty(
byte_match_statement=wafv2.CfnWebACL.ByteMatchStatementProperty(
search_string="/health",
field_to_match=wafv2.CfnWebACL.FieldToMatchProperty(uri_path={}),
positional_constraint="EXACTLY",
text_transformations=[
wafv2.CfnWebACL.TextTransformationProperty(priority=0, type="LOWERCASE")
],
)
),
action=wafv2.CfnWebACL.RuleActionProperty(allow={}),
visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty(
cloud_watch_metrics_enabled=True,
metric_name="acmecorp-health-check-allow",
sampled_requests_enabled=True,
),
)Rate limiting
Rate-based rules aggregate requests by source IP over a rolling 5-minute window. When an IP exceeds the threshold, WAF automatically blocks subsequent requests from that IP until the rate drops below the limit.
wafv2.CfnWebACL.RuleProperty(
name="RateLimitPerIP",
priority=3,
statement=wafv2.CfnWebACL.StatementProperty(
rate_based_statement=wafv2.CfnWebACL.RateBasedStatementProperty(
limit=2000,
aggregate_key_type="IP",
)
),
action=wafv2.CfnWebACL.RuleActionProperty(block={}),
visibility_config=wafv2.CfnWebACL.VisibilityConfigProperty(
cloud_watch_metrics_enabled=True,
metric_name="acmecorp-rate-limit",
sampled_requests_enabled=True,
),
)The limit is the maximum number of requests from a single IP in any 5-minute period. 2000 requests per 5 minutes works out to roughly 6-7 requests per second sustained — enough for legitimate users, low enough to throttle scrapers and brute-force attempts.
The minimum limit is 100. You can scope rate-based rules further with a scope_down_statement — for example, only rate-limit POST requests to /api/login to protect authentication endpoints without affecting read-heavy traffic.
Cost model
WAF pricing is straightforward but adds up when you attach it to high-traffic resources.
+--------------------+--------------------+ | Component | Cost | +--------------------+--------------------+ | Web ACL | $5/month | +--------------------+--------------------+ | Per rule | $1/month | +--------------------+--------------------+ | Requests | $0.60/million | +--------------------+--------------------+ | Bot Control | $10/month + | | (add-on) | $1/million | +--------------------+--------------------+
A typical setup with 3 managed rule groups and 1 rate-based rule costs $9/month in fixed fees ($5 for the ACL + $4 for 4 rules) plus $0.60 per million requests. At 10 million requests/month, that’s $15 total. At 100 million, it’s $69.
Each managed rule group counts as a single rule for billing purposes, regardless of how many individual rules it contains internally. Bot Control is a separate add-on with its own pricing — only add it if you’re dealing with sophisticated bot traffic that the IP Reputation List doesn’t catch.
WAF vs Shield vs Shield Advanced
These three services protect different layers and have very different cost profiles.
Shield Standard is free and automatic on every AWS account. It protects against Layer 3/4 DDoS attacks — SYN floods, UDP reflection, and other volumetric network-layer attacks. You don’t configure it. It just works.
WAF operates at Layer 7. It inspects HTTP request content and applies rules. It stops application-layer attacks — SQLi, XSS, SSRF, credential stuffing, API abuse. WAF requires configuration. It does nothing by default.
Shield Advanced costs $3,000/month per organization (not per resource) with a 1-year commitment. What you get: 24/7 access to the AWS DDoS Response Team (DRT), cost protection against DDoS scaling charges (your bill doesn’t spike during an attack), advanced CloudWatch metrics, and WAF is included at no additional charge for resources protected by Shield Advanced. The DRT can proactively configure WAF rules during an active attack.
For most workloads, Shield Standard plus WAF with managed rules is the right answer. Shield Advanced is for organizations where a successful DDoS attack has business impact measured in the tens of thousands per hour — the $36,000/year commitment pays for itself if it prevents a single significant incident.
WAF handles the application layer. Shield handles the network layer. Together they cover L3 through L7 — but neither replaces the other, and neither replaces application-level input validation.