Deploying and Configuring Azure Firewall using Terraform

Recently I’ve been working with Azure Firewall and deploying it into various environments to provide security and traffic control. I’ve been doing the majority of the deployment of Azure Firewall using Terraform, so wanted to outline a few tips, tricks, and provide some specific code examples to help anyone else looking to deploy this using Terraform.

For this post, I am using the “Azure Firewall Demo Lab” I created and shared within my GitHub repo: Terraform-Azure/Azure-Firewall-DemoLab at main · jakewalsh90/Terraform-Azure (github.com)

This code sets up the following basic environment:

Within this post, I’ll break down the specific Firewall elements that are deployed, and also go into more detail around Rule creation using Terraform.

Creating the Azure Firewall instance

Creating an Azure Firewall instance is very straightforward – initially, all we need is a Public IP (for management) and then the Firewall instance itself:

#Azure Firewall Setup
#Public IP
resource "azurerm_public_ip" "region1-fw01-pip" {
  name                = "region1-fw01-pip"
  resource_group_name = azurerm_resource_group.rg1.name
  location            = var.loc1
  allocation_method   = "Static"
  sku                 = "Standard"

  tags = {
    Environment = var.environment_tag
    Function    = "baselabv1-azurefirewall"
  }
}
#Azure Firewall Instance
resource "azurerm_firewall" "region1-fw01" {
  name                = "region1-fw01"
  location            = var.loc1
  resource_group_name = azurerm_resource_group.rg1.name
  sku_tier = "Premium"

  ip_configuration {
    name                 = "fw-ipconfig"
    subnet_id            = azurerm_subnet.region1-vnet1-snetfw.id
    public_ip_address_id = azurerm_public_ip.region1-fw01-pip.id
  }
}

Additional important elements that can be configured within the Firewall instance block are shown below:

  • sku_name – This will adjust the SKU name of the firewall – options here are AZFW_Hub or AZFW_VNet. Use this if you want to use the Secure Virtual Hub operating method.
  • firewall_policy_id – Use this if you need to apply a policy to the firewall from the outset. This is useful if you have other code that preconfigures the required policies.
  • dns_servers – Azure Firewall can proxy DNS traffic to specified DNS servers. Provide a list of the DNS servers using this argument.
  • threat_intel_model – This allows the selection of the mode for the threat intelligence filtering system. Values here are Off, Alert, or Deny.

For a full configuration reference – please refer to the azurerm_firewall Terraform Documentation – azurerm_firewall | Resources | hashicorp/azurerm | Terraform Registry

Building out Firewall Policies

Before getting into the creation of Policies it’s worth spending some time understanding the available options using the azurerm module. At the time of writing – the following types of Policies and Rules can be created using Terraform:

For most implementations – it’s the Application, Netowrk, and Policy Rule Collection Groups that are most likely to be required. It’s worth spending some time understanding these policy types and how they will each best suit your requirements. To demonstrate these, I will setup a Policy and some Basic rules using Terraform to highlight the methods:

Creating Firewall Policies and Rule Collections

Creating a Policy (which will contain Rules and Rule Collections) is really simple:

#Firewall Policy
resource "azurerm_firewall_policy" "region1-fw-pol01" {
  name                = "region1-firewall-policy01"
  resource_group_name = azurerm_resource_group.rg1.name
  location            = var.loc1
}

Once created, this gives a blank Policy that we can then add rules/collections to:

Creating a Rule Collection:
A rule collection is simply a collection of rules of the same type:

Using the Terraform below, you can see an example Rule Collection with a range of different rules included:

# Firewall Policy Rules
resource "azurerm_firewall_policy_rule_collection_group" "region1-policy1" {
  name               = "region1-policy1"
  firewall_policy_id = azurerm_firewall_policy.region1-fw-pol01.id
  priority           = 100

  application_rule_collection {
    name     = "blocked_websites1"
    priority = 500
    action   = "Deny"
    rule {
      name = "dodgy_website"
      protocols {
        type = "Http"
        port = 80
      }
      protocols {
        type = "Https"
        port = 443
      }
      source_addresses  = ["*"]
      destination_fqdns = ["jakewalsh.co.uk"]
    }
  }

  network_rule_collection {
    name     = "network_rules1"
    priority = 400
    action   = "Allow"
    rule {
      name                  = "network_rule_collection1_rule1"
      protocols             = ["TCP", "UDP"]
      source_addresses      = ["*"]
      destination_addresses = ["192.168.1.1", "192.168.1.2"]
      destination_ports     = ["80", "8000-8080"]
    }
  }
}

As you can see in the above code, I have created the following sample elements:

  • An Application Rule Collection that blocks access to jakewalsh.co.uk over http and https on 80/443.
  • A Network Rule Collection that allows access to 192.168.1.1 and 192.168.1.2 on TCP or UDP 80 and 8000-8080, from any source.

Once created, these show within the Policy as below:

Once we have created a Policy and Rule collections, we can then use the Firewall Manager to associate this to the required Virtual Networks:

Creating Classic Rules

These are also really easy to create using Terraform, however, for a more comprehensive and Policy driven approach, I would recommend the use of Rule Collections as I’ve outlined above! The Policy driven approach provides much easier integration and allows the use of Firewall Manager to protect VNETs. Classic Rules are still available to create though, and we can also use a range of variables as you’ll see in my example below:

resource "azurerm_firewall_network_rule_collection" "specific-range-rules" {
  name                = "specific-range-firewall-rules"
  azure_firewall_name = azurerm_firewall.region1-fw01.name
  resource_group_name = azurerm_resource_group.rg1.name
  priority            = 100
  action              = "Allow"

  rule {
    name                  = "specific-range-firewall-rules"
    source_addresses      = ["10.0.0.0/16"]
    destination_addresses = [var.region1-gateway-address-space]
    destination_ports     = ["*"]
    protocols             = ["Any"]
  }
}

You’ll notice in the above I have a variable that defines the Local Network Gateway address range (var.region1-gateway-address-space) – so in this rule, I am allowing all traffic from my Azure VNET (10.10.0.0/16) out to a destination that exists over a Site to Site VPN, based on a variable that defines the remote network address space. This can be useful in complex deployments where you need access to sites/ranges/specific IP addresses that exist over VPN connections – and also if you are already using variables to help define these ranges (perhaps for setting up Site to Site VPNs) then these can be re-used in firewall rules. When created, here’s how the rule is shown:

Of course, we could also do a rule with specific IP addresses and ports, as shown below:

resource "azurerm_firewall_network_rule_collection" "specific-destination-rules2" {
  name                = "specific-destination-firewall-rules2"
  azure_firewall_name = azurerm_firewall.region1-fw01.name
  resource_group_name = azurerm_resource_group.rg1.name
  priority            = 101
  action              = "Allow"

  rule {
    name                  = "specific-range-firewall-rules"
    source_addresses      = ["10.0.0.0/16"]
    destination_addresses = ["10.10.100.1/32"]
    destination_ports     = ["3389"]
    protocols             = ["TCP"]
  }
}

Once implemented, the Classic Rules show in the Firewall:

The importance of Service Tags!

Service Tags are also extremely important when using Azure Firewall. As with all Cloud Services, backend IP addresses and destinations used by the underlying platform are subject to changes – and updating this across all Firewalls and Rulesets can be challenging. Service Tags aims to simplify this by providing Tags that can be used within Azure Firewall Rules – which are then updated by Microsoft. A list of available Service Tags is here.

To use Service Tags within Terraform, simply include the Tag within the created Rule, the Tag is placed in “destination_addresses”:

rule {
  name                  = "AzureActiveDirectory"
  source_addresses      = ["10.1.0.0/16]
  destination_addresses = ["AzureActiveDirectory"]
  destination_ports     = ["*"]
  protocols             = ["Any"]
}

Thanks for reading!

I hope this has been helpful and highlights how easy to create and manage Azure Firewall is when using Terraform. Until next time! 🙂