I’ve been asked before to post something about this and I know many people are interested in VPN solutions that make sense for the cloud - these are very different than consumer VPN solutions!
So, this wireguard topic is for people who, like me, deploy could services in various locations that want their cloud services to talk to other cloud services over an encrypted tunnel. That makes them point-to-point tunnels, inherently. And so this topic doesn’t cover much more than static ptp
routing and ignores lots of other things.
While this may seem a little bland and limited, in fact it isn’t and offers lots of functionality. One of the use cases I’ve been struggling with for a long time is limited storage in cloud services. I don’t want
to revert to sshfs
on cloud services since it offers no reliability and is a pain to maintain - I need a much more reliable method to host files between two different cloud nodes. What I actually want is cifs
, but, I’d want all the data to be … encrypted.
One of the second use cases is being able to reach devices which are on a random IP address. You can certainly setup dynamic DNS, but, really, It’s a pain to have to maintain a DNS server just for that. Of course, you can use a cloud service for it too, but, what if your device is on a mobile phone connection and is basically firewalled?
Wireguard takes care of a lot of these problems all at once - the tunnel between the two endpoints is persistent from a userspace perspective - the link will always exist - you can set it to use static IP addresses for clients and now you can just simply do things like mount
a cifs
file system or ssh
to one of the static private IP addresses when you need to, or do things like mqtt
and have clients update statuses and retrieve messages when they’re connected - ideal for IOT scenarios.
setup basics
To start, we need to have 2 devices. We’re going to mark one as a server
and one as a client
. The server
will be listening on a fixed udp port
for consistency. We can add more clients that connect to the same server that way.
On each device, we’re going to make 2
files in /etc/systemd/network
. These define the parameters for (1) systemd-networkd
to create the wireguard kernel device, and (2) how to route packets over it.
We are going to need public and private keys. Each wireguard endpoint will need a public key to decrypt data, and a private key to encrypt data. Because we need data to flow both ways, we therefore need 2 public keys and 2 private keys. We could absolutely use the same keys for data flowing both ways, but, keys are cheap and more keys create more complexity, so it’s good practice to make additional keys for each client.
You’ll have to do the following step therefore twice:
# generate private key 1 $ wg genkey
XOSnurQik@Yqyo$V6vEuzd%hvZwdMi0xRyfYqjPtzhw=
# generate pubkey 1 $ echo
XOSnurQik@Yqyo$V6vEuzd%hvZwdMi0xRyfYqjPtzhw= | wg pubkey
ybiHD3h!rW5Zps476*iH/CJgORrGJra^ilud6NDpxEc=
(Don’t worry, these are fake keys)
Now we have 2 keys. The first one is the private
key and the second one is the public
key. We’re going to use the private
key on one side of the tunnel, and the public
key on the other
side of the tunnel.
Do this again to make private key 2
and public key 2
.
Now that we have 2 key pairs, we’re going to embed them in the systemd-networkd configuration files. First, here’s the server configuration file /etc/systemd/network/wg0.netdev
:
[NetDev]
Name=wg0
Kind=wireguard
Description=Wireguard link
[WireGuard]
PrivateKey=<private_key_1>
ListenPort=49253
[WireGuardPeer]
PublicKey=<public_key_2>
AllowedIPs=0.0.0.0/0
AllowedIPs=::/0
PersistentKeepalive = 25
Note, this is the server, and it allows any IP address to connect from anywhere. Not that that is unsafe, because wireguard will not respond to any packet that has been encrypted with the correct public key, and that one will live only on that client. The port number can be anything, of course, I picked a random value here for this example.
Make sure to substitute the <private_key_1>
and <public_key_2>
text with the actual content of the keys you generated earlier (ending with the =
character). No need for "
characters around it.
This netdev
file contains enough information for systemd-networkd
to initialize wiregaurd in the kernel, setup the keys and make wireguard listen for incoming packets from a client that has the proper keys.
To properly route packages through the tunnel, the server also needs routing and IP information, and that it gets from the /etc/systemd/network/wg0.network
file:
[Match]
Name = wg0
[Network]
Address = 10.0.0.1/32
[Route]
Destination = 10.0.0.2
To put it simple: This just configures the server to connect each (1) peer to be at 10.0.0.2 while the server itself is at 10.0.0.1. You should really pick a private IP range for a private tunnel like this, but you can obviously ignore that if you know what you’re doing.
On the client side, things are about as complex.
/etc/systemd/network/wg0.netdev
contains:
[NetDev]
Name=wg0
Kind=wireguard
Description=Wireguard link
[WireGuard]
PrivateKey=<private_key_2>
# on the client, listenport is irrelevant
ListenPort=54918
[WireGuardPeer]
PublicKey=<public_key_1>
AllowedIPs=0.0.0.0/0
AllowedIPs=::/0
Endpoint=<server_static_ip:port>
PersistentKeepalive = 25
Note here again the 2 keys - they are the inverse from the server netdev
file. The most important entry in this file is the Endpoint
entry which should list your server IP address and server listening port. If those do not match, your client will never be able to connect to the server. The ListenPort on the client isn’t used, since we have no client connect to this client. Same goes for the AllowedIPs
.
The network
file is similarly trivial as on the server:
/etc/systemd/network/wg0.network
:
[Match]
Name = wg0
[Network]
Address = 10.0.0.2/32
[Route]
Destination = 10.0.0.1
And that’s all there is to it.
Starting it up
The only thing needed now is to systemctl restart systemd-networkd
(Edit: On both machines, of course!). Nothing else is needed, everything will be taken care of. You can inspect the tunnel with sudo wg
on both ends to verify keys and ports are setup and see client status.
You can certainly fancy things up from these “starter” wireguard files and add things like DNS, default routing and more. Having just a simple point to point tunnel to start can really help to get your VPN setup quickly and avoid dealing with the complexities of other VPN sulutions.
One really attractive property of wireguard devices is that they don’t depend on any running userspace software. You can restart systemd-networkd or even just shut it down, and the tunnel will remain operational. If the endpoint goes offline for a bit, everything will reconnect without user interaction. I’ve wrestled in the past with SSH and openvpn and nothing really seems as simple and clean as wg in combination with systemd-networkd here. Yes I know wg-quick works too, but that doesn’t do anything at boot unless you make units for it, which are just as long as these 4 files, and hey, we’re already likely running systemd-networkd on the systems, especially in a cloud setting.
Feel free to post additional suggestions and ideas! Hope you enjoyed it.
PS: Clearlinux carries the wireguard kernel module by default. This may not be the case in other OSs.