@niemeyer - I took a look at this and I think this needs a good bit of design consideration (sorry for the long reply, but it is necessary).
snap calling update-alternatives directly
First off, I looked at the accesses required if a snap is allowed to run update-alternatives
directly and noticed that for --install/–remove/–query/–display/–list without --altdir/–admindir, a snap running update-alternatives itself requires:
/{,usr/}bin/update-alternatives ixr,
# --install /usr/share/gnome-shell/theme/gdm3.css gdm3.css /snap/communitheme/current/share/gnome-shell/theme/communitheme/gnome-shell.css 15
# --remove gdm3.css /snap/communitheme/current/share/gnome-shell/theme/communitheme/gnome-shell.css
/etc/alternatives/gdm3.css{,.dpkg-tmp} rw,
/usr/share/gnome-shell/theme/gdm3.css{,.dpkg-tmp} rw,
/var/lib/dpkg/alternatives/gdm3.css{,.dpkg-tmp} rw,
/var/lib/dpkg/alternatives/ r,
/var/lib/dpkg/alternatives/* r,
/var/log/alternatives.log w,
Using --altdir /var/lib/snapd/alternatives/alt --admindir /var/lib/snapd/alternatives/admin
, then the snap would instead need:
/{,usr/}bin/update-alternatives ixr,
# --altdir /var/lib/snapd/alternatives/alt --admindir /var/lib/snapd/alternatives/admin --install /usr/share/gnome-shell/theme/gdm3.css gdm3.css /snap/communitheme/current/share/gnome-shell/theme/communitheme/gnome-shell.css 15
# --altdir /var/lib/snapd/alternatives/alt --admindir /var/lib/snapd/alternatives/admin --remove gdm3.css /snap/communitheme/current/share/gnome-shell/theme/communitheme/gnome-shell.css
/var/lib/snapd/alternatives/admin/ r,
/var/lib/snapd/alternatives/alt/gdm3.css{,.dpkg-tmp} rw,
/usr/share/gnome-shell/theme/gdm3.css{,.dpkg-tmp} rw,
/var/lib/snapd/alternatives/admin/gdm3.css{,.dpkg-tmp} rw,
/var/log/alternatives.log w,
I then thought, “what if we put --altdir, --admindir and --log in in $SNAP_COMMON”? This reduces the apparmor profile changes to:
/{,usr/}bin/update-alternatives ixr,
/usr/share/gnome-shell/theme/gdm3.css{,.dpkg-tmp} rw,
Note, I looked at dpkg in bionic and found that ADMINDIR_ENVVAR can be used for --admindir, but there is nothing for --altdir or --log.
In considering the above, I noticed several considerations with the general approach of letting a snap execute update-alternatives
directly:
- when using non-default values for --admindir, --altdir and --log, non-snap invocations of ‘update-alternatives’ is unaware of these alternate locations. While the symlink is setup correctly, this could confuse administrators and users. In the case of gdm3.css where there is already an alternative defined in bionic,
update-alternatives
with default options would be ignorant of the snapd --altdir/–admindir and vice versa
- if the snap is running update-alternatives, there is no guarantee that the snap will DTRT on snap removal which could result in a dangling symlink potentially breaking the software (eg, will gdm3 continue to function correctly if
/usr/share/gnome-shell/theme/gdm3.css
is a dangling symlink?)
- if the snap is running update-alternatives, there is no guarantee it will setup the alternative correctly. This could result in a dangling symlink or a malformated/ill-configured file in --admindir
- the snap running update-alternatives itself requires 'w’riting out to an arbitrary location for managing the target symlink (eg,
/usr/share/gnome-shell/theme/gdm3.css{,.dpkg-tmp}
) and this file is not guaranteed to be in the snap’s mount namespace
- update-alternatives and its --altdir/–admindir directories don’t exist in the snap’s mount namespace
- cannot mediate the update-alternatives ‘priority’ with security policy
- update-alternatives is a command only available on dpkg-based systems and doesn’t exist in the core snap or non-dpkg classic systems
- the alternatives mechanism could allow for sandbox escape. Eg, in this case, the snap is allowed to put anything into the css which is processed by an unconfined, privileged process. Consider other cases like someone creating an alternative for ‘/etc/bash.bashrc’ which is sourced in the user’s unconfined shell
To address 2-4, we could simply say that the interface needs manual connection and follow the normal process for issuing snap declarations. If we are going this route, 1 is simply addressed by always using the global database. ‘5’ could be handled a number of ways (eg, adjust snap-confine to mount these files/directories from the host or core ships update-alternatives that uses /var/lib/snapd/hostfs/… for its --altdir/–admindir). 6 cannot be addressed since the priority is defined in the file that is placed in the --admindir, which is writable by the snap (ie, have to trust the snap to DTRT). 7 could potentially be addressed by core shipping an update-alternatives that is a no-op on non-dpkg systems.
In all, I think it is possible to allow the snap to do this using:
plugs:
alternatives:
gdm3.css:
original: /usr/share/gnome-shell/theme/gdm3.css
target: /snap/communitheme/current/share/gnome-shell/theme/communitheme/gnome-shell.css
other.file: # 'name' from 'man update-alternatives'
original: /usr/... # 'link' from 'man update-alternatives'
target: $SNAP/... # 'path' from 'man update-alternatives'
snapd then looks at the above and adjusts the apparmor profile and mount namespace accordingly. The snap then is allowed to do these commands itself to manage the alternative:
# install
(root)$ update-alternatives --altdir /var/lib/snapd/alternatives/alt --admindir /var/lib/snapd/alternatives/admin --install /usr/share/gnome-shell/theme/gdm3.css gdm3.css /snap/communitheme/current/share/gnome-shell/theme/communitheme/gnome-shell.css 15
# remove
(root)$ update-alternatives --altdir /var/lib/snapd/alternatives/alt --admindir /var/lib/snapd/alternatives/admin --remove gdm3.css /snap/communitheme/current/share/gnome-shell/theme/communitheme/gnome-shell.css
Doing it this way is possible, but again, priority is unmanaged by snapd, the system is somewhat brittle, it all requires a decent amount of trust in the snap to DTRT and requires the developer to have a pretty deep understanding of the update-alternatives dpkg mechanism.
snapd calling update-alternatives on the snap’s behalf
A much more robust implementation would have snapd call out to update-alternatives itself which addresses all of the above shortcomings except ‘8’ (confinement escape) since the snap has no additional access to the system alternates files, arbitrary files, etc and because snapd can ensure that the alternate files have sane values and no dangling symlinks. I can think of a few ways to handle this:
- snap declares some snap.yaml and calls a snapd-supplied update-alternatives which talks to snapd over DBus/unix socket/something
- snap only declares some snap.yaml and snapd takes care of the rest
- snapd only understands certain alternatives (eg, only ‘gdm3.css’) instead of being a general purpose alternatives mechanism (potentially address confinement escape depending on how strict we are with adding new alternatives)
IMHO, ‘2’ and ‘3’ are more inline with the design principles of snapd. We could sketch out what this could look like in yaml, but ultimately I think we need to define some requirements and design the whole thing properly. For whoever would pick this up, here are some questions I think need answers before designing the feature:
- should the system support only specific alternatives or be totally generalized (ie, option ‘3’ vs anything else)
- should the system support more than just dpkg-based systems?
- how much of update-alternatives do we want to expose to the user? ‘man update-alternatives’ has a lot of options for example (ie, option ‘1’ vs anything else)
- can a snap setup an alternative to something that isn’t part of its snap? In the case of communitheme-set-default, it is setting the alternative to point to something in the ‘communitheme’ snap and not ‘communitheme-set-default’
- should there by a ‘snap alternative …’ command that the user can use to do things, like add/remove/config an alternative that the snap setup?