azurefor-loopterraformflattenazure-front-door

Terraform using flatten with nested objects


Here is my situation, I am trying to deploy a legacy Azure Frontdoor instance to the new version of AFD and do it via Terraform.

Here is an excerpt from my locals block for the Firewall policy:

firewall_policy = {
    policy = {
      name     = "policy"
      sku_name = "AzureSKU"
      mode     = "prevention"
      managed_rules_list = [
        {
          type    = "Microsoft_DefaultRuleSet"
          version = "1.1"
          action  = "Block"
          exclusion = [
            {
              match_variable = "QueryStringArgNames"
              operator       = "Contains"
              selector       = "string1"
            },
            {
              match_variable = "RequestBodyPostArgNames"
              operator       = "StartsWith"
              selector       = "string2"
            },
            {
              match_variable = "RequestCookieNames"
              operator       = "EqualsAny"
              selector       = "string3"
            },
            {
              match_variable = "RequestBodyPostArgNames"
              operator       = "Contains"
              selector       = "string4"
            }
          ]
          override = [
            {
              rule_group_name = "RFI"
              rule = [
                {
                  rule_id = "931130"
                  action  = "Block"
                  exclusion = [
                    {
                      match_variable = "RequestBodyPostArgNames"
                      operator       = "Equals"
                      selector       = "string1"
                    },
                    {
                      match_variable = "QueryStringArgNames"
                      operator       = "Contains"
                      selector       = "string2"
                    },
                    {
                      match_variable = "QueryStringArgNames"
                      operator       = "Contains"
                      selector       = "string3"
                    },
                    {
                      match_variable = "QueryStringArgNames"
                      operator       = "Contains"
                      selector       = "string4"
                    }
                  ]
                }
              ]
            },
            {
              rule_group_name = "PHP"
              rule = [
                {
                  rule_id = "933100"
                  enabled = false
                  action  = "Block"
                },
                {
                  rule_id = "933110"
                  enabled = false
                  action  = "Block"
                },
                {
                  rule_id = "933120"
                  enabled = false
                  action  = "Block"
                }
              ]
            }
          ]
        }
      ]
    }

}

As you can see, there are lots of nested objects.

In my main.tf file, I call the following:

    module "Azure_FW_Policy_module" {
  for_each                                        = local.firewall_policy
  source                                          = "./frontdoorFirewallPolicy"
  cdn_frontdoor_firewall_policy_name              = each.value.name
  resource_group_name                             = var.resource_group_name
  sku_name                                        = each.value.sku_name
  mode                                            = each.value.mode
  managed_rules_list                              = each.value.managed_rules_list
  managed_rules_exclusion_list                    = each.value.managed_rules_list.exclusion
  managed_rules_overide_list                      = each.value.managed_rules_list.override
  managed_rules_overide_rule_list                 = each.value.managed_rules_list.override.rule
  managed_rules_overide_rule_exclusion_list       = each.value.managed_rules_list.override.rule.exclusion
  managed_rules_overide_rule_group_exclusion_list = each.value.managed_rules_list.override.exclusion
  custom_rules_list                               = each.value.custom_rules_list
  tags                                            = var.tags
}

The Module itself has a bunch of dynamic blocks in it, to accomodate the various configurations and looks like this:

    resource "azurerm_cdn_frontdoor_firewall_policy" "cdn_frontdoor_firewall_policy" {
  name                              = var.cdn_frontdoor_firewall_policy_name
  resource_group_name               = var.resource_group_name
  sku_name                          = var.sku_name
  enabled                           = var.enabled
  mode                              = var.mode
  custom_block_response_status_code = var.custom_block_response_status_code
  custom_block_response_body        = var.custom_block_response_body
  request_body_check_enabled        = var.request_body_check_enabled
  tags                              = var.tags
  dynamic "managed_rule" {
    for_each = toset(var.managed_rules_list)
    content {
      type    = managed_rule.value["type"]
      version = managed_rule.value["version"]
      action  = managed_rule.value["action"]
      dynamic "exclusion" {
        for_each = toset(var.managed_rules_exclusion_list)
        content {
          match_variable = exclusion.value["match_variable"]
          operator       = exclusion.value["operator"]
          selector       = exclusion.value["selector"]
        }
      }
      dynamic "override" {
        for_each = toset(var.managed_rules_overide_list)
        content {
          rule_group_name = override.value["rule_group_name"]
          dynamic "rule" {
            for_each = toset(var.managed_rules_overide_rule_list)
            content {
              rule_id = rule.value["rule_id"]
              action  = rule.value["action"]
              dynamic "exclusion" {
                for_each = toset(var.managed_rules_overide_rule_exclusion_list)
                content {
                  match_variable = exclusion.value["match_variable"]
                  operator       = exclusion.value["operator"]
                  selector       = exclusion.value["selector"]
                }
              }
            }
          }
          dynamic "exclusion" {
            for_each = toset(var.managed_rules_overide_rule_group_exclusion_list)
            content {
              match_variable = exclusion.value["match_variable"]
              operator       = exclusion.value["operator"]
              selector       = exclusion.value["selector"]
            }
          }
        }
      }
    }
  }
  dynamic "custom_rule" {
    for_each = toset(var.custom_rules_list)
    content {
      name                           = custom_rule.value["name"]
      enabled                        = custom_rule.value["enabled"]
      priority                       = custom_rule.value["priority"]
      type                           = custom_rule.value["type"]
      action                         = custom_rule.value["action"]
      rate_limit_duration_in_minutes = custom_rule.value["rate_limit_duration_in_minutes"]
      rate_limit_threshold           = custom_rule.value["rate_limit_threshold"]
      match_condition {
        match_variable     = custom_rule.value["match_variable"]
        operator           = custom_rule.value["operator"]
        negation_condition = custom_rule.value["negation_condition"]
        match_values       = custom_rule.value["match_values"]
      }
    }
  }
}

My issue is that when I run this - I get the following error:

"each.value.managed_rules_list is tuple with 1 element This value does not have any attributes."

Based on my reading, I believe that this is because I need to use the flatten command in Terraform on the firewall_policy element in order to pass this to the module:

Like here

However - despite reading the above, I am not sure exactly how I need to call flatten to account for all the nested objects and then how I can pass them to my module, as I have not used this before.


Solution

  • Terraform using flatten with nested objects while provisioning forntdoor firewall policy.

    In your main.tf, you're trying to fetch the attributes like each.value.managed_rules_list.exclusion, which assumes that managed_rules_list is a map. However, since managed_rules_list is defined as a list Terraform treats it as a tuple, and direct attribute access without specifying an index isn't valid.

    To resolve this, you should ensure that you're fetching the elements of your lists correctly. This can be done using Terraform's flatten function. Which can help manage deeply nested structures by converting nested lists into a single flat list, making them easier to iterate over.​

    Demo configuration:

    main.tf:

    locals {
      firewall_policy = {
        name     = "afdwafvkpolicy"
        sku_name = "Premium_AzureFrontDoor"
        mode     = "Prevention"
        managed_rules = [
          {
            type    = "Microsoft_DefaultRuleSet"
            version = "1.1"
            action  = "Block"
            exclusions = [
              {
                match_variable = "QueryStringArgNames"
                operator       = "Contains"
                selector       = "string1"
              },
              {
                match_variable = "RequestBodyPostArgNames"
                operator       = "StartsWith"
                selector       = "string2"
              }
            ]
            overrides = [
              {
                rule_group_name = "RFI"
                rules = [
                  {
                    rule_id = "931130"
                    action  = "Block"
                    exclusions = [
                      {
                        match_variable = "QueryStringArgNames"
                        operator       = "Contains"
                        selector       = "string3"
                      }
                    ]
                  }
                ]
              },
              {
                rule_group_name = "PHP"
                rules = [
                  {
                    rule_id = "933100"
                    action  = "Block"
                    enabled = false
                  },
                  {
                    rule_id = "933110"
                    action  = "Block"
                    enabled = false
                  }
                ]
              }
            ]
          }
        ]
        custom_rules = []
      }
    }
    
    module "afd_waf" {
      source                 = "./frontdoorFirewallPolicy"
      firewall_policy_config = local.firewall_policy
      resource_group_name    = var.resource_group_name
    }
    

    frontdoorFirewallPolicy/main.tf

    resource "azurerm_cdn_frontdoor_firewall_policy" "waf" {
      name                = var.firewall_policy_config.name
      resource_group_name = var.resource_group_name
      sku_name            = var.firewall_policy_config.sku_name
      mode                = var.firewall_policy_config.mode
    
    
      dynamic "managed_rule" {
        for_each = var.firewall_policy_config.managed_rules
        content {
          type    = managed_rule.value.type
          version = managed_rule.value.version
          action  = managed_rule.value.action
    
          dynamic "exclusion" {
            for_each = lookup(managed_rule.value, "exclusions", [])
            content {
              match_variable = exclusion.value.match_variable
              operator       = exclusion.value.operator
              selector       = exclusion.value.selector
            }
          }
    
          dynamic "override" {
            for_each = lookup(managed_rule.value, "overrides", [])
            content {
              rule_group_name = override.value.rule_group_name
    
              dynamic "rule" {
                for_each = lookup(override.value, "rules", [])
                content {
                  rule_id = rule.value.rule_id
                  action  = rule.value.action
                  enabled = lookup(rule.value, "enabled", true)
    
                  dynamic "exclusion" {
                    for_each = lookup(rule.value, "exclusions", [])
                    content {
                      match_variable = exclusion.value.match_variable
                      operator       = exclusion.value.operator
                      selector       = exclusion.value.selector
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    

    Deployment:

    enter image description here

    enter image description here

    Refer:

    https://learn.microsoft.com/en-us/azure/frontdoor/create-front-door-terraform

    https://library.tf/modules/T-Systems-MMS/cdn/azurerm/latest

    https://developer.hashicorp.com/terraform/language/functions/flatten