AWS WAF Module with Child Module rule_action_override

Hi

I have a WAF module with dynamic rules which iterate through managed rules listed as variables in the child module and set them to block.

I am trying to introduce a rule_action_override to the variable but it doesn’t detect the change when running a terraform apply.

Any help would be appreciated.

Thanks
Craig

AWSManaged rule

{
      name     = "AWSManagedRulesAnonymousIpList"
      priority = 60
      managed_rule_group_statement = [{
        name                       = "AWSManagedRulesAnonymousIpList"
        vendor_name                = "AWS"
        block_rule_action_override = []
        excluded_rule              = []
        rule_action_override = [{
          name   = "HostingProviderIPList"
          action = "count"
        }]
      }]
    },

WebACL Resource

resource "aws_wafv2_web_acl" "web_acl" {
  name        = var.web_acl_name
  description = "${var.service_name} ${var.environment_short} rule"
  scope       = var.waf_scope

  default_action {
    dynamic "allow" {
      for_each = var.default_action == "allow" ? [1] : []
      content {}
    }

    dynamic "block" {
      for_each = var.default_action == "block" ? [1] : []
      content {}
    }
  }

  dynamic "rule" {
    for_each = var.ip_sets_rule
    content {
      name     = rule.value.name
      priority = rule.value.priority

      action {
        dynamic "allow" {
          for_each = rule.value.action == "allow" ? [1] : []
          content {}
        }

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

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

      statement {
        ip_set_reference_statement {
          arn = aws_wafv2_ip_set.ipset[0].arn
        }
      }

      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "ipset-allow-metrics"
        sampled_requests_enabled   = true
      }
    }
  }

  dynamic "rule" {
    for_each = { for rule in try(var.managed_rules, []) : rule.name => rule }

    content {
      name     = rule.value.name
      priority = rule.value.priority

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

      statement {
        dynamic "managed_rule_group_statement" {
          for_each = { for managed_rule in try(rule.value.managed_rule_group_statement, []) : managed_rule.name => managed_rule }

          content {
            name        = managed_rule_group_statement.value.name
            vendor_name = managed_rule_group_statement.value.vendor_name

            dynamic "rule_action_override" {
              for_each = { for override in try(managed_rule_group_statement.value.rule_action_override, []) : override.name => override }

              content {
                name = rule_action_override.value.name
                action_to_use {
                  dynamic "count" {
                    for_each = rule_action_override.value.action == "count" ? [1] : []
                    content {}
                  }

                  dynamic "allow" {
                    for_each = rule_action_override.value.action == "allow" ? [1] : []
                    content {}
                  }

                  dynamic "block" {
                    for_each = rule_action_override.value.action == "block" ? [1] : []
                    content {}
                  }
                }
              }
            }
          }
        }
      }

      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = rule.value.name
        sampled_requests_enabled   = true
      }
    }
  }

  tags = {
    Name            = var.web_acl_name
    Environment     = var.environment
    Service         = var.service_name
    Account         = var.account
    "Business Unit" = var.business_unit
    Product         = var.product
    Owner           = var.owner
    Organisation    = var.organisation
  }

  visibility_config {
    cloudwatch_metrics_enabled = var.waf_cloudwatch_metrics_enabled
    metric_name                = "${var.service_name}-${var.environment_short}-WEBACL-metrics"
    sampled_requests_enabled   = var.waf_sampled_requests_enabled
  }
}

On quick glance I didn’t see anything wrong with the configuration. I simplified it as below and the plan is showing the changes and applied correctly as I added and removed rule_action_override from the variable.

You mentioned child module but there isn’t one in your configuration. Did you mean parent module that calls this “WAF module”? Is there more to your example that might be causing the issue instead?

variable "managed_rules" {
  type = list(any)
  default = [
    {
      name     = "AWSManagedRulesAnonymousIpList"
      priority = 60
      managed_rule_group_statement = [{
        name                       = "AWSManagedRulesAnonymousIpList"
        vendor_name                = "AWS"
        block_rule_action_override = []
        excluded_rule              = []
        rule_action_override = [{
          name   = "HostingProviderIPList"
          action = "count"
        }]
      }]
    }
  ]
}

resource "aws_wafv2_web_acl" "web_acl" {
  name  = "test-web-acl"
  scope = "REGIONAL"

  default_action {
    allow {}
  }

  dynamic "rule" {
    for_each = { for rule in try(var.managed_rules, []) : rule.name => rule }

    content {
      name     = rule.value.name
      priority = rule.value.priority

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

      statement {
        dynamic "managed_rule_group_statement" {
          for_each = { for managed_rule in try(rule.value.managed_rule_group_statement, []) : managed_rule.name => managed_rule }

          content {
            name        = managed_rule_group_statement.value.name
            vendor_name = managed_rule_group_statement.value.vendor_name

            dynamic "rule_action_override" {
              for_each = { for override in try(managed_rule_group_statement.value.rule_action_override, []) : override.name => override }

              content {
                name = rule_action_override.value.name
                action_to_use {
                  dynamic "count" {
                    for_each = rule_action_override.value.action == "count" ? [1] : []
                    content {}
                  }

                  dynamic "allow" {
                    for_each = rule_action_override.value.action == "allow" ? [1] : []
                    content {}
                  }

                  dynamic "block" {
                    for_each = rule_action_override.value.action == "block" ? [1] : []
                    content {}
                  }
                }
              }
            }
          }
        }
      }

      visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = rule.value.name
        sampled_requests_enabled   = true
      }
    }
  }
  visibility_config {
    cloudwatch_metrics_enabled = false
    metric_name                = "test-WEBACL-metrics"
    sampled_requests_enabled   = false
  }
}
Terraform will perform the following actions:

  # aws_wafv2_web_acl.web_acl will be created
  + resource "aws_wafv2_web_acl" "web_acl" {
      + application_integration_url = (known after apply)
      + arn                         = (known after apply)
      + capacity                    = (known after apply)
      + id                          = (known after apply)
      + lock_token                  = (known after apply)
      + name                        = "test-web-acl"
      + scope                       = "REGIONAL"
      + tags_all                    = (known after apply)

      + default_action {
          + allow {
            }
        }

      + rule {
          + name     = "AWSManagedRulesAnonymousIpList"
          + priority = 60

          + override_action {
              + none {}
            }

          + statement {
              + managed_rule_group_statement {
                  + name        = "AWSManagedRulesAnonymousIpList"
                  + vendor_name = "AWS"

                  + rule_action_override {
                      + name = "HostingProviderIPList"

                      + action_to_use {
                          + count {
                            }
                        }
                    }
                }
            }

          + visibility_config {
              + cloudwatch_metrics_enabled = true
              + metric_name                = "AWSManagedRulesAnonymousIpList"
              + sampled_requests_enabled   = true
            }
        }

      + visibility_config {
          + cloudwatch_metrics_enabled = false
          + metric_name                = "test-WEBACL-metrics"
          + sampled_requests_enabled   = false
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.