Unifying User Identity Across Snaps and ROCKs

With the recent introduction of non-root default OCI users for ROCKs, the existing concept of System Usernames for Snaps has been extended and re-designed.

…nothing is lost, nothing is created, everything is transformed.

Why do you need Shared Users for ROCKs?

Whether it is a container application requirement or not, best practices dictate that containers should, whenever possible, be started with a non-root user. Not only does this prevent malicious code from attacking the host via privilege escalation, but it also ensures a more controlled process execution and resource access from within the container.

But then, why “Shared Users” and not simply any non-root user?

Short answer: consistency, consistency, consistency…

True, Rockcraft could (and it will) support building ROCKs with a default OCI user from outside the Shared Users range (aka “private” users). However, outside of containers, software applications (including Snaps) tend to adopt specific users and groups to use as security mechanisms that grant access to specific software and system resources. This is one of the main motivators behind the creation of System Usernames for Snaps, and it also applies to ROCKS - the fact that these shared users are globally known and share the same properties (like UID and GID), makes it possible to safely exchange information between the system and the container.

For example, let’s say you have multiple Snaps and ROCKs installed and running on a system. If they were all configured with the same shared user, then you are guaranteed that any exchange of information between those processes will work, because the corresponding users will virtually be configured the same way and with the same properties. This might not be true if you use privately-defined users, since there won’t be any guarantee that a ROCK’s user foo will have the same UID, group and GID as user foo from another ROCK or Snap.

Here’s a practical and simple example that uses local volumes to illustrate the aforementioned scenario (the same would apply to network-mounted volumes):

$ # 1: let's take a random Docker image with a non-root user
$ docker run --rm -v foo_vol:/tmp -u nginx nginx sh -c "id; echo foo > /tmp/one; chown nginx:nginx /tmp/one"
uid=101(nginx) gid=101(nginx) groups=101(nginx)
$ # 2: now consider a different image with the same username
$ echo -e "FROM ubuntu\nRUN adduser nginx" | docker build -q -t custom-ubuntu-nginx --load -
$ docker run --rm -v foo_vol:/tmp -u nginx custom-ubuntu-nginx sh -c "id; ls -l /tmp; echo bar >> /tmp/one"
uid=1000(nginx) gid=1000(nginx) groups=1000(nginx)
total 4
-rw-r--r-- 1 101 101 4 Jun 22 07:59 one
sh: 1: cannot create /tmp/one: Permission denied

As expected, despite apparently having the same nginx username, these two images have been configured differently and their nginx user has different properties, so a conflict arises when trying to access the same file in a shared space.

However, if using shared users in Rockcraft, we are guaranteed to have exactly the same container users with the same properties.

$ docker run --rm -v foo_vol:/tmp my-rock exec sh -c "id; echo bar > /tmp/two"
uid=584792(_daemon_) gid=584792(_daemon_) groups=584792(_daemon_)
$ docker run --rm -v foo_vol:/tmp my-other-rock exec sh -c "id; echo bar > /tmp/two; ls -l /tmp/two"
uid=584792(_daemon_) gid=584792(_daemon_) groups=584792(_daemon_)
-rw-r--r-- 1 _daemon_ _daemon_ 4 Jun 22 08:05 /tmp/two

What has NOT changed?

The existing UID range (584788-585287) for system usernames in Snaps came as a result of a thorough investigation that took into account possible user conflicts with existing software and reserved user spaces. For ROCKs, we will reuse the same range and list of supported usernames. Out of the 500 allowed usernames in this range, four shared users have already been taken - snap_daemon, snap_microk8s, snap_aziotedge and snap_aziotdu - and thus the currently supported OCI users for ROCKs will start from the next available UID on that range, 584792.

What has changed?


You might find the terms “system” and “shared” users being used interchangeably. This is because unlike for Snaps, we don’t control the container runtime for ROCKs and thus we can’t enforce the existence of said usernames at the system level.

Naming convention

Up to now, the supported Snap system usernames followed a convention where snap_ always preceded the actual username, i.e. snap_<username>. Given that we are expanding the concept of Shared Users to other artefacts (like ROCKs), this prefix is no longer representative of its use and we’ve instead opted for a new naming convention that uses the underscore (“_”) character both as a prefix and suffix of the username, i.e. _<username>_. The new convention was also carefully chosen with the intent of avoiding clashes with well-known system usernames whilst providing a short yet alluding syntax to the username’s reserved nature.

With that, a new shared user _daemon_ has been added to the supported list of usernames, with the UID 584792. This is the only allowed shared user for ROCKs and will soon be also supported in Snaps.

What will happen to the existing “snap_”-prefixed Snap system usernames?

Since these four existing shared users are already being used in published Snaps, they will be kept on a legacy basis, but shall soon be discouraged from being used in new Snaps, in favour of the newly created _daemon_ and future shared users.

rockcraft.yaml’s syntax

The ROCK’s default OCI user is defined via a new top-level field run-user (as explained in the docs). If omitted, the ROCK’s default OCI user will be root. Here’s an example:

run-user: _daemon_

Great article! I think the first link is broken, gets me a 404:

here’s how it is https://canonical-rockcraft.readthedocs-hosted.com/en/latest/reference/rockcraft.yaml.html#run-user

here’s how it’s working https://canonical-rockcraft.readthedocs-hosted.com/en/latest/reference/rockcraft.yaml/#run-user

so rockcraft.yaml/ instead of rockcraft.yaml.html

Good catch @merkata. Thanks, I’ve updated it.

1 Like