Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OIDC: Cluster-wide key and salt for cookie encryption #15011

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

markylaing
Copy link
Contributor

When performing the authorization code flow two cookies are encrypted and stored on the client. After authentication, when the client is redirected to LXDs /oidc/callback endpoint, the receiving LXD cluster member must be able to decrypt these cookies to complete the PKCE part of the authentication flow.

It is possible that, if LXD is deployed behind a load-balancer or DNS SRV records are used, the redirection to /oidc/callback will reach a different cluster member to the one that started the login flow. Currently, the encryption keys used for these cookies are cluster member specific. If the call to /oidc/login starts on one cluster member, but the call to /oidc/callback reaches another cluster member, then decryption of these cookies fails and the authentication flow must be restarted.

This is documented in #13644

This PR adds the concept of a cluster wide secret key and salt. Both the key and the salt are stored in the config table but are hidden from the server configuration.

The key and salt have the following formats:

  • salt: a ULID with the time set to the time of expiry and with (crypto/rand).Reader as the source of entropy (rather than the default monotonic reader).
  • key: a 512 bytes read from (crypto/rand).Reader. In the database, this is base64 encoded and prepended with the expiry time in unix milliseconds (UTC) (delimited by a ., which base64 cannot contain).

Note that the key is not a private TLS key. It's only use is as a source for HKDF derivation of hash and block keys for cookie encryption.

Whenever the salt or key are used, the caller should check if they are still valid. If they are not valid, the caller should check if another cluster member has created a valid key/salt and stored it in the database. If the key/salt in the database is not valid, the caller should set a new one. The lxd/db/secret package has been added to handle this.

Two new configuration keys are added to manage the lifetime of the secret key (core.secret_key_lifetime default 30 (days)) and salt (core.salt_lifetime default 60 (minutes)). When the key or salt lifetime is updated, the existing key and salt are invalidated. Additionally, when a daemon joins a cluster, any secrets it is holding in memory are invalidated.

To preserve the authorization code flow in case of salt or key rotation occurring while a user is logging in, the OIDC verifier keeps old relying party configuration available in memory for 5 minutes. The verifier determines which RP to use to complete the flow by checking that it is able to decode the state cookie (which is set as part of the flow).

This PR removes dependence on the cluster private key for derivation of unique cookie encryption keys for OIDC ID and access tokens. Instead we use the cluster-wide secret key, plus the session ID, to derive unique keys per user session.

Lastly, options for instantiating a new OIDC verifier have been expanded to pass in a "host" and a context. This means that the verifier will attempt to perform discovery when the configuration is set.

Closes #13644.

Marking as draft because this is really a collection of ideas and there is no spec. If we like the approach and decide to merge, beware that this will log out all OIDC users (though if they're still logged in at the IdP, the be re-logged into LXD after a brief redirect).

@github-actions github-actions bot added Documentation Documentation needs updating API Changes to the REST API labels Feb 17, 2025
@markylaing markylaing added Bug Confirmed to be a bug Improvement Improve to current situation and removed Documentation Documentation needs updating API Changes to the REST API labels Feb 17, 2025
@markylaing markylaing self-assigned this Feb 17, 2025
@github-actions github-actions bot added Documentation Documentation needs updating API Changes to the REST API labels Feb 17, 2025
Signed-off-by: Mark Laing <[email protected]>
Adds utils for managing a cluster wide secret and salt.

Signed-off-by: Mark Laing <[email protected]>
This function, in combination with `clusterSecretInternal`,
can be used to get a cluster-wide shared secret on demand.

Signed-off-by: Mark Laing <[email protected]>
We need to unset any previously configured value so that the joining
daemon will fetch the shared secret on the next call to
`(*Daemon).getClusterSecret`.

Signed-off-by: Mark Laing <[email protected]>
To do this, we need to delete the database entry first, then reset
the `(*Daemon).clusterSecretInternal` so that new ones are generated
when required. We only delete the database entries once, from the member
that received the request.

Signed-off-by: Mark Laing <[email protected]>
This change allows the verifier to perform OIDC discovery when
it is created (on config change). Users will get faster feedback
if their OIDC configuration is incorrect.

Signed-off-by: Mark Laing <[email protected]>
This PR changes the `relyingParty` field to a slice of relying parties
associated with the time at which they became outdated.

In /oidc/callback, we use any available relying party that can decrypt
the state cookie (and therefore complete the flow).

Relying parties are only kept around for 5 minutes.

Signed-off-by: Mark Laing <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Changes to the REST API Bug Confirmed to be a bug Documentation Documentation needs updating Improvement Improve to current situation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Auth: OIDC login flow fails in an LXD cluster if different members handle different stages of the flow
1 participant