Setting up an OpenVPN server
Pre-requisites
Install openvpn
. On Debian/Ubuntu:
apt-get install openvpn easy-rsa
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)][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 yearKEY_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 (removes any existing certs. Don’t do it if you want to save anything)
./clean-all
Create the (self-signed) certificate authority (Use a password to protect the CA):
./build-ca
...
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][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
OpenVPN installs /lib/systemd/system/[email protected]
(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/[email protected] /etc/systemd/system/multi-user.target.wants/[email protected]
systemd extracts the part after the @
and before .service, and uses that to find the config file (check out the [email protected]
file)
For server.conf
and server-tcp.conf
:
sudo ln -s /lib/systemd/system/[email protected] /etc/systemd/system/multi-user.target.wants/[email protected]
sudo ln -s /lib/systemd/system/[email protected] /etc/systemd/system/multi-user.target.wants/[email protected]
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))