💾 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
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
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.
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.
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"