[LXD] OpenID Connect authentication

Project LXD
Status Draft
Author(s) @monstermunchkin
Approver(s) @stgraber @tomp
Release 5.13
Internal ID LX037

Abstract

This adds OpenID Connect as a new authentication method.

Rationale

LXD currently supports only tls and candid as authentication methods. The former uses certificates for authentication, and the latter requires a candid server which is a macaroon-based authentication service.

OpenID Connect (OIDC) is a simple identity layer on top of the OAuth 2.0 protocol. One of the advantages of OIDC is that it allows authentication without LXD having to store and manage users and passwords.

Specification

Design

The following new server configuration keys will be added:

  • oidc.issuer
  • oidc.client.id
  • oidc.client.secret

The oidc.issuer key contains the URL of the server issuing the tokens. This for example could point to a Keycloak server.

Clients are applications and services that can request authentication of a user, and communicate with the issuer using the oidc.client.id and oidc.client.secret.

OIDC can be enabled by simply setting all of the oidc.* keys.

If the client wants to communicate with the LXD server using OpenID, it sets the X-LXD-oidc HTTP header for every request. That way the server knows what kind of authentication to use.

LXD advertises the authentication methods it supports. The oidc value will be added to this list. oidc should be favored over candid which itself is favored over tls.

Adding a new remote

The user can add a new remote using lxc remote add <foo> --auth-type=oidc.

Authentication

If the client cannot be authenticated, the LXD server returns an error containing the issuer, client ID, and client secret. It also contains the type of error and can be one of the following:

  • authentication request
  • invalid token

The authentication request error means that the user needs to log in. The invalid token error most likely means that the token has expired, but it can also have a different reason. This error also contains the reason which can be one of these errors.

Using the issuer, client ID, and client secret, the user will be presented with a login page. On successful login, the client receives an access token, refresh token, and ID token. These are stored on disk.

If invalid token was returns by the LXD server, and the refresh token is still valid, a new access token will be retrieved without user interaction. Otherwise the login page will be presented again.

Once the tokens have been retrieved, the only one used for authentication by the LXD server is the access token. The others stay with the user and client. The ID token is not used for anything beyond this point, and the refresh token is for refreshing the access token.

The access token will be sent with every request using the Authorization HTTP header:

Authorization: Bearer <access_token>

Since the access token is a JSON Web Token (JWT), it can be decoded and validated offline (i.e. without having to call the OpenID Provider). The LXD server will do this on every request, and return an error if the validation fails.

Initial flow to get token

  • User adds remote with lxc remote add <remote> --auth-type=oidc
  • LXD server returns OIDC information (issuer, client ID, and client secret)
  • User is required to log in from the browser, and client uses OIDC information to get tokens
  • Client uses access token to access LXD

Valid access token

  • Client sets Authorization HTTP header with access token to access LXD

Expired access token but valid refresh token

  • Client sets Authorization HTTP header with access token to access LXD
  • LXD returns OIDC information together with the invalid token error
  • Client gets a new access token using the refresh token
  • Client uses new access token to access LXD

Expired access token and expired refresh token

  • Client sets Authorization HTTP header with access token to access LXD
  • LXD returns OIDC information together with the invalid token error
  • Client fails to get a new access token using the expired refresh token
  • User is required to log in from the browser, and client uses OIDC information to get new tokens
  • Client uses new access token to access LXD

CLI changes

The --auth-type flag for lxc remote add accepts oidc.

Upgrade handling

If there’s a remote configured which uses candid, and the LXD server adds OIDC support, the client will continue using candid. OIDC should only then be used when adding a new remote.

3 Likes

Please could you expand a little on what this is and where will it be stored? Thanks

A few comments:

  • Should switch config keys to OIDC
  • Should add a short description of what each of the server side config keys do (for those unfamiliar)
  • Add a simple bullet point list of what is going to happen when:
    • User first connects to an OIDC enabled LXD (initial flow to get token)
    • User connects to LXD later on (valid token)
    • User connects to LXD with an expired token but valid refresh token (refresh)
    • User connects to LXD with an expired token and no valid refresh token
  • Should clarify authentication support in LXD. We currently advertise whether we have tls or candid, oidc will be added to that, on remote add, oidc should be favored over candid which itself is favored over tls
  • Need to validate that this will all work with our other tools, lxd-migrate comes to mind
  • Add logic in upgrade handling section for detecting existing configured remotes in the client and keep those using candid if they’re setup for it, we should only automatically use oidc when adding a new remote
1 Like

Are there any local or DB storage requirements beyond the server configuration keys already mentioned?

No, there are no DB related changes needed for this.

Isn’t there a bit of a security concern with effectively anyone being able to get the client ID and client secret?

I did some reading regarding this, and you’re right. OAuth2 has the concepts of confidential clients and public clients.

Taken from here:

Confidential clients are applications that are able to securely authenticate
with the authorization server, for example being able to keep their registered
client secret safe.

Public clients are unable to use registered client secrets, such as applications
running in a browser or on a mobile device.

Since we cannot safely store the client secret, we need to drop it, and be a public client.