Local logins without store auth

We have an ongoing issue [1] where desktop users are unnecessarily prompted for a store login to perform local operations (e.g. removing a snap). This is because a desktop app can’t (safely) run as root and it’s impractical / unsafe to wrap the entire snapd API in a root daemon. This is a serious problem if you can’t contact the store (e.g. no network) - you can’t remove snaps from a GUI unless you have an existing Macaroon.

The agreed solution to this was to make a local Macaroon that would confirm the user can perform root operations (the Macaroon is got from snapd-login-service that runs as root and uses Polkit to authorize the user.

How close are we to having this local Macaroon?

[1] https://bugs.launchpad.net/bugs/1581713

This was discussed at the Snappy sprint (June 2017, London) with Michael Vogt and John Lenton. Here is a proposal for a technical solution that should solve this requirement.

snapd changes:

  • Return local Macaroon(s) (root and discharges) in response to /v2/users and /v2/create-user.
  • Add a new endpoint /v2/users with action="register" (async, root required) that registers a local user account with snapd and creates a local macaroon. Returns the same data as /v2/users and /v2/create-user. This action does NOT contact the store.

snapd-glib changes:

  • Support new endpoints / actions from snapd.
  • Add new D-Bus interface to snapd-login-service.

GNOME Software changes:

  • Use new snapd-glib methods to get local Macaroon.
  • Support new path for users to do store login (was previously prompted on first install/remove, new method of prompting TBD).

Process:

  1. Fresh install of Ubuntu with snap app pre-installed and no network access.
  2. User starts GNOME Software (G-S).
  3. User attempts to remove app.
  4. G-S contacts snapd-login-service to get Macaroon.
  5. snapd-login-service calls /v2/users to get Macaroons.
  6. If user account not in response, snapd-login-service calls /v2/users action="register" to register.
  7. Macaroon returned to G-S.
  8. G-S uses Macaroon to remove app using snapd.
  9. When network is connected user logs into store using existing /v2/login and replaces local Macaroon with store enabled Macaroon.

I posted this on the development sprint thread, but is there any reason why we couldn’t just get snapd to support polkit authorization for the relevant REST API calls?

I think this could be done such that it is purely a runtime dependency, and could delay connecting to the bus until polkit authentication was needed, so the only question is whether there is a good reason for snapd not to talk to the system bus.

The discussion related to polkit was moved into its own topic. Let’s please keep that part of the conversation there and have here only the aspects related to the local login itself.

Per the conversation in the sprint, I believe this is not a task for the /v2/users or /v2/create-user endpoints. The first one is so far mainly oriented towards management of users, while the latter is an endpoint for creating system users, which we don’t want to do in this case. Also, neither of those return a macaroon to the caller, and we probably don’t want to change that.

I think the proper place for this is the /v2/login endpoint, which is the only that returns a macaroon today, I think? The semantics also feel right: if you are obtaining a macaroon for someone, you haven’t just created that user. You are that user and may perform arbitrary actions as that user.

We’ll need a hint that /v2/login shouldn’t go to the store. Perhaps a detached: true parameter? Although we’ve been using the local terminology so far, it feels wrong because in all cases there is actually a local user component being created. It’s the remote component that is associated with it or not in certain cases.

Finally, we need to define what data we require for that kind of user. At the very least, I think we want a username associated with it for now. We may find use cases that require us to ignore the parameter in the future, but feels good to start with it assuming we have the information available.

Currently you pass the following to /v2/login:

{
     "username": "foo@bar.com",
     "password": "swordfish",
     "otp": "123456"
}

We could make this endpoint accept different credentials, in this case only the uid/username is required:

{
    "uid": 1000
}

If this is too implicit we could add a field to indicate what credentials are being used (defaulting to store credentials):

{
    "credentials": "unix-account",
    "username": "test"
}

In terms of data I don’t think we have anything reliable to provide except the username/uid.