During these holidays I’ve spent some time working on setting up a VPN between my on-premises network and an Azure VNet. In order to setup the connectivity I have used StrongSwan on Linux at the on-premises side and a VpnGw1 VPN Gateway in Routed/Dynamic mode on the Azure side.
Scenario
Vnet GW Address | 1.2.3.4 |
Vnet GW type | Routing / Dynamic |
Vnet IP Address Space | 10.11.0.0./16 and 10.12.0.0/16 |
Note: The above addresses are not subnets from the same VNet, but separate address spaces.
Linux Distro | Ubuntu 16.04 LTS |
StrongSwan IP | 4.3.2.1 |
StrongSwan IP Addres Space | 100.64.0.0/24 |
StrongSwan version (“ipsec version”) | Linux strongSwan U5.3.5/K4.4.0-22-generic |
Note: The Ubuntu 16.04 LTS on-premises machine was acting as a router between the VPN tunnel and my on-premises network (100.64.0.0/24).
Setup Azure
Create a new local network gateway
Choose your desired name, IP address 4.3.2.1 (on-premises public IP) and IP Address Space 100.64.0.0/24 (on-premises network).
Create a Virtual Network Gateway on your Virtual Network.
- Gateway type: VPN
- VPN type: Route-based
- SKU: VpnGw1 (or any other VpnGwX if you need more throughput)
- Virtual Network: Select the Virtual Network you’d like to connect to.
- Gateway subnet address range: If none available, first reserve part of your Virtual Network’s address space for a Gateway Subnet. More information about planning for a Virtual Network Gateway deployment can be found here.
Create the connection object tying both gateways from above
In the screenshot below I have chosen sharedsecret as our Shared Key or PSK.
Setup on-premises StrongSwan
We need to modify a few configuration files in order to get our StrongSwan up and running against our Azure Virtual Network Gateway.
/etc/ipsec.conf
# ipsec.conf - strongSwan IPsec configuration file config setup conn azure leftupdown=/usr/local/sbin/ipsec-notify.sh # Script to create a VTI and configure the necessary routing when doing "ipsec up azure" (and remove changes when doing "ipsec down azure" authby=secret type=tunnel left=4.3.2.1 # My Public IP address leftsubnet=100.64.0.0/24 # My IP address space / protected network(s) right=1.2.3.4 #Azure Dynamic Gateway rightsubnet=10.11.0.0/16,10.12.0.0/16 #Azure Vnet prefixes auto=route keyexchange=ikev2 # Mandatory for Dynamic / Route-based gateway
/etc/ipsec.secrets
# This file holds shared secrets or RSA private keys for authentication. # In this case, we use a PSK. 4.3.2.1 1.2.3.4 : PSK 'sharedsecret'
In the example above, sharedsecret is your actual PSK or Shared Key.
In /etc/strongswan.d/charon.conf, uncomment and modify this line to leave it as below to avoid routing issues:
install_routes = no
Important: Without this change, StrongSwan will add routes on a routing table with more priority than the default one. That table doesn’t show up in a “ip route list“, but you can pull it with “ip route show table 220“. If you have skipped this step and are not reading this note, you’re now probably hitting your head on a wall as everything looks fine, but your VPN still doesn’t work.
/usr/loca/sbin/ipsec-notify.sh
#!/bin/bash set -o nounset set -o errexit VTI_IF="vti${PLUTO_UNIQUEID}" case "${PLUTO_VERB}" in up-client) ip tunnel add "${VTI_IF}" local "${PLUTO_ME}" remote "${PLUTO_PEER}" mode vti \ okey "${PLUTO_MARK_OUT%%/*}" ikey "${PLUTO_MARK_IN%%/*}" ip link set "${VTI_IF}" up ip route add 10.12.0.0/16 dev "${VTI_IF}" ip route add 10.11.0.0/16 dev "${VTI_IF}" sysctl -w "net.ipv4.conf.${VTI_IF}.disable_policy=1" ;; down-client) ip tunnel del "${VTI_IF}" ;; esac
Make sure it can be executed by the user ‘strongswan’, e.g.:
# chown strongswan:strongswan /usr/local/sbin/ipsec-notify.sh # chmod 755 /usr/local/sbin/ipsec-notify.sh
The script above creates a tunnel interface, sets the link to up and creates two routes to be able to reach the Azure Virtual Network prefixes through said tunnel interface.
At this stage I was having some problems that took a while to fix. Doing some online reading (don’t we all fix everything that way?) I found this link: https://bugs.launchpad.net/ubuntu/+source/strongswan/+bug/1549436 where basically says apparmor has to be disabled for Charon and Stroke, as follows:
# apparmor_parser -R /etc/apparmor.d/usr.lib.ipsec.charon # apparmor_parser -R /etc/apparmor.d/usr.lib.ipsec.stroke # ln -s /etc/apparmor.d/usr.lib.ipsec.charon /etc/apparmor.d/disable/ # ln -s /etc/apparmor.d/usr.lib.ipsec.stroke /etc/apparmor.d/disable/
Please make sure you understand what the above implies. Disabling security features might carry a risk. In my case that risk was minimal and worth taking, but your situation might be different.
Bring the tunnel up and rock on!
Restart the ipsec daemon:
# ipsec restart Stopping strongSwan IPsec... Starting strongSwan 5.3.5 IPsec [starter]...
Now launch the Azure connection
# ipsec up azure initiating IKE_SA azure[10] to 1.2.3.4 generating IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(HASH_ALG) ] sending packet: from 4.3.2.1[500] to 1.2.3.4[500] (1124 bytes) received packet: from 1.2.3.4[500] to 4.3.2.1[500] (38 bytes) parsed IKE_SA_INIT response 0 [ N(INVAL_KE) ] peer didn't accept DH group MODP_2048, it requested MODP_1024 initiating IKE_SA azure[10] to 1.2.3.4 generating IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(HASH_ALG) ] sending packet: from 4.3.2.1[500] to 1.2.3.4[500] (996 bytes) received packet: from 1.2.3.4[500] to 4.3.2.1[500] (360 bytes) parsed IKE_SA_INIT response 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) V V ] authentication of '4.3.2.1' (myself) with pre-shared key establishing CHILD_SA azure generating IKE_AUTH request 1 [ IDi N(INIT_CONTACT) IDr AUTH SA TSi TSr N(MOBIKE_SUP) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(EAP_ONLY) ] sending packet: from 4.3.2.1[4500] to 1.2.3.4[4500] (412 bytes) received packet: from 1.2.3.4[4500] to 4.3.2.1[4500] (140 bytes) parsed INFORMATIONAL request 0 [ N(NO_ADD_ADDR) N(COOKIE2) ] generating INFORMATIONAL response 0 [ N(COOKIE2) ] sending packet: from 4.3.2.1[4500] to 1.2.3.4[4500] (132 bytes) received packet: from 1.2.3.4[4500] to 4.3.2.1[4500] (212 bytes) parsed IKE_AUTH response 1 [ IDr AUTH N(MOBIKE_SUP) SA TSi TSr ] authentication of '1.2.3.4' with pre-shared key successful IKE_SA azure[10] established between 4.3.2.1[4.3.2.1]...1.2.3.4[1.2.3.4] scheduling reauthentication in 9941s maximum IKE_SA lifetime 10481s connection 'azure' established successfully
You can check the status of your azure connection as follows:
# ipsec status azure Routed Connections: azure{1}: ROUTED, TUNNEL, reqid 1 azure{1}: 100.64.0.0/24 === 10.11.0.0/16 10.12.0.0/16 Security Associations (1 up, 0 connecting): azure[10]: ESTABLISHED 3 minutes ago, 4.3.2.1[4.3.2.1]...1.2.3.4[1.2.3.4] azure{12}: INSTALLED, TUNNEL, reqid 1, ESP SPIs: c58685fa_i 0fb5129e_o azure{12}: 100.64.0.0/24 === 10.11.0.0/16 10.12.0.0/16
Important: If you don’t see any ESP SPIs above, the tunnel is not up.
As an example, after some inactivity time the SPIs might be killed and the output would be like this:
# ipsec status azure Routed Connections: azure{1}: ROUTED, TUNNEL, reqid 1 azure{1}: 100.64.0.0/24 === 10.11.0.0/16 10.12.0.0/16 Security Associations (1 up, 0 connecting): azure[10]: ESTABLISHED 7 minutes ago, 4.3.2.1[4.3.2.1]...1.2.3.4[1.2.3.4]
As a last check, your routes from /usr/local/sbin/ipsec-notify.sh should have been added to the main routing table:
# ip route list 10.11.0.0/16 dev vti10 scope link 10.12.0.0/16 dev vti10 scope link
And that’s it. Now any on-premises machine in the 100.64.0.0/24 should be able to use this Ubuntu box as a VPN gateway to privately reach our resources in the connected Azure VNet.
Let me know in the comments section if you had any problems following the above steps.
Hello
On the esp= and ike= what did you set these values too?
LikeLike
Hello Nick,
I do not recall making any other changes, which means defaults on both sides. Azure supports a good amount of different proposals out of the box so in most occasions you shouldn’t need to change anything to make it work unless you have specific needs (e.g. compliance).
Thanks
Pedro
LikeLike
Great write up thanks, worked for me although I had to make left=%any
LikeLike