azureterraformazure-waf

Terraform deployment of Azure WAF custom rules - erroring with ApplicationGatewayFirewallMatchValueNoCollection


I'm trying to create custom WAF rules to allow traffic to specific URI's - this deployment is being done via Terraform.

The error I'm getting is:

Application Gateway Web Application Firewall Policy Name: "waf-policy-prod-uksouth"): unexpected status 400 (400 Bad Request) with error: ApplicationGatewayFirewallMatchValueNoCollection: Custom Rule 'AllowAmexPay' does not have a valid collection match variable 'RequestUri' which support selector in its condition in context 'properties.customRules[2].matchConditions[0].matchVariables[0]'.
│ 
│   with azurerm_web_application_firewall_policy.waf_policy,
│   on application_gw.tf line 224, in resource "azurerm_web_application_firewall_policy" "waf_policy":
│  224: resource "azurerm_web_application_firewall_policy" "waf_policy" {

TFVars file:

custom_rules = [
    {
        name                    = "RecitePreferences"
        priority                = "70"
        enabled                 = true
        rule_type               = "MatchRule"
        variable_name           = "RequestCookies"
        selector                = "Recite.Preferences"
        operator                = "Any"
        action                  = "Allow"
    },
        {
        name                    = "CookieConsent"
        priority                = "71"
        enabled                 = true
        rule_type               = "MatchRule"
        variable_name           = "RequestCookies"
        selector                = "CookieConsent"
        operator                = "Any"
        action                  = "Allow"
    },
        {
        name                    = "AllowAmexPay"
        priority                = "80"
        enabled                 = true
        rule_type               = "MatchRule"
        variable_name           = "RequestUri"
        selector                = "/smart-card/amex-pay"
        operator                = "Contains"
        action                  = "Allow"
    },
        {
        name                    = "AllowAmexPayComplete"
        priority                = "81"
        enabled                 = true
        rule_type               = "MatchRule"
        variable_name           = "RequestUri"
        selector                = "/smart-card/amex-pay-complete"
        operator                = "Contains"
        action                  = "Allow"
    }
]

Variables.tf file:

variable "custom_rules" {
  type  = list(object({
    name                    = string
    priority                = string
    enabled                 = bool
    rule_type               = string
    variable_name           = string
    operator                = string
    selector                = string
    action                  = string
  }))
}

Application Gateway config:

resource "azurerm_web_application_firewall_policy" "waf_policy" {
  name                = "waf-policy-${var.general_environment}-${var.general_location}"
  resource_group_name = azurerm_resource_group.prod.name
  location            = azurerm_resource_group.prod.location

  policy_settings {
    enabled                     = true
    mode                        = "Detection"
    request_body_check          = false
    file_upload_limit_in_mb     = 100
    max_request_body_size_in_kb = 128
  }
  managed_rules {
    managed_rule_set {
      type    = "OWASP"
      version = "3.2"
      
      dynamic "rule_group_override" {
        for_each = var.rule_group_override
        content {
          rule_group_name = rule_group_override.key

          dynamic "rule" {
            for_each = rule_group_override.value
            content {
            id      = rule.value.id
            enabled = rule.value.enabled
          } 
        }
      }
    }
  }

    dynamic "exclusion" {
      for_each = var.exclusion

      content {
        match_variable          = exclusion.value["match_variable"]
        selector                = exclusion.value["selector"]
        selector_match_operator = exclusion.value["selector_match_operator"]
      }
    }
    managed_rule_set {
      type = "Microsoft_BotManagerRuleSet"
      version = "1.0"
    }
  }
    dynamic "custom_rules" {
      for_each = var.custom_rules

      content {
          name          = custom_rules.value["name"]
          enabled       = custom_rules.value["enabled"]
          priority      = custom_rules.value["priority"]
          rule_type     = custom_rules.value["rule_type"]
          action        = custom_rules.value["action"]
          match_conditions {
            operator     = custom_rules.value["operator"]
            match_variables {
              variable_name = custom_rules.value["variable_name"]
              selector      = custom_rules.value["selector"]
            }
          }
        } 
      }     
}

Just wondering if there's a different/better way of passing in he variables as these are currently erroring on Apply?


Solution

  • Azure WAF custom rules deployement using Terraform

    The issue seems to with the way you define selector. It was wrongly used for RequestUri. The structure which you mentioned forced everything to use only selector.

    The RequestUri always define using match_values this is why the configuration you tried was facing a blocker. The structural configuration doesn't match the requirement.

    I tried a configuration with all the necessary changes, and I was able to provision the requirement successfully.

    Main.tf:

    resource "azurerm_web_application_firewall_policy" "example" {
      name                = "waf-policy-uksouth"
      resource_group_name = azurerm_resource_group.example.name
      location            = azurerm_resource_group.example.location
    
      policy_settings {
        enabled = true
        mode    = "Detection"
      }
    
      managed_rules {
        managed_rule_set {
          type    = "OWASP"
          version = "3.2"
        }
      }
    
      dynamic "custom_rules" {
        for_each = var.custom_rules
    
        content {
          name      = custom_rules.value.name
          priority  = tonumber(custom_rules.value.priority)
          enabled   = custom_rules.value.enabled
          rule_type = custom_rules.value.rule_type
          action    = custom_rules.value.action
    
          match_conditions {
            operator     = custom_rules.value.operator
            match_values = custom_rules.value.match_values
    
            match_variables {
              variable_name = custom_rules.value.variable_name
              selector      = lookup(custom_rules.value, "selector", null)
            }
          }
        }
      }
    }
    

    tfvars:

    custom_rules = [
      {
        name          = "AllowAmexPay"
        priority      = "80"
        enabled       = true
        rule_type     = "MatchRule"
        variable_name = "RequestUri"
        operator      = "Contains"
        match_values  = ["/smart-card/amex-pay"]
        action        = "Allow"
      },
      {
        name          = "AllowAmexPayComplete"
        priority      = "81"
        enabled       = true
        rule_type     = "MatchRule"
        variable_name = "RequestUri"
        operator      = "Contains"
        match_values  = ["/smart-card/amex-pay-complete"]
        action        = "Allow"
      },
      {
        name          = "RecitePreferences"
        priority      = "70"
        enabled       = true
        rule_type     = "MatchRule"
        variable_name = "RequestCookies"
        operator      = "Equal"
        selector      = "Recite.Preferences"
        match_values  = ["Enabled"]
        action        = "Allow"
      },
      {
        name          = "CookieConsent"
        priority      = "71"
        enabled       = true
        rule_type     = "MatchRule"
        variable_name = "RequestCookies"
        operator      = "Equal"
        selector      = "CookieConsent"
        match_values  = ["Accepted"]
        action        = "Allow"
      }
    ]
    

    variable.tf:

    variable "custom_rules" {
      type = list(object({
        name          = string
        priority      = string
        enabled       = bool
        rule_type     = string
        variable_name = string
        operator      = string
        match_values  = list(string)   <-- Add this line
        selector      = optional(string)
        action        = string
      }))
    }
    

    Deployment:

    enter image description here

    enter image description here

    Refer:

    https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/custom-waf-rules-overview#match-variables

    https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/web_application_firewall_policy#selector-2