Tags: #OpenBSD #OpenVPN #VPN #security #TLS #DTLS #hardening
OpenBSD: OpenVPN Server
This is a brief tutorial to set up a chrooted OpenVPN service in OpenBSD…
Introduction
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 |
---|---|
ca.crt | /etc/openvpn/certs/ca.crt |
private/ca.key | /etc/openvpn/private/ca.key |
vpn-ta.key | /etc/openvpn/private/vpn-ta.key |
issued/<SERVER_FQDN>.crt | /etc/openvpn/certs/<SERVER_FQDN>.crt |
private/<SERVER_FQDN>.key | /etc/openvpn/private/<SERVER_FQDN>.key |
dh.pem | /etc/openvpn/dh.pem |
crl.pem | /etc/openvpn/crl.pem |
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 192.168.9.0 255.255.255.0 # 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 192.168.9.1" # optionally, DNS Server setup
#push "dhcp-option DNS 208.67.222.222" # optionally, DNS Server setup
#push "route 10.11.12.0 255.255.255.0" # 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
persist-key
persist-tun
verb 0
chroot /var/openvpn/chrootjail # CUSTOMIZED by adminOfMine #
crl-verify /etc/openvpn/crl.pem # CUSTOMIZED by adminOfMine #
### optional management setup
#management 127.0.0.1 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 192.0.2.0/24 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
#down
up
!/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
client
dev tun
remote <SERVER_FQDN> 1194 tcp-client
resolv-retry 60
user _openvpn
group _openvpn
route 0.0.0.0 0.0.0.0 vpn_gateway
#redirect-gateway
nobind
verify-x509-name <SERVER_FQDN> name
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
persist-key
persist-tun
preresolve
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
client
dev tun
remote <SERVER_FQDN> 1194 tcp-client
resolv-retry 60
user _openvpn
group _openvpn
route 0.0.0.0 0.0.0.0 vpn_gateway
nobind
verify-x509-name <SERVER_FQDN> name
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
persist-key
persist-tun
preresolve
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:
$ zip -r <CLIENT_USERNAME>.zip <CLIENT_USERNAME>.tblk
$ 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
...
server:
interface: 192.0.2.1
access-control: 192.0.2.0/24 allow
...