From 1d9c87433d3894888f3f7f3bd9203a6b4353a5df Mon Sep 17 00:00:00 2001 From: olamide Date: Wed, 3 Apr 2024 12:09:44 +0100 Subject: [PATCH 01/12] Add waf rule to inspect for header values --- aws/waf/main.tf | 64 ++++++++++++++++++++++++++++++++++++++++++++ aws/waf/variables.tf | 15 +++++++++++ 2 files changed, 79 insertions(+) diff --git a/aws/waf/main.tf b/aws/waf/main.tf index 2ac955c6..e672c5a8 100644 --- a/aws/waf/main.tf +++ b/aws/waf/main.tf @@ -13,6 +13,70 @@ resource "aws_wafv2_web_acl" "main" { metric_name = "${var.name}-cloudfront-web-acl" } + dynamic "header_rule" { + for_each = var.header_match_rules + content { + name = "${header_rule.value["name"]}-header-match-rule" + priority = header_rule.value["priority"] + + dynamic "action" { + for_each = header_rule.value["count_override"] == true ? [1] : [] + content { + count {} + } + } + dynamic "action" { + for_each = header_rule.value["count_override"] == false ? [1] : [] + content { + block {} + } + } + statement { + byte_match_statement { + field_to_match { + single_header = lower(header_rule.value["header_name"]) + } + + positional_constraint = "CONTAINS" + + search_string = header_rule.value["header_value"] + + text_transformation { + priority = 1 + type = "LOWERCASE" + } + + dynamic "scope_down_statement" { + for_each = length(concat(rule.value["country_list"], rule.value["exempt_country_list"])) > 0 ? [1] : [] + content { + dynamic "geo_match_statement" { + for_each = length(rule.value["country_list"]) > 0 ? [1] : [] + content { + country_codes = rule.value["country_list"] + } + } + dynamic "not_statement" { + for_each = length(rule.value["exempt_country_list"]) > 0 ? [1] : [] + content { + statement { + geo_match_statement { + country_codes = rule.value["exempt_country_list"] + } + } + } + } + } + } + } + } + visibility_config { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "${rule.value["name"]}-header-match-rule" + } + } + } + dynamic "rule" { for_each = var.rate_limit_rules content { diff --git a/aws/waf/variables.tf b/aws/waf/variables.tf index aafc5924..18bb1efe 100644 --- a/aws/waf/variables.tf +++ b/aws/waf/variables.tf @@ -43,6 +43,21 @@ variable "rate_limit_rules" { })) } +variable "header_match_rules" { + description = "Rule statement to inspect and match the header for an incoming request." + type = map(object({ + name = string # Name of the header match rule group + priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF. + header_name = string # This is the name of the header to inspect for all incoming requests. + header_value = string # This is the value to look out for a matching header name for all incoming requests + count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false. + country_list = optional(list(string), []) # List of countries to apply the header match to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both. + exempt_country_list = optional(list(string), []) # List of countries to exempt from the header match rule. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both. + })) + + default = null +} + variable "allowed_ip_list" { description = "List of allowed IP addresses, these IP addresses will be exempted from any configured rules" type = list(string) From ee8b8cbbe5459dcbb6a7e84e81bf8ee0f6bb7a0e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Apr 2024 11:13:57 +0000 Subject: [PATCH 02/12] terraform-docs: automated action --- aws/waf/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/waf/README.md b/aws/waf/README.md index b57d386b..0f211b86 100644 --- a/aws/waf/README.md +++ b/aws/waf/README.md @@ -82,6 +82,7 @@ Note: For each rule, if you are providing a country list, you can only specify e | [allowed\_ip\_list](#input\_allowed\_ip\_list) | List of allowed IP addresses, these IP addresses will be exempted from any configured rules | `list(string)` | `[]` | no | | [aws\_managed\_rule\_groups](#input\_aws\_managed\_rule\_groups) | Rule statement values used to run the rules that are defined in a managed rule group. You may review this list for the available AWS managed rule groups - https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html |
map(object({
name = string # Name of the Managed rule group
priority = number # Relative processing order for rules processed by AWS WAF. All rules are processed from lowest priority to the highest.
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`.
country_list = optional(list(string), []) # List of countries to apply the managed rule to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the managed rule. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| n/a | yes | | [block\_ip\_list](#input\_block\_ip\_list) | List of IP addresses to be blocked and denied access to the ingress / cloudfront. | `list(string)` | `[]` | no | +| [header\_match\_rules](#input\_header\_match\_rules) | Rule statement to inspect and match the header for an incoming request. |
map(object({
name = string # Name of the header match rule group
priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF.
header_name = string # This is the name of the header to inspect for all incoming requests.
header_value = string # This is the value to look out for a matching header name for all incoming requests
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
country_list = optional(list(string), []) # List of countries to apply the header match to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the header match rule. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| `null` | no | | [name](#input\_name) | Friendly name of the WebACL. | `string` | n/a | yes | | [rate\_limit\_rules](#input\_rate\_limit\_rules) | Rule statement to track and rate limits requests when they are coming at too fast a rate.. For more details, visit - https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html |
map(object({
name = string # Name of the Rate limit rule group
priority = number # Relative processing order for rate limit rule relative to other rules processed by AWS WAF.
limit = optional(number, 2000) # This is the limit on requests from any single IP address within a 5 minute period
count_override = optional(bool, false) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
country_list = optional(list(string), []) # List of countries to apply the rate limit to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the rate limit. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| n/a | yes | | [resource\_arn](#input\_resource\_arn) | The Amazon Resource Name (ARN) of the resource to associate with the web ACL. This must be an ARN of an Application Load Balancer or an Amazon API Gateway stage. Value is required if scope is REGIONAL | `string` | `null` | no | From e6058570bf26a0ef8306a79d47057289d1fb6ba8 Mon Sep 17 00:00:00 2001 From: olamide Date: Wed, 3 Apr 2024 12:33:03 +0100 Subject: [PATCH 03/12] Mark the header variable input as sensitive --- aws/waf/main.tf | 6 ++++-- aws/waf/variables.tf | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/aws/waf/main.tf b/aws/waf/main.tf index e672c5a8..f817fcd3 100644 --- a/aws/waf/main.tf +++ b/aws/waf/main.tf @@ -13,7 +13,7 @@ resource "aws_wafv2_web_acl" "main" { metric_name = "${var.name}-cloudfront-web-acl" } - dynamic "header_rule" { + dynamic "rule" { for_each = var.header_match_rules content { name = "${header_rule.value["name"]}-header-match-rule" @@ -34,7 +34,9 @@ resource "aws_wafv2_web_acl" "main" { statement { byte_match_statement { field_to_match { - single_header = lower(header_rule.value["header_name"]) + single_header { + name = lower(header_rule.value["header_name"]) + } } positional_constraint = "CONTAINS" diff --git a/aws/waf/variables.tf b/aws/waf/variables.tf index 18bb1efe..ca8a825d 100644 --- a/aws/waf/variables.tf +++ b/aws/waf/variables.tf @@ -56,6 +56,8 @@ variable "header_match_rules" { })) default = null + + sensitive = true } variable "allowed_ip_list" { From 2c83c26966f5d2a893e971c34458c701a910018b Mon Sep 17 00:00:00 2001 From: olamide Date: Wed, 3 Apr 2024 12:41:21 +0100 Subject: [PATCH 04/12] Update rule reference value in dynamic block --- aws/waf/main.tf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aws/waf/main.tf b/aws/waf/main.tf index f817fcd3..db523760 100644 --- a/aws/waf/main.tf +++ b/aws/waf/main.tf @@ -16,17 +16,17 @@ resource "aws_wafv2_web_acl" "main" { dynamic "rule" { for_each = var.header_match_rules content { - name = "${header_rule.value["name"]}-header-match-rule" - priority = header_rule.value["priority"] + name = "${rule.value["name"]}-header-match-rule" + priority = rule.value["priority"] dynamic "action" { - for_each = header_rule.value["count_override"] == true ? [1] : [] + for_each = rule.value["count_override"] == true ? [1] : [] content { count {} } } dynamic "action" { - for_each = header_rule.value["count_override"] == false ? [1] : [] + for_each = rule.value["count_override"] == false ? [1] : [] content { block {} } @@ -35,13 +35,13 @@ resource "aws_wafv2_web_acl" "main" { byte_match_statement { field_to_match { single_header { - name = lower(header_rule.value["header_name"]) + name = lower(rule.value["header_name"]) } } positional_constraint = "CONTAINS" - search_string = header_rule.value["header_value"] + search_string = rule.value["header_value"] text_transformation { priority = 1 From c1208b2fdf2b122fbaf39b60612a5ec9a56b6fab Mon Sep 17 00:00:00 2001 From: olamide Date: Wed, 3 Apr 2024 13:00:59 +0100 Subject: [PATCH 05/12] Remove geo location scope down statement for header match block since it's not supported --- aws/waf/main.tf | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/aws/waf/main.tf b/aws/waf/main.tf index db523760..153f90a6 100644 --- a/aws/waf/main.tf +++ b/aws/waf/main.tf @@ -14,7 +14,8 @@ resource "aws_wafv2_web_acl" "main" { } dynamic "rule" { - for_each = var.header_match_rules + # for_each = var.header_match_rules == null ? {} : var.header_match_rules + for_each = var.header_match_rules == null ? {} : var.header_match_rules content { name = "${rule.value["name"]}-header-match-rule" priority = rule.value["priority"] @@ -47,28 +48,6 @@ resource "aws_wafv2_web_acl" "main" { priority = 1 type = "LOWERCASE" } - - dynamic "scope_down_statement" { - for_each = length(concat(rule.value["country_list"], rule.value["exempt_country_list"])) > 0 ? [1] : [] - content { - dynamic "geo_match_statement" { - for_each = length(rule.value["country_list"]) > 0 ? [1] : [] - content { - country_codes = rule.value["country_list"] - } - } - dynamic "not_statement" { - for_each = length(rule.value["exempt_country_list"]) > 0 ? [1] : [] - content { - statement { - geo_match_statement { - country_codes = rule.value["exempt_country_list"] - } - } - } - } - } - } } } visibility_config { From 16637e0d7db5f651204e23f81493041541abb4c1 Mon Sep 17 00:00:00 2001 From: olamide Date: Wed, 3 Apr 2024 13:08:50 +0100 Subject: [PATCH 06/12] Mark header_match_rules as non-sensitive so it can be used in dynamic loop --- aws/waf/variables.tf | 2 -- 1 file changed, 2 deletions(-) diff --git a/aws/waf/variables.tf b/aws/waf/variables.tf index ca8a825d..18bb1efe 100644 --- a/aws/waf/variables.tf +++ b/aws/waf/variables.tf @@ -56,8 +56,6 @@ variable "header_match_rules" { })) default = null - - sensitive = true } variable "allowed_ip_list" { From be812cf5eef91294287f21841db45baf51466e57 Mon Sep 17 00:00:00 2001 From: olamide Date: Wed, 3 Apr 2024 15:03:20 +0100 Subject: [PATCH 07/12] Remove unsed variable values --- aws/waf/variables.tf | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/aws/waf/variables.tf b/aws/waf/variables.tf index 18bb1efe..dee871fb 100644 --- a/aws/waf/variables.tf +++ b/aws/waf/variables.tf @@ -46,13 +46,11 @@ variable "rate_limit_rules" { variable "header_match_rules" { description = "Rule statement to inspect and match the header for an incoming request." type = map(object({ - name = string # Name of the header match rule group - priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF. - header_name = string # This is the name of the header to inspect for all incoming requests. - header_value = string # This is the value to look out for a matching header name for all incoming requests - count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false. - country_list = optional(list(string), []) # List of countries to apply the header match to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both. - exempt_country_list = optional(list(string), []) # List of countries to exempt from the header match rule. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both. + name = string # Name of the header match rule group + priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF. + header_name = string # This is the name of the header to inspect for all incoming requests. + header_value = string # This is the value to look out for a matching header name for all incoming requests + count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false. })) default = null From 18d1ffd2e1ab789ab69b26cee653aad9c8a60452 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 Apr 2024 14:03:55 +0000 Subject: [PATCH 08/12] terraform-docs: automated action --- aws/waf/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/waf/README.md b/aws/waf/README.md index 0f211b86..ea62d02c 100644 --- a/aws/waf/README.md +++ b/aws/waf/README.md @@ -82,7 +82,7 @@ Note: For each rule, if you are providing a country list, you can only specify e | [allowed\_ip\_list](#input\_allowed\_ip\_list) | List of allowed IP addresses, these IP addresses will be exempted from any configured rules | `list(string)` | `[]` | no | | [aws\_managed\_rule\_groups](#input\_aws\_managed\_rule\_groups) | Rule statement values used to run the rules that are defined in a managed rule group. You may review this list for the available AWS managed rule groups - https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html |
map(object({
name = string # Name of the Managed rule group
priority = number # Relative processing order for rules processed by AWS WAF. All rules are processed from lowest priority to the highest.
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`.
country_list = optional(list(string), []) # List of countries to apply the managed rule to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the managed rule. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| n/a | yes | | [block\_ip\_list](#input\_block\_ip\_list) | List of IP addresses to be blocked and denied access to the ingress / cloudfront. | `list(string)` | `[]` | no | -| [header\_match\_rules](#input\_header\_match\_rules) | Rule statement to inspect and match the header for an incoming request. |
map(object({
name = string # Name of the header match rule group
priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF.
header_name = string # This is the name of the header to inspect for all incoming requests.
header_value = string # This is the value to look out for a matching header name for all incoming requests
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
country_list = optional(list(string), []) # List of countries to apply the header match to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the header match rule. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| `null` | no | +| [header\_match\_rules](#input\_header\_match\_rules) | Rule statement to inspect and match the header for an incoming request. |
map(object({
name = string # Name of the header match rule group
priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF.
header_name = string # This is the name of the header to inspect for all incoming requests.
header_value = string # This is the value to look out for a matching header name for all incoming requests
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
}))
| `null` | no | | [name](#input\_name) | Friendly name of the WebACL. | `string` | n/a | yes | | [rate\_limit\_rules](#input\_rate\_limit\_rules) | Rule statement to track and rate limits requests when they are coming at too fast a rate.. For more details, visit - https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html |
map(object({
name = string # Name of the Rate limit rule group
priority = number # Relative processing order for rate limit rule relative to other rules processed by AWS WAF.
limit = optional(number, 2000) # This is the limit on requests from any single IP address within a 5 minute period
count_override = optional(bool, false) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
country_list = optional(list(string), []) # List of countries to apply the rate limit to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the rate limit. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| n/a | yes | | [resource\_arn](#input\_resource\_arn) | The Amazon Resource Name (ARN) of the resource to associate with the web ACL. This must be an ARN of an Application Load Balancer or an Amazon API Gateway stage. Value is required if scope is REGIONAL | `string` | `null` | no | From 7c9e1339546c1ea55ec433fa3960ce6518f19520 Mon Sep 17 00:00:00 2001 From: olamide Date: Wed, 3 Apr 2024 18:11:54 +0100 Subject: [PATCH 09/12] Remove commented line --- aws/waf/main.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aws/waf/main.tf b/aws/waf/main.tf index 153f90a6..44289ef1 100644 --- a/aws/waf/main.tf +++ b/aws/waf/main.tf @@ -14,7 +14,6 @@ resource "aws_wafv2_web_acl" "main" { } dynamic "rule" { - # for_each = var.header_match_rules == null ? {} : var.header_match_rules for_each = var.header_match_rules == null ? {} : var.header_match_rules content { name = "${rule.value["name"]}-header-match-rule" @@ -246,4 +245,4 @@ resource "aws_wafv2_ip_set" "block_ip_list" { scope = var.waf_scope ip_address_version = "IPV4" addresses = var.block_ip_list -} \ No newline at end of file +} From 5f3f55a799cc17aae3791741d484db30cab0ea50 Mon Sep 17 00:00:00 2001 From: Olamide Date: Fri, 5 Apr 2024 12:49:49 +0100 Subject: [PATCH 10/12] Enable header check for multiple headers --- aws/waf/README.md | 3 +- aws/waf/main.tf | 103 ++++++++++++++++++++++++++++++++++++++----- aws/waf/outputs.tf | 5 +++ aws/waf/variables.tf | 11 +++-- 4 files changed, 107 insertions(+), 15 deletions(-) diff --git a/aws/waf/README.md b/aws/waf/README.md index ea62d02c..44c72092 100644 --- a/aws/waf/README.md +++ b/aws/waf/README.md @@ -82,7 +82,7 @@ Note: For each rule, if you are providing a country list, you can only specify e | [allowed\_ip\_list](#input\_allowed\_ip\_list) | List of allowed IP addresses, these IP addresses will be exempted from any configured rules | `list(string)` | `[]` | no | | [aws\_managed\_rule\_groups](#input\_aws\_managed\_rule\_groups) | Rule statement values used to run the rules that are defined in a managed rule group. You may review this list for the available AWS managed rule groups - https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html |
map(object({
name = string # Name of the Managed rule group
priority = number # Relative processing order for rules processed by AWS WAF. All rules are processed from lowest priority to the highest.
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`.
country_list = optional(list(string), []) # List of countries to apply the managed rule to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the managed rule. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| n/a | yes | | [block\_ip\_list](#input\_block\_ip\_list) | List of IP addresses to be blocked and denied access to the ingress / cloudfront. | `list(string)` | `[]` | no | -| [header\_match\_rules](#input\_header\_match\_rules) | Rule statement to inspect and match the header for an incoming request. |
map(object({
name = string # Name of the header match rule group
priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF.
header_name = string # This is the name of the header to inspect for all incoming requests.
header_value = string # This is the value to look out for a matching header name for all incoming requests
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
}))
| `null` | no | +| [header\_match\_rules](#input\_header\_match\_rules) | Rule statement to inspect and match the header for an incoming request. |
map(object({
name = string # Name of the header match rule group
priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF.
header_values = map(object({ # Header values contains a map of headers to inspect. You can provide multiple headers and values, all headers will be inspected together with `AND` logic.
header_name = string # This is the name of the header to inspect for all incoming requests.
header_value = string # This is the value to look out for a matching header name for all incoming requests
not_statement = optional(bool, false) # This indicates if the result this header match should be negated. The negated result will be joined with other header match results using `AND` logic if more than 1 header is provided.
}))
count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
}))
| `null` | no | | [name](#input\_name) | Friendly name of the WebACL. | `string` | n/a | yes | | [rate\_limit\_rules](#input\_rate\_limit\_rules) | Rule statement to track and rate limits requests when they are coming at too fast a rate.. For more details, visit - https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html |
map(object({
name = string # Name of the Rate limit rule group
priority = number # Relative processing order for rate limit rule relative to other rules processed by AWS WAF.
limit = optional(number, 2000) # This is the limit on requests from any single IP address within a 5 minute period
count_override = optional(bool, false) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false.
country_list = optional(list(string), []) # List of countries to apply the rate limit to. If populated, from other countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
exempt_country_list = optional(list(string), []) # List of countries to exempt from the rate limit. If populated, the selected countries will be ignored by this rule. IF empty, the rule will apply to all traffic. You must either specify country_list or exempt_country_list, but not both.
}))
| n/a | yes | | [resource\_arn](#input\_resource\_arn) | The Amazon Resource Name (ARN) of the resource to associate with the web ACL. This must be an ARN of an Application Load Balancer or an Amazon API Gateway stage. Value is required if scope is REGIONAL | `string` | `null` | no | @@ -93,4 +93,5 @@ Note: For each rule, if you are providing a country list, you can only specify e | Name | Description | |------|-------------| | [aws\_waf\_arn](#output\_aws\_waf\_arn) | The arn for AWS WAF WebACL. | +| [waf\_logs\_sns\_topic\_arn](#output\_waf\_logs\_sns\_topic\_arn) | The arn for the SNS topic to receive the AWS WAF logs | \ No newline at end of file diff --git a/aws/waf/main.tf b/aws/waf/main.tf index 44289ef1..ef66ccf9 100644 --- a/aws/waf/main.tf +++ b/aws/waf/main.tf @@ -31,21 +31,104 @@ resource "aws_wafv2_web_acl" "main" { block {} } } - statement { - byte_match_statement { - field_to_match { - single_header { - name = lower(rule.value["header_name"]) + dynamic "statement" { + for_each = length(rule.value["header_values"]) == 1 ? rule.value["header_values"] : {} + content { + dynamic "byte_match_statement" { + for_each = statement.value["not_statement"] == false ? [1] : [] + content { + field_to_match { + single_header { + name = lower(statement.value["header_name"]) + } + } + + positional_constraint = "CONTAINS" + + search_string = statement.value["header_value"] + + text_transformation { + priority = 1 + type = "LOWERCASE" + } } } + dynamic "not_statement" { + for_each = statement.value["not_statement"] == true ? [1] : [] + content { + statement { + byte_match_statement { + field_to_match { + single_header { + name = lower(statement.value["header_name"]) + } + } - positional_constraint = "CONTAINS" + positional_constraint = "CONTAINS" - search_string = rule.value["header_value"] + search_string = statement.value["header_value"] - text_transformation { - priority = 1 - type = "LOWERCASE" + text_transformation { + priority = 1 + type = "LOWERCASE" + } + } + } + } + } + } + } + dynamic "statement" { + for_each = length(rule.value["header_values"]) > 1 ? [1] : [] + content { + and_statement { + dynamic "statement" { + for_each = rule.value["header_values"] + content { + dynamic "byte_match_statement" { + for_each = statement.value["not_statement"] == false ? [1] : [] + content { + field_to_match { + single_header { + name = lower(statement.value["header_name"]) + } + } + + positional_constraint = "CONTAINS" + + search_string = statement.value["header_value"] + + text_transformation { + priority = 1 + type = "LOWERCASE" + } + } + } + dynamic "not_statement" { + for_each = statement.value["not_statement"] == true ? [1] : [] + content { + statement { + byte_match_statement { + field_to_match { + single_header { + name = lower(statement.value["header_name"]) + } + } + + positional_constraint = "CONTAINS" + + search_string = statement.value["header_value"] + + text_transformation { + priority = 1 + type = "LOWERCASE" + } + } + } + } + } + } + } } } } diff --git a/aws/waf/outputs.tf b/aws/waf/outputs.tf index f0a1776f..dd7a7ccb 100644 --- a/aws/waf/outputs.tf +++ b/aws/waf/outputs.tf @@ -2,3 +2,8 @@ output "aws_waf_arn" { description = "The arn for AWS WAF WebACL." value = aws_wafv2_web_acl.main.arn } + +output "waf_logs_sns_topic_arn" { + description = "The arn for the SNS topic to receive the AWS WAF logs" + value = aws_sns_topic.waf_logs_sns_subscription.arn +} diff --git a/aws/waf/variables.tf b/aws/waf/variables.tf index dee871fb..69f11f71 100644 --- a/aws/waf/variables.tf +++ b/aws/waf/variables.tf @@ -46,10 +46,13 @@ variable "rate_limit_rules" { variable "header_match_rules" { description = "Rule statement to inspect and match the header for an incoming request." type = map(object({ - name = string # Name of the header match rule group - priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF. - header_name = string # This is the name of the header to inspect for all incoming requests. - header_value = string # This is the value to look out for a matching header name for all incoming requests + name = string # Name of the header match rule group + priority = number # Relative processing order for header match rule relative to other rules processed by AWS WAF. + header_values = map(object({ # Header values contains a map of headers to inspect. You can provide multiple headers and values, all headers will be inspected together with `AND` logic. + header_name = string # This is the name of the header to inspect for all incoming requests. + header_value = string # This is the value to look out for a matching header name for all incoming requests + not_statement = optional(bool, false) # This indicates if the result this header match should be negated. The negated result will be joined with other header match results using `AND` logic if more than 1 header is provided. + })) count_override = optional(bool, true) # If true, this will override the rule action setting to `count`, if false, the rule action will be set to `block`. Default value is false. })) From d9c0122085a360d2ee768a6ad751b040b7ebcb80 Mon Sep 17 00:00:00 2001 From: Olamide Date: Fri, 5 Apr 2024 13:28:13 +0100 Subject: [PATCH 11/12] Remove unused output for sns topic --- aws/waf/outputs.tf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/aws/waf/outputs.tf b/aws/waf/outputs.tf index dd7a7ccb..7bc0cce3 100644 --- a/aws/waf/outputs.tf +++ b/aws/waf/outputs.tf @@ -3,7 +3,3 @@ output "aws_waf_arn" { value = aws_wafv2_web_acl.main.arn } -output "waf_logs_sns_topic_arn" { - description = "The arn for the SNS topic to receive the AWS WAF logs" - value = aws_sns_topic.waf_logs_sns_subscription.arn -} From 86a54c36c70a39bbabfe3107a424f3df76823606 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Apr 2024 12:28:46 +0000 Subject: [PATCH 12/12] terraform-docs: automated action --- aws/waf/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/aws/waf/README.md b/aws/waf/README.md index 44c72092..a010dd45 100644 --- a/aws/waf/README.md +++ b/aws/waf/README.md @@ -93,5 +93,4 @@ Note: For each rule, if you are providing a country list, you can only specify e | Name | Description | |------|-------------| | [aws\_waf\_arn](#output\_aws\_waf\_arn) | The arn for AWS WAF WebACL. | -| [waf\_logs\_sns\_topic\_arn](#output\_waf\_logs\_sns\_topic\_arn) | The arn for the SNS topic to receive the AWS WAF logs | \ No newline at end of file