How to create an unprivileged container in incus?

I cannot find any docs which specifies the steps to create unprivileged container. Sorry if this was asked before.

all containers are unprivileged by default

2 Likes

It runs as root tho? I tried alpine 3.22 and it runs as root.

You see a root user from within your container, but in no way is it the same as your host root user. That’s the whole point of unprivileged containers.

1 Like

I see. Is it advised to also create a normal user inside container or keeping using root user?

It really depends on your use case. In most of my infrastructure, I have no such need.

It would certainly make it harder to escape container, incase of a process running root has a RCE exploit? Need an LPE and LXC escape instead of just LXC escape.

LXC escapes in unprivileged containers are basically LPE kernel vulnerabilities. Unprivileged root does not run as root on the host.

Ok, let’s go.

I create an Incus container using the image images:ubuntu/24.04/cloud. The last part, the /cloud has some significance, it means that the container image has support for cloud-init. This means that your instance will be able to receive configuration through an Incus profile, should you provide one. In what we are showing here, that’s not directly relevant. What’s relevant, is that the default cloud-init instructions for those images, will create a non-root account for you. And it’s handy. For Ubuntu images, the non-root account is ubuntu. For Alpine images, it’s alpine. And so on.

These non-root accounts are locked, i.e. there is no default password. How do you access?

$ incus launch images:ubuntu/24.04/cloud mycontainer
Launching mycontainer
$ incus exec mycontainer -- su -l ubuntu
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@mycontainer:~$ 

Is anything that has been said above directly relevant to your question? Only slightly. You would prefer to use the non-root account in a container, if you do that as part of your policy when you administer Linux systems. It’s a good practice, to go around by default as non-root and when needed, do the sudo. I applaud that.

But let’s really figure out whether the container that we just created, is really an unprivileged container. That root in the container, is it really root according to the host?

An Incus container is made of a process tree. This process tree is separated from the rest of the processes of your host using security features of the Linux kernel, including namespaces, cgroups, and more. The Process ID of a root process in the container is not a root process on the host. Let’s see it in practice. We run a process in the container and then view it from the host.

$ incus shell mycontainer 
root@mycontainer:~# vi FINDME
...keep it running...

And on the host with run ps aux --forest to show the relevant processes (search for FINDME). I simplified a bit the output. But you can see that the User ID for the processes in the container is 1000000 and not something that is used by some other process on the host.

$ ps aux --forest
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
...
root      427661  0.0  0.0  79280  7536 ?        S    00:05   0:00  \_ /opt/incus/bin/incusd forkexec mycontainer 0 0 0 -- env -- cmd su -l
1000000   427664  0.0  0.0  12408  2816 pts/1    Ss   00:05   0:00      \_ su -l
1000000   427681  0.0  0.0  10204  3072 pts/1    S    00:05   0:00          \_ -bash
1000000   428024  0.3  0.0  27556 10368 pts/1    Sl+  00:10   0:00              \_ vi FINDME
...

In the unprivileged containers, by default the containers have a User ID of 1000000 or something similar. Different containers reuse the same User ID which is fine for most cases. If you want different random User ID for each container, you can do so using the idmap key as described at Idmaps for user namespace - Incus documentation

By reading the documentation on idmaps, you learn that the User ID of 1000000 is the start of a range of UIDs that will be used in the container. The User ID 1000000 has offset 0, which means that processes with that User ID are considered as root processes in the context of the container. If you have a non-root account (with UID 1000) in the container and you are running some process, then from the point of view of the host, the UID will appear as 1000000+1000 = 1001000.

And here it is.

$ ps aux --forest
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
..
1000000   428773  0.0  0.0  12540  3072 pts/1    Ss   00:21   0:00      \_ su -l ubuntu
1001000   428789  0.0  0.0  10204  2944 pts/1    S    00:21   0:00          \_ -bash
1001000   428802  0.0  0.0  27556 10496 pts/1    Sl+  00:21   0:00              \_ vi FINDME
...
1 Like