Configure AWS WAF Regional Web ACLs

The aws:wafregional/webAcl:WebAcl resource, part of the Pulumi AWS provider, defines a WAF Regional Web ACL that evaluates HTTP requests against rules and takes actions based on matches. This guide focuses on three capabilities: IP-based blocking with regular rules, managed rule group integration, and request logging to Kinesis Firehose.

Web ACLs must be associated with Application Load Balancers. Rules reference IP sets, rule groups, or other WAF conditions that must exist separately. The examples are intentionally small. Combine them with your own ALB associations and rule definitions.

Block traffic from specific IP ranges

Application Load Balancers often need to block traffic from known malicious IP ranges while allowing legitimate requests through.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const ipset = new aws.wafregional.IpSet("ipset", {
    name: "tfIPSet",
    ipSetDescriptors: [{
        type: "IPV4",
        value: "192.0.7.0/24",
    }],
});
const wafrule = new aws.wafregional.Rule("wafrule", {
    name: "tfWAFRule",
    metricName: "tfWAFRule",
    predicates: [{
        dataId: ipset.id,
        negated: false,
        type: "IPMatch",
    }],
});
const wafacl = new aws.wafregional.WebAcl("wafacl", {
    name: "tfWebACL",
    metricName: "tfWebACL",
    defaultAction: {
        type: "ALLOW",
    },
    rules: [{
        action: {
            type: "BLOCK",
        },
        priority: 1,
        ruleId: wafrule.id,
        type: "REGULAR",
    }],
});
import pulumi
import pulumi_aws as aws

ipset = aws.wafregional.IpSet("ipset",
    name="tfIPSet",
    ip_set_descriptors=[{
        "type": "IPV4",
        "value": "192.0.7.0/24",
    }])
wafrule = aws.wafregional.Rule("wafrule",
    name="tfWAFRule",
    metric_name="tfWAFRule",
    predicates=[{
        "data_id": ipset.id,
        "negated": False,
        "type": "IPMatch",
    }])
wafacl = aws.wafregional.WebAcl("wafacl",
    name="tfWebACL",
    metric_name="tfWebACL",
    default_action={
        "type": "ALLOW",
    },
    rules=[{
        "action": {
            "type": "BLOCK",
        },
        "priority": 1,
        "rule_id": wafrule.id,
        "type": "REGULAR",
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/wafregional"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		ipset, err := wafregional.NewIpSet(ctx, "ipset", &wafregional.IpSetArgs{
			Name: pulumi.String("tfIPSet"),
			IpSetDescriptors: wafregional.IpSetIpSetDescriptorArray{
				&wafregional.IpSetIpSetDescriptorArgs{
					Type:  pulumi.String("IPV4"),
					Value: pulumi.String("192.0.7.0/24"),
				},
			},
		})
		if err != nil {
			return err
		}
		wafrule, err := wafregional.NewRule(ctx, "wafrule", &wafregional.RuleArgs{
			Name:       pulumi.String("tfWAFRule"),
			MetricName: pulumi.String("tfWAFRule"),
			Predicates: wafregional.RulePredicateArray{
				&wafregional.RulePredicateArgs{
					DataId:  ipset.ID(),
					Negated: pulumi.Bool(false),
					Type:    pulumi.String("IPMatch"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = wafregional.NewWebAcl(ctx, "wafacl", &wafregional.WebAclArgs{
			Name:       pulumi.String("tfWebACL"),
			MetricName: pulumi.String("tfWebACL"),
			DefaultAction: &wafregional.WebAclDefaultActionArgs{
				Type: pulumi.String("ALLOW"),
			},
			Rules: wafregional.WebAclRuleArray{
				&wafregional.WebAclRuleArgs{
					Action: &wafregional.WebAclRuleActionArgs{
						Type: pulumi.String("BLOCK"),
					},
					Priority: pulumi.Int(1),
					RuleId:   wafrule.ID(),
					Type:     pulumi.String("REGULAR"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var ipset = new Aws.WafRegional.IpSet("ipset", new()
    {
        Name = "tfIPSet",
        IpSetDescriptors = new[]
        {
            new Aws.WafRegional.Inputs.IpSetIpSetDescriptorArgs
            {
                Type = "IPV4",
                Value = "192.0.7.0/24",
            },
        },
    });

    var wafrule = new Aws.WafRegional.Rule("wafrule", new()
    {
        Name = "tfWAFRule",
        MetricName = "tfWAFRule",
        Predicates = new[]
        {
            new Aws.WafRegional.Inputs.RulePredicateArgs
            {
                DataId = ipset.Id,
                Negated = false,
                Type = "IPMatch",
            },
        },
    });

    var wafacl = new Aws.WafRegional.WebAcl("wafacl", new()
    {
        Name = "tfWebACL",
        MetricName = "tfWebACL",
        DefaultAction = new Aws.WafRegional.Inputs.WebAclDefaultActionArgs
        {
            Type = "ALLOW",
        },
        Rules = new[]
        {
            new Aws.WafRegional.Inputs.WebAclRuleArgs
            {
                Action = new Aws.WafRegional.Inputs.WebAclRuleActionArgs
                {
                    Type = "BLOCK",
                },
                Priority = 1,
                RuleId = wafrule.Id,
                Type = "REGULAR",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.wafregional.IpSet;
import com.pulumi.aws.wafregional.IpSetArgs;
import com.pulumi.aws.wafregional.inputs.IpSetIpSetDescriptorArgs;
import com.pulumi.aws.wafregional.Rule;
import com.pulumi.aws.wafregional.RuleArgs;
import com.pulumi.aws.wafregional.inputs.RulePredicateArgs;
import com.pulumi.aws.wafregional.WebAcl;
import com.pulumi.aws.wafregional.WebAclArgs;
import com.pulumi.aws.wafregional.inputs.WebAclDefaultActionArgs;
import com.pulumi.aws.wafregional.inputs.WebAclRuleArgs;
import com.pulumi.aws.wafregional.inputs.WebAclRuleActionArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var ipset = new IpSet("ipset", IpSetArgs.builder()
            .name("tfIPSet")
            .ipSetDescriptors(IpSetIpSetDescriptorArgs.builder()
                .type("IPV4")
                .value("192.0.7.0/24")
                .build())
            .build());

        var wafrule = new Rule("wafrule", RuleArgs.builder()
            .name("tfWAFRule")
            .metricName("tfWAFRule")
            .predicates(RulePredicateArgs.builder()
                .dataId(ipset.id())
                .negated(false)
                .type("IPMatch")
                .build())
            .build());

        var wafacl = new WebAcl("wafacl", WebAclArgs.builder()
            .name("tfWebACL")
            .metricName("tfWebACL")
            .defaultAction(WebAclDefaultActionArgs.builder()
                .type("ALLOW")
                .build())
            .rules(WebAclRuleArgs.builder()
                .action(WebAclRuleActionArgs.builder()
                    .type("BLOCK")
                    .build())
                .priority(1)
                .ruleId(wafrule.id())
                .type("REGULAR")
                .build())
            .build());

    }
}
resources:
  ipset:
    type: aws:wafregional:IpSet
    properties:
      name: tfIPSet
      ipSetDescriptors:
        - type: IPV4
          value: 192.0.7.0/24
  wafrule:
    type: aws:wafregional:Rule
    properties:
      name: tfWAFRule
      metricName: tfWAFRule
      predicates:
        - dataId: ${ipset.id}
          negated: false
          type: IPMatch
  wafacl:
    type: aws:wafregional:WebAcl
    properties:
      name: tfWebACL
      metricName: tfWebACL
      defaultAction:
        type: ALLOW
      rules:
        - action:
            type: BLOCK
          priority: 1
          ruleId: ${wafrule.id}
          type: REGULAR

The defaultAction sets the behavior for requests that don’t match any rules; here, unmatched requests are allowed. The rules array defines evaluation order via priority. Each rule references a ruleId (the WAF rule resource) and specifies an action (BLOCK or ALLOW). The IpSet defines the IP range to match, and the Rule’s predicates connect the IP set to the evaluation logic.

Apply managed rule groups to your Web ACL

AWS and third-party vendors publish managed rule groups that bundle common protection patterns, eliminating the need to maintain individual rule definitions.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.wafregional.WebAcl("example", {
    name: "example",
    metricName: "example",
    defaultAction: {
        type: "ALLOW",
    },
    rules: [{
        priority: 1,
        ruleId: exampleAwsWafregionalRuleGroup.id,
        type: "GROUP",
        overrideAction: {
            type: "NONE",
        },
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.wafregional.WebAcl("example",
    name="example",
    metric_name="example",
    default_action={
        "type": "ALLOW",
    },
    rules=[{
        "priority": 1,
        "rule_id": example_aws_wafregional_rule_group["id"],
        "type": "GROUP",
        "override_action": {
            "type": "NONE",
        },
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/wafregional"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := wafregional.NewWebAcl(ctx, "example", &wafregional.WebAclArgs{
			Name:       pulumi.String("example"),
			MetricName: pulumi.String("example"),
			DefaultAction: &wafregional.WebAclDefaultActionArgs{
				Type: pulumi.String("ALLOW"),
			},
			Rules: wafregional.WebAclRuleArray{
				&wafregional.WebAclRuleArgs{
					Priority: pulumi.Int(1),
					RuleId:   pulumi.Any(exampleAwsWafregionalRuleGroup.Id),
					Type:     pulumi.String("GROUP"),
					OverrideAction: &wafregional.WebAclRuleOverrideActionArgs{
						Type: pulumi.String("NONE"),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.WafRegional.WebAcl("example", new()
    {
        Name = "example",
        MetricName = "example",
        DefaultAction = new Aws.WafRegional.Inputs.WebAclDefaultActionArgs
        {
            Type = "ALLOW",
        },
        Rules = new[]
        {
            new Aws.WafRegional.Inputs.WebAclRuleArgs
            {
                Priority = 1,
                RuleId = exampleAwsWafregionalRuleGroup.Id,
                Type = "GROUP",
                OverrideAction = new Aws.WafRegional.Inputs.WebAclRuleOverrideActionArgs
                {
                    Type = "NONE",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.wafregional.WebAcl;
import com.pulumi.aws.wafregional.WebAclArgs;
import com.pulumi.aws.wafregional.inputs.WebAclDefaultActionArgs;
import com.pulumi.aws.wafregional.inputs.WebAclRuleArgs;
import com.pulumi.aws.wafregional.inputs.WebAclRuleOverrideActionArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new WebAcl("example", WebAclArgs.builder()
            .name("example")
            .metricName("example")
            .defaultAction(WebAclDefaultActionArgs.builder()
                .type("ALLOW")
                .build())
            .rules(WebAclRuleArgs.builder()
                .priority(1)
                .ruleId(exampleAwsWafregionalRuleGroup.id())
                .type("GROUP")
                .overrideAction(WebAclRuleOverrideActionArgs.builder()
                    .type("NONE")
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:wafregional:WebAcl
    properties:
      name: example
      metricName: example
      defaultAction:
        type: ALLOW
      rules:
        - priority: 1
          ruleId: ${exampleAwsWafregionalRuleGroup.id}
          type: GROUP
          overrideAction:
            type: NONE

When type is “GROUP”, the rule references a managed rule group instead of a single rule. The overrideAction determines how to handle matches within the group; “NONE” means use the group’s configured actions. Managed rule groups simplify maintenance by centralizing rule updates.

Send request logs to Kinesis Firehose

Security teams need visibility into blocked and allowed requests for compliance auditing and threat analysis.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.wafregional.WebAcl("example", {loggingConfiguration: {
    logDestination: exampleAwsKinesisFirehoseDeliveryStream.arn,
    redactedFields: {
        fieldToMatches: [
            {
                type: "URI",
            },
            {
                data: "referer",
                type: "HEADER",
            },
        ],
    },
}});
import pulumi
import pulumi_aws as aws

example = aws.wafregional.WebAcl("example", logging_configuration={
    "log_destination": example_aws_kinesis_firehose_delivery_stream["arn"],
    "redacted_fields": {
        "field_to_matches": [
            {
                "type": "URI",
            },
            {
                "data": "referer",
                "type": "HEADER",
            },
        ],
    },
})
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/wafregional"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := wafregional.NewWebAcl(ctx, "example", &wafregional.WebAclArgs{
			LoggingConfiguration: &wafregional.WebAclLoggingConfigurationArgs{
				LogDestination: pulumi.Any(exampleAwsKinesisFirehoseDeliveryStream.Arn),
				RedactedFields: &wafregional.WebAclLoggingConfigurationRedactedFieldsArgs{
					FieldToMatches: wafregional.WebAclLoggingConfigurationRedactedFieldsFieldToMatchArray{
						&wafregional.WebAclLoggingConfigurationRedactedFieldsFieldToMatchArgs{
							Type: pulumi.String("URI"),
						},
						&wafregional.WebAclLoggingConfigurationRedactedFieldsFieldToMatchArgs{
							Data: pulumi.String("referer"),
							Type: pulumi.String("HEADER"),
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.WafRegional.WebAcl("example", new()
    {
        LoggingConfiguration = new Aws.WafRegional.Inputs.WebAclLoggingConfigurationArgs
        {
            LogDestination = exampleAwsKinesisFirehoseDeliveryStream.Arn,
            RedactedFields = new Aws.WafRegional.Inputs.WebAclLoggingConfigurationRedactedFieldsArgs
            {
                FieldToMatches = new[]
                {
                    new Aws.WafRegional.Inputs.WebAclLoggingConfigurationRedactedFieldsFieldToMatchArgs
                    {
                        Type = "URI",
                    },
                    new Aws.WafRegional.Inputs.WebAclLoggingConfigurationRedactedFieldsFieldToMatchArgs
                    {
                        Data = "referer",
                        Type = "HEADER",
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.wafregional.WebAcl;
import com.pulumi.aws.wafregional.WebAclArgs;
import com.pulumi.aws.wafregional.inputs.WebAclLoggingConfigurationArgs;
import com.pulumi.aws.wafregional.inputs.WebAclLoggingConfigurationRedactedFieldsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new WebAcl("example", WebAclArgs.builder()
            .loggingConfiguration(WebAclLoggingConfigurationArgs.builder()
                .logDestination(exampleAwsKinesisFirehoseDeliveryStream.arn())
                .redactedFields(WebAclLoggingConfigurationRedactedFieldsArgs.builder()
                    .fieldToMatches(                    
                        WebAclLoggingConfigurationRedactedFieldsFieldToMatchArgs.builder()
                            .type("URI")
                            .build(),
                        WebAclLoggingConfigurationRedactedFieldsFieldToMatchArgs.builder()
                            .data("referer")
                            .type("HEADER")
                            .build())
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:wafregional:WebAcl
    properties:
      loggingConfiguration:
        logDestination: ${exampleAwsKinesisFirehoseDeliveryStream.arn}
        redactedFields:
          fieldToMatches:
            - type: URI
            - data: referer
              type: HEADER

The loggingConfiguration property streams request logs to a Kinesis Firehose Delivery Stream. The logDestination must point to a stream whose name begins with “aws-waf-logs-” per AWS requirements. The redactedFields property removes sensitive data (like URIs or specific headers) from logs before delivery.

Beyond these examples

These snippets focus on specific Web ACL features: IP-based blocking with regular rules, managed rule group integration, and request logging to Kinesis Firehose. They’re intentionally minimal rather than full WAF deployments.

The examples rely on pre-existing infrastructure such as Application Load Balancers, Kinesis Firehose Delivery Streams, and managed rule groups. They focus on Web ACL configuration rather than provisioning the surrounding infrastructure.

To keep things focused, common WAF patterns are omitted, including:

  • Web ACL association with Application Load Balancers
  • Rate-based rules for DDoS protection
  • String and regex matching conditions
  • Size constraint and SQL injection rules

These omissions are intentional: the goal is to illustrate how each Web ACL feature is wired, not provide drop-in security modules. See the WAF Regional WebAcl resource reference for all available configuration options.

Let's configure AWS WAF Regional Web ACLs

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Setup
What properties can't be changed after creating a Web ACL?
The name and metricName properties are immutable. Changing either will force recreation of the Web ACL.
Do I need to specify a region for my Web ACL?
The region property is required but defaults to the region configured in your provider.
What's required to create a basic Web ACL?
You must provide name, metricName, and defaultAction (either ALLOW or BLOCK). Rules are optional.
Rules & Actions
What's the difference between REGULAR and GROUP rules?
REGULAR rules use the action property to specify ALLOW or BLOCK. GROUP rules use overrideAction instead, typically set to NONE to use the group’s configured actions.
Can I mix REGULAR and GROUP rules in the same Web ACL?
Yes, the rules array can contain both types. Use type: "REGULAR" with action or type: "GROUP" with overrideAction.
Logging & Monitoring
Why isn't my Kinesis Firehose stream working with WAF logging?
Kinesis Firehose Delivery Stream names must begin with aws-waf-logs- for WAF logging to work.
How do I redact sensitive fields from WAF logs?
Configure loggingConfiguration.redactedFields.fieldToMatches with field types like URI or HEADER (with optional data for header names).

Using a different cloud?

Explore security guides for other cloud providers: