I’m exploring Terraform for automated creation of NAT rules on a Palo Alto firewall using the panos_nat_rule_group resource.
A NAT rule have multiple options for the “Translated Packet”. We can translate the “Source” and/or the “Destination” IP of the packet. Furthermore, there are several translation options of these IPs.
Is it possible to create a variable which includes all possible options like this:
variable natRules {
description = "Variable for the NAT rules"
type = list(object({
name = string
description = string
audit_comment = string
original_packet = object({
source_zones = list(string)
destination_zone = string
destination_interface = string
service = string
source_addresses = list(string)
destination_addresses = list(string)
})
translated_packet = object({
source = object({
static_ip = object({
translated_address = string
bi_directional = bool
})
dynamic_ip_and_port = object({
translated_address = object({
translated_addresses = list(string)
})
interface_address = object({
interface = string
ip_address = string
})
})
dynamic_ip = object({
translated_addresses = list(string)
})
})
destination = object({
static_translation = object({
address = string
port = number
})
dynamic_translation = object({
address = string
port = number
distribution = string
})
})
})
}))
}
Then in the “terraform.tfvars” to put the values of the different NAT rules but populate only what is needed for the particular rule. For example, if we want to translate only the Source IP to a particular static IP to populate only the necessary objects in the variable and leave the rest empty. If we want to make another translation again to put only the needed values.
natRules = [
{
name = "Static-IP-Port"
description = "Test nat rule"
audit_comment = ""
original_packet = {
source_zones = ["Inside"]
destination_zone = "Outside"
destination_interface = "any"
service = "any"
source_addresses = ["any"]
destination_addresses = ["any"]
}
translated_packet = {
source = {
static_ip = {
translated_address = "10.1.1.1"
bi_directional = false
}
dynamic_ip_and_port = {
translated_address = {
translated_addresses = [""]
}
interface_address = {
interface = ""
ip_address = ""
}
}
dynamic_ip = {
translated_addresses = [""]
}
}
destination = {
static_translation = {
address = ""
port = null
}
dynamic_translation = {
address = ""
port = null
distribution = ""
}
}
}
},
{
name = "Dynamic-IP-Port"
description = "Test nat rule"
audit_comment = ""
original_packet = {
source_zones = ["Inside"]
destination_zone = "Outside"
destination_interface = "any"
service = "any"
source_addresses = ["any"]
destination_addresses = ["any"]
}
translated_packet = {
source = {
static_ip = {
translated_address = ""
bi_directional = false
}
dynamic_ip_and_port = {
translated_address = {
translated_addresses = [""]
}
interface_address = {
interface = "ethernet1/1"
ip_address = "11.11.11.1/24"
}
}
dynamic_ip = {
translated_addresses = [""]
}
}
destination = {
static_translation = {
address = ""
port = null
}
dynamic_translation = {
address = ""
port = null
distribution = ""
}
}
}
}
]
Then in main.tf to have a single resource only which will check what variables are set and based on that to apply only that part of the resource. What kind of conditional checking can be implemented?
resource "panos_nat_rule_group" "CreateNATRules" {
dynamic "rule" {
for_each = var.natRules
content {
name = rule.value.name
description = rule.value.description
audit_comment = rule.value.audit_comment
original_packet {
source_zones = rule.value.original_packet.source_zones
destination_zone = rule.value.original_packet.destination_zone
destination_interface = rule.value.original_packet.destination_interface
service = rule.value.original_packet.service
source_addresses = rule.value.original_packet.source_addresses
destination_addresses = rule.value.original_packet.destination_addresses
}
translated_packet {
source {
static_ip {
translated_address = rule.value.translated_packet.source.static_ip.translated_address
bi_directional = rule.value.translated_packet.source.static_ip.bi_directional
}
dynamic_ip_and_port {
interface_address {
interface = rule.value.translated_packet.source.dynamic_ip_and_port.interface_address.interface
ip_address = rule.value.translated_packet.source.dynamic_ip_and_port.interface_address.ip_address
}
}
dynamic_ip {
translated_addresses = rule.value.translated_packet.source.dynamic_ip.translated_addresses
}
}
destination {
}
}
}
}
lifecycle {
create_before_destroy = true
}
}
In the variable type definition add the optional
modifier on the objects attributes that are not required in every scenario with the default value set to null
.
Then in tfvars specify only values that are required in the particular use case. Do not set them to empty strings, don't specify them at all.
On the resource level use nested dynamic blocks with a condition in the for_each
statement to translate only required values:
for_each = <SOME_OBJECT_ATTRIBUTE> != null ? {SOME_MAP_FROM_VAR} : {}
Empty {}
means do nothing if the conditional results to false.