Skip to content

Commit

Permalink
Add support for IP sets and rate limiting (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcincuber authored Oct 5, 2020
1 parent 08f7b3f commit 88624ab
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repos:
args: ['--allow-missing-credentials']
- id: trailing-whitespace
- repo: git://github.com/antonbabenko/pre-commit-terraform
rev: v1.31.0
rev: v1.43.0
hooks:
- id: terraform_fmt
- id: terraform_docs
Expand Down
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

Terraform module to configure WAF Web ACL V2 for Application Load Balancer or Cloudfront distribution.

Module supports all AWS managed rules defained in https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html.
Supported WAF v2 components:

- Module supports all AWS managed rules defained in https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html.
- Associating WAFv2 ACL with one or more Application Load Balancers (ALB)
- Blocking IP Sets
- Rate limiting IPs

## Terraform versions

Terraform 0.12. Pin module version to `~> v1.0`. Submit pull-requests to `master` branch.
Terraform 0.12 and 0.13. Pin module version to `~> v1.0`. Submit pull-requests to `master` branch.

## Usage

Expand All @@ -15,7 +20,7 @@ Please pin down version of this module to exact version.
```hcl
module "waf" {
source = "umotif-public/waf-webaclv2/aws"
version = "~> 1.4.0"
version = "~> 1.5.0"
name_prefix = "test-waf-setup"
alb_arn = module.alb.arn
Expand All @@ -27,7 +32,7 @@ module "waf" {
allow_default_action = true # set to allow if not specified
visibility_config = {
metric_name = "test-waf-setup-waf-main-metrics"
metric_name = "test-waf-setup-waf-main-metrics"
}
rules = [
Expand Down Expand Up @@ -58,7 +63,7 @@ module "waf" {
override_action = "count"
visibility_config = {
metric_name = "AWSManagedRulesKnownBadInputsRuleSet-metric"
metric_name = "AWSManagedRulesKnownBadInputsRuleSet-metric"
}
managed_rule_group_statement = {
Expand Down Expand Up @@ -96,7 +101,7 @@ module "waf" {
provider "aws" {
alias = "us-east"
version = "~> 2.68"
version = ">= 2.70"
region = "us-east-1"
}
Expand All @@ -106,7 +111,7 @@ module "waf" {
}
source = "umotif-public/waf-webaclv2/aws"
version = "~> 1.4.0"
version = "~> 1.5.0"
name_prefix = "test-waf-setup-cloudfront"
scope = "CLOUDFRONT"
Expand All @@ -129,6 +134,7 @@ Importantly, make sure that Amazon Kinesis Data Firehose is using a name startin

* [WAF ACL](https://github.com/umotif-public/terraform-aws-waf-webaclv2/tree/master/examples/core)
* [WAF ACL with configuration logging](https://github.com/umotif-public/terraform-aws-waf-webaclv2/tree/master/examples/wafv2-logging-configuration)
* [WAF ACL with ip rules](https://github.com/umotif-public/terraform-aws-waf-webaclv2/tree/master/examples/wafv2-ip-rules)

## Authors

Expand All @@ -140,13 +146,13 @@ Module managed by [Marcin Cuber](https://github.com/marcincuber) [LinkedIn](http
| Name | Version |
|------|---------|
| terraform | >= 0.12.6, < 0.14 |
| aws | >= 2.68, < 4.0 |
| aws | >= 2.70, < 4.0 |

## Providers

| Name | Version |
|------|---------|
| aws | >= 2.68, < 4.0 |
| aws | >= 2.70, < 4.0 |

## Inputs

Expand All @@ -158,6 +164,8 @@ Module managed by [Marcin Cuber](https://github.com/marcincuber) [LinkedIn](http
| create\_alb\_association | Whether to create alb association with WAF web acl | `bool` | `true` | no |
| create\_logging\_configuration | Whether to create logging configuration in order start logging from a WAFv2 Web ACL to Amazon Kinesis Data Firehose. | `bool` | `false` | no |
| enabled | Whether to create the resources. Set to `false` to prevent the module from creating any resources | `bool` | `true` | no |
| ip\_rate\_based\_rule | A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span | `any` | `null` | no |
| ip\_set\_rules | List of WAF ip set rules to detect web requests coming from particular IP addresses or address ranges. | `list` | `[]` | no |
| log\_destination\_configs | The Amazon Kinesis Data Firehose Amazon Resource Name (ARNs) that you want to associate with the web ACL. Currently, only 1 ARN is supported. | `list(string)` | `[]` | no |
| name\_prefix | Name prefix used to create resources. | `string` | n/a | yes |
| redacted\_fields | The parts of the request that you want to keep out of the logs. Up to 100 `redacted_fields` blocks are supported. | `list` | `[]` | no |
Expand Down
122 changes: 122 additions & 0 deletions examples/wafv2-ip-rules/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
provider "aws" {
region = "eu-west-1"
}

#####
# IP set resources
#####
resource "aws_wafv2_ip_set" "block_ip_set" {
name = "generated-ips"

scope = "REGIONAL"
ip_address_version = "IPV4"

# generates a list of all /16s
addresses = formatlist("%s.0.0.0/16", range(0, 50))
}

resource "aws_wafv2_ip_set" "custom_ip_set" {
name = "custom-ip-set"

scope = "REGIONAL"
ip_address_version = "IPV4"

addresses = [
"10.0.0.0/16",
"10.10.0.0/16"
]
}

#####
# Web Application Firewall configuration
#####
module "waf" {
source = "../.."

name_prefix = "test-waf-setup"

allow_default_action = true

create_alb_association = false

visibility_config = {
cloudwatch_metrics_enabled = false
metric_name = "test-waf-setup-waf-main-metrics"
sampled_requests_enabled = false
}

rules = [
{
name = "AWSManagedRulesCommonRuleSet-rule-1"
priority = "1"

override_action = "none"

visibility_config = {
cloudwatch_metrics_enabled = false
metric_name = "AWSManagedRulesCommonRuleSet-metric"
sampled_requests_enabled = false
}

managed_rule_group_statement = {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
excluded_rule = [
"SizeRestrictions_QUERYSTRING",
"SizeRestrictions_BODY",
"GenericRFI_QUERYARGUMENTS"
]
}
}
]

ip_set_rules = [
{
name = "allow-custom-ip-set"
priority = 5
# action = "count" # if not set, action defaults to allow
ip_set_reference_statement = {
arn = aws_wafv2_ip_set.custom_ip_set.arn
}

visibility_config = {
cloudwatch_metrics_enabled = false
sampled_requests_enabled = false
}
},
{
name = "block-ip-set"
priority = 6
action = "block"
ip_set_reference_statement = {
arn = aws_wafv2_ip_set.block_ip_set.arn
}

visibility_config = {
cloudwatch_metrics_enabled = false
metric_name = "test-waf-setup-waf-ip-set-block-metrics"
sampled_requests_enabled = false
}
}
]

ip_rate_based_rule = {
name = "ip-rate-limit"
priority = 2
# action = "count" # if not set, action defaults to block

rate_based_statement = {
limit = 100
aggregate_key_type = "IP"
}

visibility_config = {
cloudwatch_metrics_enabled = false
sampled_requests_enabled = false
}
}

tags = {
"Environment" = "test"
}
}
90 changes: 90 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,96 @@ resource "aws_wafv2_web_acl" "main" {
}
}

dynamic "rule" {
for_each = var.ip_set_rules
content {
name = lookup(rule.value, "name")
priority = lookup(rule.value, "priority")

action {
dynamic "allow" {
for_each = length(lookup(rule.value, "action", {})) == 0 || lookup(rule.value, "action", {}) == "allow" ? [1] : []
content {}
}

dynamic "count" {
for_each = lookup(rule.value, "action", {}) == "count" ? [1] : []
content {}
}

dynamic "block" {
for_each = lookup(rule.value, "action", {}) == "block" ? [1] : []
content {}
}
}

statement {
dynamic "ip_set_reference_statement" {
for_each = length(lookup(rule.value, "ip_set_reference_statement", {})) == 0 ? [] : [lookup(rule.value, "ip_set_reference_statement", {})]
content {
arn = lookup(ip_set_reference_statement.value, "arn")
}
}
}

dynamic "visibility_config" {
for_each = length(lookup(rule.value, "visibility_config")) == 0 ? [] : [lookup(rule.value, "visibility_config", {})]
content {
cloudwatch_metrics_enabled = lookup(visibility_config.value, "cloudwatch_metrics_enabled", true)
metric_name = lookup(visibility_config.value, "metric_name", "${var.name_prefix}-ip-rule-metric-name")
sampled_requests_enabled = lookup(visibility_config.value, "sampled_requests_enabled", true)
}
}
}
}

dynamic "rule" {
for_each = var.ip_rate_based_rule != null ? [var.ip_rate_based_rule] : []
content {
name = lookup(rule.value, "name")
priority = lookup(rule.value, "priority")

action {
dynamic "count" {
for_each = lookup(rule.value, "action", {}) == "count" ? [1] : []
content {}
}

dynamic "block" {
for_each = length(lookup(rule.value, "action", {})) == 0 || lookup(rule.value, "action", {}) == "block" ? [1] : []
content {}
}
}

statement {
dynamic "rate_based_statement" {
for_each = length(lookup(rule.value, "rate_based_statement", {})) == 0 ? [] : [lookup(rule.value, "rate_based_statement", {})]
content {
limit = lookup(rate_based_statement.value, "limit")
aggregate_key_type = lookup(rate_based_statement.value, "aggregate_key_type", "IP")

dynamic "forwarded_ip_config" {
for_each = length(lookup(rule.value, "forwarded_ip_config", {})) == 0 ? [] : [lookup(rule.value, "forwarded_ip_config", {})]
content {
fallback_behavior = lookup(forwarded_ip_config.value, "fallback_behavior")
header_name = lookup(forwarded_ip_config.value, "header_name")
}
}
}
}
}

dynamic "visibility_config" {
for_each = length(lookup(rule.value, "visibility_config")) == 0 ? [] : [lookup(rule.value, "visibility_config", {})]
content {
cloudwatch_metrics_enabled = lookup(visibility_config.value, "cloudwatch_metrics_enabled", true)
metric_name = lookup(visibility_config.value, "metric_name", "${var.name_prefix}-ip-rate-based-rule-metric-name")
sampled_requests_enabled = lookup(visibility_config.value, "sampled_requests_enabled", true)
}
}
}
}

tags = var.tags

dynamic "visibility_config" {
Expand Down
10 changes: 10 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ variable "rules" {
default = []
}

variable "ip_set_rules" {
description = "List of WAF ip set rules to detect web requests coming from particular IP addresses or address ranges."
default = []
}

variable "ip_rate_based_rule" {
description = "A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span"
default = null
}

variable "visibility_config" {
description = "Visibility config for WAFv2 web acl. https://www.terraform.io/docs/providers/aws/r/wafv2_web_acl.html#visibility-configuration"
type = map(string)
Expand Down
2 changes: 1 addition & 1 deletion versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ terraform {
required_version = ">= 0.12.6, < 0.14"

required_providers {
aws = ">= 2.68, < 4.0"
aws = ">= 2.70, < 4.0"
}
}

0 comments on commit 88624ab

Please sign in to comment.