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.
- Create a wireguard interface
- Generate a private key
- Derive a public key from the private key
- Configure the interface with the keys and other options using wg(8)
- 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!