Alright, let’s talk about the Web Application Firewall (WAF) Web ACL. This is where you get to be the bouncer for your web application, deciding which HTTP(S) requests get in and which get shown the door. The core of this bouncer’s little black book is the Web Access Control List, or Web ACL. It’s a list of rules, and it’s deceptively simple until you have to build one that doesn’t also accidentally lock you out of your own application.

Think of a Web ACL as a rulebook with a single, crucial instruction at the top: “How to decide the fate of a request if none of the other rules match.” This is your default action. You can set it to Allow or Block. I almost always start with Block for anything that’s public-facing. It’s the security equivalent of “guilty until proven innocent.” It’s saved my bacon more times than I can count.

The Three Flavors of Rules

Your Web ACL is built from rules, and these rules come in three distinct flavors, each with its own use case and, frankly, its own pricing model. AWS loves to keep you on your toes.

First, you have your individual rules. These are your custom, hand-rolled, artisanal conditions. You define a statement (like “look for SQL injection patterns in the URI”) and an action to take if it matches (like “Block”). They are powerful for hyper-specific needs but can become a management nightmare if you have hundreds of them.

Next, your own rule groups. This is just a nifty way to bundle those individual rules you created into a reusable set. Got a set of rules that protect your /api endpoints? Group ’em. Need that same set for ten different Web ACLs? Use the group. It keeps things DRY and saves you from carpal tunnel syndrome.

Finally, the star of the show for most of us: Managed Rule Groups. These are curated sets of rules written and maintained by AWS or by third-party vendors like Fortinet or CrowdStrike. They are the collective knowledge of the internet’s worst attack vectors, packaged neatly for you. You’d be insane not to use these. The AWSManagedRulesCommonRuleSet is basically a non-negotiable starting point; it covers things like SQL injection, cross-site scripting, and other common nasties. It’s like hiring a team of security experts who never sleep, take vacations, or demand a raise.

Here’s the kicker, though: managed rule groups cost money. Not a lot, but it’s per-request. So if you have a site getting a billion hits a day, the bill will make you feel things. Always check the pricing page before you deploy the “Amazon Known Bad Inputs” rule group to your production workload.

The Action Hierarchy and Rule Evaluation

This is the part that trips everyone up. Rules are evaluated in order, from top to bottom, and the first rule that matches the request and has a terminating action decides the request’s fate. Let me say that again: order matters immensely.

There are two types of actions:

  • Terminating actions: Block and Allow. These are the bouncer’s final decision. The request is either let in or thrown out. Evaluation stops immediately.
  • Non-terminating actions: Count. This is the bouncer making a note on their clipboard but letting the request proceed to the next rule. It’s incredibly useful for testing new rules without breaking anything.

The logic is simple: the Web ACL starts at the top of its list. For each rule, it checks if the request matches the conditions. If it does and the action is Block or Allow, game over. If the action is Count, it logs the match and moves on to the next rule. If the request doesn’t match the rule, it just moves on. If it gets through the entire list without a terminating match, the default action is applied.

This is why you put your most specific rules first. Got a rule that allows requests from your corporate IP to the admin panel? That should be Rule #1. Then, below it, you can have a rule that blocks all access to the admin panel. If you did it the other way around, your own admin requests would get blocked before they even reached the allow rule. I’ve done it. It’s embarrassing.

A Practical Example: Let’s Build One

Enough theory. Let’s build a Web ACL for a simple web application using CloudFormation. We’ll use a managed rule group for the heavy lifting and a custom rule to whitelist our own IP.

Resources:
  MyWebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: MyAppWebACL
      Scope: REGIONAL # Use CLOUDFRONT if using CloudFront
      DefaultAction:
        Allow: {} # For this example, we'll allow by default. You're brave.
      VisibilityConfig:
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: MyAppWebACLMetrics
      Rules:
        # Rule 1: Custom rule to allow my personal IP (non-terminating Count for demo, you'd usually use Allow)
        - Name: AllowMyIPAddress
          Priority: 0 # Top priority! Evaluated first.
          Action:
            Count: {} # Using Count so we can see it work. Change to Allow: {} for real use.
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: AllowMyIPAddressMetric
          Statement:
            IPSetReferenceStatement:
              Arn: !GetAtt MyIPSet.Arn
        # Rule 2: The AWS Managed Common Rule Set
        - Name: AWS-AWSManagedRulesCommonRuleSet
          Priority: 1
          OverrideAction:
            None: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedCommonRuleSetMetric
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesCommonRuleSet
        # Rule 3: A custom rule to block a specific path for everyone
        - Name: BlockAdminPath
          Priority: 2
          Action:
            Block: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: BlockAdminPathMetric
          Statement:
            ByteMatchStatement:
              FieldToMatch:
                UriPath: {}
              TextTransformations:
                - Priority: 0
                  Type: NONE
              SearchString: "/admin"
              PositionalConstraint: STARTS_WITH

  # The IPSet for our custom rule. Replace 192.0.2.0/24 with your actual IP.
  MyIPSet:
    Type: AWS::WAFv2::IPSet
    Properties:
      Name: MyPersonalIP
      Scope: REGIONAL
      IPAddressVersion: IPV4
      Addresses:
        - "192.0.2.0/24"

See what we did there? The highest priority rule (0) checks if the request is from my IP. If it is, it gets counted and moves on. Next, priority 1 runs the request through the managed rule set’s rules. If it finds something sketchy, it will take its own action (defined in the managed group, which is usually to block). Finally, if the request is not from my IP and not sketchy, it hits priority rule 2, which blocks anyone trying to access /admin. A request from my IP to /admin would be counted by rule 0, then likely passed by rule 1, and then blocked by rule 2. The order is absolutely critical.

Common Pitfalls and Best Practices

  • Test in Count Mode: Before you set any new custom rule to Block, set it to Count for a few days. Scour the CloudWatch logs. You will be shocked at what you find that you thought was malicious but was actually just your janky legacy application doing its thing.
  • Mind the Scope: REGIONAL is for Application Load Balancers and API Gateway. CLOUDFRONT is for CloudFront distributions. They are separate and cannot be mixed. If you create a Web ACL in the wrong scope, it simply won’t show up as an option when you try to attach it. It’s infuriating until you remember.
  • Logging is Not Automatic: You must enable logging for each Web ACL and ship the logs to an S3 bucket, CloudWatch Logs, or Kinesis Data Firehose. The visibility config in the template only controls metrics. If you don’t turn on logging, you’re flying blind when an actual attack happens. You’ll see that something was blocked, but you’ll have no idea what the payload looked like.
  • Watch Your Capacity Units: Each rule and rule group has a cost in Web ACL Capacity Units (WCUs). There’s a soft limit on the total WCUs for a Web ACL. If you go too overboard stacking dozens of managed rule groups, you might hit it. The console will usually warn you, but it’s something to be aware of.

The Web ACL is your first, best line of defense. Configure it with care, test it ruthlessly, and always, always check the logs. It’s a powerful tool, but like all powerful tools, it’s capable of causing spectacular self-inflicted wounds if you’re not paying attention.