A Hashicorp Vault secrets plugin for generating and verifying JSON Web Tokens
This plugin provides the ability to generate signed JSON Web Tokens (JWTs) without the signing keys ever leaving Vault.
The plugin works by providing a service to sign JWTs using internal private key(s). Simultaneously the plugin provides a JSON Web Key RFC compliant HTTP endpoint to publish public verification keys.
The plugin explicitly does not support verifying JWTs as a service; instead relying on clients to fetch the verification keys via HTTP and verify JWTs locally. This dramatically reduces traffic to Vault as well as allows clients to use standard client libraries for verification.
The plugin is still under early development and should be tested thoroughly before being used in any environment.
The plugin automatically rotates signing keys and publishes a history of previous keys for verification. The rotation schedule is configurable and ensures the keys will be available for verification as long as any JWTs signed with them are valid.
The plugin supports a subset of the asymmetric encryption algorithms outlined in the JWT specification.
- ES256
- ES384
- ES512
- RS256
- RS384
- RS512
Note: Due to its reliance on asymmetric encryption, the plugin will not support symmetric algorithms.
The plugin needs to be built and installed into your Vault instance's plugin directory prior to any attempt at usage. A prepackaged container is available see Container.
- Register the plugin
export PLUGIN_SHA=$(sha256sum $VAULT_PLUGIN_PATH/vault-plugin-secrets-jwt | cut -d ' ' -f1)
vault plugin register -sha256=$PLUGIN_SHA -command=vault-plugin-secrets-jwt secret jwt
- Enable the plugin
vault secrets enable jwt
- Create a role specifying the issuer (
iss
) claim of generated JWTs
vault write jwt/roles/test-role issuer=test.example.com
- Sign a JWT (with default claims)
vault write -f jwt/sign/test-role
- Retrieve JWKs for verification
curl https://$VAULT_ADDRESS/v1/jwt/jwks
A containerized version of Vault with the plugin pre-packaged inside is available for testing at
https://hub.docker.com/r/outfoxx/vault
.
You can easily start a server in dev mode, that has the plugin enabled, using:
docker run --rm -P -e VAULT_DEV_ROOT_TOKEN_ID=root outfoxx/vault
The plugin has a usable (although probably not useful) default configuration. Although prior to usage roles must be configured.
The plugin requires that any claims provided during role creation or JWT signing be explicitly
allowed in the configuration. By default, only the audience (aud
) claim is allowed.
Allow aud
and groups
claims:
vault write jwt/config allowed_claims="aud" allowed_claims="groups"
ℹ️ The allowed_claims
field is a list, passing multiple values to vault
cli allows you to
create a list.
The plugin requires that any headers provided during role creation be explicitly allowed in the configuration.
Allow iss
and path
claims:
vault write jwt/config allowed_headers="iss" allowed_headers="path"
ℹ️ The allowed_headers
field is a list, passing multiple values to vault
cli allows you to
create a list.
The plugin allows configuration of the signature algorithm used to sign JWTs. By default, the
ES256
algorithm is used.
vault write jwt/config sig_alg=RS256
When using an RSA algorithm (e.g. RS256
) you can also select the size of the RSA key that
is generated. By default, a 2048
bit key is generated.
vault write jwt/config sig_alg=RS256 rsa_key_bits=4096
Key rotation is automatically done by the plugin. You can configure the key rotation period to whatever duration you wish.
vault write jwt/config key_ttl=12h0s
When keys are rotated the previous keys are kept to allow verification. Verification keys are pruned at a time after which all generated tokens have expired.
Each generated JWT has a finite expiration. Configure the TTL used to determine each token's
expiration with the token_ttl
field. By default, each token expires after 3m0s
.
vault write jwt/config token_ttl=3m
The plugin can be configured to restrict the audience (aud
) and subject (sub
) claims to
those matching a specific pattern. By default, both claims are unrestricted.
vault write jwt/config subject_pattern=*.example.com
vault write jwt/config audience_pattern=*.example.com
Additionally, the audience (aud
) claim (which is a list of stings) can be restricted to
a maximum length. By default, audience length is unrestricted.
vault write jwt/config max_audiences=2
The issuer (iss
) claim for generated tokens can be specified in the configuration. By
default, no issuer claim is added.
vault write jwt/config issuer=vault.example.com
The "unique token id" (jti
) claim can be enabled/disabled. By default, a "unique token id" claim is added.
vault write jwt/config set_jti=true
The "not before" (nbf
) claim can be enabled/disabled. By default, a "not before" claim is added.
vault write jwt/config set_nbf=true
The "issued at" (iat
) claim can be enabled/disabled. By default, an "issued at" claim is added.
vault write jwt/config set_iat=true
Before signing a JWT a role must be configured.
When creating a role a value for the issuer
field must be provided. The role issuer field specifies the
issuer (iss
) claim for signed JWTs. This is the only method of providing the issuer claim for JWTs.
vault write jwt/roles/test-role issuer=test.example.com
Roles can additionally include any other claims that are allowed by the configuration.
echo claims '{"claims": {"groups":"test-group"}}' | vault write jwt/roles/test-role -
vault
cli, you need to pass claims
in as JSON.
ℹ️ Any claims set in a role's claims
field must be explicitly allowed in the
plugin's configuration and can no longer be set during a sign request.
Roles can additionally include any other headers that are allowed by the configuration.
echo claims '{"headers": {"iss":"some-key-issuer"}}' | vault write jwt/roles/test-role -
vault
cli, you need to pass headers
in as JSON.
ℹ️ Any headers set in a role's headers
field must be explicitly allowed in the
plugin's configuration.
The role can be configured to restrict the audience (aud
) and subject (sub
) claims to
those matching a specific pattern; this restriction is in addition to the pattern restrictions
defined in the configuration. By default, both claims are unrestricted.
vault write jwt/roles/test-role subject_pattern=*.example.com
vault write jwt/roles/test-role audience_pattern=*.example.com
Signing a JWT requires a role be configured and is easily done using the sign
service,
providing the role name.
Sign a JWT with default configured claims.
vault write -f jwt/sign/test-role
Additionally, when signing a JWT, any claims allowed by the allowed_claims
configuration and
can be specified.
echo claims '{"claims": {"groups":"test-group"}}' | vault write jwt/sign/test-role -
claims
field, it cannot
be overridden during the sign request.
The plugin uses the same mechanism as the builtin Transit
secrets engine. Using keysutil
ensures the key management and rotation is built on a solid cryptographic engine.
The original plugin started life as a learning exercise for Ian Fox and I'd like to thank him for his initial proof-of-concept. As we make improvements he has kindly allowed us to take over the project and move it forward.
We have taken the original proof-of-concept and rewrote it in hopes of providing a solid plugin that can be used in production.
- Vault Website: https://www.vaultproject.io
- Main Project Github: https://www.github.com/hashicorp/vault
- JWT docs: https://jwt.io