Repropose JWT specification for Stein

This spec was something we agreed upon as a team during the Queens
release. This commit reproposes it for Stein.

Major differences between this specification the original proposal
from Queens include:

  - the algorithm targeted for the implementation
  - the library being used to sign and validate JWTs
  - the payload data and claim information
  - asymmetric key rotation details

bp json-web-tokens

Change-Id: I598faca40a6d81dd58155165d4a323fb437f7a6c
This commit is contained in:
Lance Bragstad 2018-02-07 21:16:08 +00:00
parent e354333611
commit c87a0230b3
2 changed files with 406 additions and 197 deletions

View File

@ -1,197 +0,0 @@
..
This work is licensed under a Creative Commons Attribution 3.0 Unported
License.
http://creativecommons.org/licenses/by/3.0/legalcode
======================================================
Add JSON Web Tokens as a Non-persistent Token Provider
======================================================
`bp json-web-tokens <https://blueprints.launchpad.net/keystone/+spec/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`_.
.. _`open standard`: https://tools.ietf.org/html/rfc7519
.. _`actively maintained libraries`: https://jwt.io/#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.
.. _`fernet spec`: https://github.com/fernet/spec/blob/master/Spec.md
.. _`get changes into it`: https://github.com/fernet/spec/pull/13
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.
.. _`JSON Web Token standard`: https://tools.ietf.org/html/rfc7519
.. _`JWS`: https://tools.ietf.org/html/rfc7515
.. _`JWE`: https://tools.ietf.org/html/rfc7516
.. _`registered claims`: https://tools.ietf.org/html/rfc7519#section-4.1
.. _`Python libraries`: https://jwt.io/#libraries
.. _`PyJWT library`: https://pyjwt.readthedocs.io/en/latest/
.. _`does not yet support JWE`: https://github.com/jpadilla/pyjwt/issues/143
.. _`JWCrypto library`: http://jwcrypto.readthedocs.org/
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.
References
==========
* `JSON Web Token RFC <https://tools.ietf.org/html/rfc7519>`_
* `JSON Web Token light introduction <https://jwt.io/introduction/>`_
* `History of cryptography's adoption of fernet <https://github.com/pyca/cryptography/issues/2900>`_

View File

@ -0,0 +1,406 @@
..
This work is licensed under a Creative Commons Attribution 3.0 Unported
License.
http://creativecommons.org/licenses/by/3.0/legalcode
======================================================
Add JSON Web Tokens as a Non-persistent Token Provider
======================================================
`bp json-web-tokens <https://blueprints.launchpad.net/keystone/+spec/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`_.
.. _`open standard`: https://tools.ietf.org/html/rfc7519
.. _`actively maintained libraries`: https://jwt.io/#libraries
Problem Description
===================
We currently support one token format called fernet. The fernet token format is
a non-persistent format based on a spec by Heroku and was made the default
token format for keystone.
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 susceptible 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. This also increases the chances of interoperability between the
OpenStack ecosystem and other communities that support JWT.
.. _`get changes into it`: https://github.com/fernet/spec/pull/13
Use Cases
---------
* As a operator, I want to use a non-persistent token provider that isn't
coupled to a symmetric encryption or signing implementation. An
implementation built on asymmetric signing or encryption allows me to
distribute public keys from one node to another instead of syncing a
repository of symmetric keys. This makes it easier to deploy keystone nodes
with read-only capabilities strictly used for token validation. The
specific usecase for this allows me to deploy read-only regions keeping token
validation within the region, while having tokens issued from a central
identity management system in a separate region.
* As an operator, I want to be have a token provider to fall back on in the
event there is a security vulnerability in the `fernet spec`_ or the
`cryptography`_ implementation consumed by keystone.
* As a user, I want to be able to authenticate for a token that I can use with
other pieces of software outside of the OpenStack ecosystem that prove my
identity.
.. _`fernet spec`: https://github.com/fernet/spec/blob/master/Spec.md
.. _`cryptography`: https://github.com/pyca/cryptography
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 signed JWT (`JWS`_) containing the authentication payload.
Note that signed tokens are web safe and integrity verified, but token payload
is not opaque to its holder. It is possible to decode a token and inspect the
payload with `JWS`_ tokens. Using nested `JWE`_ tokens are the JSON Web Token
equivalent to Fernet tokens and they are encrypted and signed.
This implementation reserves the right to change, modify, or remove items in
the payload at any point in time and for any reason. Decoding and relying on
attributes within the payload is not a supported API, nor should it be assumed
as one. Users should use formal APIs to request information from keystone. The
development team will not prevent or stall changes to token payloads, which are
internal implementation details of the token provider, due to users relying on
those attributes in ways they shouldn't. Likewise, deployment that consider
information in the token payload sensitive should rely on Fernet to prevent
that information from being exposed to users.
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. The recommended
algorithm to be used is ``ES256``. Keystone should not expose the ability to
end users to ask for a specific JWS algorithm. Support should be limited to
only supported or trusted algorithms that the end user cannot specify. JWS
tokens will be integrity verified with a private key and validated using a
corresponding public key. Since the ``ES256`` implementation only uses signing
(as opposed to signed encrypted payloads), this adheres to slightly better
security practices over fernet because private keys never have to be synced
across keystone API nodes. Only public keys need to be transferred to other
keystone API servers to validate tokens across a cluster.
The payload of the JWS will use the following `registered claims`_:
* the `sub` claim will be a **required** string containing the ID of the user
who authenticated for the token
* the `exp` claim will be a **required** numeric value for token expiration
* the `iat` claim will be a **required** numeric value for the time a token was
issued
The following private claims will be used to relay additional required
information and will be prefixed with `openstack` to avoid collisions with
future upstream claims:
* `openstack_methods` will be a **required** claim denoting the authentication
methods used to obtain the token
* `openstack_audit_ids` will be a **required** claim containing the audit
information associated to a token
* `openstack_system` will be an **optional** claim only present in
system-scoped tokens
* `openstack_domain_id` will be an **optional** claim only present in
domain-scoped tokens
* `openstack_project_id` will be an **optional** claim only present in
project-scoped tokens
* `openstack_trust_id` will be an **optional** claim only present in
trust-scoped tokens
* `openstack_app_cred_id` will be an **optional** claim only present in
application credential tokens
* `openstack_group_ids` will be an **optional** claim only present in federated
tokens to carry an ephemeral user's group assignments
* `openstack_idp_id` will be an **optional** claim only present in federated
tokens to carry the ID of a user's identity provider
* `openstack_protocol_id` will be an **optional** claim only present in
federated tokens to denote the protocol used by a federated user to
authenticate
* `openstack_access_token` will be an **optional** claim only present in OAuth
tokens
The `PyJWT library`_ is already present in the requirements repository and
would be a convenient choice to use for this implementation. Both the `PyJWT
library`_ and the `JWCrypto library`_ implement support for JWS. Since the
implementation detailed in this specification is unique to ``ES256``, a library
that supports JWE isn't necessary. If supporting another encrypted token type,
like fernet, is a requirement in the future, then finding or contributing JWE
support to the consuming library would be necessary.
Users will request and present tokens in exactly the same way they currently do
with Fernet tokens. There is no need to add or change any APIs.
.. _`JSON Web Token standard`: https://tools.ietf.org/html/rfc7519
.. _`JWS`: https://tools.ietf.org/html/rfc7515
.. _`JWE`: https://tools.ietf.org/html/rfc7516
.. _`registered claims`: https://tools.ietf.org/html/rfc7519#section-4.1
.. _`Python libraries`: https://jwt.io/#libraries
.. _`PyJWT library`: https://pyjwt.readthedocs.io/en/latest/
.. _`does not yet support JWE`: https://github.com/jpadilla/pyjwt/issues/143
.. _`JWCrypto library`: http://jwcrypto.readthedocs.org/
Key Setup & Rotation
--------------------
Much like the Fernet implementation, a JWT provider will require a key rotation
strategy. Since ``ES256`` relies on asymmetric signing, the suggested rotation
strategy will be slightly different than what is known with Fernet.
The Fernet implementation requires the usage of a staged key, which is just a
key with a special name, in order to ensure tokens can be validated during the
rotation process. This won't be required with JWT and the following steps
should be sufficient to perform key rotation without token invalidation due to
missing signing keys. Assume the following steps are being performed on three
different API servers, named `K1`, `K2`, and `K3`, that need to validate
tokens issued by each other.
1. A key pair is created for each API server. `K1.private`, `K1.pub` for
`K1`, `K2.private`, `K2.pub` for `K2`, and `K3.private`, `K3.pub` for `K3`.
2. A copy of each public key is transferred to each API server. `K1`, `K2`, and
`K3` all have copies of `K1.pub`, `K2.pub`, and `K3.pub`.
At this point, tokens issued from any API server can be validated anywhere. In
the event a single API server needs to rotate keypairs:
1. A new key pair is created for `K1` called `K1-new.private` and `K1-new.pub`.
`K1` is configured to start signing tokens with both `K1.private` and
`K1-new.private.`
2. `K1-new.pub` is copied to the public key repository of each API server. So
long as `K2` and `K3` have either `K1.pub` or `K1-new.pub` they can validate
tokens issued by `K1`.
3. After `K2` and `K3` have been updated with copies of `K1-new.pub`,
`K1.private` can be removed from `K1` and `K1.pub` can be removed from `K2`
and `K3`. Tokens that were signed with only `K1.private` are unable to be
verified and `K1.pub` should only be removed after those tokens have expired
anyway.
Traditional asymmetric keys can be revoked using revocation lists. At this time
we are not going to support a revocation list implementation for JWT key pairs.
The operator has the ability to sync public keys accordingly when they rotate
new keys in and out. Keystone will only use the public keys on disk to validate
tokens. Is could change in the future, but for now it keeps the key rotation
and key utilities with keystone simpler.
Crypto-Agility & Future Work
----------------------------
This specification is targeting a single algorithm for the initial JWT
implementation. If and when keystone decides to expand the implementation to
include additional algorithms, we should allow for flexibility between
configured algorithms, which will make it easier for operators to switch from
one algorithm to another if they need to.
For example, the validation process using a JWT token provider might support
validating multiple blessed algorithms, allowing multiple tokens signed with
different algorithms to be validated without require configuration changes
except on the signing node.
Alternatives
------------
Recently, there have been various efforts that help solve authenticated
encryption. One of these efforts was sparked by a `concern`_ with JWT, namely
the `JOSE`_ header. The issue detailed in the report was specific to users
being able to specify algorithms and exploit a validation weakness in various
JWT libraries. All python libraries have been patched, but keystone should
specifically rely on validating algorithm usage and never assuming algorithms
to be supplied by end users. Please see the full `report`_ for details on the
vulnerability and why we are going to strictly validate this information.
There is a proof-of-concept implementation for Platform Agnostic Security
Tokens, or `PASETO`_ that takes a more strict stance on algorithm validation
and the intended audience of the token. The strict stance of `versioned
protocols` with `PASETO`_ is certainly advantageous, but the implementation and
idea are still in the incipient stage. It's certainly worth noting that we
should keep out eye on this development and re-evaluate it if, or when, it gets
more adoption.
For now, if keystone supplies strict algorithm validation to the JWT
implementation, we should be able to offer a comparable backup option to
fernet.
.. _`concern`: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
.. _`report`: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
.. _`JOSE`: https://tools.ietf.org/html/rfc7519#section-5
.. _`PASETO`: https://github.com/paragonie/paseto
Security Impact
---------------
Since JWT is a widely used web standard, this will have a net positive impact
on security. The implementation will use asymmetric signing, reducing risk of
having to replicate or transfer private keys from one host to another. Since
the token payloads are signed, data within the token will be readable to anyone
who has the token. The token can only be validated using the corresponding
public key of the private key used to sign the token originally. These will
still be bearer tokens and so interception of one must still be guarded
against.
Known Vulnerabilities
~~~~~~~~~~~~~~~~~~~~~
There is a documented `vulnerability`_ that affected several JWT libraries,
including one library written in Python.
In most cases, JSON Web Tokens will have a header, payload, and signature where
each section is delimited by a period (``.``). The header contains an important
piece of information, which is how the token's integrity is protected. This is
stored as the ``alg`` attribute of the header. The library verifying the token
uses the algorithm specified in the header to perform an integrity check and
compares its results to the signature portion of the token.
Security concerns have been documented and raised that describe the issues with
allowing clients to dictate algorithms used for token verification. This is a
concern specifically with applications that support asymmetric and symmetric
signing. An attacker could effectively bypass the verification check of a
token by using a published, or known, public key to generate a JWT with a
symmetric signing algorithm.
This would be applicable if keystone supported signed tokens and encrypted
tokens with the same token provider implementation. This vulnerability has been
addressed across various libraries after its discovery, but keystone should be
aware of the overall technique that lead to it in the first place. We can
mitigate this type of vulnerability in keystone by:
* Ensuring keystone doesn't blindly allow end users to specify which algorithm
is used to verify the integrity of a token (e.g., only implementing support
for ``ES256``)
* Ensure the ``alg`` supplied in the token header is only ever populated by
keystone
* Ensure keystone only issues tokens of a single encryption or signing strategy
(e.g., not allowing users to get signed token and encrypted tokens from the
same server, thus mixing asymmetric and symmetric key usage at runtime)
Specifics about the `vulnerability`_ can be found in the report.
.. _`vulnerability`: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
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 tokens.
Performance Impact
------------------
It will be worth investigating performance differences between token providers
that use asymmetric signing (JWT) and symmetric encryption (fernet). These
difference, if significant, should be published in documentation as it might be
useful for operators when choosing a token provider.
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 use JWT. 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)
-----------
Primary assignee:
Gage Hugo (gagehugo)
Lance Bragstad (lbragstad)
XiYuan Wang (wxy)
Work Items
----------
* 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 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
============
There are three different libraries we can use to implement this functionality.
1. `PyJWT`_
This library only supports token signing, or JWS. It does not support JWE,
or authenticated encryption, yet. A minimum version of **1.0.1** is
`required`_, but this library is already included in OpenStack global
requirements repository.
2. `python-jose`_
This library only supports token signing, or JWS. It does not support JWE,
or authenticated encryption, yet. This library is not included in OpenStack
global requirements.
3. `JWCrypto`_
This library supports both JWS and JWE, but it is not included in OpenStack
global requirements.
3. `Authlib`_
This library supports both JWS and JWE, but its licensing is incompatible
with OpenStack as it is AGPL.
Given the fact that the initial implementation of JWT is not going to rely on
nested JWT tokens or encrypted payloads, it's safe to assume that signing
support will be sufficient. The PyJWT library is already included in global
requirements and we don't have a case to not use that specific library, which
is compatible with OpenStack licensing.
.. _`PyJWT`: https://pyjwt.readthedocs.io/en/latest/
.. _`required`: https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
.. _`python-jose`: https://python-jose.readthedocs.io/en/latest/
.. _`JWCrypto`: http://jwcrypto.readthedocs.io/en/latest/
.. _`Authlib`: https://docs.authlib.org/en/latest/
Documentation Impact
====================
The new ``[token]/provider`` configuration option will need to be documented,
as will the new ``keystone-manage`` commands.
References
==========
* `JSON Web Token RFC <https://tools.ietf.org/html/rfc7519>`_
* `JSON Web Token light introduction <https://jwt.io/introduction/>`_
* `History of cryptography's adoption of fernet <https://github.com/pyca/cryptography/issues/2900>`_