Keys & Secrets
Inventory
| What | Where it lives | Supplied as | Lose it and… |
|---|---|---|---|
| Main org's signing keys (EC P-256 for ES256, RSA ≥ 2048 for RS256) | PEM files | keys.signing_key_path, keys.rsa_signing_key_path | Every token already issued stops verifying |
| Other orgs' signing keys | The database, encrypted under the KEK | Generated when the org is created | That org's tokens stop verifying |
| Key-encryption key (KEK) | Your secret store | keys.encryption_key (_FILE) | Every non-main org's signing keys become unreadable |
| MFA seed key | Your secret store | mfa.encryption_key (_FILE) | Every enrolled TOTP factor stops working |
| Database credentials | Your secret store | database.dsn or database.password (_FILE) | |
| Cache password | Your secret store | cache.password (_FILE) | |
| gRPC and HTTP TLS certificates | Files | server.grpc.tls.*, server.http.tls.* | |
| Client secrets | The database, as argon2id hashes | Generated by the server, shown once | Nothing to recover: register a new secret |
The _FILE convention is described in
Configuration. The Helm chart mounts every
secret as a file by default.
Signing keys
The main organization signs with the PEM keys you configure under keys.* when
keys.provider is file. EC keys may be SEC1 (EC PRIVATE KEY) or PKCS#8, and RSA keys
PKCS#1 or PKCS#8. Either key alone is allowed; with only the EC key the deployment is
ES256-only and discovery says so. keys.default_alg picks the algorithm for clients that
did not choose one.
openssl ecparam -name prime256v1 -genkey -noout -out ec.key
openssl genrsa -out rsa.key 3072The Helm chart issues both keys from cert-manager instead, with rotationPolicy: Never.
Every other organization gets its own EC P-256 and RSA-2048 keys when it is created. They
are sealed with AES-256-GCM under the KEK, bound to the organization's ID, and stored in
the database. Every key's kid is its RFC 7638 thumbprint.
The JWKS lists the current public keys and any extra public keys you publish for a
rotation. Discovery documents and the JWKS are served with
Cache-Control: public, max-age=300 and cached on the server for 300 seconds, so allow
five minutes for a key change to be seen.
Rotating the main org's signing key
There is no automatic rotation. To rotate by hand:
-
Export the current key's public half:
openssl ec -in ec.key -pubout -out ec-previous.pub openssl rsa -in rsa.key -pubout -out rsa-previous.pub -
Configure the new private keys, and publish the previous public keys so tokens already issued still verify:
keys: signing_key_path: /etc/nauthera/keys/ec-new.key rsa_signing_key_path: /etc/nauthera/keys/rsa-new.key verification_key_paths: [/etc/nauthera/keys/ec-previous.pub] rsa_verification_key_paths: [/etc/nauthera/keys/rsa-previous.pub] -
Restart every replica.
-
Once the longest access and ID token lifetime has passed since the restart, remove the previous public keys and restart again.
Relying parties that cache the JWKS must refetch it when they see an unknown kid. Most
libraries do.
Organizations other than the main one cannot rotate their keys today. Rotating them without a restart is tracked in #300.
The key-encryption key
keys.encryption_key wraps the signing keys of every organization except the main one.
It must be 32 bytes, given as raw bytes, 64 hex characters or base64:
openssl rand -hex 32 > kekSet it before you create your second organization. The KEK is not one of the startup checks. Without it, the server makes up a key in memory and logs a warning. The next restart loses that key, and every organization created in the meantime can no longer sign or verify tokens.
Rotating the KEK. Each sealed key records which KEK sealed it, so a rotation needs no downtime for existing data:
- Make the new key
keys.encryption_key. - Put the old key in
keys.retired_keys. Retired keys are used only to decrypt, and they can be supplied only through the environment variableNAUTHERA_SERVER_KEYS_RETIRED_KEYS(comma-separated), not as a file (#340). - Restart.
New organizations are sealed under the new key. Existing ones are not re-sealed, so
the old key has to stay in retired_keys for as long as those organizations exist.
Re-sealing is tracked in #353.
The MFA seed key
mfa.encryption_key encrypts every TOTP seed with AES-256-GCM. It uses the same
32-byte format as the KEK, and mfa.provider: file requires it. Rotate it the same way:
the new key in mfa.encryption_key, the old one in NAUTHERA_SERVER_MFA_RETIRED_KEYS,
then restart. Seeds stay sealed under the old key until the user enrolls again, so keep
the retired key. See Multi-Factor Authentication.
Client secrets
The server generates every client secret: 256 random bits, returned once in the response that created the client, and stored only as an argon2id hash.
- Rotation means deleting the client and creating it again under the same ID. Rotation in place is tracked in #351.
- Prefer
private_key_jwtfor confidential clients you control: the server stores only the client'sjwks_uri, and rotation happens on the client's side.
Admin client secrets
Each organization's {slug}-admin-m2m machine client also has a generated secret.
- For organizations created through the admin API, it is returned once as
admin_m2m_secretin the response. - For the main organization, it is written once to
auth.admin.bootstrap_secret_path. That path is empty by default, and then the secret is thrown away. Set the path before the very first start if you want this credential. The Helm chart leaves it off (adminBootstrap.enabled: false), because the file cannot be read out of the shell-less image without a sidecar.
Neither secret can be rotated today. See Admin API & Users for other ways to get an admin token.
TLS certificates
The server reads its HTTP and gRPC certificates and the gRPC client CA once, at startup. After a renewal, restart it before the old certificate expires. Reloading without a restart is tracked in #443.
Backing up
Back these up together, and restore them together:
- the database,
- the KEK and every retired KEK,
- the MFA seed key and every retired one,
- the main org's signing key files.
A database backup without its KEK cannot restore any organization except the main one. One without the MFA key restores every user without a working second factor. See Backups & Upgrades.
Planned
- Fail on an empty secret file instead of treating it as unset, and accept retired keys as files (#336).
- Source the mounted secrets from OpenBao (#337), and hold both encryption keys there instead of in a cluster Secret (#338).