Add JSON Web Tokens as a Non-persistent Token Provider

bp json-web-tokens

JSON Web Token is a type of non-persistent bearer token similar to the fernet tokens we use today. JWT is an open standard with actively maintained libraries.

Problem Description

We currently support two types of tokens. The UUID persistent token format is the original format. The fernet token format is a non-persistent format based on a spec by Heroku and was made the default token format for keystone.

The UUID token format suffers a number of performance and maintainability problems. It is already deprecated and we intend to eventually remove it, in favor of non-persistent token formats.

However, the fernet format has its own problems that make it non-ideal. The fernet spec is largely abandoned, making it hard to get changes into it and thereby into the cryptography implementation of it. Moreover, the fernet spec is not recognized by any standards body and therefore not as closely audited as an IETF standard, making it more susceptable to zero-day vulnerabilities. Addressing these vulnerabilities falls solely on the OpenStack, specifically the keystone, community.

It would be nice to offer a new type of token that is backed by a widely used standard before we totally remove support for UUID tokens. This also increases interoperability between the OpenStack ecosystem and other communities that support JWT.

Proposed Change

Create a new non-persistent keystone token backend based on the JSON Web Token standard. These will behave in much the same way as our current fernet tokens do.

The token will be a nested JWT which is a signed JWT (JWS) as the payload of an encrypted JWT (JWE), meaning the token data is signed and then encrypted. This is the order specifically recommended in the JWT spec due to its use of Authenticated Encryption which eliminates the need to sign after encryption.

Similar to the fernet, JWTs will require a key repository be set up to use for signing tokens. A new keystone-manage command will be added to handle secret generation and rotation which will likely re-use much of the utilities in the fernet_setup and fernet_rotate commands. JWE can use symmetric keys for encryption and signing the way fernet does but it is more typical to use an asymmetric key pair to encrypt the Content Encryption Key (which is then used as a symmetric key to encrypt the payload, but this is an implementation detail that will be handled by the chosen library), so the JWT key repository should have asymmetric key pairs which we can use for encryption and signing. The specific algorithms used will depend on the support for them in the chosen library.

The payload of the JWS will use the following registered claims:

  • the “sub” claim for the subject, i.e. the user. This claim will include the same data that fernet tokens do, such as user information and scope.
  • the “exp” claim for expiry
  • the “iat” claim for “issued at”, analogous to the timestamp field in the fernet token.

The PyJWT library is already present in the requirements repository and so would be a convenient choice to use for this implementation, but it unfortunately does not yet support JWE. We will need to evaluate the JWCrypto library which does support encryption or consider contributing the feature to PyJWT.

Users will request and present tokens in exactly the same way they currently do with either UUID or Fernet tokens. There is no need to add or change any APIs.

Alternatives

Not applicable, this is an additive change. The alternative is not to add it or to find a different token format.

Security Impact

Since JWT is a widely used web standard, this will have a net positive impact on security. The implementation will use JWE even though it is an optional feature of the JWT spec. While this will not protect against an attacker using a valid token to query keystone for information about the token, it protects against an attacker gaining information from an expired or revoked token. This will ensure that the data within the token is at least as secure as it is in fernet tokens. These will still be bearer tokens and so interception of one must still be guarded against.

Notifications Impact

Notifications for JWTs will behave in the same way that they do for fernet tokens, including for revocation events.

Other End User Impact

This will have no end user impact. They will request and use JWTs in exactly the same way that they currently use fernet or UUID tokens.

Performance Impact

Performance will be on-par with fernet tokens.

Other Deployer Impact

This is an optional, opt-in feature that will not be the default, so deployers will not be affected unless they choose to deploy this feature. In that case, deployers will need to set up a key repository before using JWTs. The key repository will contain asymmetric key pairs rather than just secret keys. The deployer will need to take care to sync and rotate keys the way they do with fernet tokens.

Developer Impact

The new token type will reuse much of the work already done for fernet tokens and will follow similar code paths, so this will be relatively easy to maintain.

Implementation

Assignee(s)

TBD: Please update this section when this spec is reproposed to a release.

Primary assignee:
<launchpad-id or None>
Other contributors:
<launchpad-id or None>

Work Items

  • Decide on an implementation library
  • Refactor the fernet utilities modules to be generic enough to work with JWT or inheritable
  • Add a keystone-manage command to set up and rotate JWT signing/encryption keys
  • Generalize the TokenFormatter class to support JWT
  • Refactor the fernet token provider module to be inheritable or generic
  • Add a keystone doctor command to validate the setup in the same way that fernet is validated

Dependencies

  • A JWT library to be decided on

Documentation Impact

The new [token]/provider configuration option will need to be documented, as will the new keystone-manage commands.