indexpost archiveatom feed syndication feed icon

WireGuard, VPN

2022-04-22

I previously hinted at the desire for layered security in a small network of computers. WireGuard turns out to be a delightfully easy way to do just that. Here I outline a simplistic configuration that meets all of my own needs for networking two servers.

What is it?

WireGuard is a virtual private network that is integrated into the Linux kernel. It is exceedingly simple and highly opinionated. For someone like me, who isn't exactly a cryptography expert, this is a good thing. I don't need to spend hours digging through byzantine configuration options and wade around vulnerable configurations — they are not available. Interestingly it is also very small, compared to other VPNs. You could conceivably read all of the source code for fun (check drivers/net/wireguard/ in your local Linux source tree).

Intended Architecture

It seems the jargon for what I intend is a "site to site virtual private network". Basically I do not need to forward traffic from clients to the wider internet and the only real "client" is another server (a physically separate machine, so far as I know — both are virtual private servers). Perhaps a picture would help:

Machine A (public-facing) firewall allow: 22, 80, 443 VPN Machine B (private) firewall deny: non-VPN

implementing it

If you look at that diagram and think something like "but that is so simple!", you are not wrong. It is actually a little curious how complex other systems make such a configuration. Happily WireGuard is nearly as simple as possible. Here is I how I set up a system like the one pictured above:

Machine A has an /etc/wireguard/wg0.conf entry like this:

[Interface]
PrivateKey = iOP7M0LDI0Avj/jb4Mr1YgmQ0wuDjAhiqbxqjAK7kHA=
ListenPort = 51820
Address    = 10.0.0.1/24

[Peer]
PublicKey  = OgPy99pGiJvY4t6n69KCRNNyD1hn66R5SOvrQOVBvTk=
AllowedIPs = 10.0.0.2/32
Endpoint   = 109.170.24.9:51820

Machine B has an /etc/wireguard/wg0.conf entry like this:

[Interface]
PrivateKey = yJbXt8NsPfQjG3GhkGuTSpRBgNn25wkpd3gV3xIpo14=
ListenPort = 51820
Address    = 10.0.0.2/24

[Peer]
PublicKey  = NGQRUWJQPsr4mADIJlYA+e44j2jFCEEvR3/DmlONEik=
AllowedIPs = 10.0.0.1/32
Endpoint   = 193.10.218.90:51820

The wireguard-tools package includes a utility for generating keys so creating them ends up looking like wg genkey | tee privatekey | wg pubkey > publickey

With that much configuration and the wireguard-tools package installed it is possible to, on each machine, invoke wg-quick up wg0. And that is it. The Endpoint is used to initiate the tunnel (it refers to the remote machine's IP address) and the AllowedIPs designates what the machine will appear as in the tunnel. As an example from Machine A I might try to ping Machine B with:

$ ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=1.17 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.934 ms

The reverse, pinging machine A from B, works the same way. Any workings of the private network are transparent. It is possible to see what the operating system "sees" from this configuration using the standard ip tool since WireGuard is integrated with Linux seamlessly:

$ ip addr
...
3: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
  link/none 
  inet 10.0.0.2/24 scope global wg0
  valid_lft forever preferred_lft forever
  link/none

Further Configuration

With a VPN in place it is a simple matter of ramping the overall security of the system up to match. In my case I want to disallow any traffic from outside the VPN to "machine B". My latest experiments have me using Fedora 35 on the server which ships with firewalld rather than my usual ufw or iptables. It turns out this makes things easier still. I only need to add a zone within the firewall and designate how traffic should (or should not) flow within zones.

Creating a new zone requires writing (or copying) an XML file that looks something like this:

# cat /etc/firewalld/zones/vpn.xml 
<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>VPN</short>
  <description>A zone for VPN traffic</description>
</zone>

Then I can add my VPN network interface to the zone with: firewall-cmd --add-interface=wg0 --zone=vpn. With that done it is possible to route traffic like: firewall-cmd --add-port=22/tcp --zone=vpn

Thoughts

Easy, right? With maybe an hour's worth of work it is possible to setup an encrypted tunnel to isolate machines. Defense-in-depth suggests we shouldn't rely only on the tunnel for security, which is why yesterday I configured an encrypted, isolated SFTP server for database backups. Just like ping, pointing a database backup tool to this VPN-connected machine requires only pointing to the IP address assigned within the tunnel.