Extended Brain Storage

OpenBSD: OpenVPN Server

Posted on April 30, 2018

This is a brief tutorial to set up a chrooted OpenVPN service in OpenBSD


OpenVPN is an open-source software, which enables administrators to implement virtual private networks and access network resources and services securely. Since the idea is to run the OpenVPN daemon chrooted under an unprivileged user/group (_openvpn), it is based on Krzysztof Pfaff’s tutorial available on openbsdsupport.org. The following steps have been tested in the latest version of OpenBSD.

Installation of OpenVPN

In OpenBSD, the OpenVPN can be installed as follows:

$ pkg_add openvpn

Some of the directories needs to be prepared in relationship to the chrooted environment:

$ install -m 700 -d /etc/openvpn/private
$ install -m 755 -d /etc/openvpn/certs
$ install -m 755 -d /var/log/openvpn

Some files need to be located in the chrootjail directory and /etc/openvpn should have symlinks to them:

$ install -m 755 -d /var/openvpn/chrootjail/etc/openvpn
$ install -m 755 -d /etc/openvpn/chrootjail/etc/openvpn/ccd  # client custom configuration directory
$ install -m 755 -d /var/openvpn/chrootjail/var/openvpn
$ install -m 755 -d /var/openvpn/chrootjail/tmp
$ ln -s /var/openvpn/chrootjail/etc/openvpn/crl.pem /etc/openvpn/crl.pem
$ ln -s /var/openvpn/chrootjail/etc/openvpn/ccd/ /etc/openvpn/ccd
$ ln -s /var/openvpn/chrootjail/etc/openvpn/replay-persist-file /etc/openvpn/replay-persist-file

Verification and filesystem check:

$ ls -alpd /etc/openvpn/certs /etc/openvpn/ccd /var/openvpn/chrootjail /var/openvpn/chrootjail/etc /var/openvpn/chrootjail/etc/openvpn /etc/openvpn/private /var/log/openvpn
lrwxr-xr-x  1 root  wheel   40 Dec 16 06:01 /etc/openvpn/ccd -> /var/openvpn/chrootjail/etc/openvpn/ccd/
drwxr-xr-x  2 root  wheel  512 Dec 16 06:00 /etc/openvpn/certs/
drwx------  2 root  wheel  512 Dec 16 06:00 /etc/openvpn/private/
drwxr-xr-x  2 root  wheel  512 Dec 16 05:55 /var/log/openvpn/
drwxr-xr-x  4 root  wheel  512 Dec 16 06:00 /var/openvpn/chrootjail/
drwxr-xr-x  3 root  wheel  512 Dec 16 06:00 /var/openvpn/chrootjail/etc/
drwxr-xr-x  2 root  wheel  512 Dec 16 06:00 /var/openvpn/chrootjail/etc/openvpn/

Creation of Certificates and Keys

This tutorial considers that the Certificate Authority (CA) was already set up and installed using the Easy-RSA.

Additionally, the security of the OpenVPN can be further hardened using the TLS Authentication (TA), which adds an HMAC signature to all SSL/TLS handshake packets for integrity verification on top of the TLS control channel to mitigate DoS attacks and attacks on the TLS stack, and protection from UDP flooding, portscanning and eventual buffer overflow vulnerabilities. This key is a shared secret. Therefore, must be copied to every client.

The TA key can be created on the OpenVPN machine (this will take “some” time):

$ cd /etc/openvpn/private/ # the location may differ (example path for OpenBSD)
$ openvpn --genkey --secret vpn-ta.key

A server request can be created and signed as follows (using the nopass parameter to omit the password protection):

$ cd /PATH/TO/Easy-RSA/ROOT/
$ easyrsa --vars=./vars build-server-full <SERVER_FQDN> nopass
Using configuration from /PATH/TO/Easy-RSA/ROOT/ca.domain.tld/safessl-easyrsa.cnf
Enter pass phrase for /PATH/TO/Easy-RSA/ROOT/ca.domain.tld/private/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'<SERVER_FQDN>'
Certificate is to be certified until Jun 12 14:19:50 2029 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated

For each VPN client, a client request (<CLIENT_USERNAME>) needs to be created and signed by the CA (the key file may be left unencrypted by using the nopass argument, but it is not recommended):

$ cd /PATH/TO/Easy-RSA/ROOT/
$ easyrsa --vars=./vars build-client-full <CLIENT_USERNAME>@<SERVER_FQDN> nopass
Note: using Easy-RSA configuration from: ./vars
Generating a 4096 bit RSA private key

Optionally, an empty certificate revocation list (CRL) can be created, in order to be prepared for future revocations of certificates, as follows:

$ cd /PATH/TO/Easy-RSA/ROOT/
$ easyrsa --vars=./vars gen-crl
An updated CRL has been created.
CRL file: /PATH/TO/Easy-RSA/ROOT/ca.domain.tld/crl.pem

Finally, the generated keys need to be copied to the right directories. The process of uploading the offline generated files onto the VPN server is not discussed here.

/PATH/TO/Easy-RSA/ROOT/OpenBSD Server Location

Note: Optionally, an admin interface can be activated as follows. However, it does not encrypt traffic and thus, should be allowed from localhost only:

$ openssl rand -base64 32 > /etc/openvpn/private/mgmt.pwd # password in clear-text (randomly generated)
$ chown root:wheel /etc/openvpn/private/mgmt.pwd
$ chmod 600 /etc/openvpn/private/mgmt.pwd

The OpenVPN admin interface can be accessed:

$ telnet localhost 1195

OpenVPN Server Configuration

In OpenBSD, the sever configuration example can be found in:

$ less /usr/local/share/examples/openvpn/sample-config-files/server.conf

However, the following server setup will work just as well (considering TCP@1194, the tun0 interface, no compression what so ever):

# vi /etc/openvpn/server_tun0.conf
port 1194
proto tcp4                                      # or udp4 if not filtered
dev tun
topology subnet                                 # https://community.openvpn.net/openvpn/wiki/Topology
server                # tun interface IP address range
ifconfig-pool-persist ipp.txt                   # optionally, an IP persistence file
ca /etc/openvpn/certs/ca.crt
cert /etc/openvpn/certs/mail.rohlix.eu.crt
key /etc/openvpn/private/mail.rohlix.eu.key     # this file should be kept secret
dh /etc/openvpn/dh.pem
tls-auth /etc/openvpn/private/vpn-ta.key 0      # this file should be kept secret
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS"              # optionally, DNS Server setup
#push "dhcp-option DNS"          # optionally, DNS Server setup
#push "route"          # optionally, routes setup
client-to-client                                # OpenVPN-based forward (not IP-based)
keepalive 10 120
cipher AES-256-GCM
auth SHA256
push "compress stub-v2"
compress stub-v2
user _openvpn
group _openvpn
verb 0
chroot /var/openvpn/chrootjail                  # CUSTOMIZED by adminOfMine #
crl-verify /etc/openvpn/crl.pem                 # CUSTOMIZED by adminOfMine #
### optional management setup
#management 1195 /etc/openvpn/private/mgmt.pwd

Using the UDP as a transport layer protocol, the VPN does not suffer from the TCP meltdown effect. On the other hand, the TCP is usually allowed when compared to the UDP on public WiFi networks (hotspots). Latency is the killer. With enough bandwidth, all things are possible though. Reader discretion advised!

For further security considerations, the following OpenVPN’s community websites can be used:

The specific cryptographic options can be shown using the following command:

$ openvpn --show-tls --show-ciphers --show-digests

If not present already, IP packet forwarding needs to be set up using the sysctl command and in the sysctl.conf file to make it permanent as follows:

$ echo "net.inet.ip.forwarding=1" >> /etc/sysctl.conf
$ sysctl net.inet.ip.forwarding=1
net.inet.ip.forwarding: 0 -> 1

The packet filter (PF) rules need to be modified accordingly, the two examples are as follows:

$ vi /etc/pf.conf
pass in on $ext_if proto tcp from any to $ext_if port {1194}
pass in quick on $tun_if # allows IP-based client-to-client communication
pass out on $ext_if from to any nat-to ($ext_if)

or preferably, with direct bruteforce detection:

$ vi /etc/pf.conf
ovn_if = "tun0"                         # OpenVPN
ovpn_ports   = "1194"
### Network/Protocol Address Translation (NAT/PAT) for outgoing IPv4 traffic
# * the quick makes it impossible to send out the traffic
# * the following rule allows the traffic to pass out of the router
match out on egress inet from ! egress to any nat-to (egress)
### Tunnels traffic
# simple IP spoofing prevention and filtering
pass in  quick on $ovn_if inet from $ovn_if:network to any
# deny any other traffic
block return in  quick on $ovn_if inet all
### General services setup on egress inwards and filtering out bruteforce
pass in quick on egress inet proto tcp from any to (egress) port 1194 \
        keep state \
        (max-src-conn 3, max-src-conn-rate 1/2, \
        overload <bruteforce> flush global)

The PF rules change needs to be applied using:

$ pfctl -nf /etc.pf.conf
$ pfctl -f /etc.pf.conf

The OpenVPN interface needs to be created as follows (including optional parameters):

$ vi /etc/hostname.tun0
!/usr/local/sbin/openvpn --daemon \
                         --config /etc/openvpn/server_$if.conf \
                         --dev $if \
                         & false

Verification of the server setup can be performed as follows:

$ /usr/local/sbin/openvpn --daemon --config /etc/openvpn/server_tun0.conf --dev tun0

The server can be set up to start automatically by running (this process is automatically performed after server restart):

$ sh /etc/netstart tun0

Optionally, should anything go wrong, the verification and troubleshooting information can be found in the following log file:

$ tail -f /var/log/daemon

Client Configuration

In OpenBSD, the client configuration example can be found in:

$ less /usr/local/share/examples/openvpn/sample-config-files/server.conf

However, the following will do just as well (and is aligned with the aforementioned TCP-based server setup):

$ vi /etc/openvpn/client.conf
dev tun
remote <SERVER_FQDN> 1194 tcp-client
resolv-retry 60
user _openvpn
group _openvpn
route vpn_gateway
verify-x509-name <SERVER_FQDN> name
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
compress stub-v2
key-direction 1
ca ca.crt
cert client.crt
key client.key
tls-auth ta.key 1

Reference to external files may not be acceptable by all OpenVPN client software. Furthermore, it is also not very practical, as delivering a single file to the client device would be much handy. Therefore, a <CLIENT_USERNAME>.ovpn file, which contains all necessary certificates and keys can be created right within the CA directory (not on the OpenVPN server) and which will be securely and privately transferred to the client/device afterwards, can be performed as follows:

Building on top of the OpenVPN client.conf file, the following client configuration file can be created within the CA directory:

$ cd /PATH/TO/Easy-RSA/ROOT/
$ mkdir openvpn
$ vi client.conf
dev tun
remote <SERVER_FQDN> 1194 tcp-client
resolv-retry 60
user _openvpn
group _openvpn
route vpn_gateway
verify-x509-name <SERVER_FQDN> name
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
compress stub-v2
key-direction 1

Since the vpn-ta.key file was generated on the OpenVPN server, it needs to be present in the <CLIENT_USERNAME>.ovpn file as well. If the server was set to vpn-ta.key 0, the client must be set to key-direction 1 or vice versa. The following commands expect that the <CLIENT_USERNAME>.ovpn file is located as follows:

$ ls -lA /PATH/TO/Easy-RSA/ROOT/openvpn/vpn-ta.key

Now, all of the external files can be concatenated as follows:

$ cd /PATH/TO/Easy-RSA/ROOT/
$ cat openvpn/client.conf > openvpn/<CLIENT_USERNAME>.ovpn
$ echo "<ca>" >> openvpn/<CLIENT_USERNAME>.ovpn
$ cat ca.crt >> openvpn/<CLIENT_USERNAME>.ovpn
$ echo "</ca>" >> openvpn/<CLIENT_USERNAME>.ovpn
$ echo "<cert>" >> openvpn/<CLIENT_USERNAME>.ovpn
$ cat issued/<CLIENT_USERNAME>.crt >> openvpn/<CLIENT_USERNAME>.ovpn
$ echo "</cert>" >> openvpn/<CLIENT_USERNAME>.ovpn
$ echo "<key>" >> openvpn/<CLIENT_USERNAME>.ovpn
$ cat private/<CLIENT_USERNAME>.key >> openvpn/<CLIENT_USERNAME>.ovpn
$ echo "</key>" >> openvpn/<CLIENT_USERNAME>.ovpn
$ echo "<tls-auth>" >> openvpn/<CLIENT_USERNAME>.ovpn
$ cat openvpn/vpn-ta.key >> openvpn/<CLIENT_USERNAME>.ovpn
$ echo "</tls-auth>" >> openvpn/<CLIENT_USERNAME>.ovpn

The .ovpn file is now available in:

$ ls -lA /PATH/TO/Easy-RSA/ROOT/openvpn/<CLIENT_USERNAME>.ovpn

Client Software

Not every software does recognise the aforementioned single .ovpn file structure.

The Linux NetworkManager fully recognises the single .ovpn file structure, after installation of the OpenVPN plugin, e.g. in Arch Linux (Artix Linux) using:

$ pacman -S networkmanager-openvpn

The latest version of the OpenVPN client app for smartphones (F-Droid, Google Play, AppStore) works fine with the single file structure.

For MacOS X, the OpenVPN client should be working. But this has not been tested. On the other hand, the Tunnelblick software can be installed from the Homebrew repository as follows:

$ brew install caskroom/cask/tunnelblick

but it requires a different file structure:

$ cd /PATH/TO/Easy-RSA/ROOT/
$ mkdir openvpn/<CLIENT_USERNAME>.tblk
$ grep -vE "user|group" client.conf > openvpn/<CLIENT_USERNAME>.tblk/client.conf
$ echo "key-direction 1" >> openvpn/<CLIENT_USERNAME>.tblk/client.conf
$ cp ca.crt openvpn/<CLIENT_USERNAME>.tblk
$ cp certs/<CLIENT_USERNAME>.crt openvpn/<CLIENT_USERNAME>.tblk
$ cp private/<CLIENT_USERNAME>.key openvpn/<CLIENT_USERNAME>.tblk
$ cp openvpn/vpn-ta.key openvpn/<CLIENT_USERNAME>.tblk

The directory can be zipped, copied to the client and removed as follows:

$ rm -rf /PATH/TO/Easy-RSA/ROOT/openvpn/<CLIENT_USERNAME>.tblk

The BlackBerry OS lost the game.

Fine Tuning

If required (and the tail -f /var/log/daemon is not sufficient), an OpenVPN-specific logging directory can be created as follows:

$ mkdir /var/log/openvpn/
$ chown _openvpn:_openvpn /var/log/openvpn/

In order to prevent the log from overflowing, the newsyslog configuration file can be changed as follows:

$ echo "/var/log/openvpn/openvpn                600  2    250   *     Z" >> /etc/newsyslog.conf

Using a private DNS server (such as Unbound), the OpenVPN interface address needs to be added to the interface (listeners) section and the IPv4 range of the clients into the access-control section as follows:

$ cat /var/unbound/etc/unbound.conf
        access-control: allow

Tags: #OpenBSD #OpenVPN #VPN #security #TLS #DTLS #hardening

⏴ Previous Post Next Post ⏵