These are the configuration files I ended up using for setting up a Wireguard-based anonymizing VPN on Debian Buster (the current stable), formatted as a shell script. It requires the Wireguard kernel module and userland tools from backports:
apt-get install -t buster-backports wireguard-tools
The idea here is to set up a network namespace containing the configuration for the wireguard interface wg0. Any program which wants to use the Wireguard interface needs to be run in that namespace. For example,
ip netns exec wireguard ping google.com. All sockets opened by that program and its children will go out over the tunnel, including DNS resolution, so they won't be able to interact in any way with the local network. Conversely, other processes (which were not started with ip-netns) will continue to use the original routing tables and DNS configuration, so things like SSH, remote desktop, and NFS mounts continue to work fine over the local network.
Because the wireguard interface is in a namespace all on its own, there is no physical interface for it to actually send out packets on. I therefore depend on some peculiar behavior in which you can create the Wireguard interface in the init namespace and then move it to another namespace, and it will still open its internal UDP socket in the init namespace.
I use resolv.conf instead of a resolver daemon and ifupdown to manage my network interfaces.
The integration with ifupdown (so that the wireguard interface comes up when eno1 does) is tricky because it can't really bring up an interface inside a network namespace. My approach is to add post-up and pre-down hooks to my eno1 interface definition which bring wireguard up and down with the physical interface. Alternatively, it would be possible to put all those hooks in a separate namespace-only interface file, bind-mount over the ifstate information, and then invoke ifup inside the namespace. Unfortunately, I think that's a setup better suited to veth tunnels where you have a corresponding
manual interface on the "outside" where you can put the hooks to do the bind-mounting/ifup switcheroo. In that case, you could simply call ifup/ifdown on the outside interface to (de)configure both ends of the tunnel. In our case, however, we have no outside interface that we can actually configure, so everything has to be tied to eno1 anyway, and we might as well put the wireguard setup hooks there directly.
I use ufw as my netfilter frontend, but as far as I can tell this isn't going to be an option for managing a separate set of firewall rules that apply only inside the namespace. netns doesn't do anything with ufw's rules files, and I don't really want to maintain my own script that does this manually and flushes out its chains inside the namespace. This is not really a problem for me because the namespace provides sufficient isolation and the usual firewall rules in effect on the "outside" namespace are not applied on wg0. That latter fact is due to one of the built-in ufw rules which uses conntrack to unconditionally allow packets returning on related/established connections. I also have a rule for allowing any outgoing connections. So Wireguard sends its handshake out through eno0 in a UDP packet, establishing a connection in conntrack, and as long as our
PersistentKeepalive is less than
$(sysctl -n net.netfilter.nf_conntrack_udp_timeout_stream) then any further communication through the tunnel is allowed.