From charlesreid1

This page gives an overview of how to install and set up an OpenVPN server on Ubuntu 18.04.

Server Prep

Note that you should run these commands as root.

Install OpenVPN

Update and install, this should have been completed earlier for the PIA VPN tunnel:

apt update
apt -y install openvpn

Install EasyRSA

Obtain and install EasyRSA to create a certificate authority and certificates for the server:

wget -qO- https://github.com/OpenVPN/easy-rsa/releases/download/2.2.2/EasyRSA-2.2.2.tgz | tar xvz -C /opt/
cp -R /opt/EasyRSA-2.2.2 /opt/easy-rsa
ln -fs /opt/easy-rsa/openssl-1.0.0.cnf /opt/easy-rsa/openssl.cnf

Setup OpenVPN Server

Set local EasyRSA variables for the certificate.

/opt/easy-rsa/local_vars

export KEY_DIR="/opt/easy-rsa/keys"
export KEY_COUNTRY="US"
export KEY_PROVINCE="CA"
export KEY_CITY="Santa Cruz"
export KEY_ORG="charlesreid1.com"
export KEY_OU="bespin VPN"
export KEY_EMAIL=""
export KEY_NAME="bespin VPN key"

Set permissions and ownership:

chmod 0644 /opt/easy-rsa/local_vars
chown root:root /opt/easy-rsa/local_vars

Prepare to generate secrets:

cd /opt/easy-rsa

Clean keys directory:

test -e /opt/easy-rsa/clean-all
. /opt/easy-rsa/vars;. /opt/easy-rsa/local_vars;/opt/easy-rsa/clean-all

Build certificate - make script non-interactive, then run:

test -e /opt/easy-rsa/build-ca
sed -i 's/--interact//g' /opt/easy-rsa/build-ca
. /opt/easy-rsa/vars;. /opt/easy-rsa/local_vars;/opt/easy-rsa/build-ca

NOTE: This will print an error like the one below, but this is not actually a problem for generating the CA.

Can't load /root/.rnd into RNG
140672703574464:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd

Build DH parameters:

test -e /opt/easy-rsa/build-dh
. /opt/easy-rsa/vars;. /opt/easy-rsa/local_vars;/opt/easy-rsa/build-dh

Build key - make script non-interactive, then run:

test -e /opt/easy-rsa/build-key-server
sed -i 's/--interact//g' /opt/easy-rsa/build-key-server
. /opt/easy-rsa/vars;. /opt/easy-rsa/local_vars;/opt/easy-rsa/build-key-server server

Make keys directory:

mkdir -p /opt/easy-rsa/keys
cd /opt/easy-rsa/keys

Generate static TLS secret:

openvpn --genkey --secret statictlssecret.key

Configure VPN Server

Here we configure the VPN so that VPN IP addresses are in the CIDR block 192.168.20.0/24.

Start by making a directory for the server configuration:

mkdir -p /opt/openvpn

Now put the server configuration in /opt/optenvpn, then copy to /etc/openvpn.

/opt/openvpn/server.conf

port 1194
proto udp
# This is LAN20 so make it dev20
dev tun20
server 192.168.20.0 255.255.255.0
# enable this line to tunnel all client traffic thru vpn
#push "redirect-gateway def1"
# use dnsmasq as a dns server
#push "dhcp-option DNS 192.168.20.1"
dh /opt/easy-rsa/keys/dh2048.pem
# tls auth: act as server
key-direction 0
# use pam for auth
plugin /usr/lib/x86_64-linux-gnu/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn
# custom client configurations
client-config-dir /etc/openvpn/clients

Copy the finalized version to the appropriate location:

cp /opt/openvpn/server.conf /etc/openvpn/server.conf
chmod 0644 /etc/openvpn/server.conf
chown root:root /etc/openvpn/server.conf

Now you have an easy backup of your original OpenVPN server config file.

Create the custom client configuration directory specified at the end of the file:

mkdir -p /etc/openvpn/clients

Create OpenVPN Server Profile

Finally, assemble the server config and keys into an OpenVPN profile file (.ovpn) so we can start this up with our startup service:

(cat /opt/openvpn/server.conf
echo '<key>'
cat /opt/easy-rsa/keys/server.key
echo '</key>'
echo '<tls-auth>'
cat /opt/easy-rsa/keys/statictlssecret.key
echo '</tls-auth>'
echo '<cert>'
cat /opt/easy-rsa/keys/server.crt
echo '</cert>'
echo '<ca>'
cat /opt/easy-rsa/keys/ca.crt
echo '</ca>'
) > /etc/openvpn/server.ovpn

Configure VPN Server Startup Service

Run this command to update the openvpn@ startup service to send separate logs for separate openvpn networks into their own log files:

grep "log-append" /lib/systemd/system/openvpn@.service &> /dev/null || sed -i 's|^ExecStart=.*|& --log-append /var/log/openvpn.%i.log|' /lib/systemd/system/openvpn@.service

Run this command to force the OpenVPN startup service to use an .ovpn file instead of a .conf file:

sed -i 's|\.conf|.ovpn|' /lib/systemd/system/openvpn@.service

A Note on the OpenVPN Startup Service

Quick side note to explain /lib/systemd/system/openvpn@.service:

This is a TEMPLATED startup service that allows you to run multiple startup services for multiple instances of openvpn for multiple VPNs. If you run service openvpn@myvpn start, it will start OpenVPN with (by default) the configuration file myvpn.conf. With the modifications above, it will start OpenVPN with the OpenVPN profile myvpn.ovpn.

Configure iptables

Just kidding, iptables doesn't require any configuration!

This VPN tunnel will not share any traffic with other networks on this server, so no need for packets on the OpenVPN tunnel to go to other devices.

Side Note - DNS

If you want to provide DNS to VPN clients, you can push a DNS rule to the VPN. This is useful even if clients aren't tunneling all their traffic through the VPN, because it replaces the DNS they are using.

You also need to use iptabels to allow packet traffic from the wifi network to the local DNS server(s). You should probably limit this to port 53 only, unless you're tunneling all traffic through the VPN.

You should also reconfigure the DNS server, dnsmasq, to set the upstream servers the VPN should use (or just leave it and keep using whatever nameservers are already defined in the dnsmasq config file).

You should also modify the OpenVPN config file - either the server or the client - to use the VPN gateway for DNS.

To push that rule to clients, put this line in the OpenVPN server configuration file:

/opt/openvpn/server.conf

push "dhcp-option DNS 192.168.20.1"

and go through the steps listed above to re-create the OpenVPN server profile.

Configure PAM

PAM is the pluggable authentication module in Linux. It provides a variety of methods for authenticating users in various contexts (for example, when you log into a desktop computer).

In the OpenVPN server config file, we included the following line to use PAM for authentication:

# use pam for auth
plugin /usr/lib/x86_64-linux-gnu/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn

This corresponds to a configuration file /etc/pam.d/openvpn that contains directives telling PAM how OpenVPN wants to authenticate users.

We can use Unix usernames/passwords to authenticate users:

/etc/pam.d/openvpn

auth required pam_unix.so

pam_unix uses Unix system accounts as the username/password to authenticate users.

Adding MFA to VPN Access

To add MFA to the VPN access (note: this will require changes to how users are created below), first install the Google Authenticator PAM library and other utilities:

apt-get -y install libpam-google-authenticator libqrencode3 qrencode

Now modify the contents of /etc/pam.d/openvpn to replace the Unix login with Google Authenticator as a method of authentication:

/etc/pam.d/openvpn (with Google Authenticator method only)

auth required /lib/x86_64-linux-gnu/security/pam_google_authenticator.so secret=/etc/openvpn/google-authenticator/${USER} user=gauth

Or, add both authentication methods, and make them both optional (meaning, one or the other). Also add the try first option to google authenticator to re-use the password entered by the user if it does not work as their Unix username/password.

/etc/pam.d/openvpn (with both Unix login and Google Authenticator methods)

auth optional pam_unix.so
auth optional /lib/x86_64-linux-gnu/security/pam_google_authenticator.so secret=/etc/openvpn/google-authenticator/${USER} user=gauth try_first_pass

Set permissions:

chmod 0644 /etc/pam.d/openvpn
chown root:root /etc/pam.d/openvpn

Also need to create a gauth user to handle Google Authenticator stuff:

addgroup gauth && useradd -g gauth gauth

Create the Google Authenticator directory:

mkdir /etc/openvpn/google-authenticator

Set ownership and permissions:

chown gauth:gauth /etc/openvpn/google-authenticator
chmod 0700 /etc/openvpn/google-authenticator

Starting the OpenVPN Server

If your OpenVPN server config file is at /etc/openvpn/server.conf then you can enable and start the service with these commands:

systemctl enable openvpn@server
systemctl start openvpn@server

Verify the service starts okay by monitoring the syslog in another window:

tail -f /var/log/syslog

Verify the tun0 interface came up and shows up with the ifconfig command:

ifconfig

Client Prep

Setup OpenVPN Client

Fix up the build key script so that it is non-interactive and has correct paths:

sed -i 's/--interact//g' /opt/easy-rsa/build-key
sed -i 's+export EASY_RSA=.*+export EASY_RSA="/opt/easy-rsa"+' /opt/easy-rsa/build-key

Configure OpenVPN Client

Configure the OpenVPN client by creating a client config file. We create this in /opt/openvpn, and use it to create the OpenVPN profile in /etc/openvpn a few steps.

/opt/openvpn/openvpn_client.conf

client
dev tun
proto udp
remote REMOTEIPOFOPENVPNSERVERGOESHERE 1194
nobind
pull
persist-key
persist-tun
remote-cert-tls server
# tls auth: act as client
key-direction 1
auth-user-pass

Now copy to /etc:

cp /opt/openvpn/openvpn_client.conf /etc/openvpn/.

Create Utility Scripts

We are going to create a couple of scripts to accomplish the task of registering users for the OpenVPN network.

First let's install some utilities that we will use.

Install Utility Helpers

sudo apt-get -y install zip unzip

Generate OpenVPN Profile

/opt/easy-rsa/gen_ovpn_profile.sh

#!/bin/bash
# Called by gen_client.sh
# Usage: ./gen_ovpn_profile.sh [USERNAME]
set -e
test "$#" -eq "1" || { echo "Provide 1 argument (username)"; exit 1; }
test -e /opt/openvpn/openvpn_client.conf || { echo "client config openvpn_client.conf not found!"; exit 1; }
test -e /opt/easy-rsa/keys/${1}.key || { echo "client key ${1} not found!"; exit 1; }
test -e /opt/easy-rsa/keys/ca.crt || { echo "server certificate ca.crt not found!"; exit 1; }
(cat /opt/openvpn/openvpn_client.conf
echo '<key>'
cat /opt/easy-rsa/keys/${1}.key
echo '</key>'
echo '<tls-auth>'
cat /opt/easy-rsa/keys/statictlssecret.key
echo '</tls-auth>'
echo '<cert>'
cat /opt/easy-rsa/keys/${1}.crt
echo '</cert>'
echo '<ca>'
cat /opt/easy-rsa/keys/ca.crt
echo '</ca>'
) > /opt/easy-rsa/keys/openvpn_${1}.ovpn

Set ownership and permissions:

chmod 0700 /opt/easy-rsa/gen_ovpn_profile.sh
chown root:root /opt/easy-rsa/gen_ovpn_profile.sh

Zip Client Files

/opt/easy-rsa/zip_client_files.sh

#!/bin/bash
# Called by gen_client.sh
# Usage: ./zip_client_files.sh [USERNAME]
set -e
test "$#" -eq "1" || { echo "Provide 1 argument (username)"; exit 1; }
FILES="/opt/easy-rsa/keys/ca.crt
/opt/easy-rsa/keys/statictlssecret.key
/opt/easy-rsa/keys/${1}.key
/opt/easy-rsa/keys/${1}.crt
/opt/easy-rsa/keys/openvpn_${1}.ovpn
/opt/easy-rsa/keys/${1}_password.txt"
for fname in ${FILES}; do
    test -e ${fname} || { echo "File ${fname} not found!"; exit 1; }
done
cd /opt/easy-rsa/keys
zip -j ${1}.zip $FILES

Set ownership and permissions:

chmod 0700 /opt/easy-rsa/zip_client_files.sh
chown root:root /opt/easy-rsa/zip_client_files.sh

Zip Client Files MFA Version

If you are setting up MFA, change the scripts as follows:

/opt/easy-rsa/zip_client_files.sh

#!/bin/bash
# Called by gen_client.sh
# Usage: ./zip_client_files.sh [USERNAME]
# Set the MFA_DISABLED env var to any value to use script for non-MFA clients
set -e
test "$#" -eq "1" || { echo "Provide 1 argument (username)"; exit 1; }
FILES="/opt/easy-rsa/keys/ca.crt
/opt/easy-rsa/keys/statictlssecret.key
/opt/easy-rsa/keys/${1}.key
/opt/easy-rsa/keys/${1}.crt
/opt/easy-rsa/keys/openvpn_${1}.ovpn"
if [ -z "${MFA_DISABLED}" ]; then
    FILES="${FILES}
    /etc/openvpn/google-authenticator/${1}_qr.png
    /etc/openvpn/google-authenticator/${1}_backup_codes.txt"
else
    FILES="${FILES}
    /opt/easy-rsa/keys/${1}_password.txt"
fi
for fname in ${FILES}; do
    test -e ${fname} || { echo "File ${fname} not found!"; exit 1; }
done
cd /opt/easy-rsa/keys
zip -j ${1}.zip $FILES

Generate and Delete Client Scripts

Generate Client Script

/opt/easy-rsa/gen_client.sh

#!/bin/bash
# Create new client and files required by new client.
# Usage: ./gen_client.sh [USERNAME]
set -e
test "$#" -eq "1" || { echo "Provide 1 argument (username)"; exit 1; }
# Run build-key
test -e /opt/easy-rsa/build-key || { echo "build-key not found!"; exit 1; }
test -e /opt/easy-rsa/gen_ovpn_profile.sh || { echo "gen_ovpn_profile.sh not found!"; exit 1; }
. /opt/easy-rsa/vars
. /opt/easy-rsa/local_vars
/opt/easy-rsa/build-key ${1}
# Generate .ovpn profile
test -e /opt/easy-rsa/keys/${1}.crt || { echo "client certificate ${1} not found!"; exit 1; }
/opt/easy-rsa/gen_ovpn_profile.sh ${1}
# Register unix user and set a password
useradd -s /bin/nologin "${1}"
# Login user needs password
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 > /opt/easy-rsa/keys/${1}_password.txt
echo "${1}:$(cat /opt/easy-rsa/keys/${1}_password.txt)" | chpasswd
# Zip up all the client's files
/opt/easy-rsa/zip_client_files.sh ${1}
rm -f /opt/easy-rsa/keys/${1}_password.txt

Set ownership and permissions:

chmod 0700 /opt/easy-rsa/gen_client.sh
chown root:root /opt/easy-rsa/gen_client.sh

Generate Client Script MFA Version

If you are using MFA to protect the VPN, change the generate client script to the following:

/opt/easy-rsa/gen_client.sh

#!/bin/bash
# Create new client and files required by new client.
# Usage: ./gen_client.sh [USERNAME]
# Set the MFA_DISABLED env var to any value to use script for non-MFA clients
set -e
test "$#" -eq "1" || { echo "Provide 1 argument (username)"; exit 1; }
# Run build-key
test -e /opt/easy-rsa/build-key || { echo "build-key not found!"; exit 1; }
test -e /opt/easy-rsa/gen_ovpn_profile.sh || { echo "gen_ovpn_profile.sh not found!"; exit 1; }
. /opt/easy-rsa/vars
. /opt/easy-rsa/local_vars
/opt/easy-rsa/build-key ${1}
# Generate .ovpn profile
test -e /opt/easy-rsa/keys/${1}.crt || { echo "client certificate ${1} not found!"; exit 1; }
/opt/easy-rsa/gen_ovpn_profile.sh ${1}
# Register unix user and set a password
useradd -s /bin/nologin "${1}"
if [ -n "${MFA_DISABLED}" ]; then
    # Login user needs password
    head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 > /opt/easy-rsa/keys/${1}_password.txt
    echo "${1}:$(cat /opt/easy-rsa/keys/${1}_password.txt)" | chpasswd
else
    # MFA user does not need password
    echo "${1}:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)" | chpasswd
    # Generate MFA client info
    MFA_LABEL="Dockstore VPN"
    sudo -H -u gauth google-authenticator -t -w3 -e10 -d -r3 -R30 -f -l "${MFA_LABEL}" -s /etc/openvpn/google-authenticator/${1} > /etc/openvpn/google-authenticator/${1}.log
    # Text file with MFA backup codes
    tail -n10 /etc/openvpn/google-authenticator/${1} > /etc/openvpn/google-authenticator/${1}_backup_codes.txt
    # Generate QR image
    AUTH_ID="$( head -n1 /etc/openvpn/google-authenticator/${1} )"
    qrencode -o /etc/openvpn/google-authenticator/${1}_qr.png -d 300 -s 10 "otpauth://totp/${1}?secret=${AUTH_ID}&issuer=${MFA_LABEL}"
fi
# Zip up all the client's files
/opt/easy-rsa/zip_client_files.sh ${1}
if [ -n "${MFA_DISABLED}" ]; then
    rm -f /opt/easy-rsa/keys/${1}_password.txt
fi

Delete Client Script

/opt/easy-rsa/del_client.sh

#!/bin/bash
# Delete a client by deleting their client files and unix user.
# Usage: ./del_client.sh [USERNAME]
set -e
test "$#" -eq "1" || { echo "Provide 1 argument (username"; exit 1; }
# Revoke client files
. /opt/easy-rsa/vars
. /opt/easy-rsa/local_vars
# Delete client openvpn files
rm -f /opt/easy-rsa/keys/${1}.zip
rm -f /opt/easy-rsa/keys/${1}.key
rm -f /opt/easy-rsa/keys/openvpn_${1}.ovpn
# Delete unix user
deluser --remove-home ${1} || echo "User does not exist!"
# Revoke client
/opt/easy-rsa/revoke-full /opt/easy-rsa/keys/${1}
echo "User account ${1} has been deleted"

Set ownership and permissions:

chmod 0700 /opt/easy-rsa/del_client.sh
chown root:root /opt/easy-rsa/del_client.sh

Delete Client Script MFA Version

/opt/easy-rsa/del_client.sh

#!/bin/bash
# Delete a client by deleting their client files and unix user.
# Usage: ./del_client.sh [USERNAME]
set -e
test "$#" -eq "1" || { echo "Provide 1 argument (username"; exit 1; }
# Revoke client files
. /opt/easy-rsa/vars
. /opt/easy-rsa/local_vars
# Delete client openvpn files
rm -f /opt/easy-rsa/keys/${1}.zip
rm -f /opt/easy-rsa/keys/${1}.key
rm -f /opt/easy-rsa/keys/openvpn_${1}.ovpn
# Delete client MFA files (if they exist)
rm -f /etc/openvpn/google-authenticator/${1}
rm -f /etc/openvpn/google-authenticator/${1}.log
rm -f /etc/openvpn/google-authenticator/${1}_qr.png
rm -f /etc/openvpn/google-authenticator/${1}_backup_codes.txt
# Delete unix user
deluser --remove-home ${1} || echo "User does not exist!"
# Revoke cert
/opt/easy-rsa/revoke-full /etc/easy-rsa/keys/${1}
echo "User account ${1} has been deleted"

Using the VPN

Process for clients to use the VPN:

  • Sign up a client using the gen_client.sh script. You specify the username with the first argument, and that's it.
  • The gen client script will produce a zip file at /opt/easy-rsa/keys with the username of the client. This zip file should be securely transferred to the client.
  • Clients unzip the file and use the contents to connect to the OpenVPN server. Use the .ovpn profile to connect, and use the username_password.txt file to log in.

Example: Create a New User

To create a new user:

cd /opt/easy-rsa/
./gen_client.sh <username>

Now get the zip file at /opt/easy-rsa/keys/USERNAME.zip to the user in a secure way.

Example: Create MFA and Non-MFA Users

If you have used the MFA versions of the scripts above, you can create an MFA user like so:

MFA_DISABLED="" ./gen_client.sh <username>

Similarly you can create a non-MFA user like so:

MFA_DISABLED="yes" ./gen_client.sh <username>

In both cases, get the file /opt/easy-rsa/keys/USERNAME.zip to the client in a secure manner.