| 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. |