Wireguard is a nifty little tool. The tagline describes it as an “extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography”. I have been using it for years to tunnel into my servers, as an Internet gateway, and as a jumpbox into the servers’ VLAN. Recently, I figured out how to configure it as a secure mesh between the servers.
Before looking at the specific cases, let us be clear that wireguard operates at a low level in the stack. All it does is make the wireguard
network device type available. Everything else is done by the standard ip(8)
command. This is obscured when we use the wg-quick(8)
helper, but that turns out to be just a shell script ultimately calling ip(8)
.
The basic shape of a wireguard config is this:
[Interface]
Address = ... # the IP address and subnet the local wg0 interface will get
SaveConfig = false # set this to false so that wireguard does not overwrite
# this config file
PostUp = ... # shell commands to run after the interface comes up; this
# is where you setup forwarding
PostDown = ... # shell commands to run after the interface comes down; this
# is where you clean up the PostUp commands
ListenPort = 51820 # port on the public internet
PrivateKey = ... # this machine's private key
[Peer]
# PEER1
PublicKey = ... # PEER1's public key, derived from its private key
AllowedIPs = ... # the IP address this peer is allowed to use on the wireguard
# interface
Endpoint = ... # PEER1's public IP address
[Peer]
# PEER2
PublicKey = ...
AllowedIPs = ...
# Endpoint = ... # if Endpoint is not configured, then this machine does not
# know how to reach PEER2... unless PEER2 first sends some
# packets to this machine, in which case the wireguard device
# will remember the sending address
# More peers
Once a config is saved to /etc/wireguard/wg0.conf
, we can bring up the network interface with systemctl start wg-quick@wg0
. Both the wg(8)
and wg-quick(8)
tools use this config file, so we need to read both man pages to make sense of the file.
A lot of the values in the following configs come in pairs which must match. If the paired values do not match, then one of the wireguard peers is probably going to silently discard any packets it receives. The paired values are:
-
*_WG_IP4/24
and*_WG_IP4/32
:: Peers can choose any IP address for the wireguard interface. The only restriction is that the values in one peer’sAddress
section must match the other’sAllowedIPs
section. Additionally,wg-quick
’s routing inference only works if the subnet inAddress
contains the address inAllowedIPs
. Ditto for*_WG_IP6/64
and*_WG_IP6/128
. -
*_PRIVATE_KEY
and*_PUBLIC_KEY
:: ThePrivateKey
in one peer’s[Interface]
section must match thePublicKey
in every other peer’s[Peer]
section.
Bridge to a server
The first scenario is connecting a laptop to a server. Connecting two machines is essentially all wireguard does, and everything else is just applications of this.
[Interface]
Address = LAPTOP_WG_IP4/24, LAPTOP_WG_IP6/64
PrivateKey = LAPTOP_PRIVATE_KEY
[Peer]
# SERVER1
PublicKey = SERVER1_PUBLIC_KEY
AllowedIPs = SERVER1_WG_IP4/32, SERVER1_WG_IP6/128
Endpoint = SERVER1_PUBLIC_IP:51820
wg0.conf
[Interface]
Address = SERVER1_WG_IP4/24, SERVER1_WG_IP6/64
SaveConfig = false
ListenPort = 51820
PrivateKey = SERVER1_PRIVATE_KEY
[Peer]
# laptop
PublicKey = LAPTOP_PUBLIC_KEY
AllowedIPs = LAPTOP_WG_IP4/32, LAPTOP_WG_IP6/128
wg0.conf
Internet gateway
To route all of a laptop’s networking through a gateway, we specify AllowedIPs = 0.0.0.0/0, ::/0
in the laptop’s [Peer]
section, and add some iptables(8)
rules to the gateway’s [Interface]
section.
...
[Peer]
# GATEWAY
PublicKey = GATEWAY_PUBLICKEY
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = GATEWAY_PUBLIC_IP:51820
...
wg0.conf
[Interface]
Address = GATEWAY_WG_IP4/24, GATEWAY_WG_IP6/64
SaveConfig = false
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o PUBLIC_IFACE -j MASQUERADE; ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -t nat -A POSTROUTING -o PUBLIC_IFACE -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o PUBLIC_IFACE -j MASQUERADE; ip6tables -D FORWARD -i %i -j ACCEPT; ip6tables -t nat -D POSTROUTING -o PUBLIC_IFACE -j MASQUERADE
ListenPort = 51820
PrivateKey = GATEWAY_PRIVATE_KEY
[Peer]
# LAPTOP
PublicKey = LAPTOP_PUBLIC_KEY
AllowedIPs = LAPTOP_WG_IP4/32, LAPTOP_WG_IP6/128
wg0.conf
Breaking down the iptables
rules, we have:
-
iptables -A FORWARD -i %i -j ACCEPT
:: Enable packet forwarding for the wireguard interface (%i
). -
iptables -t nat -A POSTROUTING -o PUBLIC_IFACE -j MASQUERADE
:: When sending packets toPUBLIC_IFACE
, enable masquerading to make them look like they came from the gateway host.POSTROUTING
,ACCEPT
, andMASQUERADE
areiptables
specific strings, butPUBLIC_IFACE
is something we should replace in the configs with a real interface name likeeth0
. -
ip6tables ...
:: Same as above, but for IPv6.
The PostDown
commands just delete (-D
) the rules added by the PostUp
commands.
Jumpbox to different network
Suppose the machines in the datacenter are connected over a VLAN, and we want the laptop to access this network through one of them. We make two changes:
-
In the laptop’s
[Peer]
section, we add the VLAN’s address and subnet toAllowedIPs
. -
In the jumpbox’s
[Interface]
section, we addPostUp
commands to enable forwarding for the wireguard interface and masquerading into the VLAN. This is similar to what we did in the gateway example.
...
[Peer]
# JUMPBOX
PublicKey = JUMPBOX_PUBLICKEY
AllowedIPs = JUMPBOX_WG_IP4/32, JUMPBOX_WG_IP6/128, VLAN_IP4/24
Endpoint = JUMPBOX_PUBLIC_IP:51820
...
wg0.conf
[Interface]
Address = JUMPBOX_WG_IP4/24, JUMPBOX_WG_IP6/64
SaveConfig = false
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o VLAN_IFACE -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o VLAN_IFACE -j MASQUERADE
ListenPort = 51820
PrivateKey = JUMPBOX_PRIVATE_KEY
[Peer]
# LAPTOP
PublicKey = LAPTOP_PUBLIC_KEY
AllowedIPs = LAPTOP_WG_IP4/32, LAPTOP_WG_IP6/128
wg0.conf
We only setup forwarding to the VLAN over IPv4. We could add the equivalent ip6tables(8)
commands to enable IPv6 as well. Similarly to the gateway example, we should replace VLAN_IFACE
with a real interface name like eth1
. We could also add more of these commands to enable forwarding to other networks or the public internet.
Mesh between servers
Finally, let us consider the case where we securely network together multiple servers into a mesh. We might want this because they are running in different datacenters, or because we do not trust the datacentre’s VLAN. All we need to do is add more [Peer]
sections to each of the machines, one for each other machine.
...
[Peer] # SERVER2
...
[Peer] # SERVER3
...
wg0.conf
...
[Peer] # SERVER1
...
[Peer] # SERVER3
...
wg0.conf
...
[Peer] # SERVER1
...
[Peer] # SERVER2
...
wg0.conf
Conclusion
Wireguard is a very useful tool for connecting multiple machines. The configuration is a bit confusing because it does multiple things, but ultimately, it is only combining simple snippets, each defining separate functionality.