Rotation des clés
Tourner les clés de signature JWT et la clé de chiffrement au repos sans interruption : période de grâce JWKS, re-chiffrement à deux clés, calendrier et réponse à compromission.
Deux familles de clés disposent d'une procédure de rotation opérable, toutes deux conçues pour s'exécuter sans interrompre les connexions ni invalider les jetons encore en vol.
Deux clés, deux procédures
| Clé | Rôle | Stockage | Rotation |
|---|---|---|---|
| Clé de signature OIDC (RS256) | Signe les access tokens et les ID tokens | Clé privée chiffrée en base, clé publique publiée au JWKS | rotate-keys |
Clé de chiffrement au repos (ENCRYPTION_KEY, AES-256-GCM) | Chiffre les secrets TOTP et les clés privées OIDC | Variable d'environnement, provisionnée depuis un coffre | ENCRYPTION_KEY_OLD + reencrypt-secrets |
Les deux commandes sont des sous-commandes du binaire de l'auth-service, invoquées ici via Compose.
Tourner la clé de signature JWT
La rotation est sans impact pour les clients : la clé précédente est marquée retirée mais reste publiée au JWKS le temps que les jetons en vol expirent, puis elle est purgée.
docker compose -f compose.prod.yml -f compose.tls.yml --env-file .env.prod \
exec auth-service /usr/local/bin/auth-service rotate-keys 48Un seul appel fait trois choses : il génère une nouvelle clé active, retire la clé active précédente (toujours publiée), et purge toute clé retirée depuis plus que la période de grâce en heures (48 par défaut). Comme les vérificateurs sélectionnent la clé publique par kid, les jetons signés avec la clé retirée continuent de se valider jusqu'à la purge.
La période de grâce doit être au moins aussi longue que la durée de vie de vos access tokens et ID tokens (OAUTH_ACCESS_TOKEN_TTL, OAUTH_ID_TOKEN_TTL), sinon un jeton encore valide ne pourrait plus être vérifié.
Chaque rotation émet un événement d'audit oauth.signing_key.rotated (et oauth.signing_key.purged quand des clés sont purgées), visible dans le journal d'audit.
Tourner la clé de chiffrement au repos
ENCRYPTION_KEY protège des données non régénérables (secrets TOTP) : la rotation doit être sans perte. Le mécanisme garde l'ancienne clé disponible en déchiffrement seul, le temps de tout re-chiffrer sous la nouvelle.
- Générez la nouvelle clé :
openssl rand -base64 32. - Reconfigurez et redémarrez :
ENCRYPTION_KEYprend la nouvelle valeur,ENCRYPTION_KEY_OLDprend la précédente. Les lectures continuent de fonctionner (le déchiffrement essaie la clé primaire, puis chaque ancienne clé), les écritures utilisent désormais la nouvelle clé. - Re-chiffrez les secrets existants sous la clé primaire avec
reencrypt-secrets(idempotent). - Tournez les clés de signature pour que la nouvelle clé de signature active soit chiffrée avec la nouvelle
ENCRYPTION_KEY: lancezrotate-keys. - Après la période de grâce, purgez les clés de signature retirées (encore chiffrées avec l'ancienne clé) : lancez
rotate-keys 48. Puis relancezreencrypt-secrets: il doit rapporter 0 re-chiffré avant d'aller plus loin. - Retirez
ENCRYPTION_KEY_OLDde la configuration, redémarrez, et détruisez l'ancienne clé dans le coffre : plus aucune donnée n'en dépend.
reencrypt-secrets s'invoque comme rotate-keys :
docker compose -f compose.prod.yml -f compose.tls.yml --env-file .env.prod \
exec auth-service /usr/local/bin/auth-service reencrypt-secretsTant que ENCRYPTION_KEY_OLD contient encore l'ancienne clé, aucune perte n'est possible : si une étape est interrompue, il suffit de la relancer. La variable accepte plusieurs clés séparées par des virgules pour des rotations successives rapprochées.
Le re-chiffrement émet un événement d'audit crypto.secrets.reencrypted.
Quand tourner
- Clé de signature : tous les 30 à 90 jours, via un cron opérateur.
- Clé de chiffrement : selon le calendrier fixé par votre politique de sécurité, et immédiatement en cas de compromission suspectée.
- En cas de compromission : tournez sans attendre. Pour invalider les jetons signés avec une clé compromise, purgez-la du JWKS avec une période de grâce courte (par exemple
rotate-keys 0), en acceptant que les jetons non encore expirés soient rejetés : c'est précisément le but.
Un cron mensuel pour la clé de signature :
0 3 1 * * docker compose -f /opt/obexal/compose.prod.yml -f /opt/obexal/compose.tls.yml --env-file /opt/obexal/.env.prod exec -T auth-service /usr/local/bin/auth-service rotate-keys 48 >> /var/log/obexal/rotate.log 2>&1Notes opérationnelles
- Il n'y a pas d'auto-rotation in-process : les rotations sont des commandes opérateur explicites, exécutées une invocation à la fois pour éviter les courses entre réplicas.
- Sauvegardez avant chaque rotation, comme avant chaque migration.
- Le format stocké ne change pas d'une rotation à l'autre : les valeurs restent
base64(nonce || ciphertext || tag), et l'authentification GCM fait échouer très vite l'essai d'une mauvaise clé.