Saturday, August 1, 2020

VPN Gateway VM in VirtualBox (with Kill Switch)

PDF - version

Setup:

  • A Linux host PC (Ubuntu 18.04/20.04) Running an up-to-date Virtual Box.
  • Guest: A headless Virtual server (Ubuntu 18.04/20.04)
  • Valid VPN account with a 3rd party. (in my case: NordVPN)

Goal:

  • Start the virtual machine (guest) headless as a service when booting the Host machine.
  • The VM must be able to log in automatically as a service without manually providing account credentials 
  • The VM uses a bridged network so it has a reachable IP on the LAN
  • Using the VM-IP as a gateway for devices that need a VPN connection.
  • No other outbound connection possible for devices using this gateway when the VPN-connection to NordVPN goes down, No internet connection is available.

NOTE: I've got my inspiration for this setup from the fine guys over @ Craft Computing.  Credits to them for their awesome instruction videos. Link to the Video

Create the VM

I’ve used the following specifications to install the VPN-server in virtual box:
(not covered in this article)
    • 1 CPU Changed to 2 Cores. 1 Core caused too much packet-loss with the UDP protocol.
    • 1Gb Ram
    • Bridged networking (static IP or reserved IP - see later) 
    • 8GB VDI dynamic size
    • apply all the latest upgrades to be current
    • Enable SHH-Server [ sudo apt install openssh-server ]

Setting up the VM to be a VPN gateway:

Setup Staic IP address:

For Ubuntu 18.04 and up, netplan is the suggested way to set up a static IP.

sudo netplan generate
sudo nano /etc/netplan/50-cloud-init.ymal

Edit as follows: 
(given 192.168.1.0/24 is your network and 192.168.1.1 is your main gateway on the router,
and enp0s is your network adapter - Often it is eth0 instead. Use ifconfig to find out)


     │ File: /etc/netplan/50-cloud-init.yaml
─────|──────────────────────────────────────
   1 │ network:
   2 │     version: 2
   3 │     renderer: networkd
   4 │     ethernets:
   5 │         enp0s3:
   6 │             dhcp4: no
   7 │             dhcp6: no
   8 │             addresses: [192.168.1.29/24, ]
   9 │             gateway4: 192.168.1.1
  10 │             nameservers:
  11 │                 addresses: [1.1.1.1,1.0.0.1,8.8.8.8,8.8.4.4]


Now apply these setting to your VM:

sudo netplan apply

[test if you have the correct static IP setup for your VM after reboot]


Install dependencies and applications needed:


sudo apt update
sudo apt upgrade
sudo apt install openvpn unzip


Download the ovpn files for your provider ( in my case: NordVPN)

wget https://downloads.nordcdn.com/configs/archives/servers/ovpn.zip

Extract the ovpn.zip & move them to the right place:


unzip ovpn.zip
sudo mv ovpn_tcp /etc/openvpn/ovpn_tcp
sudo mv ovpn_udp/ /etc/openvpn/ovpn_udp 

Browse the list and make your choice (udp / tcp) country / server:
In my case, I go for us5000.nordvpn.com.udp.ovpn
But you can go for any other of your choice from the 2 folders (5000+ servers)


NOTE: you might want to acquire a new zip file on a regular basis
to get the latest server list for your VPN provider. 


Create a script to connect to NordVPN:


cd /etc/openvpn
sudo nano auth.txt
sudo nano connect.sh

Add your account credentials (one on each line) 
───────┬───────────────────────────────────
       │ File: auth.txt
───────┼───────────────────────────────────
   1   │ Your NordVPN-UserName
   2   │ Your NordVPN password



Add the following line to the file 
──────────────────────────────────────────────
File: connect.sh
──────────────────────────────────────────────
sudo openvpn --config "/etc/openvpn/ovpn_udp/us5000.nordvpn.com.udp.ovpn" --auth-user-pass /etc/openvpn/auth.txt

NOTE:  "us5000.nordvpn.com.udp.ovpn"
use the server that you find works best for you (udp protocol)


Add the correct IP-tables:


cd /etc/openvpn
sudo nano iptables.sh

Add the following lines: 
───────┬──────────────────────────────────────────────────────────────
       │ File: iptables.sh
───────┼───────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ # Flush all existing settings:
   3   │ iptables -t nat -F
   4   │ iptables -t mangle -F
   5   │ iptables -F
   6   │ iptables -X
   7   │ 
   8   │ 
   9   │ # Block All:
  10   │ iptables -P OUTPUT DROP
  11   │ iptables -P INPUT DROP
  12   │ iptables -P FORWARD DROP
  13   │ 
  14   │ 
  15   │ # Allow Localhost communictaion:
  16   │ iptables -A INPUT -i lo -j ACCEPT
  17   │ iptables -A OUTPUT -o lo -j ACCEPT
  18   │ 
  19   │ 
  20   │ # Allow communication with a DHCP server:
  21   │ iptables -A OUTPUT -d 255.255.255.255 -j ACCEPT
  22   │ iptables -A INPUT -s 255.255.255.255 -j ACCEPT
  23   │ 
  24   │ 
  25   │ # Allow communication within your local network
  26   │ iptables -A INPUT -s 192.168.1.0/24 -d 192.168.1.0/24 -j ACCEPT
  27   │ iptables -A OUTPUT -s 192.168.1.0/24 -d 192.168.1.0/24 -j ACCEPT
  28   │ ## Replace this CIDR with your ##
  29   │ 
  30   │ # Allow established sessions to receive traffic:
  31   │ iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
  32   │ 
  33   │ 
  34   │ # Allow TUN
  35   │ iptables -A INPUT -i tun+ -j ACCEPT
  36   │ iptables -A FORWARD -i tun+ -j ACCEPT
  37   │ iptables -A FORWARD -o tun+ -j ACCEPT
  38   │ iptables -t nat -A POSTROUTING -o tun+ -j MASQUERADE
  39   │ iptables -A OUTPUT -o tun+ -j ACCEPT
  40   │ 
  41   │ 
  42   │ # allow VPN connection
  43   │ iptables -I OUTPUT 1 -p udp --destination-port 1194 -m comment --comment "Allow VPN connection" -j ACCEPT
  44   │ 
  45   │ 
  46   │ # Block All
  47   │ iptables -A OUTPUT -j DROP
  48   │ iptables -A INPUT -j DROP
  49   │ iptables -A FORWARD -j DROP
  50   │ 
  51   │ 
  52   │ echo "saving"
  53   │ iptables-save > /etc/iptables/rules.v4
  54   │ echo "done"


Set correct permissions and rights for the scripts generated:


cd /etc/openvpn
sudo chown root:root auth.txt connect.sh iptables.sh
sudo chmod +x connect.sh iptables.sh
sudo chmod 600 auth.txt

VPN scripts that will be started as a service at boot time:


sudo nano /usr/local/bin/mystartup.sh
sudo chown root:root /usr/local/bin/mystartup.sh
sudo chmod +x /usr/local/bin/mystartup.sh

Add the following to the script:
───────┬─────────────────────────────────
       │ File: mystartup.sh
───────┼──────────────────────────────────────────────
   1   │ #!/bin/sh -e
   2   │ sudo bash /etc/openvpn/iptables.sh &
   3   │ sleep 15
   4   │ sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
   5   │ sudo bash /etc/openvpn/connect.sh &
   6   │ 
   7   │ exit 0


Adding a service to the VM:


Using systemd to start the vpn connection as a service at boot, we need a service script to launch "mystartup.sh" 
 

sudo nano /etc/systemd/system/mystartup.service


File content:
───────┬─────────────────────────────────────────────────
       │ File: mystartup.service
───────┼──────────────────────────────────────────────────
   1   │ # mystartup.service  (systemd)      
   2   │  
   3   │ [Unit]
   4   │ Description=Starts the VPN connection
   5   │ ConditionPathExists=/usr/local/bin/mystartup.sh
   6   │   
   7   │ [Service]
   8   │ type=forking
   9   │ ExecStart=/usr/local/bin/mystartup.sh start
  10   │ TimeoutSec=0
  11   │ StandardOutput=tty
  12   │ RemainAfterExit=yes
  13   │ SysVstartPriority=99
  14   │ 
  15   │ [Install]
  16   │ WantedBy=multi-user.target


Enabling  "mystartup.service" as a service:


sudo systemctl enable mystartup
 

Starting this "mystartup.service" service:


sudo systemctl start mystartup
 

Stopping this "mystartup.service" service:


sudo systemctl stop mystartup

Status of this "mystartup.service" service:


sudo systemctl status mystartup


This should show something like this:
 
mystartup.service - Starts the VPN connection
   Loaded: loaded (/etc/systemd/system/mystartup.service; enabled; vendor preset: enabled)
   Active: active (exited) since Sat 2020-08-01 10:27:31 CEST; 5h 14min ago
 Main PID: 832 (code=exited, status=0/SUCCESS)
    Tasks: 4 (limit: 1093)
   CGroup: /system.slice/mystartup.service
           ├─1149 sudo bash /etc/openvpn/connect.sh
           ├─1150 bash /etc/openvpn/connect.sh
           ├─1151 sudo openvpn --config             /etc/openvpn/ovpn_udp/us5000.nordvpn.com.udp.ovpn             --auth-user-pass /etc/openvpn/auth.txt
           └─1152 openvpn --config             /etc/openvpn/ovpn_udp/us5000.nordvpn.com.udp.ovpn             --auth-user-pass /etc/openvpn/auth.txt



That should be it! You can test if you have a tunnel by checking the output of ifconfig.
And also see what your external IP is with curl https://ipinfo.io/ip


Start VM as a service at boot time of the Host:


In the assumption, your VM is named `VPN` then VirtualBox has a way of starting VM’s as a service described in the VirtualBox documentation. However, It seems to fail more than work.  
In Recent distribution driven by the "systemd" implementation, it is fairly easy to create a service for this.


  1. Your user must be a member of the vboxusers group:
    sudo usermod -aG vboxusers YourUserName


  1.  Create a service file in /etc/systemd/system

    sudo nano /etc/systemd/system/autostart_vpn.service
───────┬────────────────────────────────────────────────
       │ File: /etc/systemd/system/autostart_vpn.service
───────┼────────────────────────────────────────────────
   1   │ [Unit]
   2   │ Description=VM VPN
   3   │ After=network.target vboxdrv.service
   4   │ Before=runlevel2.target shutdown.target
   5   │  
   6   │ [Service]
   7   │ User=YourUserName
   8   │ Group=vboxusers
   9   │ Type=forking
  10   │ Restart=yes
  11   │ TimeoutSec=7min
  12   │ IgnoreSIGPIPE=no
  13   │ KillMode=process
  14   │ GuessMainPID=no
  15   │ RemainAfterExit=yes
  16   │  
  17   │ ExecStart=/usr/bin/VBoxManage startvm VPN --type headless
  18   │ ExecStop=/usr/bin/VBoxManage controlvm VPN acpipowerbutton
  19   │  
  20   │ [Install]
  21   │ WantedBy=multi-user.target

  1. Enable the service “autostart_vpn.service”:
    sudo systemctl enable autostart_vpn

  2. Start the service:
    sudo systemctl start autostart_vpn


The next time your host boots, it will fire up a VM that can serve your network as a VPN gateway. 
Enjoy.


3 comments:

  1. Excellent write-up on how to get this going! I used this and a video on YouTube from Craft Computing to setup mine. Everything works good with the primary subnet the server and my computers are on, but I can't get other subnets like my streaming subnet, IoT devices to work with this setup for nothing. They just won't pass traffic over the VPN even though I can ping them just fine from the server console. Any ideas how to get this to work with multiple subnets?

    ReplyDelete
    Replies
    1. Thank you for your comment, Glad I could be of help.
      You might want to setup the VPN on your main subnet. Or run multiple VPN-VM's. one for each subnet. But make sure to check your devices if you have IPv6 enabled. This solution is IPv4 only, and if your devices have both enabled, they might choose to bypass the VPN and go strait to the main network using IPv6. Alternatively, you could run a pfSense router on an old computer or virtualize it, and setup a VPN for your entire network and or selective VLans.

      Delete
  2. Been trying to get this working for days and finally got it working thanks to this post. Thanks for the time and effort to write it up!

    ReplyDelete

Please be courteous, even if you do not share the same view.