From c87a0230b343abcb33fc1301cfd09718c46bbdf2 Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Wed, 7 Feb 2018 21:16:08 +0000 Subject: [PATCH] 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 --- specs/keystone/backlog/json-web-tokens.rst | 197 ---------- specs/keystone/stein/json-web-tokens.rst | 406 +++++++++++++++++++++ 2 files changed, 406 insertions(+), 197 deletions(-) delete mode 100644 specs/keystone/backlog/json-web-tokens.rst create mode 100644 specs/keystone/stein/json-web-tokens.rst diff --git a/specs/keystone/backlog/json-web-tokens.rst b/specs/keystone/backlog/json-web-tokens.rst deleted file mode 100644 index 307b77a9..00000000 --- a/specs/keystone/backlog/json-web-tokens.rst +++ /dev/null @@ -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 `_ - - -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: - - -Other contributors: - - -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 `_ -* `JSON Web Token light introduction `_ -* `History of cryptography's adoption of fernet `_ diff --git a/specs/keystone/stein/json-web-tokens.rst b/specs/keystone/stein/json-web-tokens.rst new file mode 100644 index 00000000..16cb56bc --- /dev/null +++ b/specs/keystone/stein/json-web-tokens.rst @@ -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 `_ + + +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 `_ +* `JSON Web Token light introduction `_ +* `History of cryptography's adoption of fernet `_