Home-made PC Router and NAS Server [Page 6] - VPN

OpenVPN logo

VPN setup

UPDATE September 2023: Cleaned up commands, changed scp to sftp as scp is deprecated. Verified guide works for Ubuntu Server LTS 22.04.

One of my requirements was to be able to connect to my home network from outside, securely. Creating a VPN offers these benefits to me:

  • Connect to my files from outside
  • Encrypt traffic from my client to my server (useful to work securely from public Wi-Fi etc)
  • Mask my real location, so I appear to be in the location of my server (useful for watching catch-up TV whilst I'm in another country)
  • Bypass Work/Hotel proxy servers so I can browse the Internet freely via my home Internet connection when I'm away (useful for accessing blocked sites such as YouTube, BlogSpot etc, for legitimate reasons!)

Open VPN is a great piece of software to do this, and although there is complicated forwarding, it works very nicely once set up well.

The VPN would be installed on the server, and a pinhole in iptables has be added already on the router page to allow the VPN to be accessed from the Internet (see UDP port 1194).

Before continuing though, check with your Internet Service Providers (ISP) that you are not behind a NAT of their own. Due to the lack of public IP v4 addresses on the Internet now, many ISPs are now putting multiple customers behind one single public IP. I know mine was and I had to pay a little extra on top of my bill to get a single public/static IP address of my own.

Having a single public IP is required for you to connect to your VPN from outside your home. When will they support a single IP v6 address? I don't know!

Ubuntu 20.04 and 22.04 comes with easyrsa3, so whilst my old guide worked when I configured the VPN on 18.04 and then upgraded to 20.04, on a fresh install it's not perfect.

This guide has been updated to work best with Ubuntu 20.04 and 22.04. My old VPN guide for reference is here

I found that digitalocean already had updated guides for Ubuntu 20.04, so my configuration is pretty much a copy of the excellent work there. This includes creating a new machine as a signing authority for signing the encryption certificate, instead of using a self-signed certificate.

Building the Certificate Authority (CA) Server

Rather than create a new computer for building a certificate authority server, we can either install the certificate authority (CA) on the same computer (bit insecure) or build a simple virtual machine and install Ubuntu Server 20.04 on there for the job.

VMs can be built in Virtual Box, a free and open-source hypervisor developed by Oracle. Follow my Virtual Box and GUI setup guide here in order to setup the pre-requites we need to build our virtual CA server.

Then follow my Ubuntu Server 20.04 install guide here in that virtual machine. Since Ubuntu Server has no GUI, minimal resources are needed, but you might want to allocate 4GB RAM and at least two CPU cores to the virtual machine in order for the install to complete quicker.

Remember after the install to do the update and upgrade and setup SSH access. For security, I encrypted my CA server both with VirtualBox disk encryption and LUKS encryption during the install.

To make this basic server a CA server, first install and prepare easy-rsa:

sudo apt install easy-rsa
mkdir ~/easy-rsa
ln -s /usr/share/easy-rsa/* ~/easy-rsa/
chmod 700 ~/easy-rsa

You should get an output on the last command like:
init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /home/dan/easy-rsa/pki

Now create a Certificate Authority:

cd ~/easy-rsa
nano vars

Fill this vars file with your own information:

set_var EASYRSA_REQ_COUNTRY    "UK"
set_var EASYRSA_REQ_PROVINCE   "GB"
set_var EASYRSA_REQ_CITY       "London"
set_var EASYRSA_REQ_ORG        "electrodan"
set_var EASYRSA_REQ_EMAIL      "dan@example.com"
set_var EASYRSA_REQ_OU         "electrodan"
set_var EASYRSA_ALGO           "ec"
set_var EASYRSA_DIGEST         "sha512"

Then initialise the PKI:

cd ~/easy-rsa
./easyrsa init-pki

Then build the CA:

cd ~/easy-rsa
./easyrsa build-ca nopass

You should get an output like:
CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/home/dan/easy-rsa/pki/ca.crt

This ca.crt is the CA's public certificate file that will be transferred to and used on the main server for the VPN setup.

Setting up Open VPN

Install Open VPN and Easy RSA

Both openvpn and easy-rsa are recommended to setup a VPN, and both are available in the Ubuntu repositories:

sudo apt-get update
sudo apt install openvpn easy-rsa

Like we did for the CA server, we'll prepare easy-rsa for creating certificate requests and importing the signed certificates after:

mkdir ~/easy-rsa
ln -s /usr/share/easy-rsa/* ~/easy-rsa/
chmod 700 ~/easy-rsa/

Now create a PKI:

cd ~/easy-rsa
nano vars

Fill this vars file with the recommended ec algorithm and SHA512 digest:

set_var EASYRSA_ALGO           "ec"
set_var EASYRSA_DIGEST         "sha512"

Now we can initialise the PKI

cd ~/easy-rsa
./easyrsa init-pki

Create, sign and import the server certificate

Easy RSA is ready, so let's use it to submit a certificate request for the certificate named 'server':

cd ~/easy-rsa
./easyrsa gen-req myserver nopass

You should get an output on the last command like:
Keypair and certificate request completed. Your files are:
req: /home/dan/easy-rsa/pki/reqs/myserver.req
key: /home/dan/easy-rsa/pki/private/myserver.key

The myserver.key (this is the private key) file needs to go into the openvpn directory:

sudo cp /home/dan/easy-rsa/pki/private/myserver.key /etc/openvpn/server/

The myserver.req file is the certificate request. This will need to be copied to our CA server for signing, so start that VM if it's not already. Check the SSH access is working fine, then exit back to your main server.

The following command will copy the myserver.req file to our CA server. Assuming, 10.0.1.195 is the IP address of your CA server. Replace 'dan' with your username!

sftp dan@10.0.1.195

sftp> put /home/dan/easy-rsa/pki/reqs/myserver.req /tmp
sftp> exit

Now SSH onto the CA server and import and sign that request:

ssh dan@10.0.1.195
cd ~/easy-rsa
./easyrsa import-req /tmp/myserver.req myserver
./easyrsa sign-req server myserver
exit

You'll be asked some confirmations, and should end up with a message like:
Certificate created at: /home/dan/easy-rsa/pki/issued/myserver.crt

This is the signed certificate file for our VPN which must be copied to the VPN server. Back on the main server, SCP will allow you to easily download that signed crt file to the right place. We also need the certificate authority crt file too (ca.crt), so copy that too:

sftp dan@10.0.1.195

sftp> get /home/dan/easy-rsa/pki/issued/myserver.crt /tmp/myserver.crt
sftp> get /home/dan/easy-rsa/pki/ca.crt /tmp/ca.crt
sftp> exit

sudo mv /tmp/ca.crt /etc/openvpn/server
sudo mv /tmp/myserver.crt /etc/openvpn/server

Harden with Cryptographic Material

Next, we'll harden the VPN with cryptographic material, which involves generating a tls-crypt pre-shared key, using easyrsa. These commands are run on the main VPN server (not the CA server):

cd ~/easy-rsa
openvpn --genkey secret ta.key
sudo mv ta.key /etc/openvpn/server

Create client keys and certificates

We're getting there - but now we need to create some keys for any client to connect. The VPN server and the CA server is needed for this step. Some initial steps to create a directory named client-configs. In here we'll store everything we need to create open vpn client configuration files.

mkdir -p ~/client-configs/keys
chmod -R 700 ~/client-configs
sudo cp /etc/openvpn/server/ta.key ~/client-configs/keys/
sudo cp /etc/openvpn/server/ca.crt ~/client-configs/keys/

The next set of commands below are a complete set of commands for one client. It will:

  1. Generate a certificate request for the client named 'mylaptop' (example)
  2. Copy the private key for mylaptop to the client-configs/keys directory
  3. Copy the certificate request file (mylaptop.req) to the CA server
  4. SSH to the CA server, import the request, and sign it
  5. SCP the signed certificate back to the VPN server
cd ~/easy-rsa
./easyrsa gen-req mylaptop nopass
cp pki/private/mylaptop.key ~/client-configs/keys/
sftp dan@10.0.1.195

sftp> put pki/reqs/mylaptop.req /tmp
sftp> exit

ssh dan@10.0.1.195
cd ~/easy-rsa
./easyrsa import-req /tmp/mylaptop.req mylaptop
./easyrsa sign-req client mylaptop
exit

sftp dan@10.0.1.195

sftp> get /home/dan/easy-rsa/pki/issued/mylaptop.crt /home/dan/client-configs/keys/
sftp> exit

Repeat these commands for every client you're intending to allow on your VPN. If it's just yourself, you can of course use the same key for all your personal laptops and phones, but not at the same time, so I usually create one for my laptop, and one for my phone.

We'll create ovpn files for sending to our clients later, first we need to do some server configuration...

Configure the open vpn server

We'll start with the example server.conf file, and adjust it to our needs:

sudo cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz /etc/openvpn/server/
sudo gunzip /etc/openvpn/server/server.conf.gz
sudo nano /etc/openvpn/server/server.conf

Once nano (or your preferred editor) opens, these lines we should change, we'll do the following in server.conf

  1. Change the user and group the server runs under
  2. Change the port and protocol the VPN runs on
  3. Force DNS traffic down the VPN
  4. HMAC adjustments - enable TLS Crypt, adjust the cipher, adjust the message digest algorithm, and disable Diffie-Hellman seeding
  5. Ensure TLS 1.2 is the minimum allowed version

To change the user/group to nobody/nogroup - uncomment some lines (remove the ;):

user nobody
group nogroup

To make the VPN accessible from a variety of locations where only HTTPS access is allowed (like hotels etc), I recommend changing the server to run on port 443 (which also means it should run using TCP protocol instead of UPD)

port 443

proto tcp

If you do change the protocol to TCP, at the bottom of the file change explicit-exit-notify from 1 to 0.

explicit-exit-notify 0

If you want to mask your location, override DNS and access the Internet, also uncomment these options (remove the ; in front). With these added, I can connect to my VPN anywhere in the world and websites will think I'm connecting from my home (UK). This is wonderfully useful for accessing TV catchup services whilst on holiday etc.

push "redirect-gateway def1 bypass-dhcp"

push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"

These next set of modifications (under the HMAC section) enable the tls-crypt using the ta.key setup earlier (note: comment tls-auth, add tls-crypt), improve the cipher to AES-256-GCM, add SHA256 for the HMAC message digest algorithm, and remove the Diffie-Hellman seed file:


;tls-auth ta.key 0 # This file is secret
tls-crypt ta.key
tls-version-min 1.2

;cipher AES-256-CBC
cipher AES-256-GCM
auth SHA256

;dh dh2048.pem
dh none

Note, 'server 10.8.0.0 255.255.255.0' is in the sample configuration file. This means that clients connecting to the VPN will be given IP addresses starting 10.8.0, which is good as it does not conflict with our main DHCP server (which hands addresses out on 10.0.1).

To check the config file and apply the settings, restart the VPN service. For the first start, we also need to enable the service. All of this can be done by systemctl.

sudo systemctl -f enable openvpn-server@server.service 
sudo systemctl -f start openvpn-server@server.service 
sudo systemctl status openvpn-server@server.service

IP packet forwarding

In order for the VPN to work, we need to do packet forwarding (permanently). If you are setting up a router too, this step may have already been done (see router page). Edit /etc/sysctl.conf:

sudo nano /etc/sysctl.conf

 

Uncomment the line (remove hash) #net.ipv4.ip_forward=1

# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1

# Uncomment the next line to enable packet forwarding for IPv6
#  Enabling this option disables Stateless Address Autoconfiguration
#  based on Router Advertisements for this host
#net.ipv6.conf.all.forwarding=1

 

Save and Exit (Ctrl+O, Ctrl+X)

Reload the parameters by running:

sudo sysctl -p

IP Tables - Firewall and forwarding

Our server should be set to block all incoming traffic. This will, however, prevent the VPN from being accessible from the outside

If you're just building a VPN server (not a router) with a single network card, skip the lines related to the WAN interface.

Remember to adjust enp2s0 and enp4s0 to the correct network interfaces on your machine (use the command 'ip address' to see the interfaces in use).

The IP Tables script needs to be adjusted - in the *nat section, add these POSTROUTING MASQUERADE lines. These allow VPN clients to access both your local network, and the WAN (Internet):

sudo nano /etc/iptables/rules.v4
*nat
# Allow traffic from OpenVPN client to LAN interface
-A POSTROUTING -s 10.8.0.0/24 -o enp2s0 -j MASQUERADE
# Allow traffic from OpenVPN client to WAN interface
-A POSTROUTING -s 10.8.0.0/24 -o enp4s0 -j MASQUERADE

COMMIT

In the *filter section, add these INPUT and FORWARD lines. These allow UDP port 1194 or TCP port 443 (use the appropriate line only) to be reached through the firewall, allow VPN connections to all ports on the server itself, and allow forwarding of traffic from VPN tun0 (10.8.0.0 IP addresses - our VPN clients) to the LAN and WAN. Note, interface enp1s0f1 is my WAN interface (the one that the outside Internet connection runs to).

# openvpn - accept from WAN - this line if you used the default UPD/port 1194
-A INPUT -i enp1s0f1 -m state --state NEW -m udp --dport 1194 -j ACCEPT
# openvpn - accept from WAN - this line if you used TCP/port 443
#-A INPUT -i enp1s0f1 -m state --state NEW -m tcp --dport 443 -j ACCEPT
# openvpn - accept all incoming traffic from the vpn tun0 interface
-A INPUT -i tun0 -j ACCEPT

# Allow forwarding from OpenVPN LAN
-A FORWARD -i tun0 -j ACCEPT

Using the setup I detailed in the router page, we can persist the IP tables:

sudo iptables-restore /etc/iptables/rules.v4

I've added one rule that accepts all incoming traffic from the tun0 interface (that would be all our VPN clients on 10.8.0.0) - however this makes the server have no firewall to the VPN clients, which is inconsistent from our LAN setup.

To change this, we can remove the rule '-A INPUT -i tun0 -j ACCEPT' and instead change our LAN rules to match source IP addresses instead of matching traffic coming from the LAN interface only.

For example, to allow SSH access from the LAN and the VPN (but still block from the Internet), we can assign it to the range of IPs on 10.0.0.0/8. This will cover our VPN IPs (which are 10.8.0.0) and our LAN IPs (which are 10.0.1.0). For example, to enable SSH access from LAN and VPN clients, change:
-A INPUT -i enp2s0 -p tcp --dport 22 -j ACCEPT
to:
-A INPUT -s 10.0.0.0/8 -p tcp --dport 22 -j ACCEPT

Connecting a client

I'll attempt to connect my phone, with Wi-Fi disconnected using my 4G signal to my home network.

Before I do, first I need to prepare the files to transfer to the phone and build a single .ovpn file I can import to the open vpn client on my phone.

I'll use the same method DigitalOcean recommends of creating a 'base' configuration to generate configurations for every client needed. This will be done in the client-configs directory created earlier:

mkdir -p ~/client-configs/files
cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf ~/client-configs/base.conf
nano ~/client-configs/base.conf

Inside base.conf, we'll create do the following:

  1. Specify the protocol (UPD or TCP)
  2. Specify the public IP and port for your connection
  3. Specify to run as nobody and nogroup (like the server)
  4. Comment out the references to the ca, cert and key directives
  5. Also comment out the references to the tls-auth
  6. Adjust the HMAC cipher and auth directives to match the server
  7. Specify the key direction for clients

For the public IP, this needs to be the public address of your VPN connection. Use a service like https://www.whatismyip.com/ to find out your public IP, or from the server run 'wget -qO- ifconfig.co'. I recommend signing up with a DNS provider like https://www.noip.com to get if your ISP has you behind a NAT with other customers because if that's the case then you won't be able to reach your VPN port opened on the server a more memorable hostname that can cope with IP address changes or buy a static IP from your provider like I have. You may need to buy the static IP because it will be blocked inbound by the ISP NAT.

Let's adjust the protocol to TCP (if you did for the server too) and give the public IP/hostname (replace x.x.x.x with this). I'm showing port 443 as an example for the port.

;proto udp
proto tcp

;remote my-server-2 1194
remote x.x.x.x 443

Now uncomment the nobody and nogroup directives:

# Downgrade privileges after initialization (non-Windows only)
user nobody
group nogroup

Comment the ca, cert, key and tls-auth directives:

;ca ca.crt
;cert client.crt
;key client.key

;tls-auth ta.key 1

Comment the default cipher, and add cipher AES-256-GCM and auth SHA256:

;cipher AES-256-CBC
cipher AES-256-GCM
auth SHA256

Finally add this line to specify the key direction - required for clients:

key-direction 1

Save and exit out of base.conf.

We'll create a shell script for creating the individual client configurations from this base.conf file:

nano ~/client-configs/make_config.sh
#!/bin/bash

# First argument: Client identifier

KEY_DIR=~/client-configs/keys
OUTPUT_DIR=~/client-configs/files
BASE_CONFIG=~/client-configs/base.conf

cat ${BASE_CONFIG} \
    <(echo -e '<ca>') \
    ${KEY_DIR}/ca.crt \
    <(echo -e '</ca>\n<cert>') \
    ${KEY_DIR}/${1}.crt \
    <(echo -e '</cert>\n<key>') \
    ${KEY_DIR}/${1}.key \
    <(echo -e '</key>\n<tls-crypt>') \
    ${KEY_DIR}/ta.key \
    <(echo -e '</tls-crypt>') \
    > ${OUTPUT_DIR}/${1}.ovpn

This script is simply taking our base.conf and creating an individual client ovpn file by combining that base.conf with the certificate authority already named ca.crt placed in client-configs/keys, the client's certificate we created using our CA server named e.g., myphone.crt already in client-configs/keys, the clients private key myphone.key also already in client-configs/keys and the ta.key created for also already in client-configs/keys.

The final ovpn file will get created in client-configs/files

To run the script, make it executable first, then call it with the exact name of the client you are generating for (e.g., mylaptop, myphone):

chmod 700 ~/client-configs/make_config.sh
cd ~/client-configs
./make_config.sh myphone

The myphone.ovpn file will now be in ~/client-configs/files. Now transfer it to your phone or other client. Probably copying it temporarily somewhere on your samba file share and browsing to that via Windows or an app that supports Samba network shares.

After transferring the final ovpn file to my Android phone, installing the OpenVPN app and importing the profile, I was able to connect immediately from my 4G mobile data.

A quick visit to whatismyipaddress.com from my phone shows that the IP is the same as my server's IP address on my home network, even though I am connected to the mobile network using 4G.

If you cannot access any Internet after connecting to your VPN, double check the forwarding and NAT rules in your iptables configuration.

The same can be done with my laptop, installing the OpenVPN windows client https://openvpn.net/index.php/open-source/downloads.html

Use the same make_config.sh to create another ovpn file.

Drop the ovpn file into C:\Program Files\OpenVPN\config

Run OpenVPN.

You will not be able to connect to the VPN if you are already connected to the Wi-Fi network. To test it, I just tethered to my phone's 4G connection and used the laptop through there.

If you're not so lucky though, the only way to fully check it works through the firewall is to use someone else's connection.

Once connected, OpenVPN will assign you an IP and the forwarding rules we setup earlier will grant us access to our local network, as if we were there. You should be able to SSH to your server, see your NAS shared files and whatever else!

References and more reading:
DigitalOcean - How To Set Up and Configure an OpenVPN Server on Ubuntu 20.04
DigitalOcean - How To Set Up and Configure a Certificate Authority (CA) On Ubuntu 20.04
OpenVPN - Routing all client traffic (including web-traffic) through the VPN