Skip to content

Instantly share code, notes, and snippets.

@filcab
Created September 12, 2016 22:28
Show Gist options
  • Save filcab/736c105c8be417e39ab2dd93c39961eb to your computer and use it in GitHub Desktop.
Save filcab/736c105c8be417e39ab2dd93c39961eb to your computer and use it in GitHub Desktop.
Small OpenVPN setup tutorial

Setting up an OpenVPN server

References:

Pre-requisites

Install openvpn. On Debian/Ubuntu, something like this should install all you need:

# apt-get install openvpn easy-rsa  # IIRC, easy-rsa is a dependency of openvpn

Certificate setup

# cd /etc/openvpn       # Pick any directory
# make-cadir easy-rsa   # Directory to store easy-rsa CA

Edit the variables in easy-rsa/vars and set appropriate key sizes, etc. Important vars:

  • KEY_SIZE: Size of certificate keys, etc. Set to 4096 or bigger (in 2016). I used 8196, but that’s probably overkill for now :-) Check Applied Crypto Hardening (ACH) for some advice.
  • CA_EXPIRE: ACH mentions 5 years as a “sane value” (remember that you’ll have to switch all certs signed by the CA when it expires)
  • KEY_EXPIRE: ACH recommends 1 year
  • KEY_COUNTRY, KEY_PROVINCE, KEY_CITY, KEY_ORG, KEY_EMAIL, KEY_OU: Set to whatever you want. This is certificate information and you’re only setting up a private VPN, not something mission critical, right?

Change into the easy-rsa directory and source the vars file, since those are used by most scripts:

# cd easy-rsa
# source vars

Start with a clean slate:

# ./clean-all    # Removes any existing certs. Don’t do it if you want to save anything.

Create the (self-signed) certificate authority (Use a password to protect the CA):

# ./build-ca
Generating a 8192 bit RSA private key
...

Generate the server’s certificate (no password, since we want the server to start automatically when restarting the computer).

# ./build-key-server RPi2    # Identifier can be whatever you want
Generating a 8192 bit RSA private key
...
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

Generate Diffie-Hellman parameters. This makes it harder to crack keys en-masse over the Internet (explanation for this falls out of the scope of this tutorial. Check WeakDH). Warning: on iOS 9 with OpenVPN1.0.7, DH parameters of 8192 resulted in not being able to connect. Generating DH parameters takes a long time. Feel free to leave a terminal running this command and do the rest of the tutorial on another one (remember to cd to the proper directory and source vars).

# ./build-dh

This command takes a long time. 8192 takes days on a Raspberry Pi2 (even on a tricked out Mac Pro from 2014, it takes >24h). 4096 should take a while, but be tolerable. 4096 bit keys take ~8h on an RPi2. From experiments, it seems 8192 doesn't work on iOS OpenVPN app. Feel free to override KEY_SIZE for this command:

# KEY_SIZE=4096 ./build-dh # allows for an 8192 default for everything else

Generate a shared secret key, which is useful to protect against DoS attacks:

# openvpn --genkey --secret ta.key

Create/edit the server configuration file. /etc/openvpn/server.conf is a good place. Example configuration for a UDP server (commented):

mode server
tls-server

# From Applied Crypto Hardening (3.2.3. Recommended cipher suites)
# To have a shared cipher with iOS 9 we need TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 (no ECDHE)
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384
auth SHA512

# Make sure we only use TLS 1.2
tls-version-min 1.2

# Set up CRL (not needed for now)
#crl-verify /etc/openvpn/crl.pem

# Make the replay window slightly larger due to mobile clients
replay-window 256 60

# IP/hostname to bind to
# Not needed if you only have one network interface, or if you don’t mind binding to all of them.
local 10.27.58.91

port 1194 ## default openvpn port
proto udp

# Can't use tap (network bridging. Would allow broadcast traffic) with iOS clients (and probably others)
#dev tap0 ## If you need multiple tap devices, add them here
dev tun # “tunnel”. Only supported dev for iOS clients.

# Certificates and encryption
ca ca.crt
cert RPi2.crt
key RPi2.key  # This file should be kept secret
#dh dh8192.pem  # dh8192 is too much for the iOS client :-(
dh dh4096.pem
tls-auth ta.key 0 # Shared secret among server and clients

# Encrypt data channel with AES 256. Compress data with LZO
cipher AES-256-CBC
comp-lzo

# Tunnel configuration: Assign a subnet for VPN clients.
server 10.55.43.0 255.255.255.0
# Persist IP assignments to clients in case of server restart
ifconfig-pool-persist ipp.txt

# Force all traffic through VPN
push "redirect-gateway def1"
push "remote-gateway 10.27.58.1”

# Push your DNS server IP
push "dhcp-option DNS 10.27.58.1”
push "dhcp-option DOMAIN vpn.mydomain.com”

max-clients 10 # Not very important if it’s a private server

# Drop privileges after setting up (be sure to create the user first)
user openvpn
group nogroup

# chroot to this directory after initialization (less access to the system if compromised)
chroot /etc/openvpn/chroot

# Persist keys and tunnels over `SIGUSR1` (“soft restarts”)
# Useful to use when dropping privileges
persist-key
persist-tun

# Set appropriate timeouts (check the man page)
keepalive 10 120

# Keep current status on this file.
status openvpn-status.log
# Log verbosity (logs can be read with journalctl)
verb 3

If desired, create a TCP config file by copying over the UDP version and editing the lines with the server (pick a different range) proto (tcp) and port (if you want) directives (Having a TCP server on port 80 or 443 is very useful to bypass firewalls, since those ports are usually open. Feel free to try UDP on port 53 (DNS) or 123 (NTP)).

Copy over files created with easy-rsa to /etc/openvpn to have everything in the same place (If you don’t want to copy, adjust the paths on the server configuration file):

# cp ca.crt dh4096.pem RPi2.crt RPi2.key ta.key /etc/openvpn

Create a client template config file. I usually keep it in /etc/openvpn/clients/iOS-config-template.ovpn:

### iOS configuration file for OpenVPN

# From Applied Crypto Hardening
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384
auth SHA512
# https://openvpn.net/index.php/open-source/documentation/howto.html#mitm
remote-cert-tls server

# Host name and port for the server (default port is 1194)
# note: replace with the correct values your server set up
remote vpn.example.net 1194
#proto tcp-client  # Uncomment this if you’re configuring for a TCP server

# SSL/TLS parameters - files created previously
ca ca.crt

# Since we specified the tls-auth for server, we need it for the client
# note: 0 = server, 1 = client
tls-auth ta.key 1

# Specify same cipher as server
cipher AES-256-CBC

# Use compression
comp-lzo

Write this script to /etc/openvpn/openvpn-unify (or somewhere else, it’s not sensitive). It turns a regular configuration file plus the certificates and keys into a “unified” configuration file, so we don’t need to copy over several files. This unified file will contain the shared secret between the server and the clients (same for every client), so should be protected.

#!/usr/bin/env python
from __future__ import print_function


import re
import sys


def process_config(f, out):
    for line in f:
        print(process_line(line.rstrip()), file=out)


def process_line(line):
    if re.match('^ *#', line):
        return line
    s = line.split(' ', 2)

    if s[0] == 'ca' or s[0] == 'cert' or s[0] == 'key':
        comment = ' %s' % s[2] if len(s) > 2 else ''
        line = generate_inline(s[0], s[1], comment)
    elif s[0] == 'tls-auth':
        m = re.match('^tls-auth ([^ ]+) ([01])(.*)$', line)
        if not m:
            print('Error in tls-auth line: %s' % line)
            sys.exit(1)
        path = m.group(1)
        type = m.group(2)
        comment = m.group(3)
        line = 'key-direction %d\n' % int(type)
        line += generate_inline('tls-auth', path, comment)

    return line


def generate_inline(name, path, comment):
    ret = '<%s>%s\n' % (name, comment)
    with open(path, 'r') as f:
        ret += f.read()
    ret += '</%s>\n' % name
    return ret


def main():
    if len(sys.argv) == 1:
        print('Error: usage: %s input.ovpn [output.ovpn]' % sys.argv[0])
        sys.exit(1)

    with open(sys.argv[1], 'r') as f:
        if len(sys.argv) > 2:
            with open(sys.argv[2], 'w') as out:
                process_config(f, out)
        else:
            process_config(f, sys.stdout)

if __name__ == '__main__':
    main()

Make it executable:

# chmod +x /etc/openvpn/openvpn-unify

Create the unified config file for this server’s clients:

# /etc/openvpn/openvpn-unify /etc/openvpn/clients/iOS-config-template.ovpn > /etc/openvpn/clients/iOS-config.ovpn

TODO

tls-verify and others, which allow the server/clients to verify parts of each other’s certificates before allowing the connection to go through.

systemd dealings:

OpenVPN installs /lib/systemd/system/openvpn@.service (at least on Debian) To get systemd to start the VPN server with a config file in /etc/openvpn/myconfigfile.conf, do:

# sudo ln -s /lib/systemd/system/openvpn@.service /etc/systemd/system/multi-user.target.wants/openvpn@myconfigfile.service

systemd extracts the part after the @ and before .service, and uses that to find the config file (check out the openvpn@.service file)

For server.conf and server-tcp.conf:

# sudo ln -s /lib/systemd/system/openvpn@.service /etc/systemd/system/multi-user.target.wants/openvpn@server.service
# sudo ln -s /lib/systemd/system/openvpn@.service /etc/systemd/system/multi-user.target.wants/openvpn@server-tcp.service

Feel free to start the servers now:

# sudo systemctl restart 'openvpn*.service'

Networking

To be able to route everything through the VPN you’ll need to enable IP forwarding and tell the firewall to forward traffic.

Add these lines to /etc/rc.local or your favourite boot up script:

echo 1 > /proc/sys/net/ipv4/ip_forward
iptables-restore /etc/iptables-saved

Write this (after substituting the IP range and network interface) to /etc/iptables-saved:

# iptables-saved start (replace iface name with yours, IP ranges for the OpenVPN servers too)
*nat
:PREROUTING ACCEPT [4368:1166153]
:INPUT ACCEPT [3735:1124005]
:OUTPUT ACCEPT [1010:98992]
:POSTROUTING ACCEPT [1010:98992]
-A POSTROUTING -s 10.0.4.0/24 -o enxb827eb6b34f7 -j MASQUERADE
-A POSTROUTING -s 10.0.3.0/24 -o enxb827eb6b34f7 -j MASQUERADE
COMMIT

Add the OpenVPN user:

# adduser --system --shell /usr/sbin/nologin --no-create-home openvpn_server

Set up the chroot directory (needs a tmp directory inside it):

# mkdir -p /etc/openvpn/chroot/tmp

Adding VPN clients

To add a VPN client, you’ll need to generate a certificate and sign it with the server CA. Ideally, the client will provide a certificate request, but this tutorial doesn’t do that. Each client should have their own certificate so we can revoke them one by one if needed.

Generate a PKCS#12 file with client-name as the common name, and sign it with the server’s certificate:

# ./build-key-pkcs12 client-name    # Builds a key with label "client-name”.

Provide a strong password. Take the client-name.p12 file and store it somewhere safe.

Get the client-name.p12 file onto the iOS device. Easiest ways are:

  • Create a temporary web server on your local network and use Safari to get the PKCS#12
  • Email it to yourself and open the attachment

Get the iOS-config.ovpn file onto the OpenVPN app on your iOS device using iTunes (safer), or email/Safari (OpenVPN should get the file when you open it).

Open the OpenVPN iOS app, import the configuration, and connect. All should be good :-) If not:

  • Did you add the DNS entry, or double-check the IP?
  • Check the logs (on the iOS app or with journalctl -xf on the server (yes, assuming Linux and system))
@dsommers
Copy link

dsommers commented Jul 4, 2017

Seriously, the systemd steps here are horrendous.

First of all, you will never need to run ln -s manually. For that you just do systemctl enable $UNIT where $UNIT can be openvpn@CONFIG_NAME. So if you have a file name /etc/openvpn/mytunnel.conf, then you use systemctl enable openvpn@mytunnel (without the .conf extension).

Secondly, the OpenVPN upstream recommended way is to use the newer systemd unit files we ship. That is: openvpn-server@.service and openvpn-client@.service.

Put your server configs into: /etc/openvpn/server and client configurations into /etc/openvpn/client. Then you use systemctl {start|stop|status|enable|disable} openvpn-{client,server}@CONFIG_NAME to manage that service. To let systemd take care of the logging, do not use the --log option the OpenVPN configs at all ... and then use journalctl -u openvpn-{client,server}@CONFIG_NAME --since today.

The reason for splitting out server and client is related to that these new unit files locks down the OpenVPN process even further, but client and servers requires slightly different capabilities. In addition, the client configurations greatly benefits of having --nobind, so that is added by default for client configurations too.

But there's more:

  • The --status line in the config example is superfluous using these new unit files.
  • The --ifconfig-pool-persist isn't as great as many believes, it's an best-effort solution but gives no guarantees at all. If you want a consistent IP address on your client, use --client-config-dir and set the IP address explicitly in the CCD config file for each client.
  • The use of --auth SHA512 is really not that efficient. Rather use SHA256.
  • If using only OpenVPN v2.4 server and clients, consider to use --tls-crypt instead of --tls-auth
  • Don't set --tls-cipher yourself. The default in recent OpenVPN versions are mostly up-to-date to what is needed, and it removes the need to update this if the official recommendations changes later on. Updating OpenVPN will be enough.
  • Using --tls-min-version is reasonable, but as long as OpenVPN and OpenSSL/mbed TLS are up-to-date, TLSv1.2 is what it will default to regardless.
  • Building a CA key of 8192 is completely overkill. I have heard several cryptographers even claim 4096 keys are mostly wasting of CPU cycles. The downsize of such large keys is that it makes tunnel establishing an re-negotiations slower. The same goes for DH parameters.
  • Don't use --comp-lzo. If you want to compression, use --compress. But there are also concerns about coupling compression with encryption, there are many pitfalls with this which can more easily make it possible to identify packet types being transmitted inside the tunnel by just looking at the encrypted traffic. The general recommendation is to not do compression. In addition, most files being transported these days have already some compression, so the benefit of compressing compressed data is mostly missing. What --compress adds is that it compresses more of the data retrieved from the TUN/TAP packets (IP headers, Ethernet frames, etc). Bottom line is, it probably doesn't give you as much as you'd like but adds a potential cost on the confidentiality of the tunnel.
  • The server configuration lacks --remote-cert-tls client.
  • The client configuration lacks --verify-x509-name - this restricts clients to connect to servers providing an expected X.509 subject. If you just want to do a match on the CN (typically hostname) field, use --verify-x509-name $HOSTNAME name

And most gravely: DO NOT EVER STORE YOUR PRIVATE CA KEY FILE ON THE OPENVPN SERVER
With easy-rsa (but also other CA solutions), the CA key is often not password encrypted. Which means anyone can issue new certificates offline - all they need is a copy of your private CA key. By doing that, they will also be capable of connecting to your VPN server, or even setup their own VPN server and make clients connecting to their server instead - and the clients wouldn't notice. Bottom line is: Put your CA files (in particular the private CA key) on a storage medium which is mostly offline. The files needed on the server and clients are:

Option/file Server Client Comment
--ca Yes Yes Public information.
--cert Yes Yes Public information.
--key Yes Yes Private key, keep secret.
--dh Yes No Public information, but no harm in restricting it.
--tls-auth or --tls-crypt Yes Yes Private key shared between clients and server, protect it well everywhere deployed.
--pkcs12 Yes Yes Can replace --ca, --cert and --key into a single file. Protect this file as a private secret key.

All of these files can also be embedded into the configuration file. If that is done, the configuration file must be protected as you would do with the private keys.

All this is why we in the upstream OpenVPN community have started to point users at this wiki page, the guidance you get on the Interwebs can be quite full of flaws. We don't believe in a "standard" setup, as there are too many variables which makes up a VPN. It is more important to understand which options you need to get a safe start and why you need to use them. Configuring a VPN isn't easy if you want to do it correctly and securely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment