Title: WireGuard and Linux network namespaces
Author: Solène
Date: 02 July 2024
Tags: security vpn network linux
Description: In this article, you will learn how to establish a VPN in
a dedicated network namespace on Linux

# Introduction

This guide explains how to setup a WireGuard tunnel on Linux using a
dedicated network namespace so you can choose to run a program on the
VPN or over clearnet.

I have been able to figure the setup thanks to the following blog post,
I enhanced it a bit using scripts and sudo rules.
Mo Ismailzai's blog: Creating WireGuard jails with Linux network namespaces
# Explanations

By default, if you connect WireGuard tunnel, its "allowedIps" field
will be used as a route with a higher priority than your current
default route.  It is not always ideal to have everything routed
through a VPN, so you will create a dedicated network namespace that
uses the VPN as a default route, without affecting all other software.

Unfortunately, compared to OpenBSD rdomain (which provide the same
features in this situation), network namespaces are much more
complicated to deal with and requires root to run a program under a
namespace.

You will create a SAFE sudo rule to allow your user to run commands
under the new namespace, making it more practical for daily use.

# Setup

## VPN tunnel and namespace

You need a wg-quick compatible WireGuard configuration file, but do not
make it automatically used at boot.

Create a script (for root use only) with the following content, then
make it executable:

```shell
#!/bin/sh

# your VPN configuration file
CONFIG=/etc/wireguard/my-vpn.conf

# this directory is used to have a per netns resolver file
mkdir -p /etc/netns/vpn/

# cleanup any previous VPN in case you want to restart it
ip netns exec vpn ip l del tun0
ip netns del vpn

# information to reuse later
DNS=$(awk '/^DNS/ { print $3 }' $CONFIG)
IP=$(awk '/^Address/ { print $3 }' $CONFIG)

# the namespace will use the DNS defined in the VPN configuration file
echo "nameserver $DNS" > /etc/netns/vpn/resolv.conf

# now, it creates the namespace and configure it
ip netns add vpn
ip -n vpn link set lo up
ip link add tun0 type wireguard
ip link set tun0 netns vpn
ip netns exec vpn wg setconf tun0 <(wg-quick strip "$CONFIG")
ip -n vpn a add "$IP" dev tun0
ip -n vpn link set tun0 up
ip -n vpn route add default dev tun0
ip -n vpn add

# extra check if you want to verify the DNS used and the public IP assigned
#ip netns exec vpn dig ifconfig.me
#ip netns exec vpn curl https://ifconfig.me
```

This script autoconfigure the network namespace and the VPN interface +
the DNS server to use.  There are extra checks at the end of the script
that you can uncomment if you want to take a look at the public IP and
DNS resolver used just after connection.

Running this script will make the netns "vpn" available for use.

The command to run a program under the namespace is `ip netns exec vpn
your command`, it can only be run as root.

## Sudo rule

Now you need a specific rule so you can use sudo to run a command in
vpn netns as your own user without having to log in as root.

Add this to your sudo configuration file, in my example I allow the
user `solene` to run commands as `solene` for the netns vpn:

```sudoers
solene ALL=(root) NOPASSWD: /usr/sbin/ip netns exec vpn /usr/bin/sudo -u solene -- *
```

When using this command line, you MUST use full paths exactly as in the
sudo configuration file, this is important otherwise it would allow you
to create a script called `ip` with whatever commands and run it as
root, while `/usr/sbin/ip` can not be spoofed by a local script in
$PATH.

If I want a shell session with the VPN, I can run the following
command:

```
sudo /usr/sbin/ip netns exec vpn /usr/bin/sudo -u solene -- bash
```

This runs bash under the netns vpn, so any command I'm running from it
will be using the VPN.

# Limitations

It is not a real limitation, but you may be caught by it, if you make a
program listening on localhost in the netns vpn, you can only connect
to it from another program in the same namespace.  There are methods to
connect two namespaces, but I do not plan to cover it, if you need to
search about this setup, it can be done using socat (this is explained
in the blog post linked earlier) or a local bridge interface.

# Conclusion

Network namespaces are a cool feature on Linux, but it is overly
complicated in my opinion, unfortunately I have to deal with it, but at
least it is working fine in practice.