OpenStack Keystone Fernet tokens

Fernet is a secure messaging format explicitly designed for use in API tokens by Heroku. They address many of the same problems that OpenStack faces, and make some of the same design considerations that have already appeared in the OpenStack community. They're non-persistent, lightweight, and reduce the operational overhead required to run a cloud.

>>> print(fernet_token)
gAAAAABU7roWGiCuOvgFcckec-0ytpGnMZDBLG9hA7Hr9qfvdZDHjsak39YN98HXxoYLIqVm19Egku5YR3wyI7heVrOmPNEtmr-fIM1rtahudEdEAPM4HCiMrBmiA1Lw6SU8jc2rPLC7FK7nBCia_BGhG17NVHuQu0S7waA306jyKNhHwUnpsBQ

Non-persistent: Unlike UUID tokens, they don't need to be persisted to a database.

Yep! That means this will be true forever:

> SELECT * FROM `token`;
Empty set (0.00 sec)

(at least until we can issue a DROP TABLE...)

Lightweight: Unlike PKI tokens, they disregard the kitchen sink in favor minimal identity information and a dynamic authorization context.

They typically fall into the range of 180 to 240 bytes. And from a client's perspective, you can treat them exactly like UUID tokens.

Symmetric encryption: Fernet encrypts the payload using AES-CBC and signs using a SHA256 HMAC.

In short, OpenStack's authentication and authorization metadata is neatly bundled into a MessagePacked payload, which is then encrypted and signed as a Fernet token. OpenStack Kilo's implementation supports a three-phase key rotation model that requires zero downtime in a clustered environment.

By Luiscardo (Own work) [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons

How do you pronounce "Fernet"?

Literally fer-net, as the token format is named after the Italian bitters of the same name.

I'm inclined to pronounce it fair-nay, with a silent T and a bit of improvised Italian flair, but I've been corrected several times to pronounce it as fer-net.

Tim Bell laughed and suggested that both pronunciations were wrong, but there's no way I can twist my tongue into producing the syllables he used to pronounce it.

More importantly, linguists will tell you that if your audience understands your meaning, then you have effectively communicated, regardless of grammar or pronunciation. So my conclusion is that if you pronounce it as fer-net then everyone is more likely to correctly spell it when searching for documentation, and that's all that really matters.

What does Fernet taste like?

I'll defer:

It tasted like boiled woodchips. It tasted like some hideous pre-Hippocratic Chinese remedy, or maybe the kind of after-dinner mint they’d offer in hell. — Jason O’Bryan

What do Fernet tokens contain?

Fernet tokens contain minimal identity and authorization information: just enough for keystone to rebuild a complete token validation response.

In the general case (a project-scoped token), a token payload will contain a globally-unique user ID and a globally-unique project ID, in addition to some metadata such as a token creation timestamp, the token lifespan, auditing ID(s), and authentication methods. Domain-scoped tokens will have a domain ID instead of a project ID. A trust-based token will contain a trust ID. A federation-based token will contain information about the federation itself.

All of this, except for the token creation timestamp, is encrypted because it's part of the Fernet token's payload (the encrypted message). Fernet acts as a container to that payload and is specified in further detail here.

If you want to explore a Fernet payload, you'll see just how deep the rabbit hole goes.

How big are Fernet tokens?

Here's an example of a project-scoped Fernet token:

gAAAAABU7roWGiCuOvgFcckec-0ytpGnMZDBLG9hA7Hr9qfvdZDHjsak39YN98HXxoYLIqVm19Egku5YR3wyI7heVrOmPNEtmr-fIM1rtahudEdEAPM4HCiMrBmiA1Lw6SU8jc2rPLC7FK7nBCia_BGhG17NVHuQu0S7waA306jyKNhHwUnpsBQ%3D

That's 186 bytes, and we're striving to keep them all under 255 bytes. Although there's no hard limit at 255 bytes, we recognize that longer tokens become a user experience issue, if not a pain to store in your existing database. Unfortunately, the upper bound on token size is actually a function of your deployment's configuration complexity). Things like non-UUID user IDs (from an LDAP backend) risk adding additional bloat.

More alarmingly, federated Fernet tokens contain the OpenStack group IDs produced by your identity provider's mapping configuration — which is an unbounded list. So if you're mapping users into more than a group or two, you're going to quickly see Fernet quickly inflate in size.

Are Fernet tokens still bearer tokens?

Yes. They must be protected from unintentional disclosure, just like UUID, PKI and PKIZ tokens.

There is no request-signing component, although I'd like to see that implemented next. The hurdle is that all clients, including middleware, will be substantially affected as they will obviously have to start signing every request in addition to storing an additional secret credential (the signing key).

Can Fernet tokens be validated offline?

Sort of, a little bit, depending? There's a tiny bit of validation you can do offline, such as verifying that the token is a properly composed Fernet token, and verifying the creation date. But without knowing the TTL, the signing key, or the encryption key, you can't do much else.

What about performance?

Fernet tokens perform up to 85% faster than UUID tokens, and up to 89% faster than PKI tokens. See the detailed benchmarks for yourself.

Are there any known issues with Fernet tokens?

Checkout the current bug list in Keystone.

How do I configure my deployment to use Fernet?

There are only two steps required. Or maybe three.

First, if you're on stable/kilo, configure Keystone to issue Fernet tokens by changing the token provider in keystone.conf:

[token]
provider = keystone.token.providers.fernet.Provider

Or if you're on master (stable/liberty or newer):

[token]
provider = fernet

Second, you need to initialize your key repository:

$ mkdir /etc/keystone/fernet-keys/
$ keystone-manage fernet_setup

This will populate your keystone.conf [fernet_tokens] key_repository directory (/etc/keystone/fernet-keys/ by default) with a pair of keys to get you started: one staged key used for decrypting tokens, and one primary key used for encrypting tokens.

Third, and this is only if you're deploying a multi-node keystone cluster, you'll then want to replicate the contents of your keystone.conf [fernet_tokens] key_repository directory onto each additional keystone node. This ensures that each node in the cluster can validate the tokens produced by any other node in the cluster.

How does key rotation work in a cluster?

The trick to key rotation in a cluster is avoiding race conditions. What if node A starts encrypting tokens with a new encryption key before node B is aware of that encryption key at all?

Lance Bragstad illustrates our rotation solution which avoids this pitfall entirely by introducing a three phase key rotation strategy.

How does keystonemiddleware.auth_token handle Fernet tokens?

keystonemiddleware.auth_token has some exceptional logic to handle PKI-based tokens, so you might expect that it has to do something special with Fernet tokens as well. As it turns out, that's not the case at all.

auth_token recognizes PKI-based tokens and takes a separate code path in order to validate them "offline" (without making calls back to Keystone for every token) which is an entirely different code path to that of online validation (where a call to Keystone needs to be made at least once for every token, and then cached). The trick with Fernet is that both UUID and Fernet tokens are validated online, and therefore share the exact same code path. No exceptional behavior is required.