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
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.
To start, we need to have 2 devices. We’re going to mark one as a
server and one as a
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
[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
<public_key_2> text with the actual content of the keys you generated earlier (ending with the
= character). No need for
" characters around it.
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
[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.
[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
network file is similarly trivial as on the server:
[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.