Creating a Wireguard VPN on OpenBSD

Introduction

According to the website, “Wireguard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.“

While userland support for Wireguard has been in OpenBSD for quite some time, a native implementation that works out-of-the-box has just recently been committed. The following article explains how to set up a Wireguard VPN tunnel between two endpoints.

Before you read further

Please note that this article is about wireguard's native implementation, so you need to have a recent version of OpenBSD 6.7-current running. If you don't want to compile stuff on your own, you need at least a snapshot version as of Tue Jun 23 22:59:09 MDT 2020. Use sysctl kern.version to find out which snapshot is installed.

Roadwarrior Setup

The following section describes a roadwarrior setup, i.e. one or more roaming clients connect and establish a VPN tunnel to a static endpoint. A point-to-point setup is even easier to configure so it's left out on purpose.

The tunnel endpoint is called server in the examples, has the host name wgserver.example.com, the IP address is aa.bb.cc.dd and the Wireguard Tunnel listens on Port 443 UDP. The port number can be arbitrarily chosen, however, Port 443 is seldomly firewalled on public networks like Hotel or public transport networks.

Create a wg interface and set a private key. Since this is the server's side, the port is statically set to 443.

server# ifconfig wg0 create wgkey $(openssl rand -base64 32) wgport 443

ifconfig shows now the public key (wgpubkey) that needs to be configured at client's side. Note that root permissions are needed to see the key, otherwise, ifconfig won't show it:

server# ifconfig wg0
wg0: flags=8082<BROADCAST,NOARP,MULTICAST> mtu 1420
    index 9 priority 0 llprio 3
    wgport 443
    wgpubkey RF8qxBg7HwWoeGvKzkSh3oV42TG32HT5gVV75k1UWiI=
    groups: wg

Repeat the same steps as above on the client, set a private key and get the public key that needs to be configured on the server:

client# ifconfig wg0 create wgkey $(openssl rand -base64 32)
client# ifconfig wg0
wg0: flags=8082<BROADCAST,NOARP,MULTICAST> mtu 1420
        index 13 priority 0 llprio 3
        wgpubkey bmUgcaOtlEFcIoSlwPQ3qO/c14nJHYr+a7Ms/kJmOFA=
        groups: wg

Now set the allowed IP addresses on the server and specify the client's public key. The allowed IP addresses indicate the IP addresses a peer is allowed to send from. That is, in order for an incoming packet from a peer to reach the host, the decrypted IP source address must be in the peer's allowed-ip ranges.

server# ifconfig wg0 wgpeer bmUgcaOtlEFcIoSlwPQ3qO/c14nJHYr+a7Ms/kJmOFA= wgaip 192.168.23.2/32
server# ifconfig wg0
wg0: flags=8082<BROADCAST,NOARP,MULTICAST> mtu 1420
        index 9 priority 0 llprio 3
        wgport 443
        wgpubkey RF8qxBg7HwWoeGvKzkSh3oV42TG32HT5gVV75k1UWiI=
        wgpeer bmUgcaOtlEFcIoSlwPQ3qO/c14nJHYr+a7Ms/kJmOFA=
                tx: 0, rx: 0
                wgaip 192.168.23.2/32
        groups: wg

As above, do the same on the client's side. Add the server's pubkey, specify the allowed IP addresses and also set the FQDN/IP address and port of your endpoint (here wgserver.example.com, Port 443). To send all traffic over the VPN tunnel, the allowed IP addresses are set to 0.0.0.0/0. If that's not desired a narrower IP address range can be set here.

client# ifconfig wg0 wgpeer RF8qxBg7HwWoeGvKzkSh3oV42TG32HT5gVV75k1UWiI= wgendpoint wgserver.example.com 443 wgaip 0.0.0.0/0
client# ifconfig wg0
wg0: flags=8082<BROADCAST,NOARP,MULTICAST> mtu 1420
        index 13 priority 0 llprio 3
        wgpubkey bmUgcaOtlEFcIoSlwPQ3qO/c14nJHYr+a7Ms/kJmOFA=
        wgpeer RF8qxBg7HwWoeGvKzkSh3oV42TG32HT5gVV75k1UWiI=
                wgendpoint aa.bb.cc.dd 443
                tx: 0, rx: 0
                wgaip 0.0.0.0/0
        groups: wg

Finally, the wg interfaces on both server and the client need an IP address. While this example uses IPv4 only, setting IPv6 addresses is also possible.

server# ifconfig wg0 192.168.23.1/24 up

client# ifconfig wg0 192.168.23.2/24 up

A ping can be used to test if the connection works as expected:

client$ ping -c 2 192.168.23.1 
PING 192.168.23.1 (192.168.23.1): 56 data bytes
64 bytes from 192.168.23.1: icmp_seq=0 ttl=255 time=40.210 ms
64 bytes from 192.168.23.1: icmp_seq=1 ttl=255 time=16.928 ms

Route Traffic over the Tunnel

The setup above is good to get an encrypted connection between peers, however, the roadwarriors cannot send their traffic over the tunnel, yet. To do so, the server needs to forward the traffic to the Internet (via NAT) and the client needs to route the traffic towards the tunnel.

On the server side, enable IPv4 forwarding the NAT the traffic via pf. Setting the sysctl to 1 enables it immediately and setting it in /etc/sysctl.conf will make the setting persistent over reboot:

server# sysctl net.inet.ip.forwarding=1

server# cat /etc/sysctl.conf
net.inet.ip.forwarding=1

Now instruct pf to NAT the traffic. Add the following lines to /etc/pf.conf.

# Allow connection to UDP 443
pass in proto udp from any to any port 443 keep state
# NAT the traffic from the wg0 interface
match out on egress from (wg0:network) to any nat-to (egress:0)

Verify the config and then add it to pf.

server# pfctl -f /etc/pf.conf -n
server# pfctl -f /etc/pf.conf

On the client side two additional routes are needed:

  1. A more specific route to the server (aa.bb.cc.dd) over the local default gateway (ww.xx.yy.zz)
  2. A default route towards the IP address of the tunnel endpoint

Set the rules as follows:

client# route add -priority 2 aa.bb.cc.dd ww.xx.yy.zz
client# route add -priority 7 default 192.168.23.1

Start the VPN Tunnel on Boot

To start the Wireguard tunnel upon boot a hostname.if file must be created. Since both peers need to know each others public key, the secret key cannot generated with openssl as seen above. The key has to be created before and then manually added to the file. wgpeer contains the public key of the server's side so you need to create a secret key there before.

client# openssl rand -base64 32
k1yKSr6UxjUaz5jztdnxmq6OH7zvkYUfcuLPQ1dUKq8=

client# cat /etc/hostname.wg0
wgkey k1yKSr6UxjUaz5jztdnxmq6OH7zvkYUfcuLPQ1dUKq8=
wgpeer fyTpRR6zhLXyebxkfGMEgEEORqLT74GWlkF+mT51qng= wgendpoint aa.bb.cc.dd 443 wgaip 0.0.0.0/0
inet 192.168.23.2/24
!route add -priority 2 aa.bb.cc.dd ww.xx.yy.zz
!route add -priority 7 default 192.168.23.1
up
server# openssl rand -base64 32
5/rge6grBrKv8M3ybM9Nqor82XeHaFfblZ/5Ue/qhfQ=

server# cat /etc/hostname.wg0
wgkey 5/rge6grBrKv8M3ybM9Nqor82XeHaFfblZ/5Ue/qhfQ=
wgport 443
wgpeer zWjBPaEA/Qpzi6QQQauDeC6Ulk7fIPTX1EEcpvEDkxg= wgaip 192.168.23.2/32
inet 192.168.23.1/24
up

Working with multiple Clients

Adding more roadwarriors to the server is as simple as adding another wgpeer. Follow the instructions above to set up another client. Once that is done, the new client's public key needs to be added to the server.

server# ifconfig wg0 wgpeer mi238gX8Jwcwm1kHyuuDleUF/h98ZoW/ORgIQZ7B9hk= wgaip 0.0.0.0/0

The number of configured peers can be seen in the ifconfig output:

server# ifconfig wg0
wg0: flags=80c7<UP,BROADCAST,DEBUG,RUNNING,NOARP,MULTICAST> mtu 1420
        index 16 priority 0 llprio 3
        wgport 443
        wgpubkey fyTpRR6zhLXyebxkfGMEgEEORqLT74GWlkF+mT51qng=
        wgpeer r1u7ITBMFdIBU8P2IByHIpS847LSznTy1PlG5tL560g=
                wgendpoint 1.2.3.4 12666
                tx: 135692, rx: 88500
                last handshake: 208 seconds ago
                wgaip 192.168.23.2/32
        wgpeer mi238gX8Jwcwm1kHyuuDleUF/h98ZoW/ORgIQZ7B9hk=
                wgendpoint 1.2.3.5 12766
                tx: 124, rx: 532
                last handshake: 1 seconds ago
                wgaip 0.0.0.0/0
        groups: wg
        inet 192.168.23.1 netmask 0xffffff00 broadcast 192.168.23.255

Debug the Connection

In case things didn't work out, debugging can be enabled/disabled with:

# ifconfig wg0 debug
# ifconfig wg0 -debug

$Id: wireguard.md,v 1.3 2020/06/25 17:10:45 cvs Exp $