4. OpenVPN

OpenVPN is a full-featured SSL VPN which implements OSI layer 2 or 3 secure network extension using the industry standard SSL/TLS protocol, supports flexible client authentication methods based on certificates, smart cards, and/or username/password credentials, and allows user or group-specific access control policies using firewall rules applied to the VPN virtual interface [OVPN-HOWTO]. Its cross-platform portability, renown security and ease of use have made OpenVPN one of the most popular VPN solutions today.

Unlike IPsec, OpenVPN is not tightly integrated into the Operating System's kernel, but runs as a user-mode daemon and communicates with the TCP/IP stack via a tun(4) pseudo-device. Please refer to [OVPN-SEC2] for a detailed overview of the OpenVPN protocol and security model.

In the next paragraphs, we will implement the same VPN topology as in the previous chapter, though replacing IPsec with OpenVPN. The VPN1 machine will act as the server and wait for incoming connections from VPN2.

4.1 Installation and configuration

OpenVPN installation simply requires adding a couple of packages on both server and client(s):

4.1.1 Setting up the PKI

The first step in configuring OpenVPN is to set up the Public Key Infrastructure, by creating:

The CA private key will be used to sign the server and client certificates; this will allow the two VPN endpoints to mutually authenticate each other simply by verifying the CA signature of the other party's certificate, without having to previously know any other certificate but their own (see [OVPN-PKI] for further details).

OpenVPN provides a set of scripts, located in /usr/local/share/examples/openvpn/easy-rsa/2.0/, that greatly simplify the process of creating and managing the PKI. These scripts require, as a preliminary step, that you initalize a bunch of parameters in the vars file with your organization's data, to avoid being prompted for the same information every time you create a new certificate:

/usr/local/share/examples/openvpn/easy-rsa/2.0/vars
export EASY_RSA="`pwd`"

export OPENSSL="openssl"
export PKCS11TOOL="pkcs11-tool"
export GREP="grep"

export KEY_CONFIG="$EASY_RSA/openssl.cnf"
export KEY_DIR="$EASY_RSA/keys"

echo NOTE: If you run ./clean-all, I will be doing a rm -rf on $KEY_DIR

export PKCS11_MODULE_PATH="dummy"
export PKCS11_PIN="dummy"

export KEY_SIZE=1024
export CA_EXPIRE=3650
export KEY_EXPIRE=3650

export KEY_COUNTRY="IT"
export KEY_PROVINCE="Italy"
export KEY_CITY="Milan"
export KEY_ORG="Kernel Panic Inc."
export KEY_EMAIL="danix@kernel-panic.it"

Now, after sourcing the vars file, you can initialize the PKI by building the Diffie-Hellman parameters and creating the root CA certificate and key:

# cd /usr/local/share/examples/openvpn/easy-rsa/2.0/
# . ./vars
NOTE: when you run ./clean-all, I will be doing a rm -rf on /usr/local/share/example/opevvpn/easy-rsa/2.0/keys
# ./clean-all
# ./build-dh
Generating DH parameters, 1024 bit long safe prime, generator 2
This is going to take a long time
[ ... ]
# ./pkitool --initca
Using CA Common Name: Kernel Panic Inc. CA
Generating a 1024 bit RSA private key
.........................++++++
......++++++
writing new private key to 'ca.key'
-----
#

The next step is creating the certificate and key for the VPN server:

# ./pkitool --server vpn1.kernel-panic.it
Generating a 1024 bit RSA private key
........++++++
...........................................++++++
writing new private key to 'vpn1.kernel-panic.it.key'
-----
Using configuration from /usr/local/share/examples/openvpn/easy-rsa/2.0/openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'IT'
stateOrProvinceName   :PRINTABLE:'Italy'
localityName          :PRINTABLE:'Milan'
organizationName      :PRINTABLE:'Kernel Panic Inc.'
commonName            :PRINTABLE:'vpn1.kernel-panic.it'
emailAddress          :IA5STRING:'danix@kernel-panic.it'
Certificate is to be certified until Jun  2 08:41:51 2019 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
#

Next, we will use the pkitool utility to generate as many client certificates as we need:

# ./pkitool vpn2.kernel-panic.it
Generating a 1024 bit RSA private key
...................++++++
..++++++
writing new private key to 'vpn2.kernel-panic.it.key'
-----
Using configuration from /usr/local/share/examples/openvpn/easy-rsa/2.0/openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'IT'
stateOrProvinceName   :PRINTABLE:'Italy'
localityName          :PRINTABLE:'Milan'
organizationName      :PRINTABLE:'Kernel Panic Inc.'
commonName            :PRINTABLE:'vpn2.kernel-panic.it'
emailAddress          :IA5STRING:'danix@kernel-panic.it'
Certificate is to be certified until Jun  2 08:47:25 2019 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
#

So we have generated all the certificates and keys we need; you can find them in the /usr/local/share/examples/openvpn/easy-rsa/2.0/keys directory, ready to be copied to the appropriate machines. But before proceeding to copy the key files, we need to create, on both server and clients, the directory (/etc/openvpn/private) that will contain the private keys and assign it restrictive permissions to prevent unauthorized access.

# mkdir -p /etc/openvpn/private
# chmod 700 /etc/openvpn/private

The following are the files that must be copied from the CA-signing machine to the OpenVPN hosts:

Finally, remember to delete all the files in /usr/local/share/examples/openvpn/easy-rsa/2.0/keys/:

# ./clean-all

4.1.2 Server configuration

OpenVPN supports a number of configuration parameters, allowing you to deeply customize its behaviour. These parameters can be either passed from the command-line or in a configuration file. Omitted parameters take the default value.

Below is a sample configuration file (see [OVPN-MAN] for a complete list of all the available parameters):

/etc/openvpn/server.conf
# Transport protocol to use. Available protocols are udp and tcp-server
proto udp
# TCP/UDP port to bind to
port 1194
# Name of the tun(4) device to use
dev tun0

# Uncomment to enable the management interface on port 1195. The password file
# only contains the management password on a single line.
#management 127.0.0.1 1195 /etc/openvpn/private/mgmt.pwd

# Path to the CA certificate
ca /etc/openvpn/ca.crt
# Path to the server's certificate file
cert /etc/openvpn/vpn1.kernel-panic.it.crt
# Path to the private key file
key /etc/openvpn/private/vpn1.kernel-panic.it.key
# Path to the file containing the Diffe-Hellman parameters
dh /etc/openvpn/dh1024.pem

# Address range for the tun(4) interfaces
server 10.0.1.0 255.255.255.0
# Uncomment to allow clients to dynamically change address (useful for
# road-warriors)
#float 

# Send periodic keepalive messages
keepalive 10 120
# Use lzo compression to reduce network utilization
comp-lzo

# User the OpenVPN daemon should run as
user _openvpn
# Group the OpenVPN daemon should run as
group _openvpn
# Make the server daemonize after initialization
daemon openvpn

# Don't re-read key files upon receiving a SIGUSR1 signal
persist-key
# Don't close and reopen the tun(4) device upon receiving a SIGUSR1 signal
persist-tun

# Add a route to the local network to the client's routing table
push "route 172.16.0.0 255.255.255.0"
# Add routes to the remote networks to the server's routing table
route 192.168.0.0 255.255.255.0
route 192.168.1.0 255.255.255.0
# Directory for client-specific configuration files
client-config-dir /etc/openvpn/ccd

# Uncomment to periodically write status information to the specified file
#status /var/log/openvpn-status.log
# Uncomment to raise verbosity level for debugging
#verb 11

The client-config-dir directive in the server configuration file allows you to specify a directory containing client-specific configuration files. These files must have have the same name as the client's X509 Common Name, specified during the creation of the certificates. In this case, we will create a file named /etc/openvpn/ccd/vpn2.kernel-panic.it, which will specify which private networks can be reached through the OpenVPN client:

/etc/openvpn/ccd/vpn2.kernel-panic.it
iroute 192.168.0.0 255.255.255.0
iroute 192.168.1.0 255.255.255.0

Though very similar, both the route and iroute directives are necessary, because route controls the routing from the kernel to the OpenVPN server (via the tun(4) interface) while iroute controls the routing from the OpenVPN server to the remote clients [OVPN-HOWTO].

4.1.3 Client configuration

The client-side configuration is pretty similar to server-side configuration. The address and port of the server are specified via the remote directive. Make sure that the configuration matches the server configuration, in particular that they both use the same protocol, device type and that they both enable or disable lzo compression.

/etc/openvpn/client.conf
# Act as a client
client
# IP address (or hostname) and port of the OpenVPN server. You may specify
# multiple 'remote' options for redundancy.
remote 1.2.3.4 1194

# Transport protocol to use. Available protocols are udp and tcp-client
proto udp
# Name of the tun(4) device to use
dev tun0

# Uncomment if you connect through an HTTP proxy. The authfile must contain
# user and password on 2 lines. The authentication type can be 'none', 'basic'
# or 'ntlm'
#http-proxy proxy_addr proxy_port /etc/openvpn/private/authfile auth_type
# Make the server daemonize after initialization
daemon openvpn
# Send periodic keepalive messages
keepalive 10 120

# Don't bind to the local address and port, i.e. don't wait for incoming
# connections
nobind

# User the OpenVPN daemon should run as
user _openvpn
# Group the OpenVPN daemon should run as
group _openvpn
# Directory to chroot to after initialization
chroot /var/empty

# Don't re-read key files upon receiving a SIGUSR1 signal
persist-key
# Don't close and reopen the tun(4) device upon receiving a SIGUSR1 signal
persist-tun

# Path to the CA certificate
ca /etc/openvpn/ca.crt
# Path to the client's certificate file
cert /etc/openvpn/vpn2.kernel-panic.it.crt
# Path to the private key file
key /etc/openvpn/private/vpn2.kernel-panic.it.key

# Require that the peer certificate has the nsCertType field set to 'server'
ns-cert-type server
# Use lzo compression to reduce network utilization
comp-lzo

# Uncomment to periodically write status information to the specified file
#status /var/log/openvpn-status.log
# Uncomment to raise verbosity level for debugging
#verb 11

4.2 Starting the VPN

Before starting the VPN, we have to enable IP forwarding on both gateways, since they will have to perform routing of network traffic:

# sysctl net.inet.ip.forwarding=1

Uncomment the following line in /etc/sysctl.conf(5) to re-enable IP forwarding after reboot:

/etc/sysctl.conf
net.inet.ip.forwarding=1

So we're ready to start the VPN! Just run the following command on the server:

vpn1# openvpn --config /etc/openvpn/server.conf

and the following on the client:

vpn2# openvpn --config /etc/openvpn/client.conf

To finish, we just have to create the configuration file for the tun(4) interface on the server (starting OpenVPN from this file improves compatibility with PF):

/etc/hostname.tun0
up
!/usr/local/sbin/openvpn --daemon --config /etc/openvpn/server.conf

and on the client:

/etc/hostname.tun0
up
!/usr/local/sbin/openvpn --daemon --config /etc/openvpn/client.conf