terraform-aws-waf
terraform-aws-waf copied to clipboard
When `redacted_fields` variable property `single_header` list has more than one header item
Describe the Bug
When single_header
property has more than one header in the list, Terraform PLAN fails with Error: Too many single_header blocks
Error: Too many single_header blocks
on ../../main.tf line 85, in resource "aws_wafv2_web_acl_logging_configuration" "default":
85: content {
No more than 1 "single_header" blocks are allowed
Expected Behavior
Since redacted_fields
can only accept one block of each property type ( method
, uri_path
, query_string
and single_header
), the expectation is for multiple redacted_fields
for each single_header
# When `redacted_fields` is:
redacted_fields = {
default = {
single_header = [
"accept-encoding",
"accept-language"
]
}
}
# The plan should be something like this:
# module.waf.aws_wafv2_web_acl_logging_configuration.default[0] will be created
+ resource "aws_wafv2_web_acl_logging_configuration" "default" {
+ id = (known after apply)
+ log_destination_configs = (known after apply)
+ resource_arn = (known after apply)
+ redacted_fields {
+ single_header {
+ name = "accept-encoding"
}
}
+ redacted_fields {
+ single_header {
+ name = "accept-language"
}
}
}
...truncated output
Steps to Reproduce
# examples/complete/main.tf
provider "aws" {
region = var.region
}
resource "aws_cloudwatch_log_group" "default" {
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_logging_configuration#log_destination_configs
# data firehose, log group, or bucket name must be prefixed with `aws-waf-logs-`
name = format("aws-waf-logs-%s", module.this.id)
tags = module.this.tags
}
module "waf" {
source = "../.."
visibility_config = {
cloudwatch_metrics_enabled = false
metric_name = "rules-example-metric"
sampled_requests_enabled = false
}
log_destination_configs = [aws_cloudwatch_log_group.default.arn]
redacted_fields = {
default = {
single_header = [
"accept-encoding",
"accept-language"
]
}
}
# https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
managed_rule_group_statement_rules = [
{
name = "AWS-AWSManagedRulesAdminProtectionRuleSet"
priority = 1
statement = {
name = "AWSManagedRulesAdminProtectionRuleSet"
vendor_name = "AWS"
}
visibility_config = {
cloudwatch_metrics_enabled = true
sampled_requests_enabled = true
metric_name = "AWS-AWSManagedRulesAdminProtectionRuleSet"
}
},
{
name = "AWS-AWSManagedRulesAmazonIpReputationList"
priority = 2
statement = {
name = "AWSManagedRulesAmazonIpReputationList"
vendor_name = "AWS"
}
visibility_config = {
cloudwatch_metrics_enabled = true
sampled_requests_enabled = true
metric_name = "AWS-AWSManagedRulesAmazonIpReputationList"
}
},
{
name = "AWS-AWSManagedRulesCommonRuleSet"
priority = 3
statement = {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
visibility_config = {
cloudwatch_metrics_enabled = true
sampled_requests_enabled = true
metric_name = "AWS-AWSManagedRulesCommonRuleSet"
}
},
{
name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
priority = 4
statement = {
name = "AWSManagedRulesKnownBadInputsRuleSet"
vendor_name = "AWS"
}
visibility_config = {
cloudwatch_metrics_enabled = true
sampled_requests_enabled = true
metric_name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
}
},
# https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-bot.html
{
name = "AWS-AWSManagedRulesBotControlRuleSet"
priority = 5
statement = {
name = "AWSManagedRulesBotControlRuleSet"
vendor_name = "AWS"
rule_action_override = {
CategoryHttpLibrary = {
action = "block"
custom_response = {
response_code = "404"
response_header = {
name = "example-1"
value = "example-1"
}
}
}
SignalNonBrowserUserAgent = {
action = "count"
custom_request_handling = {
insert_header = {
name = "example-2"
value = "example-2"
}
}
}
}
managed_rule_group_configs = [
{
aws_managed_rules_bot_control_rule_set = {
inspection_level = "COMMON"
}
}
]
}
visibility_config = {
cloudwatch_metrics_enabled = true
sampled_requests_enabled = true
metric_name = "AWS-AWSManagedRulesBotControlRuleSet"
}
}
]
byte_match_statement_rules = []
rate_based_statement_rules = []
size_constraint_statement_rules = []
xss_match_statement_rules = []
sqli_match_statement_rules = []
geo_match_statement_rules = []
geo_allowlist_statement_rules = []
regex_match_statement_rules = []
ip_set_reference_statement_rules = []
context = module.this.context
}
Screenshots
No response
Environment
No response
Additional Context
One possible (BREAKING) fix would be to change the redacted_fields
from a map object to just an object and separate the dynamic blocks.
# variables Line#997: https://github.com/cloudposse/terraform-aws-waf/blob/main/variables.tf#L977
variable "redacted_fields" {
type = object({
method = optional(bool, false)
uri_path = optional(bool, false)
query_string = optional(bool, false)
single_header = optional(list(string), [])
})
default = {}
nullable = false
}
# main.tf Line#22: https://github.com/cloudposse/terraform-aws-waf/blob/main/main.tf#L22
resource "aws_wafv2_web_acl_logging_configuration" "default" {
count = local.enabled && length(var.log_destination_configs) > 0 ? 1 : 0
resource_arn = one(aws_wafv2_web_acl.default[*].arn)
log_destination_configs = var.log_destination_configs
# Method
dynamic "redacted_fields" {
for_each = var.redacted_fields.method ? [true] : []
content {
method {}
}
}
# Query String
dynamic "redacted_fields" {
for_each = var.redacted_fields.query_string ? [true] : []
content {
query_string {}
}
}
# Uri Path
dynamic "redacted_fields" {
for_each = var.redacted_fields.uri_path ? [true] : []
content {
uri_path {}
}
}
# Single Header
dynamic "redacted_fields" {
for_each = toset(var.redacted_fields.single_header)
content {
single_header {
name = redacted_fields.value
}
}
}
... truncated - the rest of the code is not included.