Configuring Always On device based VPN using Azure VPN Gateway

What is Always On VPN?

Always On VPN is the recommended replacement for Microsoft’s DirectAccess, it is also a Microsoft Product that allows you have a constant VPN connection to a selected network.

In this blog, I am going to show you how you can use an Always On device based VPN setup utilising an Azure VPN Gateway

Requirements & Restrictions

  • Always On VPN can be configured either device (device certificate) or user based when using an Azure VPN Gateway
  • Windows 10 Enterprise requirement for user devices
  • The Azure VPN Gateway must be route-based configuration
  • Azure VPN Gateway SKU must be VpnGw1 or above, basic Gateway is not supported
  • Note the maximum connections on each Gateway limitation (You may require more for your setup, will include the common 3 Gateways below
  • The device must be a domain joined computer running Windows 10 Enterprise or Education version 1809 or later.
  • The tunnel is only configurable for the Windows built-in VPN solution and is established using IKEv2 with computer certificate authentication.
  • Only one device tunnel can be configured per device.
Vpn GatewayBandwidthP2S Tunnels
VPNGw1650 MbpsMax 250
1-128: Included
129-250: £0.008/hour per connection
VPNGw21 GbpsMax 500
1-128: Included
129-500: £0.008/hour per connection
VPNGw31.25 GbpsMax 1000
1-128: Included
129-1000: £0.008/hour per connection

Why will the Azure VPN Gateway be used for?

An Always On VPN device tunnel is a certificate-based authentication, the Always On VPN device tunnel is authenticated against a certificate CA that is issued on your VPN Gateway. The VPN Gateway will then authorise a successful connection if the user’s certificate matches with the CA.

What certificates to use?

In this blog, I am using self-signed certificates but in an actual production-like environment a verified CA would be recommended

Create self-signed certificates as below

Create a Root CA and Client self-signed certificates

#Create Certs - Root 
$tamopsrootcert = New-SelfSignedCertificate -Type Custom -KeySpec Signature `
-Subject "CN=tamopsvpnrootcert" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:CurrentUserMy" -KeyUsageProperty Sign -KeyUsage CertSign 


#Create Certs - Client
New-SelfSignedCertificate -Type Custom -DnsName P2SChildCert -KeySpec Signature `
-Subject "CN=P2SChildCert" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:CurrentUserMy" `
-Signer $tamopsrootcert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")

Both certificates now available in your Personal Certificate store of current user

Configure Point-to-Site Configuration on Azure VPN Gateway

Address Pool:- Needs to be configured, this pool is the IP Address that connected VPN traffic source will be coming from

Tunnel Type:- IKEv2 and OpenVPN (SSL) or IKEv2

Upload Root Certificate created above public key to the Azure VPN Gateway

PowerShell script below to achieve these changes

#Extract Root Cert
$certfind = Get-ChildItem -Path Cert:CurrentUserMy | ?{$_.Subject -eq 'CN=tamopsvpnrootcert'}

export-Certificate  -cert $certfind -FilePath C:UsersThomasDesktopexportcert.cer -type CERT  -NoClobber
certutil -encode C:UsersThomasDesktopexportcert.cer C:UsersThomasDesktopuseme.cer

#Upload configuration changes to Azure VPN Gateway
$P2SRootCertName = "P2SRootCert.cer"
$filePathForCert = "C:UsersThomasDesktopuseme.cer"
$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2($filePathForCert)
$CertBase64 = [system.convert]::ToBase64String($cert.RawData)
$p2srootcert = New-AzVpnClientRootCertificate -Name $P2SRootCertName -PublicCertData $CertBase64
Add-AzVpnClientRootCertificate -VpnClientRootCertificateName $P2SRootCertName -VirtualNetworkGatewayname "tamopsvpngw" -ResourceGroupName "vnet-vpn" -PublicCertData $CertBase64

Create VPN profile

Always On VPN profile example taken from docs.microsoft

<VPNProfile>  
  <NativeProfile>  
<Servers>azuregateway-1234-56-78dc.cloudapp.net</Servers>  
<NativeProtocolType>IKEv2</NativeProtocolType>  
<Authentication>  
  <MachineMethod>Certificate</MachineMethod>  
</Authentication>  
<RoutingPolicyType>SplitTunnel</RoutingPolicyType>  
 <!-- disable the addition of a class based route for the assigned IP address on the VPN interface -->
<DisableClassBasedDefaultRoute>true</DisableClassBasedDefaultRoute>  
  </NativeProfile> 
  <!-- use host routes(/32) to prevent routing conflicts -->  
  <Route>  
<Address>192.168.3.5</Address>  
<PrefixSize>32</PrefixSize>  
  </Route>  
  <Route>  
<Address>192.168.3.4</Address>  
<PrefixSize>32</PrefixSize>  
  </Route>  
<!-- need to specify always on = true --> 
  <AlwaysOn>true</AlwaysOn> 
<!-- new node to specify that this is a device tunnel -->  
 <DeviceTunnel>true</DeviceTunnel>
<!--new node to register client IP address in DNS to enable manage out -->
<RegisterDNS>true</RegisterDNS>
</VPNProfile>

From this VPN profile, modify <servers> to include your Azure Gateway Address. This is found by downloading the VPN client from Azure VPN Gateway


Once downloaded, extract the .zip and inside General folder, review the VPNSettings.xml

Reviewing .xml you will find <VpnServer>, this is to be updated inside your VPNProfile.xml

  <VpnServer>azuregateway-6f0b0078-26a7-4fca-9dad-34fdfe627981-c9b472fa6a8e.vpn.azure.com</VpnServer>

Update <Route> to specific routes you want your Always On VPN connection to access. For this example, I have include the full vNET

  <Route>  
<Address>10.0.1.0</Address>  
<PrefixSize>24</PrefixSize>  
  </Route>  

Final VPNProfile.xml

<VPNProfile>  
  <NativeProfile>  
<Servers>azuregateway-6f0b0078-26a7-4fca-9dad-34fdfe627981-c9b472fa6a8e.vpn.azure.com</Servers>  
<NativeProtocolType>IKEv2</NativeProtocolType>  
<Authentication>  
  <MachineMethod>Certificate</MachineMethod>  
</Authentication>  
<RoutingPolicyType>SplitTunnel</RoutingPolicyType>  
 <!-- disable the addition of a class based route for the assigned IP address on the VPN interface -->
<DisableClassBasedDefaultRoute>true</DisableClassBasedDefaultRoute>  
  </NativeProfile> 
  <!-- use host routes(/32) to prevent routing conflicts -->  
  <Route>  
<Address>10.0.1.0</Address>  
<PrefixSize>24</PrefixSize>  
  </Route>   
<!-- need to specify always on = true --> 
  <AlwaysOn>true</AlwaysOn> 
<!-- new node to specify that this is a device tunnel -->  
 <DeviceTunnel>true</DeviceTunnel>
<!--new node to register client IP address in DNS to enable manage out -->
<RegisterDNS>true</RegisterDNS>
</VPNProfile>

Further details on building a VPN Profile in this docs.microsoft article

Creating and deploying Always On VPN Profile

VPNProfile.xml to be exported in format below using this PowerShell

 $ProfileXML = Get-Content C:UserstamopsdesktopVPNProfile.xml

# Escape spaces in profile name
$ProfileNameEscaped = $ProfileName -replace ' ', '%20'
$ProfileXML = $ProfileXML -replace '<', '<'
$ProfileXML = $ProfileXML -replace '>', '>'
$ProfileXML = $ProfileXML -replace '"', '"'
 
 $ProfileXML | Out-File -FilePath ($env:USERPROFILE + 'desktopVPN_Profile.xml')


Now time to deploy your VPN Always On Profile, in my example I am deploying onto a test Virtual Machine using PowerShell with PsEXEC as below:-

PsExec.exe -i -s C:windowssystem32WindowsPowerShellv1.0powershell.exe

Take the output of VPN_Profile.xml and add into variable $ProfileXML as below, this is the script you will be running with PsExec.exe

$ProfileName = 'TamOps Always On VPN'
 
$ProfileXML = '<VPNProfile>  
  <NativeProfile>  
<Servers>azuregateway-6f0b0078-26a7-4fca-9dad-34fdfe627981-c9b472fa6a8e.vpn.azure.com</Servers>  
<NativeProtocolType>IKEv2</NativeProtocolType>  
<Authentication>  
  <MachineMethod>Certificate</MachineMethod>  
</Authentication>  
<RoutingPolicyType>SplitTunnel</RoutingPolicyType>  
 <!-- disable the addition of a class based route for the assigned IP address on the VPN interface -->
<DisableClassBasedDefaultRoute>true</DisableClassBasedDefaultRoute>  
  </NativeProfile> 
  <!-- use host routes(/32) to prevent routing conflicts -->  
  <Route>  
<Address>10.0.1.0</Address>  
<PrefixSize>24</PrefixSize>  
  </Route>   
<!-- need to specify always on = true --> 
  <AlwaysOn>true</AlwaysOn> 
<!-- new node to specify that this is a device tunnel -->  
 <DeviceTunnel>true</DeviceTunnel>
<!--new node to register client IP address in DNS to enable manage out -->
<RegisterDNS>true</RegisterDNS>
</VPNProfile>'
 
$ProfileNameEscaped = $ProfileName -replace ' ', '%20'
 
$ProfileXML = $ProfileXML -replace '<', '<'
$ProfileXML = $ProfileXML -replace '>', '>'
$ProfileXML = $ProfileXML -replace '"', '"'
 
$nodeCSPURI = './Vendor/MSFT/VPNv2'
$namespaceName = "rootcimv2mdmdmmap"
$className = "MDM_VPNv2_01"
 
$session = New-CimSession
 
try {
    $newInstance = New-Object Microsoft.Management.Infrastructure.CimInstance $className, $namespaceName
    $property = [Microsoft.Management.Infrastructure.CimProperty]::Create("ParentID", "$nodeCSPURI", 'String', 'Key')
    $newInstance.CimInstanceProperties.Add($property)
    $property = [Microsoft.Management.Infrastructure.CimProperty]::Create("InstanceID", "$ProfileNameEscaped", 'String', 'Key')
    $newInstance.CimInstanceProperties.Add($property)
    $property = [Microsoft.Management.Infrastructure.CimProperty]::Create("ProfileXML", "$ProfileXML", 'String', 'Property')
    $newInstance.CimInstanceProperties.Add($property)
 
    $session.CreateInstance($namespaceName, $newInstance)
    $Message = "Created $ProfileName profile."
    Write-Host "$Message"
}
catch [Exception] {
    $Message = "Unable to create $ProfileName profile: $_"
    Write-Host "$Message"
    exit
}
$Message = "Complete."
Write-Host "$Message"



You will now have a successful Always On VPN connection

Also note, the certificates created/used need to be in personal/TrustedRoot (CA cert) stores of your device.

5 comments

  1. Have you found a way to make this work with Azure AD authentication that uses the users credentials at logon to connect?

    1. Hi Gary, you may want to incorporate an NPS VM setup along with this to handle the Azure AD Auth? Depending on your initial requirements

  2. This guide really helped me to set this up outside of all of the missing characters in the scripts due to how WordPress screws up formatting. I kept getting “Unable to create Always On VPN profile: A general error occurred that is not covered by a more specific error code and thought it was my XML profile, but it turns out it was due to the script snippet from this site missing characters (the 3 lines that start with “$ProfileXML = $ProfileXML -replace”). Once I figured that out, the profile was created, and I was able to connect immediately.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s