Using GitHub and Terraform to deploy Azure resources - Part 5

Table Of Contents

Intro

I want to deploy some network services to my environment in this part of the blog series. I first need to update my peerings to use the VPN gateway. The VPN requires a slight change in both ends of the current peering. I also want to add an Azure Firewall, which means using route tables, and finally, I want to add network security groups to my subnets in the AVD virtual network. Below is a design diagram for this part of the series.

Peering update

The peerings I created in the last part of this series need an update to make use of the VPN gateway, it is a small change, but it is in both peering files. I have added the code below.

Peering.tf for “rg-connectivity-network-001.”

data "azurerm_virtual_network" "RemotevNet" {
  name                = var.RemotevNet.name
  resource_group_name = var.RemotevNet.resourcegroup
}

resource "azurerm_virtual_network_peering" "AVD-To-Connectivity" {
  name                      = "Connectivity-To-AVD"
  resource_group_name       = azurerm_resource_group.resourcegroup.name
  virtual_network_name      = azurerm_virtual_network.vnet.name
  remote_virtual_network_id = data.azurerm_virtual_network.RemotevNet.id
  allow_gateway_transit     = true  
}

Peering.tf for “rg-avd-network-001.”

data "azurerm_virtual_network" "RemotevNet" {
  name                = var.RemotevNet.name
  resource_group_name = var.RemotevNet.resourcegroup
}

resource "azurerm_virtual_network_peering" "AVD-To-Connectivity" {
  name                      = var.RemotevNet.connectionname
  resource_group_name       = azurerm_resource_group.resourcegroup.name
  virtual_network_name      = azurerm_virtual_network.vnet.name
  remote_virtual_network_id = data.azurerm_virtual_network.RemotevNet.id
  use_remote_gateways       = true 
}

The changes above are the “allow_gateway_transit” for the hub network and the “use_remote_gateways” for the AVD network. With these changes, I now have connectivity between my on-premises environment and both the connectivity and AVD network in Azure.

Without Azure firewall

If you skip the Azure firewall, you can skip the route tables. You can, however, use route tables to route all traffic to your on-premises firewall if you want to.

Below is a diagram of the environment if you skip the Azure firewall.

Azure firewall

WARNING!!!! Azure Firewall is expensive, so if you are running a lab environment only you might want to skip this step

I want to add an Azure Firewall to keep my Azure environment safe.

If you do want to create an Azure Firewall in your lab, you can shut it down when you are not using your lab and thereby save money. Read this article on the Microsoft docs page:

https://docs.microsoft.com/en-us/azure/firewall/firewall-faq#how-can-i-stop-and-start-azure-firewall

So with all the warnings out of the way, let us look at the terraform code to deploy it.

I have added the code below to my main.tf file in the folder “rg-connectivity-network-001.”

If you are copying the code from GitHub, please notice that I have uncommented these lines. I commented the lines out because I am in a Microsoft preview regarding Azure Firewall, so I can’t use the deployment for my environment. The code is fully tested and working fine.

data "azurerm_subnet" "AzureFirewallSubnet" {
  virtual_network_name = azurerm_virtual_network.vnet.name
  name = "AzureFirewallSubnet"
  resource_group_name = azurerm_virtual_network.vnet.resource_group_name
  depends_on = [
    azurerm_subnet.subnets
  ] 
}
resource "azurerm_public_ip" "public-ip-AzureFirewall" {
  name                = "pip-${var.AzureFirewallName}"
  location            = azurerm_resource_group.resourcegroup.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
  allocation_method   = "Static"
  sku                 = "Standard"
}
resource "azurerm_firewall" "AzureFirewall" {
  name                = var.AzureFirewallName
  location            = azurerm_resource_group.resourcegroup.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
  sku_name            = "AZFW_VNet"
  sku_tier            = "Standard"

  ip_configuration {
    name                 = "configuration"
    subnet_id            = data.azurerm_subnet.AzureFirewallSubnet.id
    public_ip_address_id = azurerm_public_ip.public-ip-AzureFirewall.id
  }
}

resource "azurerm_firewall_policy" "FirewallPolicy" {
  name                = "afwp-connectivity-001"
  resource_group_name = azurerm_resource_group.resourcegroup.name
  location            = azurerm_resource_group.resourcegroup.location
}

The code above adds a public IP address used by Azure Firewall. The public IP address must have a static IP and be of the type “Standard.” The last part is creating an Azure Firewall policy for which we will create rules in the next section.

Azure Firewall policy

I am using the Azure Firewall, so I need a few rules. In this blog series, I will keep it simple with just internet and on-premises access rules. I like to have my policy rules in a separate file. I feel the additional file gives me a better overview of what rules are in place.

data "azurerm_firewall_policy" "AzureFirewallPolicy" {
    name = "afwp-connectivity-001"
    resource_group_name = azurerm_resource_group.resourcegroup.name

}

resource "azurerm_firewall_policy_rule_collection_group" "FirewallAVDRuleCollection" {
  name               = "rcg-avd"
  firewall_policy_id = data.azurerm_firewall_policy.AzureFirewallPolicy.id
  priority           = 1000
  application_rule_collection {
    name     = "rc_avd_webbrowsing"
    priority = 5000
    action   = "Allow"
    rule {
      name = "rule_avd_webbrowsing"
      protocols {
        type = "Http"
        port = 80
      }
      protocols {
        type = "Https"
        port = 443
      }      
      source_addresses  = ["172.17.0.0/16"]
      destination_fqdns = ["*"]
    }
  }

  network_rule_collection {
    name     = "rc_avd"
    priority = 1000
    action   = "Allow"
    rule {
      name                  = "rule-avd-to-onpremises"
      protocols             = ["TCP", "UDP","ICMP"]
      source_addresses      = ["172.17.0.0/16"]
      destination_addresses = ["192.168.1.0/24", "192.168.10.0/24"]
      destination_ports     = ["*"] 
    }
  } 
}
resource "azurerm_firewall_policy_rule_collection_group" "FirewallOnPremisesRuleCollection" {
  name               = "rcg-onpremises"
  firewall_policy_id = data.azurerm_firewall_policy.AzureFirewallPolicy.id
  priority           = 1100
  
  network_rule_collection {
    name     = "rc_onpremises"
    priority = 1100
    action   = "Allow"
    rule {
      name                  = "rule-onpremises-to-avd"
      protocols             = ["TCP", "UDP", "ICMP"]
      source_addresses      = ["192.168.1.0/24", "192.168.10.0/24"]
      destination_addresses = ["172.17.0.0/16"]
      destination_ports     = ["*"] 
    }
  } 
}

With the code above, I have two firewall rule groups, one for my on-premises network and one for my AVD network. I allow all traffic to flow between these two networks. In production, I narrow down traffic to only what is needed.

Route tables

Now that I have the firewall configured, I need to route the traffic to and from my virtual networks through the firewall. To accomplish this, I use route tables. I will create two route tables in my environment, one for the AVD subnets and one for the Gateway subnet, which is in the Connectivity virtual network. In the routing table for AVD, I will create a few routes, the main route for 0.0.0.0/0 (meaning all traffic that doesn’t have a more precise route created), to send traffic to my firewall (172.16.0.68). I have two routes defined for VPN, ensuring that my VPN traffic goes to the traffic instead of using the network peering we have. I also have routes defined for some Azure services that need to talk directly from the virtual network to the internet.

For the gateway subnet, I will send traffic to 172.17.0.0/16 to the firewall (172.16.0.68).

I have added the code below to main.tf for “rg-connectivity-network-001.”

resource "azurerm_route_table" "routes" {
  name                          = "rt-${azurerm_virtual_network.vnet.name}"
  location                      = azurerm_resource_group.resourcegroup.location
  resource_group_name           = azurerm_resource_group.resourcegroup.name
  disable_bgp_route_propagation = true

  
  route {
    name                    = "udr-onprem-to-avd"
    address_prefix          = "172.17.0.0/16"
    next_hop_type           = "VirtualAppliance"
    next_hop_in_ip_address  = "172.16.0.68"
  }
}

data "azurerm_subnet" "subnets" {
  name                  = "GatewaySubnet"
  virtual_network_name  = azurerm_virtual_network.vnet.name
  resource_group_name   = azurerm_resource_group.resourcegroup.name
}
resource "azurerm_subnet_route_table_association" "routetableassociation" {
  subnet_id      = data.azurerm_subnet.subnets.id
  route_table_id = azurerm_route_table.routes.id
}

I have added the code below for main.tf for “rg-avd-network-001.”

resource "azurerm_route_table" "routes" {
  name                          = "rt-${azurerm_virtual_network.vnet.name}"
  location                      = azurerm_resource_group.resourcegroup.location
  resource_group_name           = azurerm_resource_group.resourcegroup.name
  disable_bgp_route_propagation = true

  route {
    name           = "udr-azure-kms"
    address_prefix = "23.102.135.246/32"
    next_hop_type  = "Internet"
  }
  route {
    name                    = "udr-internet"
    address_prefix          = "0.0.0.0/0"
    next_hop_type           = "VirtualAppliance"
    next_hop_in_ip_address  = "172.16.0.68"
  }
  route {
    name                    = "udr-vpn-001"
    address_prefix          = "192.168.1.0/24"
    next_hop_type           = "VirtualAppliance"
    next_hop_in_ip_address  = "172.16.0.68"
  }
  route {
    name                    = "udr-vpn-002"
    address_prefix          = "192.168.10.0/24"
    next_hop_type           = "VirtualAppliance"
    next_hop_in_ip_address  = "172.16.0.68"
  }
}
resource "azurerm_subnet_route_table_association" "routetableassociation" {
  for_each = var.Subnets
  subnet_id      = azurerm_subnet.subnets[each.value["name"]].id
  route_table_id = azurerm_route_table.routes.id
}

With the above code in place, my traffic flows through the firewall when sending between the AVD subnet and my on-premises location.

Network security groups

The last resource I want to create in the part of the series is network security groups. Not all subnets allow for network security groups, so I will focus only on the subnets I have in the AVD virtual network in this blog series. The network security groups are an extra layer of security that ensures that I can block or allow traffic into and out of my subnets. I primarily use the network security groups to control network traffic between subnets in the same virtual network, whereas the firewall is between virtual networks. It is possible to use the firewall for all the traffic, but it also has a cost to analyze the traffic in the firewall, but the network security groups are free of charge.

I will create a network security group for each of my subnets in the AVD virtual network. Below is the code for the network security groups.

resource "azurerm_network_security_group" "networksecuritygroups" {
  for_each = var.Subnets
  name                = "nsg-${each.value["name"]}"
  location            = azurerm_resource_group.resourcegroup.location
  resource_group_name = azurerm_resource_group.resourcegroup.name  
}

resource "azurerm_subnet_network_security_group_association" "nsg_association" {
  for_each = var.Subnets
  subnet_id                 = azurerm_subnet.subnets[each.value["name"]].id
  network_security_group_id = azurerm_network_security_group.networksecuritygroups[each.value["name"]].id 
}

Summary

I can now complete this fifth part of the blog series. I created the Azure firewall and ensured that the virtual network for AVD and my on-premises network flow through the firewall. I also added network security groups to the subnets in the AVD virtual network. l

The next part of this blog series will be about adding AVD services to the environment.

Any feedback is welcome, so reach out on Twitter or LinkedIn, so I can fix any errors or optimize the code I am using.

Part 1: https://www.cloudninja.nu/post/2022/06/github-terraform-azure-part1/

Part 2: https://www.cloudninja.nu/post/2022/06/github-terraform-azure-part2/

Part 3: https://www.cloudninja.nu/post/2022/06/github-terraform-azure-part3/

Part 4: https://www.cloudninja.nu/post/2022/06/github-terraform-azure-part4/

Part 6: https://www.cloudninja.nu/post/2022/07/github-terraform-azure-part6/

Part 7: https://www.cloudninja.nu/post/2022/08/github-terraform-azure-part7/

Part 8: https://www.cloudninja.nu/post/2022/08/github-terraform-azure-part8/

Link for all the code in this post

I have put all the code used in this blog post on my GitHub repository so you can download or fork the repository if you want to.

https://github.com/mracket/GitHub-Terraform

Comments