Ubuntu/OpenVPN Server
From charlesreid1
This page gives an overview of how to install and set up an OpenVPN server on Ubuntu 18.04.
Contents
- 1 Server Prep
- 2 Client Prep
- 3 Using the VPN
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.