Adding users to system groups on Ubuntu Core

This is basically a brain dump of my thoughts on managing system groups on Ubuntu Core systems, so it doesn’t get lost if I close the tabs.

Currently it is not possible to add users to system groups on Ubuntu Core systems. There are a number of use cases where it would be nice to change this:

  1. The default configuration of tools like sudo and polkitd use membership of the sudo group to identify administrator users. Currently snap create-user works around this by adding configuration to /etc/sudoers.d, but that only helps sudo.
  2. Non-root access to the docker daemon requires membership of the docker group (core20 bug #72).
  3. Membership of the dialout group gives access to serial ports (ref).
  4. Membership of the kvm group gives access to the /dev/kvm device for hardware virtualisation access.

The Status Quo

Currently Ubuntu Core desktops configure the nameservice switch to consult two user/group databases:

passwd:         files extrausers
group:          files extrausers
shadow:         files extrausers
gshadow:        files

The backends are consulted in the order listed in the configuration file. The files NSS backend reads the databases stored in /etc, which are read only on Ubuntu Core systems.

The extrausers backend is similar but reads databases from /var/lib/extrausers, which is writable on Ubuntu Core. The extrausers backend includes a check to make sure IDs are >= 500, on the basis that it should not be used to define system users and groups

Some of the tools from shadow-utils have been patched to allow managing users and groups in the /var/lib/extrausers, but it is a bit spotty.

The membership of of a group is declared in its group database entry. This means that membership of groups declared in /etc/group is effectively fixed.

core20 PR #82 was an attempt to fix the docker group by moving it to the extrausers database, but was ineffective because the IDs are less than 500. Further more, removing users and groups from the /etc databases is problematic if we want Ubuntu Core devices to be able to migrate forward to new UC releases: there might be files on disk using the removed uids/gids.

A Possible Solution

Glibc 2.24 introduced a new group merging feature:

Through the use of [SUCCESS=merge] directive, this allows the results of two or more group backends to be merged to produce a getgrnam or getgrgid result. The main use case for this was to allow extending system groups via LDAP, but it has also been used by systemd-homed to include group memberships in a user record.

It looks like it would be fairly easy to integrate into an Ubuntu Core system:

  1. Update the /etc/nsswitch.conf configuration for the group database:
    group:          files [SUCCESS=merge] extrausers
  2. Patch nss-extrausers to not ignore low ID groups.
  3. Patch shadow-utils to allow modifying of system group membership by creating a new record in /var/lib/extrausers/group with the same name and ID as found in /etc/group.

The main challenges are:

  1. Ubuntu 16.04 shipped with glibc 2.23. If we want this to work on Ubuntu Core 16 systems, then the [SUCCESS=merge] feature would need to be back-ported.
  2. libnss-extrausers was orphaned upstream in September 2016. If this forms an important part of an Ubuntu Core system and we need to make changes, it would be worth addressing the maintenance question. It’s source code repo did not get migrated with Debian’s move to Gitlab, but an archive can be found here.

I had a go at implementing this in a test image:

  1. Relax libnss-extrausers’ minimum group ID restriction:
  2. Create a base snap that includes the [SUCCESS=merge] directive for the group line of its /etc/nsswitch.conf file.
  3. Built an image using that base snap as the boot file system.

After booting the system, I created a user and edited /var/lib/extrausers.conf to add myself to the sudo group:

$ grep sudo /etc/group
$ cat /var/lib/extrausers/passwd
james:x:1000:1000:James Henstridge,,,:/home/james:/bin/bash
$ cat /var/lib/extrausers/group

Asking the nameservice switch for the sudo group merges the two entries (note that it has picked the password field from the /etc/group database):

$ getent group sudo
$ id james
uid=1000(james) gid=1000(james) groups=1000(james),27(sudo)

This does not extend to snaps running with different bases:

$ snap run --shell network-manager.nmcli
james@ubuntu:/home/james$ getent group sudo
james@ubuntu:/home/james$ id james
uid=1000(james) gid=1000(james) groups=1000(james)

This is due to (a) using an unpatched libnss-extrausers, and (b) snap-confine bind mounting the base’s /etc/nsswitch.conf, losing the [SUCCESS=merge] directive.