Title: Full WireGuard setup with OpenBSD | |
Author: Solène | |
Date: 09 October 2021 | |
Tags: openbsd wireguard vpn | |
Description: | |
# Introduction | |
We want all our network traffic to go through a WireGuard VPN tunnel | |
automatically, both WireGuard client and server are running OpenBSD, | |
how to do that? While I thought it was simple at first, it soon became | |
clear that the "default" part of the problem was not easy to solve, | |
fortunately there are solutions. | |
This guide should work from OpenBSD 6.9. | |
pf.conf man page about NAT | |
WireGuard interface man page | |
ifconfig man page, WireGuard section | |
# Setup | |
For this setup I assume we have a server running OpenBSD with a public | |
IP address (1.2.3.4 for the example) and an OpenBSD computer with | |
Internet connectivity. | |
Because you want to use the WireGuard tunnel as the default route, you | |
can't define a default route through WireGuard as this, that would | |
prevent our interface to reach the WireGuard endpoint to make the | |
tunnel working. We could play with the routing table by deleting the | |
default route found on the interface, create a new route to reach the | |
WireGuard server and then create a default route through WireGuard, but | |
the whole process is fragile and there is no right place to trigger a | |
script doing this. | |
Instead, you can assign the network interface used to access the | |
Internet to the rdomain 1, configure WireGuard to reach its remote peer | |
through rdomain 1 and create a default route through WireGuard on the | |
rdomain 0. Quick explanation about rdomain: they are different routing | |
tables, default is rdomain 0 but you can create new routing tables and | |
run commands using a specific routing table with "route -T 1 exec ping | |
perso.pw" to make a ping through rdomain 1. | |
```network diagram in text | |
+-------------+ | |
| server | wg0: 192.168.10.1 | |
| |---------------+ | |
+-------------+ | | |
| public IP | | |
| 1.2.3.4 | | |
| | | |
| | | |
/\/\/\/\/\/\/\ |WireGuard | |
| internet | |VPN | |
\/\/\/\/\/\/\/ | | |
| | | |
| | | |
|rdomain 1 | | |
+-------------+ | | |
| computer |---------------+ | |
+-------------+ wg0: 192.168.10.2 | |
rdomain 0 (default) | |
``` | |
# Configuration | |
The configuration process will be done in this order: | |
1. create the WireGuard interface on your computer to get its public | |
key | |
2. create the WireGuard interface on the server to get its public key | |
3. configure PF to enable NAT and enable IP forwarding | |
4. reconfigure computer's WireGuard tunnel using server's public key | |
5. time to test the tunnel | |
6. make it default route | |
Our WireGuard server will accept connections on address 1.2.3.4 at the | |
UDP port 4433, we will use the network 192.168.10.0/24 for the VPN, the | |
server IP on WireGuard will be 192.168.10.1 and this will be our future | |
default route. | |
## On your computer | |
We will make a simple script to generate the configuration file, you | |
can easily understand what is being done. Replace "1.2.3.4 4433" by | |
your IP and UDP port to match your setup. | |
```shell script | |
PRIVKEY=$(openssl rand -base64 32) | |
cat <<EOF > /etc/hostname.wg0 | |
wgkey $PRIVKEY | |
wgpeer wgendpoint 1.2.3.4 4433 wgaip 0.0.0.0/0 | |
inet 192.168.10.2/24 | |
up | |
EOF | |
# start interface so you can get the public key | |
# we should have an error here, this is normal | |
sh /etc/netstart wg0 | |
PUBKEY=$(ifconfig wg0 | grep 'wgpubkey' | cut -d ' ' -f 2) | |
echo "You need $PUBKEY to setup the remote peer" | |
``` | |
## On the server | |
### WireGuard | |
Like we did on the computer, we will use a script to configure the | |
server. It's important to get the PUBKEY displayed in the previous | |
step. | |
```shell script | |
PUBKEY=PASTE_PUBKEY_HERE | |
PRIVKEY=$(openssl rand -base64 32) | |
cat <<EOF > /etc/hostname.wg0 | |
wgkey $PRIVKEY | |
wgpeer $PUBKEY wgaip 192.168.10.0/24 | |
inet 192.168.10.1/24 | |
wgport 4433 | |
up | |
EOF | |
# start interface so you can get the public key | |
# we should have an error here, this is normal | |
sh /etc/netstart wg0 | |
PUBKEY=$(ifconfig wg0 | grep 'wgpubkey' | cut -d ' ' -f 2) | |
echo "You need $PUBKEY to setup the local peer" | |
``` | |
Keep the public key for next step. | |
## Firewall | |
You want to enable NAT so you can reach the Internet through the server | |
using WireGuard, edit /etc/pf.conf to add the following line (after the | |
skip lines): | |
```pf.conf configuration line | |
pass out quick on egress from wg0:network to any nat-to (egress) | |
``` | |
Reload with "pfctl -f /etc/pf.conf". | |
NOTE: if you block all incoming traffic by default, you need to open | |
UDP port 4433. You will also need to either skip firewall on wg0 or | |
configure PF to open what you need. This is beyond the scope of this | |
guide. | |
## IP forwarding | |
We need to enable IP forwarding because we will pass packets from an | |
interface to another, this is done with "sysctl | |
net.inet.ip.forwarding=1" as root. To make it persistent across | |
reboot, add "net.inet.ip.forwarding=1" to /etc/sysctl.conf (you may | |
have to create the file). | |
From now, the server should be ready. | |
## On your computer | |
Edit /etc/hostname.wg0 and paste the public key between "wgpeer" and | |
"wgaip", the public key is wgpeer's parameter. Then run "sh | |
/etc/netstart wg0" to reconfigure your wg0 tunnel. | |
After this step, you should be able to ping 192.168.10.1 from your | |
computer (and 192.168.10.2 from the server). If not, please double | |
check the WireGuard and PF configurations on both side. | |
## Default route | |
This simple setup for the default route will truly make WireGuard your | |
default route. You have to understand services listening on all | |
interfaces will only attach to WireGuard interface because it's the | |
only address in rdomain 0, if needed you can use a specific routing | |
table for a service as explained in rc.d man page. | |
Replace the line "up" with the following: | |
```hostname.if configuration | |
wgrtable 1 | |
up | |
!route add -net default 192.168.10.1 | |
``` | |
Your configuration file should look like this: | |
```hostname.if configuration example | |
wgkey YOUR_KEY | |
wgpeer YOUR_PUBKEY wgendpoint REMOTE_IP 4433 wgaip 0.0.0.0/0 | |
inet 192.168.10.2/24 | |
wgrtable 1 | |
up | |
!route add -net default 192.168.10.1 | |
``` | |
Now, add "rdomain 1" to your network interface used to reach the | |
Internet, in my setup it's /etc/hostname.iwn0 and it looks like this. | |
```hostname.if example | |
join network wpakey superprivatekey | |
join home wpakey notsuperprivatekey | |
rdomain 1 | |
up | |
autoconf | |
``` | |
Now, you can restart network with "sh /etc/netstart" and all the | |
network should pass through the WireGuard tunnel. | |
# Handling DNS | |
Because you may use a nameserver in /etc/resolv.conf that was provided | |
by your local network, it's not reachable anymore. I highly recommend | |
to use unwind (in every case anyway) to have a local resolver, or | |
modify /etc/resolv.conf to use a public resolver. | |
unwind can be enabled with "rcctl enable unwind" and "rcctl start | |
unwind", from OpenBSD 7.0 you should have resolvd running by default | |
that will rewrite /etc/resolv.conf if unwind is started, otherwise you | |
need to write "nameserver 127.0.0.1" in /etc/resolv.conf | |
# Bypass VPN | |
If you need for some reason to run a program and not route its traffic | |
through the VPN, it is possible. The following command will run | |
firefox using the routing table 1, however depending on the content of | |
your /etc/resolv.conf you may have issues resolving names (because | |
127.0.0.1 is only reachable on rdomain 0!). So a simple fix would be | |
to use a public resolver if you really need to do so often. | |
```command | |
route -T 1 exec firefox | |
``` | |
route man page about exec command | |
# WireGuard behind a NAT | |
If you are behind a NAT you may need to use the KeepAlive option on | |
your WireGuard tunnel to keep it working. Just add "wgpka 20" to | |
enable a KeepAlive packet every 20 seconds in /etc/hostname.wg0 like | |
this: | |
```hostname.if example | |
wgpeer YOUR_PUBKEY wgendpoint REMOTE_IP 4433 wgaip 0.0.0.0/0 wgpka 20 | |
[....] | |
``` | |
ifconfig man page explaining wgpka parameter | |
# Conclusion | |
WireGuard is easy to deploy but making it a default network interface | |
adds some complexity. This is usually simpler for protocols like | |
OpenVPN because the OpenVPN daemon can automatically do the magic to | |
rewrite the routes (and it doesn't do it very well) and won't prevent | |
non-VPN access until the VPN is connected. |