Design
UPDATE 2019-04-11: also refer to shared-users, system-users and private-users
UPDATE 2019-09-13: update to reflect outcomes
This design has been discussed with Gustavo, the snapd team and the security team. Details remain and will be discussed in this topic.
Overall design
snapd will work in conjunction with the snap store to use a ‘global user/ID database’. This database will provide a predictable 1 to 1 mapping of numeric ID to name. When a snap is installed using one of these users/IDs, snapd will consult the global database and create a group with a name that matches the global name (prefixed with snap_
) and gid that matches the global user/ID. It then creates a user with a name that matches the global name (prefixed with snap_
) and uid that matches the global user/ID with the primary group set to the global name. In this manner, snaps may specify these usernames/groups in their yaml and use the uid/gid in the squashfs such that the uid/gids will resolve properly via getpwnam(), getpwuid(), getgrnam(), getgrgid(), etc both inside and outside the snap.
To avoid clashes with existing uids and gids, snapd will use high numbered users/IDs in the 32-bit range. Historically, early on 15-bit uids were supported and later the more common 16-bit uids. Linux and modern Unix systems now all support 32-bit uids with all but the most specialized filesystems (eg, cramfs) also supporting 32-bit uids by default (eg, ext2 and ext4 supports the non-default nouid32 option for interoperability with older (ancient) kernels). Notably, squashfs is confirmed to support 32-bit uids and gids.
To use, a snap optionally declares the username/groups it wants to use in the snap.yaml. Eg:
name: foo
apps:
...
system-usernames:
snap_foo: shared
snap_bar:
scope: shared
baz: external # not implemented
snap_norf: private # not implemented, 'snap_' requirement TBD
The store will issue a snap declaration assertion allowing the use of the name.
Upon installation, snapd will verify the snap declaration to see if the snap should be able to use the user/ID. If not, ignore. Otherwise snapd will first create the group (--extrausers
is used on Core only):
$ sudo groupadd [--extrausers] \
--system \
--gid <group/ID from global db> \
snap_<name from global db>
then create the user with that group:
$ sudo useradd [--extrausers] \
--system \
--home-dir /nonexistent --no-create-home \
--shell /bin/false \
--gid <user/ID from global db> --no-user-group \
--uid <user/ID from global db> \
snap_<name from global db>
then snapd will generate security policy that allows use of this username and group.
At this point there are predictable uids/gids on the system that can safely be used by the squashfs, however if the snap does getent passwd foo
it won’t be found because snap_foo
was created. snapd should create a mapping such that inside the snap, the user doesn’t need snap_
to be prefixed, but outside it does. While the implementation details are not finalized yet, in the core/base snap, ship a glibc NSS module and add it to /etc/nsswitch.conf (will need to bind mount this on classic). This module will appropriately map the getpwnam(), getpwuid(), etc calls correctly for the snap’s processes.
Locally unasserted snap installs won’t have a snap declaration assertion telling snapd what users/IDs to create/use. For these, snapd will consult the store’s ‘global user/ID database’ and grant the use. If the username doesn’t exist in the ‘global user/ID database’, the snapd will use a private user/ID range and will assign the username/group from this pool, locally (TBD).
Use case 1: System user and groups
This case is mostly handled by specifying ‘external’ scoping for the username. External scoping means that the username/group does not use the snap_
prefix. The snap declares what it wants to use and it can then chown, setuid, etc to the requested user/group. Eg, the lxd snap might have:
name: lxd
system-usernames:
- lxd: external
which will create the lxd
user. Administrators then must use sudo adduser <user> lxd
to grant access to the lxd socket to the <user>
. Importantly, external scoping is not implemented at this time because snapd cannot yet guarantee a predictable uid/gid is available for snapd (eg, lxd
may already be on the system) and therefore cannot be depended upon for use case 4 (chroot), below. External scoping may be implemented at a feature date. In the meantime, projects may request that snapd provide a shared-scope username (eg, snap_lxd
) and adjust their documentation accordingly.
Use case 2: Device access
For things not covered by udev and systemd automatically via shipped configuration, we can use setfacl to tag devices via udev rules. Eg, today serial devices are root:dialout 0660 and have no udev ACLs on them. Moving forward, we want a new backend that would be used by interfaces like the serial-port interface to specify that all serial-ports can be accessed via a particular group (eg, snap_serial-port
) and the backend would add a udev rule to setfacl the serial-port devices (or use the uaccess mechanism) to ‘snap_serial-port’ such that sudo adduser <user> snap_serial-port
would grant access to the serial ports for that user.
To be compatible with use case 4, this use case requires the group be maintained in the global user/ID database like this with ‘shared’ scoping (see below). Eg, have snap_serial-port
in the global user/ID database which a new interface backend will use to have snapd create the user when the interface is connected.
An example workaround based on the above that might inform the implementation is documented.
Use case 3: Opt-in per-snap users/groups
This case is completely handled when using ‘shared’ scoping which is when different snaps request to use the same username. It will use a username with the snap_
prefix. Eg:
system-usernames:
snap_foo: shared
Currently unimplemented, a future improvement will use ‘private’ scoping which allows a single snap to specify a username unique to the snap. Eg:
system-usernames:
XXX: private
It is likely that snap_
will be prefixed, but otherwise this needs design. To support squashfs files using predictable uids/gids, then this probably needs to be maintained in a private global user/ID list (to not expose private or brand snaps).
Use case 4: Chroot environments
This case is completely handled by the current design. The snap declares what it wants to use and the squashfs can reliably have files with uids/gids matching the requested global users/IDs.
Open questions
- While not discussed above, consider exposing usernames/groups from the host in the snap’s runtime (eg, the NSS module prefixes
classic_
to uids on the host). Consider how to handle exact overlaps (shouldclassic_
be prepended or not?), uids/gids that have different usernames/group names between the host and snap and usernames/group names that have different uids/gids between the host and snap
Components to modify
- snapd
- user backend for creating users/groups
- backend for setfacl/uaccess
- snap declaration verification
- support private user/ID ranges
- seccomp updates to allow using the uids/gids
- store
- easily view and update the global user/ID database (preseeded with archive grep of users and groups created in debs via adduser/addgroup/useradd/groupadd)
- easily update snap declarations
- pass global user/ID database to the review tools
- review tools
- warn if using uid/gid without snap decl (ie, standard human review, like with classic confinement)
- don’t complain if not -all-root anymore, but do complain if uids, gids are not in the declared list
- complain if use users/IDs from the private user/ID range
Phase 1
As an initial first phase, a single shared scope user will be created, snap_daemon
, whose uid/gid is hardcoded in snapd (ie no store integration) that any snap is free to use without a snap declaration. Eg:
system-usernames:
snap_daemon: shared
This will allow a snap to setuid, setguid, chown, etc to for privilege dropping and file ownership. The uid/gid will be consistent for all snaps and thus accommodate use case 2 and 4 immediately.