Some early notes on WireGuard

November 5, 2017

WireGuard is a new(ish) secure IP tunnel system, currently only for Linux. Yesterday I wrote about why I've switched over to it; today is for some early notes on things about it that I've run into, especially in ways it's different from my previous IKE IPSec plus GRE setup.

For the most part, my WireGuard configuration is basically their simple example configuration, but with a single peer. The important bit I had to get my head around is the AllowedIPs setting, which controls which traffic is allowed to flow inside the secure tunnel. My home machine may receive traffic to its 'inside' IP from anywhere, so it must have an AllowedIPs of 0.0.0.0/0. My work machine, as my WireGuard touchdown point, should only see traffic from my home machine and that traffic should only be coming from my home machine's inside IP; it has an AllowedIPs of just that IP address.

(I did specify Endpoint on my work machine, which I think means that my work machine, the 'server', can initiate the initial connection handshake if necessary if it has packets to send to my home machine and my home machine hasn't already got things going.)

Unlike IKE (and GRE), WireGuard itself has no way to restrict where traffic from a particular peer is allowed to originate; peers are authenticated (and restricted) purely by their public key, and this public key will be accepted from any IP address that can talk to you. In fact, WireGuard will happily update its idea of where a peer is if you send it appropriate traffic. If you want this sort of IP-based access restriction, you will have to add it yourself by putting both ends of the WireGuard tunnel on fixed UDP port numbers and then using iptables (or nftables) to restrict who can send IP packets to them.

(WireGuard packets are UDP, so an attacker who's managed to get a copy of your keys could forge the IP origin on traffic they send. However, an active connection requires an initial handshake to negotiate symmetric keys, so the attacker can't get anywhere just with the ability to send packets but not receive replies.)

Unlike IKE (again), WireGuard has no user-visible concept of a connection being 'up' (with encryption successfully negotiated with the remote end) or 'down'; a WireGuard network device is always up, although it may or may not pass traffic. This means that you don't have a chance to run scripts when the connection comes up or goes down, for example to establish or withdraw routes through the device. In the past I was tearing down my GRE tunnel on IPSec failure, which had security implications, but with WireGuard the tunnel and its routes stay up all the time and I'll have to manually tear it down at home if the other end breaks and I need things to still mostly work. This is more secure even if it's potentially less convenient.

(If I cared enough I could set up connection monitoring that automatically tore down the routes if the work end of the tunnel couldn't be pinged for long enough.)

WireGuard lets you set the firewall mark (fwmark) for outgoing encrypted packets, which turned out to be necessary for me for solving what I'll call the recursive VPN problem, where your remote VPN touchdown point is itself on a subnet that you want to route over the VPN. In fact my case is extra-tricky, because I want non-WireGuard IP traffic to my VPN touchdown address to flow over the WireGuard tunnel. What I did was set a fwmark in WireGuard and then used policy-based routing to force traffic with that mark to bypass the tunnel:

ip route add default dev ppp0 table 11
[...]

# Force Wireguard marked packets out ppp0, no matter what.
ip rule add fwmark 0x5151 iif lo priority 4999 table 11

(The fwmark value is arbitrary.)

This is much less magic than the IPSec equivalent, and as a result I have more confidence that it won't suffer from occasional bugs.

The fwmark stuff is especially important (and useful) because the current WireGuard software is missing the ability to bind outgoing packets to a specific IP address on a multi-address host. As far as I can see, outgoing packets may someday be sent out from whatever IP address WireGuard finds convenient, instead of the IP alias that you've designated as the VPN touchdown. WireGuard on the other end will then explicitly update its idea of the peer address, even if it was initially configured with another one. I may be missing something here, and I should ask the WireGuard people about this; the might accept it as a feature request (or a bug). I'm not sure if you can fix it with policy based routing cleverness, but you might be able to.

The best way to understand WireGuard configuration files is to think of them as interface-specific configuration files; I sort of missed this initially. Since you apply them with 'wg setconf <interface> <file>', they can only include a single interface's parameters. Somewhat inconveniently, they include secret information (your private key) and so must be unreadable. Similarly, it's a bit inconvenient that checking connection status with wg show requires root privileges, although you can work around that with sudo.


Comments on this page:

The fwmark stuff is especially important (and useful) because the current WireGuard software is missing the ability to bind outgoing packets to a specific IP address on a multi-address host. [...] I may be missing something here, and I should ask the WireGuard people about this.

WireGuard will actually reply to packets using as its source address and source interface the destination address and destination interface of previous peer incoming packets. In most cases for most people, this alleviates the need to bind(2) to a particular IP address. If a packet is going out for the first time, then it simply uses the default IP address for that route, which can be modified using ordinary routing table stuff.

Somewhat inconveniently, they include secret information (your private key) and so must be unreadable.

You don't have to include it in the file if you don't want. You could leave out the private key, and after running setconf, simply run `wg set wg0 private-key <(pass WireGuard/keys/wg0)`, if you're keeping your private keys in pass or something. You can of course automate this using wg-quick(8)'s PostUp hook (see the %i substitution).

Similarly, it's a bit inconvenient that checking connection status with wg show requires root privileges, although you can work around that with sudo.

Actually, wg(8) only requires CAP_NET_ADMIN, like the rest of ip(8). ip(8) only requires capabilities for setting, not getting, but as this is security-related software, I'd like to avoid all leaks and helpful things, so requiring the cap for getting and setting seems most sensible. It also avoids having to deal with giving back different information (such as the private key) based on what the permissions are, which is behavior that seems a bit too invisible to be happy with.

If you have more questions, don't hesitate to poke me. I'm zx2c4 on Freenode in #wireguard.

Written on 05 November 2017.
« Why I've switched from GRE-over-IPSec to using WireGuard
How collections.defaultdict is good for your memory usage »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Sun Nov 5 02:24:37 2017
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.