Configure a P2S VPN connection to your Azure Virtual Desktop Environment using Terraform

So you have an Azure Virtual Desktop environment and you need to check something on a session host. These machines don’t have a public ip address so you need a way of connecting to it on the private ip address.

You can use Azure Bastion and connect through the Azure portal or with the native client if you have a Bastion on the Standard sku. The other way to connect is through a VPN tunnel.

In this blog post I’ll describe the steps you need to create a P2S VPN tunnel with Terraform. I’m going to configure the VPN gateway to use a Certificate to authenticate. The script I use to generate the root and client certificate is from Wim Matthyssen. You can find his blog about it here.

Where does the VPN Gateway belong in your Azure environment?

In a typical Hub-Spoke virtual network environment you put the VPN Gateway in the Hub. In my example in the “rg-hub-jvn-networking-01” resource group.

I have a separate networking resource group for the AVD network that is peered with the hub to complete the hub-spoke topology. Don’t forget to make sure that you configure the peering between your hub and spoke correctly.

What resources to put in your Terraform file?

  • Public IP for the VPN Gateway
  • Diagnostic Settings for the Public IP
  • VPN Gateway

I also use a couple of data blocks to import the following resources:

  • Log Analytics Workspace for the diagnostic settings of the public ip
  • Hub virtual network
  • GatewaySubnet
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=3.4.0"
    }
  }
}

provider "azurerm" {
  features {}
}

provider "azurerm" {
  features {}
  alias           = "hub"
  subscription_id = var.subscription_id_mgmt
}
provider "azurerm" {
  features {}
  alias           = "prod"
  subscription_id = var.subscription_id_prd
}
provider "azurerm" {
  features {}
  alias           = "identity"
  subscription_id = var.subscription_id_identity
}
provider "azurerm" {
  features {}
  alias           = "avd"
  subscription_id = var.subscription_id_avd
}

data "azurerm_virtual_network" "hub" {
  provider            = azurerm.hub
  name                = "vnet-${var.env}-${var.prefix}-we-01"
  resource_group_name = "rg-${var.env}-${var.prefix}-networking-01"
}
data "azurerm_log_analytics_workspace" "law" {
  name                = "law-${var.env}-${var.prefix}-01"
  resource_group_name = "rg-${var.env}-${var.prefix}-management-01"
}
data "azurerm_subnet" "gateway" {
  name                 = "GatewaySubnet"
  resource_group_name  = "rg-${var.env}-${var.prefix}-networking-01"
  virtual_network_name = data.azurerm_virtual_network.hub.name

}
resource "azurerm_public_ip" "pip" {
  name                = "pip-${var.env}-${var.prefix}-vpng-01"
  location            = data.azurerm_virtual_network.hub.location
  resource_group_name = data.azurerm_virtual_network.hub.resource_group_name

  allocation_method = "Dynamic"
  tags = {
    "Critical"    = "Yes"
    "Solution"    = "Public IP VPNG"
    "Costcenter"  = "It"
    "Location"    = "We"
  }
}
resource "azurerm_monitor_diagnostic_setting" "vpng-pip-diag" {
  provider = azurerm.hub
  name = "diag-pip-${var.prefix}-vpng"
  target_resource_id = azurerm_public_ip.pip.id
  log_analytics_workspace_id = data.azurerm_log_analytics_workspace.law.id
  log {
    category = "DDoSProtectionNotifications"
    enabled  = true

    retention_policy {
      enabled = true
    }
   
  }
  log {
    category = "DDoSMitigationFlowLogs"
    enabled = true

    retention_policy {
      enabled = true
    }
  }
  log {
    category = "DDoSMitigationReports"
    enabled =true
  }

  metric {
    category = "AllMetrics"

    retention_policy {
      enabled = true
    }
  }
  
}

resource "azurerm_virtual_network_gateway" "gateway" {
  name                = "vpng-${var.env}-${var.prefix}-01"
  location            = data.azurerm_virtual_network.hub.location
  resource_group_name = data.azurerm_virtual_network.hub.resource_group_name

  type     = "Vpn"
  vpn_type = "RouteBased"

  active_active = false
  enable_bgp    = false
  sku           = "VpnGw1"
  tags = {
    "Critical"    = "Yes"
    "Solution"    = "VPN Gateway"
    "Costcenter"  = "It"
    "Location"    = "We"
  }

  ip_configuration {
    name                          = "vnetGatewayConfig"
    public_ip_address_id          = azurerm_public_ip.pip.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = data.azurerm_subnet.gateway.id
  }

  vpn_client_configuration {
    address_space = ["172.16.101.0/24"]

    root_certificate {
      name = "p2s-jvn-root-cert"

      public_cert_data = <<EOF
Your cert goes here
EOF

    }
  }
}

P2S config

When the deployment is finished we can check the config of the P2S connection. To view this info go to your VPN Gateway and select Point-to-site configuration in the left blade. You can see that it has certificate selected as authentication type.

You can also see the root cert and the certificate information on the right.

How do you connect with the VPN Gateway?

To connect you can use the Azure VPN Client that you can download from the Microsoft store.

To get the correct VPN config you can download the xml file as you can see in below screenshot. You will get a zip with the name of your VPN Gateway. the file you need is in the Azure VPN folder. Click the + sign in the client and select import to import the xml file.

When everything is configured as it should you can connect to your VPN Gateway.

Connect to the AVD session hosts

Now that the VPN is working and the Hub-Spoke config is configured it’s possible to connect to the hosts with the private ip.

This concludes my blog post about how to deploy a P2S VPN connection to an AVD spoke virtual network.

I hope this blog post can help you and if you have any more questions feel free to contact me.

1 thought on “Configure a P2S VPN connection to your Azure Virtual Desktop Environment using Terraform

Leave a Reply

Your email address will not be published. Required fields are marked *