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

OpenVPN logo

VPN setup

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 in-secure public WiFi 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!

To set up the VPN, I followed mainly the guide on linuxconfig.org and digital ocean (linked below), but since my original build the VPN has been hardened to TLS1.2 and longer key lengths thanks to information from linode and blog.g3rt.nl. Here are the details:

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

We will create a user and group for the VPN to run under. This is safer than running the VPN service under root or your own user as it cannot then access system or user files (if the service was compromised):

sudo adduser --system --shell /usr/sbin/nologin --no-create-home ovpn
sudo groupadd ovpn
sudo usermod -g ovpn ovpn

Generating certificates and keys

Now let's generate some keys. The first is a HMAC signature file, required for TLS handshake between the server and client.

openvpn --genkey --secret /etc/openvpn/server/ta.key

The next is the Diffie-Hellman parameter, used to randomly generate data for establishing perfect forward secrecy (PFS), a feature that gives assurance session keys will not be compromised. I've specified a 4096-bit DH file, which takes a seriously long time to generate. It may be quicker on more modern processors, but my 3rd generation Core i5 took about 20 minutes!

sudo openssl genpkey -genparam -algorithm DH -out /etc/openvpn/server/dhp4096.pem -pkeyopt dh_paramgen_prime_len:4096

Now, let's generate VPN keys, with Easy RSA. The first step is to create a CA (Certificate Authority). This will create a ca folder in your user directory (can be elsewhere if you want), and then navigate into it.

make-cadir ~/ca
cd ~/ca
ls

A number of files should be present:

build-ca  build-inter  build-key-pass    build-key-server  build-req-pass  inherit-inter  openssl-0.9.6.cnf  openssl-1.0.0.cnf  revoke-full  vars
build-dh  build-key    build-key-pkcs12  build-req         clean-all       list-crl       openssl-0.9.8.cnf  pkitool            sign-req     whichopensslcnf

Link the latest version of openssl-1.0.0.cnf to openssl.cnf.

ln -s openssl-1.0.0.cnf openssl.cnf

Edit the vars file and change the defaults to your preference:

nano vars

Note - the below is the only options I changed (EKY_SIZE, and the KEY_COUNTRY, KEY_PROVINCE etc).

# as well as the one-time DH parms
# generation process.
export KEY_SIZE=4096

# In how many days should the root CA key expire?
export CA_EXPIRE=3650

# In how many days should certificates expire?
export KEY_EXPIRE=3650

# These are the default values for fields
# which will be placed in the certificate.
# Don't leave any of these fields blank.
export KEY_COUNTRY="UK"
export KEY_PROVINCE="GB"
export KEY_CITY="London"
export KEY_ORG="electrodan"
export KEY_EMAIL="me@me.co.uk"
export KEY_OU="electrodan"

# X509 Subject Field
export KEY_NAME="EDANVPN"

Save and exit (Ctrl+O and Ctrl+X). Then source the variables so that they are present in your shell and the next commands will use them.

source ./vars

Run ./clean-all to create the keys directory and its containing files:

./clean-all

Now we can generate the credentials for the server. Prompts will appear so you can accept the defaults (should read what you edited in the vars file above). For 'Common Name', use your server’s hostname:

./build-ca

Now generate the private key for the server. Prompts will appear like before so you can accept the defaults. Again for 'Common Name', use your server’s hostname. Leave the challenge password blank:

./build-key-server server

Output should look a bit like this:

Generating a 4096 bit RSA private key
....................................................++
................++
writing new private key to 'server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [UK]:
State or Province Name (full name) [GB]:
Locality Name (eg, city) [London]:
Organization Name (eg, company) [electrodan]:
Organizational Unit Name (eg, section) [electrodan]:
Common Name (eg, your name or your server's hostname) [server]:
Name [EDANVPN]:
Email Address [me@me.co.uk]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /home/dan/ca/openssl.cnf
Can't open /home/dan/ca/keys/index.txt.attr for reading, No such file or directory
140370933236160:error:02001002:system library:fopen:No such file or directory:../crypto/bio/bss_file.c:74:fopen('/home/dan/ca/keys/index.txt.attr','r')
140370933236160:error:2006D080:BIO routines:BIO_new_file:no such file:../crypto/bio/bss_file.c:81:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'UK'
stateOrProvinceName   :PRINTABLE:'GB'
localityName          :PRINTABLE:'London'
organizationName      :PRINTABLE:'electrodan'
organizationalUnitName:PRINTABLE:'electrodan'
commonName            :PRINTABLE:'server'
name                  :PRINTABLE:'EDANVPN'
emailAddress          :IA5STRING:'me@me.co.uk'
Certificate is to be certified until Dec 27 17:47:38 2028 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

Now copy the crt and key files to your openvpn directory (ta.key and dhp4096.pem will already be there as we generated them there).

sudo cp ./keys/{ca.crt,server.crt,server.key} /etc/openvpn/server

Also, generate certificates for clients to use:

cd ~/ca
source ./vars

For each certificate required:

./build-key myphone
./build-key mylaptop
./build-key wifesphone   ...etc

We now have a number of files from all the work above:

  • ca.crt - 'Certificate Authrority'. This will be needed on the server and for clients
  • server.crt - 'Server certificate'. This will be needed on the server only
  • server.key - 'Server key'. This will be needed on the server only
  • myphone.crt (etc as specified) - 'Client certificate'. This will be needed on the client only
  • myphone.key (etc as specified) - 'Client key'. This will be needed on the client only
  • ta.key - 'HMAC signature key file'. For TLS-crypt. This will be needed on the server and for clients.

Configuration

We can now configure the VPN service. A sample configuration file is available as a base to use, we can extract that sample to /etc/openvpn/server.conf and then edit that file:

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

In this file, do the following:

  1. 'ca ca.crt' - change this to the full path of the Certificate Authority file 'ca /etc/openvpn/server/ca.crt'
  2. 'cert server.crt' - change this to the full path of the Server's Certificate file 'cert /etc/openvpn/server/server.crt'
  3. 'key server.key' - change this to the full path of the Server's Key file 'ca /etc/openvpn/server/server.key'
  4. 'dh dh2048.pem' - change this to the full path of the Diffie-Hellman file 'dh /etc/openvpn/server/dhp4096.pem'
  5. 'ca ca.crt' - change this to the full path of the ca file 'ca /etc/openvpn/server/ca.crt'
  6. 'tls-auth ta.key 0 # This file is secret' - comment this line by adding ; in front
  7. 'cipher BF-CBC # Blowfish (default)' - comment this line by adding ; in front
  8. 'cipher AES-128-CBC # AES' - comment this line by adding ; in front
  9. 'cipher DES-EDE3-CBC # Triple-DES' - comment this line by adding ; in front
  10. ';user nobody' - change this to 'user ovpn' (this is the user we created earlier)
  11. ';group nobody' - change this to 'group ovpn' (this is the user we created earlier)

Add the following:

  1. 'tls-crypt /etc/openvpn/server/ta.key' - this is the full path of the HMAC signature file
  2. 'auth SHA512' - use the strong SHA512 for message authentication (HMAC)
  3. 'tls-version-min 1.2' - force the client to use TLS version 1.2 (TLS1.2) only (not the insecure TLS version 1.0 or 1.1)
  4. 'tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256' - force the client to use a limited set of secure and common TLS ciphers
  5. 'ncp-ciphers AES-256-GCM:AES-256-CBC' - force the client to use a limited set of secure and common NCP ciphers

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 great use for catchup services.

  1. push "redirect-gateway def1 bypass-dhcp"
  2. push "dhcp-option DNS 208.67.222.222"
  3. push "dhcp-option DNS 208.67.220.220"

Here's a full list of configuration parameters I used in my server.conf file

port 1194
proto udp
dev tun
ca /etc/openvpn/server/ca.crt
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key  # This file should be kept secret
dh /etc/openvpn/server/dhp4096.pem

server 10.8.0.0 255.255.255.0

ifconfig-pool-persist ipp.txt

push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"

keepalive 10 120

user ovpn
group ovpn

persist-key
persist-tun

tls-crypt /etc/openvpn/server/ta.key
auth SHA512    # This needs to be in client.ovpn too though.
tls-version-min 1.2
tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256
ncp-ciphers AES-256-GCM:AES-256-CBC

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:

sudo service openvpn stop
sudo service openvpn start
sudo service openvpn status

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 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:

# openvpn - accept from WAN
-A INPUT -i enp4s0 -m state --state NEW -m udp -p udp --dport 1194 -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 be without a 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). 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 wifi disconnected using my 4G signal to my home network.

Before I do, first I need to prepare the files to transfer to the phone.

These files are needed (assume earlier I created a client key for a device named myphone):

  • myphone.crt - in your ~/ca/keys folder
  • myphone.key - in your ~/ca/keys folder
  • ca.crt - in your ~/ca/keys folder
  • ta.key - in your /etc/openvpn/server folder
  • client.ovpn - a sample can be found in /usr/share/doc/openvpn/examples/sample-config-files

The client.ovpn file we will use as a base to create a unified profile file, using the same method described at Digital Ocean:

Instead of transferring files from the server, I just copied the contents of the files above in turn from my Windows SSH client and saved the contents into a new text file, saved as myphone.ovpn.

  1. Near the top, edit the line
    remote my-server-1 1194
    Replace my-server-1 with the public IP so your connection (or if you use dynamic DNS, then that)
  2. Downgrade privileges to nobody
    # Downgrade privileges after initialization (non-Windows only)
    user nobody
    group nogroup
    
  3. Comment out these ares:
    #ca ca.crt
    #cert client.crt
    #key client.key
    #tls-auth ta.key 1
    
  4. Add:
    cipher AES-256-CBC
    
  5. Now carefully paste the contents of the other files (including any BEGIN CERTIFICATE and END CERTIFICATE sections) into an XML syntax at the bottom of the file:
    <ca>
    (insert the complete contents of ca.crt here)
    </ca>
    <cert>
    (insert the complete contents of myphone.crt here)
    </cert>
    <key>
    (insert the complete contents of myphone.key here)
    </key>
    <tls-crypt>
    (insert the complete contents of ta.key here)
    </tls-crypt>
    

I then transferred my final ovpn file to my Android phone, installed the OpenVPN app and imported the profile. I was able to connect immediately.

A quick visit to whatismyipaddress.com 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, 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

Make a copy of myphone.ovpn, named mylaptop.ovpn (or a better name as you prefer).

This time you only need to update the <cert> and <key> sections with the crt and key files you generated earlier for mylaptop (the ./build-key command).

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 WiFi network. To test it, I just tethered to my phone's 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:
linuxconfig.org - OpenVPN Setup on Ubuntu 18.04 Bionic Beaver Linux
DigitalOcean - How To Set Up an OpenVPN Server on Ubuntu 18.04
Linode - Set up a Hardened OpenVPN Server
blog.g3rt.nl - 16 tips on OpenVPN security
OpenVPN - Routing all client traffic (including web-traffic) through the VPN