Obexal Docs

Docs/Auto-hébergement/Rotation des clés

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ôleStockageRotation
Clé de signature OIDC (RS256)Signe les access tokens et les ID tokensClé privée chiffrée en base, clé publique publiée au JWKSrotate-keys
Clé de chiffrement au repos (ENCRYPTION_KEY, AES-256-GCM)Chiffre les secrets TOTP et les clés privées OIDCVariable d'environnement, provisionnée depuis un coffreENCRYPTION_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 48

Un 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.

Attention

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.

  1. Générez la nouvelle clé : openssl rand -base64 32.
  2. Reconfigurez et redémarrez : ENCRYPTION_KEY prend la nouvelle valeur, ENCRYPTION_KEY_OLD prend 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é.
  3. Re-chiffrez les secrets existants sous la clé primaire avec reencrypt-secrets (idempotent).
  4. Tournez les clés de signature pour que la nouvelle clé de signature active soit chiffrée avec la nouvelle ENCRYPTION_KEY : lancez rotate-keys.
  5. 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 relancez reencrypt-secrets : il doit rapporter 0 re-chiffré avant d'aller plus loin.
  6. Retirez ENCRYPTION_KEY_OLD de 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-secrets
Note

Tant 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>&1

Notes 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é.