Proposal: new REST API for theme installation

This is a follow up to Automatic theme snap installation notes and Snapd-desktop-integration snap ownership and auto-connection request, outlining a potential REST API to support the snapd-desktop-integration service in its role of installing theme snaps. The main constraints are:

  1. We don’t want to grant snapd-desktop-integration access to the snapd-control interface.
  2. We don’t want to grant every snap the ability to install themes.
  3. The act of initiating installation of snaps must be protected by Polkit authorisation.

So the plan is to add a new interface whose plug will grant access to just the methods required by snapd-desktop-integration and no more. Ideally the method used to grant access should be scale to handle future API subsets we might want to expose.

Authentication

At present, snapd provides its REST API on two sockets:

  • /run/snapd.socket – only available with snapd-control
  • /run/snapd-snap.socket – available to all snaps, used by the snapctl utility.

A single HTTP server responds to connections on both sockets, with some access decisions based on which socket the client connects to.

The simplest option would be to add a third socket, and have the new interface grant access to it via AppArmor. This is probably not the best option if we expect to see more API subsets emerge.

An alternative would be to reuse the existing snapd-snap.socket, which has a few advantages:

  1. The socket is already available to snaps.
  2. There is a mechanism to identify which snap a client is via $SNAP_COOKIE.

Once we have the snap instance name from the cookie, it is a trivial matter to check for a connected plug from our new interface.

There are still some loose ends to tie up:

  1. can we fit this access control logic into Command.canAccess? Currently the /v2/snapctl endpoint accepts the cookie through a JSON encoded request body, which we don’t want to read at this early a point. The cookie is essentially a bearer token, so perhaps it would make sense to pass it in the Authorization request header?

  2. the current logic in Command.canAccess skips the possibility of a Polkit authorisation check in the c.SnapOK code path (i.e. requests coming over the snapd-snap.socket socket). Rather than complicating the function further, I wonder if we can restructure the access control needs into a list of simpler checks that return one of (granted, rejected, continue), with the last instructing to continue on to the next check.

New REST API Endpoints

GET /v2/themes

This endpoint would be used to check whether there are available but uninstalled snaps that satisfy a set of themes. The endpoint would take the following query parameters:

  • gtk-theme: GTK theme name
  • icon-theme: icon theme name
  • sound-theme: sound theme name

Any of these parameters can be passed multiple times to check for multiple themes at once. In practice, this is only needed for icon-theme, since both desktop icons and mouse cursors are distributed as “icon themes”, and the user might pick different versions for each.

This is not just a theoretical concern: as an example, Ubuntu 16.04 in its default configuration sets the desktop icon theme to ubuntu-mono-dark and mouse cursor theme to DMZ-White.

The response body would indicate the support status for these themes. Maybe a JSON response something like this?

{
  "gtk-themes": {
    "theme1": "installed | available | unavailable",
    ...
  },
  "icon-themes": {
    ...
  },
  "sound-themes": {
    ...
  }
}

Depending on what sort of UI we want to present to the user, it might also be useful to provide the snap metadata for themes in the “available” state, since the client won’t have access to /v2/find.

POST /v2/themes/install

This endpoint would be used to install available theme snaps. As it installs new snaps, it must be protected by a Polkit authorisation check.

The endpoint accepts a JSON encoded request body of the following form:

{
  "gtk-themes": ["gtk-theme1", ...],
  "icon-temes": ["icon-theme1", ...],
  "sound-themes": ["sound-theme1", ...]
}

As this is a potentially long running task where we might want to present progress to the user, it would make sense to treat this as an async response similar to other endpoints that install or refresh snaps.

Of course, this leaves open the question of how to let the client monitor the progress of the change. We probably don’t want to provide unrestricted access to the /v2/changes/{id}, for instance. Can we tag the source of the change in some way, and use that to make an access control decision?

1 Like

I agree that for this we want to use /run/snapd-snap.socket, in general what happens over that socket should use /v2/snapctl but given that the functionality here is nor really relevant for all or many snaps but one (or a few), using a dedicated endpoint seems fine to me.

About refactoring canCaccess we need to be careful and it’s about the details but doesn’t sound completely unreasonable.

it should be possible to do something along those lines, might need a bit of work depending how we do it internally.

1 Like