In my previous article on how to setup an Azure Virtual WAN with Terraform, I talked about how useful V-WAN was and showed how to setup a multi-region Hub-and-Spoke network with it using Terraform. I may have alluded to some of the shared network services that could be attached. After you have your V-WAN setup, you’re probably gonna want to connect to it to do some stuff. One of the most useful things is enabling VPN connectivity.

Setting up Point-2-Site VPN

It’s always good to RTFM. If you know how to do something manually, you have a shot at automating it. If you can’t do it manually you are SOL. Sorry, that’s it, it’s over. So if you’re like me and you don’t setup Point-to-Site VPN on a daily basis, you might want to brush up on how it all works like I did by reading the manual that was graciously provided for the ClickOps engineers of the world in the Microsoft Azure official documentation.

First let’s start by setting up how our VPN clients are going to authenticate with our VPN. VPN Server Config

resource "azurerm_vpn_server_configuration" "p2s_config" {

  name                = "vpng-${var.name}"
  resource_group_name = var.resource_group_name
  location            = var.location
  vpn_authentication_types = ["AAD"]
  vpn_protocols            = ["OpenVPN"]

  azure_active_directory_authentication {
    tenant   = "https://login.microsoftonline.com/${var.tenant_id}/"
    audience = var.audience
    issuer   = "https://sts.windows.net/${var.tenant_id}/"
  }

}

First notice that resource_group_name and location are set using input variables. This is because the code I am showing you is from my fit-for-purpose Terraform module that assumes you provision the resource group outside of the module and pass in these details as input variables. If you were to include this resource in your root module you’ll likely reference an existing Azure Resource Group using the resource reference that looks like this: azurerm_resource_group.main.resource_group_name and likewise the location attribute would be also pulled from the azurerm_resource_group in the same way.

Now, what were we talking about? Ah, yes, the VPN Server Configuration. There are many ways to configure the azurerm_vpn_server_configuration. resource type. Different VPN types and different authentication methods. I’m setting this up so that it works with Entra ID and Azure VPN. As a result, you need to make sure your specify your Entra ID Tenant ID and the Entra ID “Application ID” for the “Azure VPN application”. The most important thing is to authorize the “Azure VPN application”.

Registering the Azure VPN Application

To authorize the Azure VPN application, sign in to the Azure portal using an account with the “Cloud Application Administrator” role. Grant admin consent for your organization by copying and pasting the URL specific to your deployment location (e.g., Public, Azure Government) into your browser’s address bar. You literally just copy pasta the URL into your address bar and hit enter. As long as you are logged in with the correct Entra ID user it should work. I suppose I could probably have Terraform do this automatically — that might make for an interesting use of the http provider.

Essentially, this action allows the “Azure VPN application” to access and read user profiles. If you’re using a non-native “Cloud Application Administrator” account, replace “common” in the URL with your Microsoft Entra Tenant ID.

After accessing the URL, select the appropriate administrator account (if prompted), and then click “Accept” on the Permissions requested page. Finally, navigate to Microsoft Entra ID in the Azure portal, select “Enterprise applications” from the left pane, and verify that “Azure VPN” is listed among the applications.

You can follow these official Microsoft instructions to see all the different options for setting it up. There are many ways to do it but I would recommend setting it up manually (the way I just explained it). For more information see the detailed guide from the official Microsoft documentation. This process yields the two values we are looking for:

  1. Tenant ID (which we already had)
  2. Azure VPN application identifier

Now we just plug them in. The Azure VPN application identifier is going to be our audience. This can be a bit confusing but its something you’ll have to get used to when working with Authentication and Authorization (authN and authZ) systems. You often need to understand the relationships between many different systems and, like all relationships, things are relative. We use the common vernacular audience, tenant, etc. to ensure we map the correct system into the correct role in the authN/authZ process. Otherwise, KLANG will happen.

tenant_id             = "YOUR ENTRA ID TENANT ID"
audience              = "Azure VPN Application ID"

Setup the VPN Gateway

Now that our VPN is talking to Entra (our authentication mechanism) we need to give the people a door they can open in the form of a public endpoint that their VPN client software can reach out an talk to.

Most Likely your VPN clients are going to be connecting over the internet. Therefore, you will need a Public IP Address — or a PIP, for short.

resource "azurerm_public_ip" "vpn" {

  name                = "pip-vpng-${var.name}"
  resource_group_name = var.resource_group_name
  location            = var.location
  allocation_method   = "Static"

}

With that out of the way you need to setup the thing that sits behind that PIP and answers the phone. That’s the VPN Gateway. The first thing we need to do is consult the IPAM Oracle and find an IP Address Range that we want our VPN clients to use when they get through the door.

vpn_address_space     = "10.37.0.0/24"

This is a good value to pass in as an input variable to make your Terraform module more reusable. Hard-coded CIDR blocks are a major downer and should be avoided.

Now to declare the VPN Gateway. WATCH OUT! Do not be fooled by the imposter that is azurerm_vpn_gateway. Yes, I know this might sound like the correct resource, but I assure you, when working with V-WAN, it is not!

The resource you are looking for is azurerm_vpn_server_configuration. Yes, I know what you are saying, it just doesn’t quite have the same ring to it, but I assure you, this is what you want (at least at time of writing with version v4.4.0 of the azurerm provider. The big things you need to do on this thing is: Attach it to a Virtual Hub on the V-WAN, probably the “primary” one but depending on your needs you might want to create your own Hub for it or need to create multiple to support a highly dispersed work force.

Connect it to the Point-to-Site VPN Server Configuration that is going to handle authentication and all that.

resource "azurerm_point_to_site_vpn_gateway" "p2s_config" {
  name                = "vpng-${var.name}"
  resource_group_name = var.resource_group_name
  location            = var.location

  virtual_hub_id              = azurerm_virtual_hub.primary.id
  vpn_server_configuration_id = azurerm_vpn_server_configuration.p2s_config.id
  scale_unit                  = 1
  connection_configuration {
    name = "${var.name}-gateway-config"

    vpn_client_address_pool {
      address_prefixes = [var.vpn_address_space]
    }
  }
}

I created a module for this too. You might be wondering, ‘why wouldn’t you include this in the V-WAN module you showed us in the previous article?’ That’s a good question. The reason is that I like to make modules that follow the single responsibility principle. Need a V-WAN but don’t need P2S VPN? No problem. Don’t use this module. Need a V-WAN and a P2S VPN? No problem! Use both modules and stick them together like two little lego bricks! The V-WAN is not tightly coupled with the P2S VPN and vice versa. It gives consumers of these modules the flexability to provision sophisticated solutions but they ‘opt-in’ to additional features, they don’t simply opt out. It also makes my modules a heck of a lot simpler to read and understand what the crap they are doing.

module "vpn" {
  source  = "markti/azure-terraformer/azurerm//modules/network/vpn/entra/vwan"
  version = "1.0.15"

  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  name                = "${var.application_name}-${var.environment_name}"
  address_space       = var.vpn_address_space
  tenant_id           = var.tenant_id
  audience            = var.audience
  virtual_hub_id      = module.vwan.primary_hub_id

}

Anyways, now that we have our P2S VPN setup on our V-WAN, now we need to connect to it.

Setup the Azure VPN Client

In order to connect, simply export the VPN configuration file by going to the Point-to-site configuration page in the Azure Portal clicking on “Download VPN client.” It will produce a ZIP file, which will be named after your gateway (i.e., vpng-something-something). Extract the zip file and navigate to the “AzureVPN” folder to locate the azurevpnconfig.xml, which contains the VPN connection settings. You can distribute this XML file to users who need to configure their Azure VPN Client, ensuring they have valid Microsoft Entra ID credentials to connect successfully. There is an import command within the Azure VPN Client which will load your new P2S VPN connection into the Azure VPN. The nice thing about V-WAN is you only need one P2S VPN and then you are connected to the entire network.

Conclusion

So there you have it — a not-so-quick rundown on getting your Point-to-Site VPN up and running on Azure V-WAN using Terraform. It’s a bit of a trek, but once you’re through the weeds of configurations and resources, you’ve got yourself a robust VPN setup that’s as scalable as it is secure. Now, go forth and enjoy that seamless connectivity.

Happy Azure Terraforming!