A mesh VPN using OpenBSD and WireGuard

WireGuard is a new coming to OpenBSD 6.8 and it looks like a simple and efficient way to connect computers.

I own a few VPS (hello Vultr, hello OpenBSD.amsterdam) that tend to be connected through filtered public services and/or SSH tunnels. And that’s neither efficient nor easy to manage. Here comes the wg(4) era where all those peers will communicate with a bit more privacy and ease of management.

Road-warrior meets Server

Lets start with reading wg(4) and ifconfig(8) man pages. There are private keys involved in the process. And as far as I can understand, there is no specific directive on how to store them. I first thought about storing them in /etc/ssl/private. But I decided to treat those private keys like a do with wireless passphrases ; and store them in the hostname.if(5) file.

Using a pre-shared key isn’t mandatory but it helps because it “(…) offers a post-quantum resistance to the Diffie-Hellman exchange”. So let’s create and use one for that peer-to-peer connection:

# openssl rand -base64 32

Remember that key. It will have to be referenced in both peer’s configuration file.

On the Road-Warrior side, generate a private key and configure a basic WireGuard interface:

# cat > /etc/hostname.wg0 << EOF
wgkey $(openssl rand -base64 32) wgport 51820 netmask

# chmod 0600 /etc/hostname.wg0

On the Server side, repeat the previous commands.

On both side, we can now see the public key using ifconfig(8) and looking at the wgpubkey line. Grab the public key from the Server and reference it in the Road-Warrior’s configuration. Then grab the public key from the Road-warrior and reference it in the Server’s configuration.

root@roadwarrior# cat >> /etc/hostname.wg0 << EOF
wgpeer <Insert Server Public Key> \
wgpsk <Insert Preshared Key> \
wgendpoint <Insert Server IP or FQDN> 51820 \
wgaip <Insert Server wg0 IP>/32

root@server# cat >> /etc/hostname.wg0 << EOF
wgpeer <Insert Road-Warrior Public Key> \
wgpsk <Insert Preshared Key> \
wgendpoint <Insert Road-Warrior Last Known IP> 51820 \
wgaip <Insert Roard-Warrior wg0 IP>/32

On both side, mount the wg0 interface and check connectivity:

root@roadwarrior# sh /etc/netstart wg0
root@roadwarrior# pfctl -f /etc/pf.conf
root@roadwarrior# ping server

root@server# sh /etc/netstart wg0
root@server# pfctl -f /etc/pf.conf
root@server# ping roadwarrior

Both side should be pinging each other. Using ifconfig, you can check the peer connection. Be sure to allow traffic on configured UDP port in pf.conf(5) and reload pf(4) after the wg0 interface creation.

You may want to set the Server as the default router for the Road-Warrior. This is not what I want here. I’m just enabling remote connection to UDP and TCP ports on the Server ; ports that I don’t want to be exposed directly on the Wild Wild Web.

Server-to-Server configuration

To add another server to the mesh is quite straight forward. Simply replicate the private key generation/configuration on the New Server. Then teach the Server about the New Server and vice-versa. Then do the same with New Server and Road-Warrior.

root@newserver# cat > /etc/hostname.wg0 << EOF
wgkey $(openssl rand -base64 32) wgport 51820 netmask

wgpeer <Insert Server Public Key> \
wgpsk <Insert Preshared Key> \
wgendpoint <Insert Server IP or FQDN> 51820 \
wgaip <Insert Server wg0 IP>/32

root@newserver# chmod 0600 /etc/hostname.wg0
root@newserver# sh /etc/netstart wg0
root@newserver# pfctl -f /etc/pf.conf

root@server# cat >> /etc/hostname.wg0 << EOF
wgpeer <Insert NewServer Public Key> \
wgpsk <Insert Preshared Key> \
wgendpoint <Insert NewServer FQDN or IP> 51820 \
wgaip <Insert NewServer wg0 IP>/32
root@server# sh /etc/netstart wg0

Test the connectivity using ICMP. I did an iperf3(1) test:

user@newserver# iperf3 -c -f M
[ ID] Interval Transfer Bitrate
[ 5] 0.00-10.01 sec 1.02 MBytes 0.85 Mbits/sec sender
[ 5] 0.00-10.72 sec 1.01 MBytes 0.79 Mbits/sec receiver

This is clearly good since the connection between those two servers are connected via ADSL with an upload rate of 0,82 Mbps (according to SpeedTest) and 1023 Kbps (according to the box information GUI).

You don’t have to use the same preshared key for each peer couple. It’s up to you to choose high security and ease of use.

As for now, this is what has been achieved:

This can be reproduced to connect many more servers while keeping a decent decentralized connectivity. Dealing with the Private/Public keys might be a bit challenging. But I feel like it’s worth the feature. Time will tell.

Thanks to Matt Dunwoodie and Jason A. Donenfeld for the wg(4) driver ; and to all OpenBSD devs to make all this fun possible.

Author: Joel Carnat

@work Technical Architect and SysAdmin ; @home OpenBSD and FOSS, Karate, Kobudō, Jōdō, Bodyweight workout, Photography & Music

5 thoughts on “A mesh VPN using OpenBSD and WireGuard”

  1. Great article! Thanks for this. Is it possible to connect to OpenBSD (server) VPN, from Windows (10)? By either its default VPN Client or by a 3rd party software?


  2. Thanks for writing this article, it was a good read :) I wonder does wg handle updating the public IP of road-warrior after the initial connection or is this something which needs to be done by hand?

    1. Thank you. As far as I can tell, wg manages IP modifications on its own. One of my server has just changed its public IP (Internet provider switch) and all nodes are still pinging each other. It should be the same for a road-warrior.

  3. Thanks for the response. I found the WireGuard white paper in the OpenBSD wg(4) man page and it seems to agree, the public IP will update dynamically. Though, admittedly a lot of it went over my head.

    Again, thanks for the great article! I’m learning about VPNs and will be sure to explore wg more.

Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.