Using wireguard on OpenBSD
Earlier this week, I was casually discussing various VPN's with my colleagues. I've tried my hand at OpenVPN a couple times in my life, but was turned off by the complicated setup, poor iOS compatibility (at the time), and slow reconnection speeds. The conversation quickly came to revolve around a relative newcomer to the VPN world: wireguard. With the promise of ease of use, minimalistic code base, proven security, wireguard threatens to take the VPN world by storm.
Currently, the project is not yet complete, with no stable 1.0 version released currently. However, wireguard is under active development, apparently with multiple donors, to include PIA. Personally, I do enjoy simple, yet robust software. With wirebase's codebase currently around 4K lines of code, the project should be much easier to debug and verify.
Wireguard will most likely be added to the Linux kernel within the coming months. Now since I predominantly use OpenBSD, and with most tutorials online being written with the Linux audience in mind, I figured that configuration on OpenBSD would be difficult. Boy was I wrong.
It literally took one article to show how to create a functioning point to point tunnel from my home router to my phone. It was so easy, that I thought about adding this VPN tech to the playbook for my home OpenBSD router project.
However, a single p-to-p tunnel doesn't do a whole lot of good when you have more than one mobile user. A single tunnel required on tun* device to be configured. Logically, a new tun* device would need to be created for each new user. This would aid in tracking data usage to individual endpoints. But I would rather not have to create more devices than needed. But theoretically, using only one tun* for multiple endpoints would prevent me from being able to accurately track endpoint usage, with that data being limited to the overall usage of the tun device.
I had a bit of trouble being able to get multiple endpoints to be routed through a single tun* device, as they are designed to operate as p-to-p connection, from my understanding. I was able to get around that by adding a static route to the interface, as I will show below.
Compiling from source
I started working on a wgctl script to help manage wireguard installation, configuration, and management, but it turned into a much larger project than I anticipated. Perhaps I will finish it one day. The first step for running wireguard on OpenBSD is to compile the code from source.
# Declare dir to build in
builddir=/tmp/wireguard
# Downloaded required packages
pkg_add gmake go
# Make build dir and cd
mkdir $builddir && cd $builddir
# Clone the repos
git clone https://git.zx2c4.com/wireguard-go
git clone https://git.zx2c4.com/WireGuard
# Let's make some binaries!
cd $buildir/wireguard-go
gmake
cp wireguard-go /usr/local/sbin/
cd $builddir/WireGuard/src/tools
gmake
cp wg /usr/local/sbin/
Adding networking stuff
Next, let's create the tun* interface. I chose to use tun3, but you could use whatever tun* device that is not currently in use. You may notice that the file below calls a server.conf file that we have not created yet.
/etc/hostname.tun3
inet 10.10.0.1 255.255.255.0
!/usr/local/sbin/wireguard-go -f tun3 &
!/bin/sleep 2
!/usr/local/sbin/wg setconf tun3 /etc/wireguard/configs/server.conf
!/bin/sleep 2
!/sbin/route add -inet 10.10.0.0/24 10.10.0.1
We will also have to nat the interface. Add this line to your pf.conf, with the port specified being whatever you choose, but it will have to match the server.conf file that we will create later.
/etc/pf.conf addition
# Wireguard tunnel
pass in on egress inet proto { tcp udp } from any to any port 9832
pass out on egress inet from (tun3:network) nat-to (egress:0)
If you haven't already, you will also have to enable inet forwarding as well, in sysctl.conf, or by running sysctl net.inet.ip.forwarding=1
.
/etc/sysctl.conf addition
net.inet.ip.forwarding=1
Creating wireguard keys
For wireguard to function, we will also need to generate some key pairs for the server itself, and for any clients we would like to connect. Shown below is the method to create one server key pair, and five client key pairs.
# Declare and make some paths
keys=/etc/wireguard/keys
mkdir -p $keys
# Create the server's public and private keys
wg genkey | tee $keys/server-private.key | wg pubkey > $keys/server-public.key
# Create client keys 1-5
for i in {1..5}
do
wg genkey | tee $keys/client${i}-private.key | wg pubkey > $keys/client${i}-public.key
done
Wireguard config files
Now let's shed some light on the magic that are wireguard config files.
The server.conf file must include the server's private key, created above at /etc/wireguard/keys/server-private.key, along with a port wireguard will listen on. This port must match when port that was opened in pf.conf earlier. The server.conf must also include each client's public key, as well as their allowed ip range, which must be unique for each client.
/etc/wireguard/configs/server.conf
[Interface]
PrivateKey = [server-private.key]
ListenPort = 9832
[Peer]
PublicKey = [client1-public.key]
AllowedIPs = 10.10.0.4/32
[Peer]
PublicKey = [client2-public.key]
AllowedIPs = 10.10.0.6/32
...
Now for each client, the configuration is somewhat reversed.
The interface block here specifies client1's allowed ip and range, as well as the DNS server of choice. Here, I simply use my router's DNS record, since I have an unbound daemon running.
The '[peer]' configured here is actually the wireguard server itself. The server's public key is specified, as well as the current IP of the server. Please note, that if your router's IP ever changes, this IP will have to be updated.
AllowedIP's here specifies that all IP's are accessible to the client.
/etc/wireguard/configs/client1.conf
[Interface]
Address = 10.10.0.4/32
PrivateKey = [client1-private.key]
DNS = 192.168.0.1
[Peer]
PublicKey = [server-public.key]
Endpoint = [router egress ip]:9832
AllowedIPs = 0.0.0.0/0
Connecting mobile devices
There are wireguard apps currently available on android and iOS. The app have the capability of scanning qr codes to import a client config. Luckily, there is a package available on OpenBSD that can help us generate qr codes! Just install the libqrencode pkg and run the following command to generate the qr code.
cat /etc/wireguard/configs/client1.conf | qrencode -t ansiutf8
At the very end of this walk-through, I will show you a script that I wrote to simplify creating keys and configuration files called wgkeys.sh. I also did integrate this qrencode command into wgkeys.sh. Once the libqrencode pkg has been installed, you can simply run the following to display client1's qr code:
wgkeys.sh -q 1
Starting wireguard
The final step is to bring up the tun3 interface. This should initialize the device, start wireguard-go on tun3, insert the server.conf, and establish the static route.
sh /etc/netstart tun3
Using wireguard
Wireguard is easily managed by wireguard-go and wg.
# This will display the running config, server, and peers
wg show
# This will show you the running config
wg showconf
# Start running wireguard on tun device. You should only have to do this once
wireguard-go -f tun3
# Use new config for tun3
wg setconf tun3 /path/to/server.conf
Making it easier
Alrighty, now we are add the easy part. This could all be done manually, but I thought it would be easiest to create a little script that generates all the configs and keys for me. This way, I don't have to worry about my own mistakes. Running wgkeys.sh will generate the keys, server.conf, and five client.conf's for usage. You can view the script here.
The basic usage is as follows:
- Run ./wgkeys.sh to create the server and five client keys.
- keys are stored in /etc/wireguard/keys
- configs are stored in /etc/wireguard/configs
- Run ./wgkeys.sh -q [1..5] to display a qr code that can be scanned by a mobile app. Please note that the libqrencode pkg must be installed for this to work.
Has been tested on OpenBSD 6.4