OpenVPN on OpenBSD
Posted on 2017-06-05 18:14:00 from Vincent in OpenBSD VPN
I've read several articles concerning the setup of OpenVPN on OpenBSD. But I've decided to post this one because none I've read address the problem coming from the setuid.
Indeed, to avoid that OpenVPN stays in memory with the root user, you can configure it to setuid to an anther user, usually _openvpn.
Problem, when you close the connection, this user must have the correct rights to set back your networks configuration settings: route, resolv.conf.
1. Installation of OpenVPN
1.1 Introduction
I will not goes into all details. You can find good information here and here or here. But you can follow my methodology as a receip and you will have OpenVPN on your OpenBSD server, on your OpenBSD client and on Android.
In short:
- I'll generate the certificates on a dedicated machines
- Install and configure OpenVPN on my server
- Install and configure OpenVPN on an OpenBSD client
- Install and configure OpenVPN on an android machine
The goal of this VPN is to surf internet always from the same country.
In my case, this allow me to use some of my internet subscriptions (which use the ip location concept) from all over the world.
Form every places on the earth, my internet facing IP will always be the same, the one of my VPN server.
1.2 Create the certificates
For security reasons, the certificates will not be created on the server where OpenVPN server will run.
So, on this dedicated machine, I propose you to do:
pkg_add openvpn easy-rsa
mkdir -p /my/certificate/repository
cd /my/certificate/repository
cp -r /usr/local/share/easy-rsa .
cd easy-rsa
cp vars.example vars
Edit vars, change and uncomment required settings.
I propose to, at least, change the settings starting by EASYRSA_REQ.
Then generate the certificates.
./easyrsa init-pki
./easyrsa build-ca or ./easyrsa build-ca nopass
Up to you to decide is you want to protect your Certificate Authority activities by a password.
./easyrsa build-server-full vpnserver nopass
./easyrsa build-client-full client1 nopass
Generate as many certificates as you want, and give it a clear name to each of them.
We generate the Deffie Hellman file and the TLS-Auth file
./easyrsa gen-dh
openvpn --genkey --secret vpn-ta.key
The TA key is optional, but I propose to generate one because, following the documentation, this adds an additional layer of HMAC authentication on top of the TLS control channel to mitigate DoS attacks and attacks on the TLS stack.
1.2 On the OpenVPN server
1.2.1 The OpenVPN part
Install OpenVPN:
pkg_add openvpn
Copy the required certificates just created. I propose to post them in a /etc/openvpn directory. As you may suppose, thanks to transfer those certificate in a secured way. Not via ftp or poor email systems; but prefer scp
scp pki/ca.crt <user>@<server>:openvpn/
scp vpn-ta.key <user>@<server>:openvpn/
scp pki/issued/vpnserver.crt <user>@<server>:openvpn/
scp pki/private/vpnserver.key <user>@<server>:openvpn/
scp pki/dh.pem <user>@<server>:openvpn/
Then ssh to your server and do (you should be root for this):
cp /usr/local/share/examples/openvpn/sample-config-files/server.conf /etc/openvpn/server.conf
In server.conf, edit at least: port, ca, cert, key, dh, push bypass dhcp, push dns, tls-auth.
Here after my complete server.conf file
port 1194
proto udp
dev tun
ca /etc/openvpn/ca.crt
cert /etc/openvpn/vpnserver.crt
key /etc/openvpn/vpnserver.key # This file should be kept secret
dh /etc/openvpn/dh.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"
keepalive 10 120
tls-auth /etc/openvpn/vpn-ta.key 0 # This file is secret
cipher AES-256-CBC
comp-lzo
user _openvpn
group _openvpn
persist-key
persist-tun
status openvpn-status.log
verb 3
To avoid port scanners scanning default ports, I propose you to change the port and select a non default one.
As you can see, this setup will propose the clients to bypass the dhcp rules and to push some DNS entries. In this case, I will push the OpenDNS ip addresses. You can put what ever you prefer as DNS.
1.2.2 The kernel part
To finalise the setup of you server, you have to tell it that he has to accept and forward ip messages.
In /etc/sysctl.conf, please assure that you have:
net.inet.ip.forwarding=1
If you do not want to reboot your machine, you can type this:
sysctl net.inet.ip.forwarding=1
1.2.3 The firewall part
Please make sure that you have at least this in your pf.conf file.
# vpn
pass in on $ext_if proto udp from any to $ext_if port {1194}
pass in quick on $tun_if
pass out on $ext_if from 10.8.0.0/24 to any nat-to ($ext_if)
$ext_if is your external interface. To know it, just type "ifconfig". If you are running on a virtual environment, it could be "vio0"
1.2.4 To start it
To start your OpenVPN, you just have to start it with reference to your configuration file.
For test phases, start it in a foreground mode, just type:
openvpn --config /etc/openvpn/server.conf
For a more definitive solution, and as stated in the documentation, it's recommended to put this command in the /etc/hotsname.tun file
up
!/usr/local/sbin/openvpn --daemon --config /etc/openvpn/server.conf
2. OnpenVPN client on OpenBSD
For the client side, we will put all required certificates and configuration elements in one single file: a .ovpn file.
Such .ovpn file can be transferred to your OpenVPN client.
I will show it can be used on OpenBSD and on Android.
2.1. Create the ovpn file
On the machine where you have create the client's certificates, please perform the following instructions:
cd /my/certificate/directory/easy-rsa
cp /usr/local/share/examples/openvpn/sample-config-files/client.conf .
Edit client.conf and after "client" add:
script-security 2
up client.up
down client.down
Assure that "remote", "port" and "comp-lzo" are corresponding to the setting in your server.conf
Comment the lines with "ca ca.cert", "cert client.crt", "key client.key" and the line with "tls-auth ta.key"
Generate your client.ovpn by executing the following commands:
cat client.conf > client.ovpn
echo "<ca>" >> client.ovpn
cat pki/ca.crt >> client.ovpn
echo "</ca>" >> client.ovpn
echo "<cert>" >> client.ovpn
cat pki/issued/client1.crt >> client.ovpn
echo "</cert>" >> client.ovpn
echo "<key>" >> client.ovpn
cat pki/private/client1.key >> client.ovpn
echo "</key>" >> client.ovpn
echo "key-direction 1" >> client.ovpn
echo "<tls-auth>" >> client.ovpn
cat vpn-ta.key >> client.ovpn
echo "</tls-auth>" >> client.ovpn
Voila. Your client configuration file is ready. You just have to transfer it (in a secured way) to your client.
2.2. Start OpenVPN client on OpenBSD
You just have to execute the following:
openvpn --config client1.ovpn
But because OpenVPN change the uid of the process, it cannot perform correctly the commands to restore the system where it was before the opening of the VPN.
So, let check my tricks here under.
2.3. Start OpenVPN client on Android
I'm not a fan of google's stuff, so I propose you to download OpenVPN from f-Droid here
The easiest way to configure your mobile, is to use the .opvn file you have created here above.
I remind that it's not the goal to use the same certificates for different users / devices, so create certificates for your android mobile too.
Once on your mobile, you just have to import it from the OpenVPN App. And that's it :-).
You will have VPN with your server and you will get access to internet (since this is the goal of this VPN) via your VPN server.
3. Some tricks
3.1 Allow doas on /sbin/route when starting OpenVPN
I mention first that you could chroot your OpenVPN and keep it running with root user. But I would explain the other solution: run OpenVPN in the main system but with a setuid to the _openvpn user.
Where you run OpenVPN on OpenBSD, I propose you a trick to avoid annoying situations where your routes are not restored after a stop of OpenVPN. This is basically a problem on the clients.
Typically this is the problem you could have. We see that after the setuid, the problems start:
Sun May 28 18:41:53 2017 /sbin/route add -net 128.0.0.0 -netmask 128.0.0.0 10.8.0.13
add net 128.0.0.0: gateway 10.8.0.13
Sun May 28 18:41:53 2017 /sbin/route add -net 10.8.0.1 -netmask 255.255.255.255 10.8.0.13
add net 10.8.0.1: gateway 10.8.0.13
Sun May 28 18:41:53 2017 GID set to _openvpn
Sun May 28 18:41:53 2017 UID set to _openvpn
Sun May 28 18:41:53 2017 Initialization Sequence Completed
^c
Sun May 28 18:41:56 2017 event_wait : Interrupted system call (code=4)
Sun May 28 18:41:56 2017 /sbin/route delete -net 10.8.0.1 10.8.0.13 -netmask 255.255.255.255
route: must be root to alter routing table
Sun May 28 18:41:56 2017 ERROR: OpenBSD/NetBSD route delete command failed: external program exited with error status: 1
Sun May 28 18:41:56 2017 /sbin/route delete -net xx.xx.xx.xx 192.168.3.1 -netmask 255.255.255.255
route: must be root to alter routing table
Sun May 28 18:41:56 2017 ERROR: OpenBSD/NetBSD route delete command failed: external program exited with error status: 1
Sun May 28 18:41:56 2017 /sbin/route delete -net 0.0.0.0 10.8.0.13 -netmask 128.0.0.0
route: must be root to alter routing table
Sun May 28 18:41:56 2017 ERROR: OpenBSD/NetBSD route delete command failed: external program exited with error status: 1
Sun May 28 18:41:56 2017 /sbin/route delete -net 128.0.0.0 10.8.0.13 -netmask 128.0.0.0
route: must be root to alter routing table
Sun May 28 18:41:56 2017 ERROR: OpenBSD/NetBSD route delete command failed: external program exited with error status: 1
Sun May 28 18:41:56 2017 Closing TUN/TAP interface
Basically we see that OpenVPN is using commands "/sbin/route" to perform some changes when you start and stop it. It does this on both the client and the server.
Some of those changes are performed after the setuid to the user you have mentioned in the OpenVPN configuration's file. So, my proposed solution is to replace the /sbin/route program by the following script (route_my):
#!/bin/sh
if [ "$(id -u)" = "0" -o "$1" = "show" ]; then
/sbin/rroute $*
else
doas /sbin/rroute $*
fi
As you can see I have renamed the original /sbin/route by /sbin/rroute.
This small script allows the user referenced in /etc/doas.conf to run the /sbin/route command now renamed /sbin/rroute. In other words, you must add in your /etc/doas.conf file, the following:
permit nopass _openvpn as root cmd /sbin/rroute
Please note that the user must be the one you have put in your OpenVPN configuration file: server.conf or client.conf. On OpenBSD is _openvpn
This is not a solution that will resist to an upgrade of your system, so I propose you to have the following startup script for OpenVPN (start_openvpn.sh):
#!/bin/sh
if [ "`file -b /sbin/route | grep "ELF"`" ]; then
echo "we move /sbin/route to /sbin/rroute"
doas mv /sbin/route /sbin/rroute
echo "we move route_my to /sbin/route"
doas cp route_my /sbin/route
else
echo "/sbin/route type is:"
file -b /sbin/route
fi
echo "start openvpn"
doas openvpn --config /etc/openvpn/server.conf
Basically this script check if the /sbin/route is a binary. In such case, it replace is by a local script called route_my which is the script here above.
As you can notice, I use several "doas" in order to be able to start OpenVPN from my own user.
Such setup is perfect for a VPN client where you want to start/stop the VPN when you want and without rebooting your machine.
As consequence, you have to have the following in /etc/doas.conf:
permit nopass vi as root cmd openvpn
permit nopass vi as root cmd cp args route_my /sbin/route
permit nopass vi as root cmd mv args /sbin/route /sbin/rroute
3.2 Allow OpenVPN to update the /etc/resolv.conf
On the client side, there is still one small problem with OpenVPN on OpenBSD: the DNS.
Indeed, to update the DNS entries in resolv.conf, we have to explain to OpenVPN how to do it.
For that I'm using shell script provided by OpenVPN but not present in the OpenBSD package :(.
Basically you have to copy the client.up script and client.down script on your machine. And make sure that those scripts are executable (chmod +x ...)
The "client.up" is run by OpenVPN before the setuid to _openvpn. So, it works perfectly. BUT, the "client.down" does not work as it is, since it's run by the user _openvpn.
The most "OpenVPN way to do it", would be to use a plugin provide with the package which is called: openvpn-plugin-down-root.so. But I've done it the "OpenBSD way", with doas.
So, I have to adapt it like this:
if [ -e /etc/resolv.conf.ovpnsave ] ; then
# cp + rm rather than mv in case it's a symlink
doas cp /etc/resolv.conf.ovpnsave /etc/resolv.conf
doas rm /etc/resolv.conf.ovpnsave
fi
I have to make sure that _openvpn can do those command by adapting accordingly /etc/doas.conf
permit nopass _openvpn as root cmd cp args /etc/resolv.conf.ovpnsave /etc/resolv.conf
permit nopass _openvpn as root cmd rm args /etc/resolv.conf.ovpnsave
Then you have to adapt your OpenVPN configuration file like this:
# Specify that we are a client and that we
# will be pulling certain config file directives
# from the server.
client
script-security 2
up client.up
down client.down
In the first part of the configuration file, you have to add the last 3 lines. It tells to OpenVPN that he can run scripts and it gives the location of them.
Each time you will start OpenVPN, the client.up will be executed. Each time you stop/kill the OpenVPN process he will execute the script client.down
Note: my client.ovpn and client.up and client.down are on the same folder. If you want to put them in separated folder, please adapt your client.ovpn file accordingly.
1. From Mark on Sun Dec 10 20:35:17 2017
Thank you so much. I have been reading manuals and searching online for 2 days for a solution. Many questions have been posted over the years about this issue, but nobody seems to ever post an answer. I have even read one response claiming that OBSD does not use resolv.conf (which we know is brutally incorrect. I have been plagued with DNS leaks due to OpenBSD reolv.conf not updating and your client.up & client.down scripts worked flawlessly. NO more leaks.
2. From Vincent on Tue Dec 12 06:22:58 2017
Thanks
3. From Markus Hutmacher on Sun Jul 29 22:27:29 2018
Hi, thanks for sharing this. I'm running OpenBSD 6.3 and had to put an "rm -f" into the client.down script as well as into the doas.conf file. This means "permit nopass _openvpn as root cmd rm args -f etc/resolv.conf.ovpnsave" Without the -f I was asked for the root-password when bringing the tunnel down. Regards Markus