💾 Archived View for axionfield.space › gemlog › 20220524-isolate-yourself.gmi captured on 2024-02-05 at 09:37:37. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

Isolate yourself

I use my Linux laptop at work. Because that's what it is. I don't want to use a

crap company laptop loaded with malware, sorry, security mesures all day. My

laptop is configured to always connect to my home network through Wireguard when

not directly connected to my internal network. This works fine. However, at work

I faced 2 problems:

- The corporate network does not allow to connect to Wireguard.

- I still need to use the internal network for very specific tasks (accessing

internal sources repos etc).

The first point is addressed by connecting to the corporate guest wifi network.

This allows basically any protocol, but is heavily (read stupidely) restricted.

But this is not a problem, as everything goes into the Wireguard tunnel. The

second point is addressed by using the ethernet connection.

But there's obviously a problem when both are connected at the same time. I

could have fixed that by banging my head trying to find a network routing policy

that would make it possible to bypass Wireguard for certain networks, but

network change, and all in all the concept of bypassing my catch-all wireguard

tunnel is not something I want to do anyway.

Network namespaces

Enters Linux Network Namespace (or netns for short). This basically allows to

create multiple isolated network stacks, put interfaces into them, and have them

completely isolated from the rest of the network. You can then explicitely tell

an application to run in a particular netns. Since I use a USB-C hub with an

ethenet adapter, I can easily dedicate it to a company netns then run a browser

(or whatever) in that namespace when I need to, leaving my nice wireguard setup

alone.

The first thing to do is to create a netns

$ sudo ip netns add evilcorp
$ ip netns list
evilcorp

Then we can assign the network interface(s) to that netns.

$ sudo ip link set enp0s20f0u3u1 netns evilcorp

This will move the interface enp0s20f0u3u1 to the netns we just created. You can

see that if you run ip link, this interface is not there anymore. Instead it

is in our evilcorp namespace:

$ sudo ip netns exec evilcorp ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
	link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
10: enp0s20f0u3u1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
	link/ether de:ed:de:ad:ee:ff brd ff:ff:ff:ff:ff:ff

We now need to get connectivity here. While I would have loved to have native

support for network namespaces in systemd-network, it's not the case. So I will

use good old dhclient:

$ sudo ip netns exec evilcorp dhclient

You should get an IP address and everything. You can then run applications in

that namespace with the same exec method. For instance:

$ sudo ip netns exec evilcorp nslookup internal-service.local

But here, we are running this as root. If you want to run a normal application

as your current user, you will need to get a bit creative:

$ sudo -E ip netns exec evilcorp sudo -E -u "$USER" zsh -c 'flatpak run org.mozilla.firefox'

You still need sudo privileges, but the command you invoke in your shell will

inherit all of your environment, and everything should work as if you ran it

yourself.

Look mom, no hands

Ok this is all nice and dandy, but it would be even better to not have to do

anything. So here's how to automate it.

First, create a udev rule in /etc/udev/rules.d/99-autonetns.rules:

SUBSYSTEM=="net", \
	ACTION=="add", \
	NAME=="enp0s20f0u3u1", \
	RUN+="/usr/bin/systemctl --no-block start netns-bind@$name.service"

Just replace the interface name in NAME by yours. The systemd people around

you may argue that I could use ENV{SYSTEMD_WANTS}, but I just can't get it to

work.

Then reload the rules:

$ sudo udevadm control --reload-rules

This will enable an instance of netns-bind-evilcorp.service, passing in the interface

name. Now let's create that service:

[Unit]
Description=Configure the given interface to the evilcorp netns
Requires=ntns@evilcorp.service
After=ntns@evilcorp.service

[Service]
Type=oneshot
ExecStartPre=ip netns exec evilcorp dhclient -r -lf /var/lib/dhclient/dhclient.evilcorp.leases
ExecStart=ip link set %I netns evilcorp
ExecStartPost=ip netns exec evilcorp dhclient -lf /var/lib/dhclient/dhclient.evilcorp.leases

If you don't want dhclient to modify your resolve.conf, which I don't, because

I'm using systemd-resolved, you can add a file in /etc/dhclient-enter-hooks to

override the function make_resolv_conf():

#/bin/sh
make_resolv_conf() {
	echo "no touching"
}

You can see that this service depends on netns@.service, which is just used to

create the namespace:

[Unit]
Description=Configure the %I network namespace

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=ip netns add evilcorp
ExecStop=ip netns delete evilcorp

[Install]
WantedBy=default.target

Now, let's reload systemd daemons:

$ systemctl daemon-reload

Plug in your ethernet adapter, and you should be able to see the nic with an

address assisgned in the network namespace.

Finally, let's add a little launcher (adapt to your liking), so you can easily

execute anything in the network namespace:

#!/bin/bash

case $1 in
	firefox) args="flatpak run org.mozilla.firefox" ;;
	chrome|chromium) args="flatpak run org.chromium.Chromium" ;;
	*) args="$(printf "%s " "$@")" ;;
esac

sudo -E ip netns exec evilcorp sudo -E -u "$USER" bash -c -- "$args"