I’m exploring Terraform for automated creation of Palo Alto firewall security rules using the panos_security_policy resource.
Having all security rules like this in the main.tf works as expected when adding / modifying / deleting the rules.
resource "panos_security_policy" "rules" {
rule {
name = "Rule-1"
audit_comment = "Initial config"
source_zones = [panos_zone.dmz.name]
source_addresses = [panos_panorama_address_group.addgrp1.name]
source_users = ["any"]
destination_zones = [panos_zone.internet.name]
destination_addresses = ["any"]
applications = ["ssh"]
services = ["application-default"]
categories = ["any"]
action = "allow"
}
rule {
name = "Rule-2"
audit_comment = "Initial config"
source_zones = [panos_zone.dmz.name]
source_addresses = [panos_panorama_address_group.addgrp1.name]
source_users = ["any"]
destination_zones = [panos_zone.internet.name]
destination_addresses = ["any"]
applications = ["any"]
services = ["any"]
categories = ["any"]
action = "deny"
}
lifecycle {
create_before_destroy = true
}
}
But I dont want to keep all details about the rules in the main.tf Especialy if the rulebase contains hundreds of rules. I want to keep them in the variables file. I’ve created something like this:
resource "panos_security_policy" "CreateSecurityRules" {
for_each = var.securityRules
rule {
name = each.value.name
source_zones = each.value.source_zones
source_addresses = each.value.source_addresses
source_users = each.value.source_users
destination_zones = each.value.destination_zones
destination_addresses = each.value.destination_addresses
applications = each.value.applications
services = each.value.services
categories = each.value.categories
action = each.value.action
}
lifecycle {
create_before_destroy = true
}
}
variable securityRules {
type = map(object({
name = string
source_zones = list(string)
source_addresses = list(string)
source_users = list(string)
destination_zones = list(string)
destination_addresses = list(string)
applications = list(string)
services = list(string)
categories = list(string)
action = string
}))
}
rule1 = {
name = "First rule"
source_zones = ["Inside"]
source_addresses = ["any"]
source_users = ["any"]
destination_zones = ["Outside"]
destination_addresses = ["any"]
applications = ["any"]
services = ["application-default"]
categories = ["any"]
action = "allow"
}
rule2 = {
name = "Second rule"
source_zones = ["Inside"]
source_addresses = ["any"]
source_users = ["any"]
destination_zones = ["DMZ"]
destination_addresses = ["any"]
applications = ["any"]
services = ["application-default"]
categories = ["any"]
action = "allow"
}
rule3 = {
name = "Third rule"
source_zones = ["DMZ"]
source_addresses = ["any"]
source_users = ["any"]
destination_zones = ["Outside"]
destination_addresses = ["any"]
applications = ["any"]
services = ["application-default"]
categories = ["any"]
action = "allow"
}
}
Unfortunately, I don’t have any luck with this code. Initially, Terraform manages to create the rules on the firewall, but not in the right order – something which is very important. If I rerun Terraform again, the expectation is that no changes are needed, but it does make some – either delete some rules or assign null values.
I assume the issue is with the logic behind the map looping (p.s. I tried with lists as well, again with no success).
Could you please help me solve this problem?
Terraform manages to create the rules on the firewall, but not in the right order – something which is very important
According to panos_security_policy, the security rule ordering will match how they appear in the terraform plan file.
I did some quick tests using a variable of type map(object)
and a null_resource
– results will be displayed in the plan file sorted alphabetically by map key.
Consider the following module:
variable "securityRules" {
type = map(object({
name = string
}))
}
resource "null_resource" "securityRules" {
for_each = var.securityRules
triggers = {
name = each.value.name
}
}
Running terraform plan
with the following terraform.tfvars
file:
securityRules = {
"rule100" = {
name = "xyz"
}
"rule2" = {
name = "bar"
}
"rule1" = {
name = "foo"
}
}
Result is:
Terraform will perform the following actions:
# null_resource.securityRules["rule1"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "foo"
}
}
# null_resource.securityRules["rule100"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "xyz"
}
}
# null_resource.securityRules["rule2"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "bar"
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
Ooops! Rules do get sorted by map key but not the way I expected (rule100
should be the last one).
Changing the keys slightly and running terraform plan
:
securityRules = {
"rule0100" = {
name = "xyz"
}
"rule0002" = {
name = "bar"
}
"rule0001" = {
name = "foo"
}
}
Result is the following:
Terraform will perform the following actions:
# null_resource.securityRules["rule0001"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "foo"
}
}
# null_resource.securityRules["rule0002"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "bar"
}
}
# null_resource.securityRules["rule0100"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "xyz"
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
Results are now properly sorted.
Changing the order of the elements in the map:
securityRules = {
"rule0002" = {
name = "bar"
}
"rule0001" = {
name = "foo"
}
"rule0100" = {
name = "xyz"
}
}
Result is the same as before:
Terraform will perform the following actions:
# null_resource.securityRules["rule0001"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "foo"
}
}
# null_resource.securityRules["rule0002"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "bar"
}
}
# null_resource.securityRules["rule0100"] will be created
+ resource "null_resource" "securityRules" {
+ id = (known after apply)
+ triggers = {
+ "name" = "xyz"
}
}
Plan: 3 to add, 0 to change, 0 to destroy.