34.3 Deploying WAF on CloudFront, ALB, and API Gateway
Alright, let’s get our hands dirty. Deploying WAF isn’t just about flipping a switch; it’s about strategically placing your digital bouncers at the right doors. You have three main front doors: CloudFront (your CDN), an Application Load Balancer (your traffic distributor), and API Gateway (your, well, API gateway). The process is conceptually similar for each, but the devil—and the AWS console UI—is in the details.
First, the golden rule: a WAF Web ACL is a standalone object. You create it first, pour your rules into it, and then you go associate it with your resource. This is brilliant because you can write one powerful ACL and attach it to multiple resources (e.g., your ALB and your CloudFront distribution). Think of it like a single, reusable playbook for your security team.
Creating Your Web ACL: The Rulebook
You start in the AWS WAF console. Don’t bother looking for it in the CloudFront, ALB, or API Gateway sections; it has its own home. Create a new Web ACL, select the correct Region (this is critical, we’ll get to that), and give it a name that’s better than test-acl-please-ignore.
Now, for the rules. AWS provides a set of excellent managed rule groups from AWS and trusted partners. These are your first line of defense. Deploying the AWSManagedRulesCommonRuleSet is basically non-negotiable; it catches a huge swath of common junk like SQL injection and cross-site scripting. It’s like installing a deadbolt on your front door. But here’s the pro tip: always set these managed rules to Count mode first. Let it run for a day or two, check the logs (please tell me you’re enabling logs), and make sure it’s not flagging your legitimate traffic as hostile. Then, and only then, switch it to Block.
Here’s a quick Terraform example to create an ACL with that core rule set. Notice the scope field—it’s set to REGIONAL because we’re attaching it to an ALB first.
resource "aws_wafv2_web_acl" "main" {
name = "my-global-acl"
description = "A managed rule ACL for my ALB"
scope = "REGIONAL" # For ALB or API Gateway
default_action {
allow {}
}
rule {
name = "AWS-Common-Rules"
priority = 1
override_action {
count {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-Common-Rules"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "my-global-acl"
sampled_requests_enabled = true
}
}
The Regional vs. Global Gotcha (This One Bites Everyone)
This is the part AWS designers clearly cooked up on a Friday afternoon. WAFv2 has two distinct “flavors” that are completely separate in the backend, and the console does a surprisingly poor job of making this obvious:
- Regional: For Application Load Balancers, API Gateway REST/HTTP APIs, and AppSync GraphQL APIs. You must create the Web ACL in the exact same region as the resource.
- Global: For CloudFront distributions. You must create the Web ACL in the US East (N. Virginia) region only, and you must set
scope = "CLOUDFRONT".
You cannot associate a Regional Web ACL with a CloudFront distribution, and you cannot associate a Global Web ACL with an ALB. They are like two different species that refuse to interbreed. I’ve lost hours of my life to this. Don’t be me.
The Terraform code for a Global (CloudFront) ACL is nearly identical, but with two key changes:
resource "aws_wafv2_web_acl" "cloudfront_global" {
name = "my-cloudfront-acl"
description = "Managed rules for CloudFront"
scope = "CLOUDfront" # <- This is the big one
# The provider must be set to us-east-1 for this resource!
provider = aws.us-east-1
default_action {
allow {}
}
rule {
# ... same rules as before ...
}
visibility_config {
# ... same config as before ...
}
}
Attaching the ACL: Making the Introduction
Once your ACL exists, you introduce it to your resource. The method varies:
- For CloudFront: In the WAF console, find your Global ACL, and in the “Associated resources” tab, associate it with your distribution. Easy.
- For ALB: This is done from the Load Balancer console. Select your ALB, go to the “Integrated services” tab, and click “Associate WAF web ACL”. You can’t do this from the WAF side. Of course you can’t. Why would consistency be a thing?
- For API Gateway (REST API): For HTTP APIs, you do this in the WAF console. For older REST APIs, you have to go through the API Gateway console stages settings. It’s a mess. They know it’s a mess.
Best Practices and Pitfalls
- Start in Count Mode: I said it before, I’ll say it again. Blocking blind is a great way to launch a denial-of-service attack against your own users.
- Logging is Non-Optional: Enable WAF logging to S3 and/or CloudWatch Logs. The logs are rich with data about which rules are firing and why. This is your only way to tune your rules effectively.
- Mind the Capacity: Each rule in your ACL consumes WCUs (Web ACL Capacity Units). The managed rule groups tell you their consumption. It’s a big bucket (1,500 for most ACLs), but if you go adding every fancy managed rule you can find, you might hit the limit. Plan your ruleset.
- Custom Rules are Your Superpower: Once you’re comfortable, write custom rules. Is a specific endpoint being hammered? Write a rate-based rule for that path. Getting weird traffic from a specific country you don’t serve? Geo-block it. This is where WAF moves from a generic shield to your personalized armor.