Unprivileged LXC-containers on Debian Stretch/Buster
In an ideal (from a capitalist point of view) world I'd come to work every morning and spend eight hours hunched over my desk churning out mails, reports, documents, code, case files – whatever.
Luckily, that's not the case. There are times where I'll need a break or a moment to clear my head when I hit a mental roadblock. In those moments I will definitely check on my latest order on Amazon, browse Twitter for a little bit or do other stuff which could be considered “private usage” of my work-issued computer system.
I run Debian on my work system, which means that (in combination with things such as NoScript) the chances of me getting hit by some run-of-the-mill drive-by infection is pretty low, and the information and files I work with should be relatively safe from harm.
Nonetheless, I like compartmentalization between my private information and work stuff, and I've been achieving that by installing a separate instance of Debian in Virtualbox.
In all honesty, while it was a working solution, it was a shitty one at that. I have a comparatively powerful computer system at work, which is unfortunately crammed into a tiny case with insufficient cooling, which means that if I did anything inside the virtual machine, the sweet sound of jet engines filled my office and annoyed my coworkers.
In addition to that, without a dedicated GPU to pass through to the virtual machine, the performance and the user experience wasn't exactly what I would call great, quite sluggish and everything but smooth. Eventually I halfheartedly looked for an alternative to replace this solution and ended up finding it in a place that I normally avoid – containers.
I dislike container-technology a lot, first and foremost Docker. While Docker does have a lot of technical short-comings, in all areas from performance to especially security (and some of them may well come from my very limited exposure to it) it's the culture surrounding it that I truly dislike.
We treat Docker as something – a tool for stable, operational use – it hasn't been designed to be, which is a tool to ease development and efforts and to simplify working together on something that can then eventually turned into an outcome that can be deployed in a proper fashion. We actively encourage people to not understand what they are running, to sacrifice the admittedly time-consuming process of understanding your infrastructure for a simply “Just re-deploy it until it works, lol!”.
Before anyone lynches me: I know that this is a complex, highly-nuanced topic / question, and this isn't the place to discuss it. The point I was trying to make is: I rarely look at containers much.
But,as it turned out, containers (or more specifically, LXC) are the ideal solution to my problem:
- Resource-friendly with little overhead
- Compartmentalization (and added security, since contrary to Docker, I can run unprivileged containers)
- Necessary tools built into the Linux kernel
It's important to stay aware that this isn't foremost about security. If you want that, look at proper virtualization and/or things like Qubes. This is just an attempt to keep my dumb ass from pasting my PGP-passphrase into Twitter or accidentally posting a horribly offensive meme into my work chat.
So after a little bit of research, I went to work. Because I encountered an unexpected amount of pitfalls, I decided to write my experiences down for you, so you can avoid some of them.
This blog post will consist of two parts. In the first one – this – I'll walk through getting a Debian machine ready to run unprivileged containers. In the second part I'll talk, or write, about running graphical applications in containers.
As per usual with these sort of posts I'm definitely and absolutely not responsible for any form of misconfiguration or data loss. This worked for me, and your mileage may vary greatly. Please don't firebomb my apartment.
The tools lxc relies on, such as cgroups and namespaces, are already there on your machine, so there's not much left in terms of requirements to install:
sudo apt-get install lxc bridge-utils
Since you are most likely going to want some form of connectivity, your containers are going to need a network interface. Since the containers don't provide any services that need to be accessible from the outside, a bridge interface that NATs connections through your physical interface should be sufficient. Add the following to
iface lxc-nat inet static
up iptables -t nat -A POSTROUTING -o enp0s25 -j MASQUERADE
Obviously, you should replace the name of the interface with the name of the interface you want to NAT > traffic through.
To enable forwarding of IPv4-traffic uncomment the line
/etc/sysctl.conf. To activate this without rebooting, run
sudo sysctl -w
sudo ifup lxc-nat the output of
ip addr should look like this:
6: lxc-nat: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether 46:a1:37:87:f2:18 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 brd 10.0.0.255 scope global lxc-nat
valid_lft forever preferred_lft forever
inet6 fe80::44a1:37ff:fe87:f218/64 scope link
valid_lft forever preferred_lft forever
You could also configure this, since we're talking about desktop-use here, through
network-manager, however I found the bridges configured through it to be, for whatever reason, error-prone and unstable, so I'm going to stick to the manual route.
That was everything that needed to be done beforehand, now it's time to actually get started with LXC. Firstly you need to make sure that
/etc/subgid are filled with user- and group-IDs your user is allowed to impersonate. This should be already the case, nonetheless check that both files
look like this:
By default, unprivileged users don't have access to network devices through LXC, there's a quota set by
lxc-user-nic – which you can set by editing
yourusername veth lxc-nat 10
Now you need to supply LXC with a default set of configuration options it should apply to all newly generated containers, in
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxc-nat
lxc.network.ipv4.gateway = 10.0.0.1
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
Don't forget to set up a DHCP-server for your containers or to configure a static IP-address in
~/.local/share/lxc/<containername>/config – otherwise your container won't even start:
lxc.network.ipv4.address = 10.0.0.2
And that's it! You're done! You can now run unprivil.. except you can't, yet. This is the part where I feel my blood pressure rising because I'm now going to tell you what you have to do in order to avoid the errors I got. I won't explain much, but I'll link the explanation for the avid reader to read for him- or herself.
sudo sh -c 'echo "kernel.unprivileged_userns_clone=1" > /etc/sysctl.d/80-lxc-userns.conf' && systemcl --system(Source
sudo apt-get install acl && cd ~/ && setfacl -m u:100000:x . .local/ && setfacl -m u:100000:x . .local/share(Source)
echo 'lxc.environment = PATH' > ~/.config/lxc/default.conf(I don't have a source for that yet, but for some odd reason the $PATH of root in the container is broken)
While this sounds Windows-ish to most people, now is a good time to reboot your computer in order to apply those kernel patches that have been waiting to come to play for months now .. and to make sure that all the black magic behind the curtains, with groups and users, is working as intended. You can skip this, but the chances of you getting to see seemingly senseless errors increases tenfold. Ye be warned!
After all this is set and done, you are finally ready to create your first unprivileged container:
lxc-create -t download -n teststesttestseverywhere
Wait for the download to finish, don't forget to configure an IP-address and run:
lxc-start -n teststesttesteverywhere
And that's it, you're good to go. You can now run whatever software you want containerized, even GUI-programs if you access them over
ssh, bundled with X-Forwarding. There is a way to run GUI-applications natively from inside a container, but that's a topic with a whole subset of bugs by itself & a topic for another post.