The Setup Part 2: Wireguard

I know. I hear you. You’ve been wondering about the wireguard setup. So many people have paged me, sent desperate letters, offered huge amounts of money - I want you all to know, I heard the message. You want the write up on Wireguard.

Here it is!

Setting it up was a breeze, as I said before. The instructions are helpfully outlined in the quick start on the Wireguard website. The gist of the set up is as follows.

  1. Create a wireguard interface
  2. Generate a private key
  3. Derive a public key from the private key
  4. Configure the interface with the keys and other options using wg(8)
    1. Optionally automate 1-4 with wg-quick(8)

Creating the interface

My server box used for the Wireguard endpoint is running Debian Bullseye and so Wireguard - until we’re on Linux 5.6 - is available as a DKMS module. I installed the wireguard-dkms package available in the [testing] repos as well as wireguard-tools, which includes the wg and wg-quick utilities.

# apt install wireguard-dkms wireguard-tools

After that’s completed, I set up the interface which I called wg0. For this I just wanted a point-to-point tunnel with another peer, so assigning it 10.7.0.1/32 and a peer to 10.7.0.2/32 made sense to me.

# ip link add dev wg0 type wireguard
# ip addr add dev wg0 10.7.0.1 peer 10.7.0.2
# ip addr show dev wg0
11: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> [...]
link/none
inet 10.7.0.1/32 scope global wg0
   valid_lft forever preferred_lft forever

Then I hopped over to my old laptop running Alpine Linux and created its wireguard interface too. Alpine’s DKMS modules are in the wireguard-lts package.

# apk add wireguard-lts wireguard-tools-wg wireguard-tools-wg-quick
# ip addr add dev wg0 10.7.0.2 peer 10.7.0.1
# ip addr show dev wg0
11: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> [...]
link/none
inet 10.7.0.2/32 scope global wg0
   valid_lft forever preferred_lft forever

Generate the keys

This is honestly my favorite part of Wireguard - key generation. Following along with the guide, it mentions setting your umask so the keys have the right permissions. Then its simply generating the base64-encoded public/private keypair.

# umask 077
# wg genkey > private.key
# wg pubkey < private.key > public.key

Additionally, I set up a pre-shared key for extra layers of security theater. These can be transferred to peers and used alongside the asymmetric keys.

# wg genpsk > preshared.key
# ls
preshared.key  private.key  public.key

I repeated this on the peer machine as well. That’s really it!

# # Host A
# cat public.key
cyZe37SZkp+Mf2qt1Hn1Q+loCKrd+q5wX6ngdcwV510=

# # Host B
# cat public.key
BNc6GTahUbRrjCtSV3n2B0ZLFAT+OGGSxqiVHmsSiC8=

Mise en place

The interface just needs to know a few things before turning it on like where the keys are and who to let speak to it (over UDP).

# wg set wg0 listen-port 44144 private-key ./private.key

You can optionally set quite a few things (see wg(8)). I set a specific port for future firewall rules and configured the private key path. The wg utility will spit out some nice information for you.

# wg
interface: wg0
  public key: cyZe37SZkp+Mf2qt1Hn1Q+loCKrd+q5wX6ngdcwV510=
  private key: (hidden)
  listening port: 44144

Now repeat on the peer. I’ll increment the port so I can tell at a glance who’s who.

# wg set wg0 listen-port 44244 private-key ./private.key
# wg
interface: wg0
  public key: BNc6GTahUbRrjCtSV3n2B0ZLFAT+OGGSxqiVHmsSiC8=
  private key: (hidden)
  listening port: 44244

For this point-to-point connection I also needed to configure a few more settings before connecting - the endpoint and allowed-ips. This tripped me up a little bit, but the endpoint is the WAN address of the peer and the allowed-ip value is what is allowed from the other side of the connection (i.e. through the tunnel).

On host A, public key cyZe37SZkp+Mf2qt1Hn1Q+loCKrd+q5wX6ngdcwV510=:

# wg set wg0 peer BNc6GTahUbRrjCtSV3n2B0ZLFAT+OGGSxqiVHmsSiC8= \
    preshared-key ./preshared.key endpoint $WAN_ADDRESS:44244 \
    allowed-ips 10.7.0.2

On host B, public key BNc6GTahUbRrjCtSV3n2B0ZLFAT+OGGSxqiVHmsSiC8=:

# wg set wg0 peer cyZe37SZkp+Mf2qt1Hn1Q+loCKrd+q5wX6ngdcwV510= \
    preshared-key ./preshared.key endpoint $WAN_ADDRESS:44144 \
    persistent-keepalive 25 allowed-ips 10.7.0.1

After initial set-up I noticed that the connection would die to the backend after some time. Pinging Host A (Nginx front) from Host B would renew the handshake. Setting the keep-alive value to 25 seconds prevented the firewall from dropping the packets after a long time of silence. See the Wireguard Quickstart page for more info.

I also added the nftables firewall rules as mentioned in my Part 1 post.

Turn up the volume

With just a few minutes of effort and some reading everything was ready to go. I have a wireguard interface, some public and preshared keys, and some peers configured. Kickoff is (unfortunately? fortunately?) anticlimactic - just set the interface up on both machines.

# ip link set wg0 up
# wg
interface: wg0
  public key: cyZe37SZkp+Mf2qt1Hn1Q+loCKrd+q5wX6ngdcwV510=
  private key: (hidden)
  listening port: 44144

peer: BNc6GTahUbRrjCtSV3n2B0ZLFAT+OGGSxqiVHmsSiC8=
  preshared key: (hidden)
  endpoint: $WAN_ADDRESS:44244
  allowed ips: 10.7.0.2/32
  latest handshake: 34 seconds ago
  transfer: 13.47 MiB received, 3.50 MiB sent
# ping 10.7.0.2
PING 10.7.0.2 (10.7.0.2) 56(84) bytes of data.
64 bytes from 10.7.0.2: icmp_seq=1 ttl=64 time=23.9 ms

--- 10.7.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 23.855/23.855/23.855/0.000 ms

wg-quick and wg0.conf

To automate all of this setting up and configuration, there’s a configuration file that can be saved and read from. There’s also the handy wg-quick(8) tool I mentioned. I saved my initial config with wg showconf > wg0.conf.

Be aware though, showconf will display the all configured keys in plaintext to sdout. It’s a very simple configuration file. If such a file already existed, wg can set configuration from it with wg setconf wg0 /path/to/file.

wg-quick can do all the set up and tear down for us if we have a config file. It uses the same config as wg(8) with a few additional options. The main one I needed was Address for the interface. There are also available options for running bash interpreted commands before or after a connection is set up with the PreUp, PostUp, PreDown, and PostDown. I don’t have any need for those at the moment, but they’re very useful if you need dynamic iptables(8) or DNS rules set.

After editing, here’s mine with keys snipped.

# cat wg0.conf
[Interface]
Address = 10.7.0.1/32
ListenPort = 44144
PrivateKey = [snip]

[Peer]
PublicKey = BNc6GTahUbRrjCtSV3n2B0ZLFAT+OGGSxqiVHmsSiC8=
PresharedKey = [snip]
AllowedIPs = 10.7.0.2/32
Endpoint = $WAN_ADDRESS:44244

With wg-quick you don’t need to do any manual interface creation or configuration with ip(8). Simply run:

# wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.7.0.1/32 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 10.7.0.2/32 dev wg0

To tear down, wg-quick down wg0 will delete the interface. Easy peezy!

Persistent system service

Debian provides a systemd service file for wg-quick that will start up the interface at boot. I ran systemctl enable wg-quick@wg0.service.

For the Alpine machine, I just edited the /etc/network/interfaces file to do what wg-quick would do in its place. I’m not very familiar with OpenRC, and this fit my needs just fine. Many thanks to the nameless elves editing the Alpine wiki page on wireguard

# cat /etc/network/interfaces
[snip]

auto wg0
iface wg0 inet static
        address 10.7.0.2
        netmask 255.255.255.255
        pre-up ip link add dev wg0 type wireguard
        pre-up wg setconf wg0 /etc/wireguard/wg0.conf
        post-up ip route add 10.7.0.0/24 dev wg0
        post-down ip link delete dev wg0

FIN

I’m very pleased with Wireguards performance and ease of use. It’s been reliable and performant for at least the few weeks it’s been running and I’m interested to test out the container functionality mentioned on this Wireguard page.

It’s now replaced my previous OpenVPN point-to-point tunnels and my laptop road-warrior setup. Highly recommended if you have a need for a VPN.

This is PART 2 of my fabulous setup (read Part 1 if you’d like). Stay tuned for PART 3.

Jared

100 Days: Day 3

Blogging is hard!