VPN to Azure on PI
This is howto setup an OpenVPN within Azure in less than 10 Minutes!!!
Introduction
This Howto is about to create a VPN running OpenVPN between your Home-LAN (On-premises) and Azure.
This will allow you to route any traffic from your Home-LAN into Azure and vice versa.
The setup is using SNAT on both VPN Server which is the quickest way to solve routing and security issues (eg NSG) and is therefore less secure and has a lower performance because of NAT, but depending on the proposed solution it is possible to skip the SNAT / Netfilter setup.
Optional it is possible to use the Home-OpenVPN Server as default gateway for Home-Clients, this way the home client is adapting the Azure Public IP Address. This is very useful if a public IP address is needed within another country. Note that this configuration requires SNAT for sure. Also note that this can result in a high traffic usage for which you get charged extra by Azure.
The setup is using Port 443 to communicate between the VPN Server because this port is almost alwyas open on WLans but any other port can be chosen too.
Requirements
- Your Home LAN
- Raspberry-PI or alternative another Linux Server
- Azure Subscription
- Azure CLI Tools installed somewhere
- Logged on terminal into AZ
Quick installations steps
- The following steps needed to setup the VPN:
- Create an Azure Network using the Azure CLI
- Create Routing between networks using the Azure CLI
- Create VM using the Azure CLI
- Install OpenVPN on a VM within Azure
- Setup both OpenVPN Server
- Setup Home LAN
- Setup the Home LAN routing
Overview
Azure
Create a custom Resurce-ID
Create the Resource-ID VPN-Test. This Resource-ID is used through all command samples below.
az group create --name VPN-Test --location eastus
root@rb01:~# az group create --name VPN-Test --location eastus { "id": "/subscriptions/727d7068-94e3-494a-965a-xxxxx/resourceGroups/VPN-Test", "location": "eastus", "managedBy": null, "name": "VPN-Test", "properties": { "provisioningState": "Succeeded" }, "tags": null, "type": "Microsoft.Resources/resourceGroups" }
Setup Vnet
Create a Custom Virtual Net
Create the Virtual-Net VNet01. This Name is used through all command samples below.
az network vnet create --name VNet01 \ --resource-group VPN-Test \ --location eastus \ --address-prefix 10.0.0.0/16 root@rb01:~# az network vnet create --name VNet01 --resource-group VPN-Test --location eastus --address-prefix 10.0.0.0/16 { "newVNet": { "addressSpace": { "addressPrefixes": [ "10.0.0.0/16" ] }, "bgpCommunities": null, "ddosProtectionPlan": null, "dhcpOptions": { "dnsServers": [] }, "enableDdosProtection": false, "enableVmProtection": false, "etag": "W/\"38ff6021-dede-4633-a431-de744603f625\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/virtualNetworks/VNet01", "ipAllocations": null, "location": "eastus", "name": "VNet01", "provisioningState": "Succeeded", "resourceGroup": "VPN-Test", "resourceGuid": "ab5d3b7d-4c7e-4243-aba5-a30a72017666", "subnets": [], "tags": {}, "type": "Microsoft.Network/virtualNetworks", "virtualNetworkPeerings": [] } } |
|
Create a Subnet SN01
az network vnet subnet create \ --address-prefix 10.0.1.0/24 \ --name SN01 \ --resource-group VPN-Test \ --vnet-name VNet01 root@rb01:~# az network vnet create --name VNet01 --resource-group VPN-Test --location eastus --address-prefix 10.0.0.0/16 { "newVNet": { "addressSpace": { "addressPrefixes": [ "10.0.0.0/16" ] }, "bgpCommunities": null, "ddosProtectionPlan": null, "dhcpOptions": { "dnsServers": [] }, "enableDdosProtection": false, "enableVmProtection": false, "etag": "W/\"38ff6021-dede-4633-a431-de744603f625\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/virtualNetworks/VNet01", "ipAllocations": null, "location": "eastus", "name": "VNet01", "provisioningState": "Succeeded", "resourceGroup": "VPN-Test", "resourceGuid": "ab5d3b7d-4c7e-4243-aba5-a30a72017666", "subnets": [], "tags": {}, "type": "Microsoft.Network/virtualNetworks", "virtualNetworkPeerings": [] } } |
|
Create a Subnet SN02
az network vnet subnet create \ --address-prefix 10.0.2.0/24 \ --name SN02 \ --resource-group VPN-Test \ --vnet-name VNet01 root@rb01:~# az network vnet subnet create --address-prefix 10.0.2.0/24 --name SN02 --resource-group VPN-Test --vnet-name VNet01 { "addressPrefix": "10.0.2.0/24", "addressPrefixes": null, "delegations": [], "etag": "W/\"1930d1f7-7d54-4642-bc87-c73173238b76\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/virtualNetworks/VNet01/subnets/SN02", "ipAllocations": null, "ipConfigurationProfiles": null, "ipConfigurations": null, "name": "SN02", "natGateway": null, "networkSecurityGroup": null, "privateEndpointNetworkPolicies": "Enabled", "privateEndpoints": null, "privateLinkServiceNetworkPolicies": "Enabled", "provisioningState": "Succeeded", "purpose": null, "resourceGroup": "VPN-Test", "resourceNavigationLinks": null, "routeTable": null, "serviceAssociationLinks": null, "serviceEndpointPolicies": null, "serviceEndpoints": null, "type": "Microsoft.Network/virtualNetworks/subnets" } |
|
Create a Route Table
az network route-table create \ --name MyRouteTable \ --resource-group VPN-Test root@rb01:~# az network route-table create \ > --name MyRouteTable \ > --resource-group VPN-Test {- Finished .. "disableBgpRoutePropagation": false, "etag": "W/\"3fca8554-5b01-4201-9ed2-1c55dc55244d\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/routeTables/MyRouteTable", "location": "eastus", "name": "MyRouteTable", "provisioningState": "Succeeded", "resourceGroup": "VPN-Test", "routes": [], "subnets": null, "tags": null, "type": "Microsoft.Network/routeTables" } |
|
Create VPN Route
az network route-table route create \ --name ToVPNTun \ --resource-group VPN-Test \ --route-table-name myRouteTable \ --address-prefix 10.9.0.0/24 \ --next-hop-type VirtualAppliance \ --next-hop-ip-address 10.0.1.4 root@rb01:~# az network route-table route create \ > --name ToVPNTun \ > --resource-group VPN-Test \ > --route-table-name myRouteTable \ > --address-prefix 10.9.0.0/24 \ > --next-hop-type VirtualAppliance \ > --next-hop-ip-address 10.0.1.4 {- Finished .. "addressPrefix": "10.9.0.0/24", "etag": "W/\"00429bd4-3e32-440d-8163-c543d9781c56\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/routeTables/myRouteTable/routes/ToVPNTun", "name": "ToVPNTun", "nextHopIpAddress": "10.0.1.4", "nextHopType": "VirtualAppliance", "provisioningState": "Succeeded", "resourceGroup": "VPN-Test", "type": "Microsoft.Network/routeTables/routes" } |
|
Create Home Route
az network route-table route create \ --name ToPrivateSubnet \ --resource-group VPN-Test \ --route-table-name myRouteTable \ --address-prefix 192.168.178.0/24 \ --next-hop-type VirtualAppliance \ --next-hop-ip-address 10.0.1.4 root@rb01:~# az network route-table route create \ > --name ToPrivateSubnet \ > --resource-group VPN-Test \ > --route-table-name myRouteTable \ > --address-prefix 192.168.178.0/24 \ > --next-hop-type VirtualAppliance \ > --next-hop-ip-address 10.0.1.4 {- Finished .. "addressPrefix": "192.168.178.0/24", "etag": "W/\"6f85a76c-1969-4f9e-9190-ba419c7f4436\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/routeTables/myRouteTable/routes/ToPrivateSubnet", "name": "ToPrivateSubnet", "nextHopIpAddress": "10.0.1.44", "nextHopType": "VirtualAppliance", "provisioningState": "Succeeded", "resourceGroup": "VPN-Test", "type": "Microsoft.Network/routeTables/routes" } |
|
Associate Subnet SN01
az network vnet subnet update \ --name SN01 \ --vnet-name Vnet01 \ --resource-group VPN-Test \ --route-table MyRouteTable root@rb01:~# az network vnet subnet update \ > --name SN01 \ > --vnet-name Vnet01 \ > --resource-group VPN-Test \ > --route-table MyRouteTable { "addressPrefix": "10.0.1.0/24", "addressPrefixes": null, "delegations": [], "etag": "W/\"437692a4-54c5-4cb9-b9c5-8216f36ed6da\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/virtualNetworks/Vnet01/subnets/SN01", "ipAllocations": null, "ipConfigurationProfiles": null, "ipConfigurations": [ { "etag": null, "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/networkInterfaces/vm-az-vpngw01VMNic/ipConfigurations/ipconfigvm-az-vpngw01", "name": null, "privateIpAddress": null, "privateIpAllocationMethod": null, "provisioningState": null, "publicIpAddress": null, "resourceGroup": "VPN-Test", "subnet": null } ], "name": "SN01", "natGateway": null, "networkSecurityGroup": null, "privateEndpointNetworkPolicies": "Enabled", "privateEndpoints": null, "privateLinkServiceNetworkPolicies": "Enabled", "provisioningState": "Succeeded", "purpose": null, "resourceGroup": "VPN-Test", "resourceNavigationLinks": null, "routeTable": { "disableBgpRoutePropagation": null, "etag": null, "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/routeTables/MyRouteTable", "location": null, "name": null, "provisioningState": null, "resourceGroup": "VPN-Test", "routes": null, "subnets": null, "tags": null, "type": null }, "serviceAssociationLinks": null, "serviceEndpointPolicies": null, "serviceEndpoints": null, "type": "Microsoft.Network/virtualNetworks/subnets" } |
|
Associate Subnet SN02
az network vnet subnet update \ --name SN02 \ --vnet-name Vnet01 \ --resource-group VPN-Test \ --route-table MyRouteTable root@rb01:~# az network vnet subnet update \ > --name SN02 \ > --vnet-name Vnet01 \ > --resource-group VPN-Test \ > --route-table MyRouteTable { "addressPrefix": "10.0.2.0/24", "addressPrefixes": null, "delegations": [], "etag": "W/\"1fa47832-5492-4c2b-b7e4-ea0a7a4e2e7e\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/virtualNetworks/Vnet01/subnets/SN02", "ipAllocations": null, "ipConfigurationProfiles": null, "ipConfigurations": [ { "etag": null, "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/networkInterfaces/vm-sn02-client01VMNic/ipConfigurations/ipconfigvm-sn02-client01", "name": null, "privateIpAddress": null, "privateIpAllocationMethod": null, "provisioningState": null, "publicIpAddress": null, "resourceGroup": "VPN-Test", "subnet": null } ], "name": "SN02", "natGateway": null, "networkSecurityGroup": null, "privateEndpointNetworkPolicies": "Enabled", "privateEndpoints": null, "privateLinkServiceNetworkPolicies": "Enabled", "provisioningState": "Succeeded", "purpose": null, "resourceGroup": "VPN-Test", "resourceNavigationLinks": null, "routeTable": { "disableBgpRoutePropagation": null, "etag": null, "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/routeTables/MyRouteTable", "location": null, "name": null, "provisioningState": null, "resourceGroup": "VPN-Test", "routes": null, "subnets": null, "tags": null, "type": null }, "serviceAssociationLinks": null, "serviceEndpointPolicies": null, "serviceEndpoints": null, "type": "Microsoft.Network/virtualNetworks/subnets" }
|
|
Setup the OpenVPN Port (443) to our Azure Gateway
az vm open-port --resource-group VPN-Test \ --name vm-az-vpngw01 \ --port 443 \ --priority 910 root@rb01:~# az vm open-port --resource-group VPN-Test --name vm-az-vpngw01 --port 443 --priority 910 {- Finished .. "defaultSecurityRules": [ .... .... { "access": "Allow", "description": null, "destinationAddressPrefix": "*", "destinationAddressPrefixes": [], "destinationApplicationSecurityGroups": null, "destinationPortRange": "443", "destinationPortRanges": [], "direction": "Inbound", "etag": "W/\"5373a937-ddaf-4392-b364-5a720fdf3723\"", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Network/networkSecurityGroups/vm-az-vpngw01NSG/securityRules/open-port-443", "name": "open-port-443", "priority": 910, "protocol": "*", "provisioningState": "Succeeded", "resourceGroup": "VPN-Test", "sourceAddressPrefix": "*", "sourceAddressPrefixes": [], "sourceApplicationSecurityGroups": null, "sourcePortRange": "*", "sourcePortRanges": [], "type": "Microsoft.Network/networkSecurityGroups/securityRules" } ], "subnets": null, "tags": {}, "type": "Microsoft.Network/networkSecurityGroups" } |
|
Create Virtual Machines
Create vm-az-vpngw01 with Static Public IP
Note that this sample includes a static public IP address
az vm create --resource-group VPN-Test \ --name vm-az-vpngw01 --location eastus \ --image "Debian:debian-10:10:latest" \ --vnet-name VNet01 \ --subnet SN01 \ --admin-username azadmin --admin-password xxxxxxxxx \ --size Standard_B2s \ --public-ip-address myPublicIpAddress \ --public-ip-address-allocation static
root@rb01:~# az vm create --resource-group VPN-Test \ > --name vm-az-vpngw01 --location eastus \ > --image "Debian:debian-10:10:latest" \ > --vnet-name VNet01 \ > --subnet SN01 \ > --admin-username azadmin --admin-password xxxxxxxxxx \ > --size Standard_B2s \ > --public-ip-address myPublicIpAddress \ > --public-ip-address-allocation static {- Finished .. "fqdns": "", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Compute/virtualMachines/vm-az-vpngw01", "location": "eastus", "macAddress": "00-0D-3A-8C-CC-FB", "powerState": "VM running", "privateIpAddress": "10.0.1.4", "publicIpAddress": "52.188.151.230", "resourceGroup": "VPN-Test", "zones": "" } |
|
Create a Client within SN02. No Static Public IP
Note that this sample does not includes a public IP address
az vm create --resource-group VPN-Test \ --name vm-sn02-client01 --location eastus \ --image "Debian:debian-10:10:latest" \ --vnet-name VNet01 \ --subnet SN02 \ --admin-username azadmin --admin-password xxxxxxxx \ --size Standard_B2s \ --public-ip-address "" root@rb01:~# az vm create --resource-group VPN-Test \ > --name vm-sn02-client01 --location eastus \ > --image "Debian:debian-10:10:latest" \ > --vnet-name VNet01 \ > --subnet SN02 \ > --admin-username azadmin --admin-password xxxxxxxxxxx \ > --size Standard_B2s \ > --public-ip-address {- Finished .. "fqdns": "", "id": "/subscriptions/727d7068-94e3-494a-965a-XXXXX/resourceGroups/VPN-Test/providers/Microsoft.Compute/virtualMachines/vm-sn02-client01", "location": "eastus", "macAddress": "00-0D-3A-8B-C7-33", "powerState": "VM running", "privateIpAddress": "10.0.2.4", "publicIpAddress": "", "resourceGroup": "VPN-Test", "zones": "" }
|
|
Setup the Azure OpenVPN Gateway
Install
Install and setup
Install openvpn, nftables and other required tools
sudo apt-get install openvpn nftables mc dnsutils net-tools dnsutils curl lynx
Setup IP Forward
- Allow IP Forward next to other features. Edit /etc/sysctl.conf
net.core.default_qdisc=fq net.ipv4.tcp_congestion_control=bbr net.ipv4.ip_forward=1
- Run sysctl to apply the above changes
sysctl -p
Setup Nftables
#!/usr/sbin/nft -f flush ruleset table ip filter_v4 { chain INPUT { type filter hook input priority 0; policy accept; } chain OUTPUT { type filter hook output priority 0; policy accept; } chain FORWARD { type filter hook output priority 0; policy accept; } } table ip nat { chain PREROUTING { type nat hook prerouting priority -100; policy accept; } chain POSTROUTING { type nat hook postrouting priority 100; policy accept; ip saddr 10.9.0.0/24 oifname "eth0" counter snat to 10.0.1.4 comment "SNAT for TUN" ip saddr 192.168.178.0/24 oifname "eth0" counter snat to 10.0.1.4 comment "SNAT for HOME" } }
- Alternative you could set masquerade which is easier to configurate but has a less performance than snat
ip saddr 10.9.0.0/24 oif "eth0" counter masquerade comment "VPN Masq Rule" ip saddr 192.168.178.0/24 oif "eth0" counter masquerade comment "Home Masq Rule"
Start/Stop/Enable Nftables
- Run to manual start (apply) the script:
nft -f /etc/nftables.conf
- Run to manual stop nft:
nft flush ruleset
- To enable at system start run:
systemctl enable nftables
Setup OpenVPN
Server Key
Get the existing static key file or create a new one using:
openvpn --genkey --secret /etc/openvpn/static.key
Configuration
Setup the configuration in /etc/openvpn/server.conf
dev tun proto tcp-server port 443 ifconfig 10.9.0.1 10.9.0.2 route 192.168.178.0 255.255.255.0 cipher AES-256-CBC comp-lzo keepalive 10 60 persist-key persist-tun secret /etc/openvpn/static.key log /var/log/openvpn.log verb 6
Apply the new configuration to systemctl
systemctl daemon-reload
Restart OpenVPN
systemctl restart openvpn
Setup the Raspery-PI OpenVPN Gateway
Install
Install and setup
Install openvpn, nftables and other required tools
sudo apt-get install openvpn nftables mc dnsutils net-tools dnsutils curl lynx
Setup IP Forward
- Allow IP Forward next to other features. Edit /etc/sysctl.conf
net.core.default_qdisc=fq net.ipv4.tcp_congestion_control=bbr net.ipv4.ip_forward=1
- Run sysctl to apply the above changes
sysctl -p
Setup Nftables
#!/usr/sbin/nft -f flush ruleset table ip filter_v4 { chain INPUT { type filter hook input priority 0; policy accept; } chain OUTPUT { type filter hook output priority 0; policy accept; } chain FORWARD { type filter hook output priority 0; policy accept; } } table ip nat { chain PREROUTING { type nat hook prerouting priority -100; policy accept; } chain POSTROUTING { type nat hook postrouting priority 100; policy accept; ip saddr 10.0.0.0/16 oifname "eth0" counter snat to 10.0.1.4 comment "SNAT for Azure VNet01" } }
- Alternative you could set masquerade which is easier to configurate but has a less performance than snat
ip saddr 10.0.1.0/24 oif "eth0" counter masquerade comment "SN01 Masq Rule" ip saddr 10.0.2.0/24 oif "eth0" counter masquerade comment "SN02 Masq Rule"
Start/Stop/Enable Nftables
- Run to manual start (apply) the script:
nft -f /etc/nftables.conf
- Run to manual stop nft:
nft flush ruleset
- To enable at system start run:
systemctl enable nftables
Setup OpenVPN
Server Key
Get the existing static key file or create a new one using:
openvpn --genkey --secret /etc/openvpn/static.key
Configuration
Setup the configuration in /etc/openvpn/server.conf
remote 52.188.151.230 proto tcp-client port 443 dev tun ifconfig 10.9.0.2 10.9.0.1 cipher AES-256-CBC comp-lzo keepalive 10 60 persist-key persist-tun secret /etc/openvpn/static.key log /var/log/openvpn.log verb 6 #Default Routing route 10.0.1.0 255.255.255.0 route 10.0.2.0 255.255.255.0
Apply the new configuration to systemctl
systemctl daemon-reload
Restart OpenVPN
systemctl restart openvpn
Setup Routing
The quickest way to setup routing within the Home-LAN is to do this on your ISP Router, the following is showing the static route table on a Fritz Box
Standard Route
Home-LAN RB01
The standard route allows the HOME-LAN clients to access the Azure Subnet01 and Subnet01 via RB01, all other packages
will pass the default gateway of 192.168.179.1.
This setup will work without SNAT in most cases.
Within the OpenVPN configuration it is needed to define the routing like this:
root@rb01 ~ # cat /etc/openvpn/server.conf | grep route route 10.0.1.0 255.255.255.0 route 10.0.2.0 255.255.255.0
root@rb01 ~ # route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.178.1 0.0.0.0 UG 0 0 0 eth0 10.0.1.0 10.9.0.1 255.255.255.0 UG 0 0 0 tun0 10.0.2.0 10.9.0.1 255.255.255.0 UG 0 0 0 tun0 10.9.0.1 0.0.0.0 255.255.255.255 UH 0 0 0 tun0 192.168.178.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
Home-LAN VM-WIN01
- Within the standard setup the windows home-client keeps his default gateway
C:\Windows\system32>route print =========================================================================== Persistent Routes: Network Address Netmask Gateway Address Metric 0.0.0.0 0.0.0.0 192.168.178.1 1 ===========================================================================
- The standard setup alows the Windows Home-Client to reach machines within AZ SN1
C:\Windows\system32>tracert 10.0.2.4 Tracing route to 10.0.2.4 over a maximum of 30 hops 1 <1 ms <1 ms <1 ms FRITZ-NAS [192.168.178.1] 2 1 ms 219 ms 118 ms 10.9.0.1 3 111 ms 108 ms 107 ms 10.0.2.4 Trace complete.
Obtain your public IP Address, it should be the one from your ISP Router
C:\Windows\system32>curl ipconfig.io 37.x.y.z
Route through Azure
Home-LAN RB01
This setup allows the Home-LAN VPN Server to become a default gateway for clients,
to do this setup the redirect-gateway autolocal option and remove the static route options
root@rb01 ~ # cat /etc/openvpn/server.conf | grep gateway#route 10.0.1.0 255.255.255.0#route 10.0.2.0 255.255.255.0redirect-gateway autolocal
Then restart openvpn (# systemctl restart openvpn) and see the kernel route:
root@rb01 ~ # route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.9.0.1 0.0.0.0 UG 0 0 0 tun0 10.9.0.1 0.0.0.0 255.255.255.255 UH 0 0 0 tun0 192.168.178.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
Changing the default route on the Home-LAN Windows client:
C:\Windows\system32> route -p change 0.0.0.0 mask 0.0.0.0 192.168.178.201
Obtain again your public IP Address, it should now be the one from Azure
C:\Windows\system32>curl ipconfig.io 52.188.151.230