OpenVPN with password authentication on Ubuntu 15.04

OpenVPN logo

All the howtos and guides I found for setting up OpenVPN seemed to use certificate authentication of clients. In principal this is not a bad idea, but in practice such a setup is a lot more work to manage well. None of the guides I found gave any advice on how to configure and maintain CRL, which is extremely important with such an environment. In addition, all the guides advocated creating client certificates on the server, finding some way to transport a none-trivial amount of sensitive data from the server to the client (none-trivial in this case means more the can be read over the phone or easily re-typed). Both of these concerns greatly reduces the security of such a system, and in my opinion makes a username/password base authentication system a more secure option. Mind you, it’s possible to do the PKI based authentication system securely; it’s just a lot more work, and not really well documented in the guides and howtos available (or at least the once easily discoverable via Google).

Be advised: Before you take my advice on anything, you should known that I have a grand total of 3 days experience with OpenVPN. I do however have more then 20 years experience with Linux and TCP/IP networking in general. Feel free to use the comment section or to email me at bbj@bbj.io if you find any mistakes or have suggestions for a better setup.

The goal of this is to set up OpenVPN with username/password authentication, in a way where every connected user can use the same OpenVPN profile-file (.ovpn-file). We will also set up OpenVPN to listen on two alternative ports, to maximise the odds of managing to connect event in the presence of strict and/or stupid firewalls. In addition to the default UDP port 1194, OpenVPN will also accept connections on UDP port 53 and TCP port 443. Both of these are extremely difficult to firewall without “collateral damage”. As a nice bonus, when using UDP port 53, you may event be able to connect from an open WiFi network “protected” behind a captive portal.

Install OpenVPN and Easy-RSA

apt-get install openvpn easy-rsa

Remove the default configuration

rm -Rf /etc/openvpn/*

Create user, group and folders

mkdir /etc/openvpn/{bin,etc,tmp,user}
groupadd openvpn
useradd -d / -g openvpn openvpn
chmod 755 /etc/openvpn/{bin,etc,user}
chown openvpn:openvpn /etc/openvpn/tmp
chmod 700 /etc/openvpn/tmp

Setup Easy-RSA environments

source /usr/share/easy-rsa/vars
export EASY_RSA="/usr/share/easy-rsa"
export KEY_CONFIG="$($EASY_RSA/whichopensslcnf $EASY_RSA)"
export KEY_DIR="/etc/openvpn/etc"
/usr/share/easy-rsa/clean-all
/usr/share/easy-rsa/pkitool --initca
/usr/share/easy-rsa/pkitool --server server
openssl dhparam -out /etc/openvpn/etc/dh2048.pem 2048

Create OpenVPN configuration files

We create a common configuration file for all 3 protocol/port combination, and include this from the specific configurations. I’ve set this up with 3 private network (one for each protocol/port combo): 172.17.3.0/24 for UDP port 1194, 172.17.4.0/24 for UDP port 53 and 172.17.5.0 for TCP port 443. We will set up NAT (or what iptables calls MASQUERADE) for these ranges below. Adjust the value for local to the IP of your OpenVPN server.

cat > /etc/openvpn/etc/server-common.conf << EOF
local 77.40.156.9
dev tun
float
keepalive 10 120
ca /etc/openvpn/etc/ca.crt
cert /etc/openvpn/etc/server.crt
key /etc/openvpn/etc/server.key
dh /etc/openvpn/etc/dh2048.pem
topology subnet
client-config-dir /etc/openvpn/user
ccd-exclusive
client-cert-not-required
username-as-common-name
tmp-dir /etc/openvpn/tmp
user openvpn
group openvpn
persist-key
persist-tun
verb 3
mute 20
script-security 2
client-connect /etc/openvpn/bin/event
client-disconnect /etc/openvpn/bin/event
auth-user-pass-verify /etc/openvpn/bin/auth via-file
EOF
cat > /etc/openvpn/server-1194-udp.conf << EOF
config /etc/openvpn/etc/server-common.conf
port 1194
proto udp
server 172.17.3.0 255.255.255.0
EOF
cat > /etc/openvpn/server-53-udp.conf << EOF
config /etc/openvpn/etc/server-common.conf
port 53
proto udp
server 172.17.4.0 255.255.255.0
EOF
cat > /etc/openvpn/server-443-tcp.conf << EOF
config /etc/openvpn/etc/server-common.conf
port 443
proto tcp
server 172.17.5.0 255.255.255.0
EOF

This script is used for authenticating users from /etc/openvpn/etc/<USERNAME>.password. Save as /etc/openvpn/bin/auth. This may look unsafe, but the OpenVPN man-page states the following concerning the username field:

To protect against a client passing a maliciously formed username or password string, the username string must consist only of these characters: alphanumeric, underbar (‘_’), +, dash (‘-’), dot (‘.’), or at (‘@’)

#!/bin/bash
[ -f "$1" ] || exit 1
mapfile -t args < "$1"
user="${args[0]}"
[ "${user}" != "" ] || exit 1
pass="${args[1]}"
[ "${pass}" != "" ] || exit 1
file="/etc/openvpn/etc/${user}.password"
[ -f "${file}" ] || exit 1
save="$(<${file})"
[ "${save}" != "" ] || exit 1
[ "${save}" = "${pass}" ] && exit 0
exit 1
chmod +x /etc/openvpn/bin/auth

This script will log connect and disconnect events. You can extend it to trigger other events as needed. Save as /etc/openvpn/bin/event.

#!/bin/bash
echo "$(date): ${common_name} ${script_type} ${trusted_ip}" >> \
 /var/tmp/openvpn.event.log
chmod +x /etc/openvpn/bin/event

We overwrite /etc/default/openvpn to auto-start all 3 listeners at boot, reload the configuration and then manually start the 3 listeners.

echo 'AUTOSTART="server-1194-udp server-53-udp server-443-tcp"' \
 > /etc/default/openvpn
systemctl daemon-reload
service openvpn@server-1194-udp start
service openvpn@server-53-udp start
service openvpn@server-443-tcp start

Setting up NAT (MASQUERADE) for the three private networks

Run the following commands and also add them to your /etc/rc.local or equivalent to have them run at boot.

iptables -I FORWARD --match state \
 --state ESTABLISHED,RELATED --jump ACCEPT
iptables -A FORWARD -p tcp \
 -s 172.17.3.0/24  -j ACCEPT
iptables -A FORWARD -p tcp \
 -s 172.17.4.0/24  -j ACCEPT
iptables -A FORWARD -p tcp \
 -s 172.17.5.0/24  -j ACCEPT
iptables -t nat -A POSTROUTING \
 -s 172.17.3.0/24 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING \
 -s 172.17.4.0/24 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING \
 -s 172.17.5.0/24 -o eth0 -j MASQUERADE
echo 1 > /proc/sys/net/ipv4/ip_forward

Create common client profile

This profile (.ovpn-file) can be used by all clients, and contains no sensitive information. As such we make it available for download via HTTPS. This makes it easier to configure clients. /home/bbj/www/bbj.io/ is the webroot for https://bbj.io/, substitute for the relevant path on your own system (or copy the file to your web server if it’s on a different machine).

cat > /home/bbj/www/bbj.io/bbj.io.ovpn << EOF
client
dev tun
<connection>
remote 77.40.156.9 1194 udp
</connection>
<connection>
remote 77.40.156.9 53 udp
</connection>
<connection>
remote 77.40.156.9 443 tcp
</connection>
persist-key
persist-tun
mute-replay-warnings
<ca>
$(cat /etc/openvpn/etc/ca.crt)
</ca>
auth-user-pass
setenv CLIENT_CERT 0
ns-cert-type server
verb 3
EOF

If the profile is sent with the proper content-type, many platforms will automatically offer to load it into the OpenVPN client.

echo "AddType application/x-openvpn-profile .ovpn" \
 >> /home/bbj/www/bbj.io/.htaccess

Setting up a new user

These steps are all that is required to set up av new user. To remove the user, just undo (delete the two files). Individual client options can be set in the users individual config file. Refer to the OpenVPN man-page for valid options for per-user configuration. This sets up an account with username “username” and a good random password. Execute cat /etc/openvpn/etc/username.password to see the password.

cat > /etc/openvpn/user/username << EOF
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DOMAIN bbj.io"
push "dhcp-option DNS 172.17.2.1"
EOF
openssl rand -base64 18 > /etc/openvpn/etc/username.password
chown openvpn:openvpn /etc/openvpn/etc/username.pasword
chmod 400 /etc/openvpn/etc/username.password

Seems to be working quite well

iOS screenshot