Aller au contenu
Lecture : 0 %

Secrets : la rotation comme test de vérité

Pourquoi un secret que vous n'avez jamais tourné est un secret dont vous ignorez les copies, et comment concevoir chaque secret pour que sa rotation soit un geste banal plutôt qu'une opération à risque.

Par Elias Varen6 min de lecture

Soit le mot de passe de votre base de production. S'il fallait le changer dans l'heure, combien de processus tomberaient, et lesquels ? Si la réponse est « il faudrait regarder », vous connaissez la valeur de ce secret, mais pas ses copies.

La rotation n'est pas une mesure d'hygiène à cocher tous les quatre-vingt-dix jours, c'est le seul test qui révèle où un secret vit réellement. Pour un secret jamais tourné, l'inventaire reste une hypothèse.

Ce que la première rotation révèle

La première fois que j'ai tourné la clé d'un prestataire de mail sur notre plateforme, le fichier d'environnement du serveur a été mis à jour, l'application redémarrée, les envois vérifiés. Deux jours plus tard, les mails d'une tâche planifiée échouaient, parce que le worker tournait dans un conteneur lancé des semaines plus tôt, avec l'ancienne valeur en mémoire, et que personne ne l'avait redémarré. L'incident était bénin, et il apportait précisément l'information que la rotation devait produire.

Les copies qu'une rotation fait apparaître sont toujours les mêmes :

  • le processus longue durée qui a lu la variable au démarrage ;
  • la variable de CI, dupliquée dans deux repos ;
  • le poste d'un développeur, dans un .env.local jamais nettoyé ;
  • le script de sauvegarde, qui a sa propre copie du mot de passe ;
  • l'image Docker construite avec le secret en argument de build, donc lisible dans ses couches ;
  • le tableau de bord d'un outil tiers, où quelqu'un l'a collé « pour tester ».

Chacune est une surface de fuite que personne ne surveillait. On ne révoque vite que ce qu'on sait remplacer vite, et le jour d'une fuite est le plus mauvais moment pour le découvrir.

Deux valeurs valides en même temps

Une rotation sans interruption exige une période où l'ancienne et la nouvelle valeur sont acceptées toutes les deux. Un secret qui ne permet pas ce recouvrement impose une coupure, et une rotation qui impose une coupure ne sera jamais faite.

t0   A valide               tout le monde utilise A
t1   A et B valides         on déploie B chez les consommateurs
t2   A et B valides         on vérifie que plus personne n'utilise A
t3   B valide               A est révoquée

L'étape t2 est celle qu'on saute, et sans mesure de qui utilise encore A, t3 devient un pari.

Pour PostgreSQL, je ne change pas le mot de passe d'un rôle. J'alterne entre deux rôles de connexion qui héritent du même rôle propriétaire.

SQL
-- Les droits sont portés par un rôle sans connexion
CREATE ROLE app_owner NOLOGIN;

-- Deux rôles de connexion, un seul actif à la fois
CREATE ROLE app_a LOGIN PASSWORD '…' IN ROLE app_owner;
CREATE ROLE app_b LOGIN PASSWORD '…' IN ROLE app_owner;

-- t2 : qui utilise encore l'ancien ?
SELECT usename, application_name, client_addr, count(*)
  FROM pg_stat_activity
 WHERE usename = 'app_a'
 GROUP BY 1, 2, 3;

-- t3 : l'ancien ne peut plus se connecter
ALTER ROLE app_a NOLOGIN;

La requête sur pg_stat_activity sert de test de vérité. Tant qu'elle retourne une ligne, une copie de l'ancien secret reste en service, et le application_name renseigné par chaque consommateur indique laquelle.

Pour les secrets de signature (le secret d'un webhook, la clé qui signe les cookies de session ou les tokens), le recouvrement se fait à la vérification. Le code doit accepter une liste de clés en vérification et n'en utiliser qu'une en signature.

PHP
final class SigningKeys
{
    /** @param list<string> $previous clés encore acceptées en vérification */
    public function __construct(
        private readonly string $current,
        private readonly array $previous = [],
    ) {}

    public function sign(string $payload): string
    {
        return hash_hmac('sha256', $payload, $this->current);
    }

    public function verify(string $payload, string $signature): bool
    {
        foreach ([$this->current, ...$this->previous] as $key) {
            if (hash_equals(hash_hmac('sha256', $payload, $key), $signature)) {
                return true;
            }
        }
        return false;
    }
}

Vingt lignes, à écrire le jour où le secret est introduit. Les écrire le jour de la fuite, sous pression, sur un chemin d'authentification, est une tout autre affaire.

Classer les secrets par ce que coûte leur rotation

Les secrets ne se tournent pas tous de la même manière, et c'est ce classement qui rend l'inventaire utile.

TypeRecouvrementCe qui casse en cas d'erreur
Clé d'API d'un prestatairedeux clés actives côté prestataireles appels sortants
Mot de passe de basedeux rôles alternéstoute l'application
Secret de webhook entrantliste en vérificationles événements entrants, en silence
Clé de signature de sessionliste en vérificationtout le monde est déconnecté
Clé de chiffrement de donnéesversions de clé, rechiffrementdes données illisibles, définitivement

La dernière ligne est d'une autre nature. Tourner une clé de chiffrement ne se limite pas à remplacer une valeur, puisque les données chiffrées avec l'ancienne doivent rester lisibles. C'est le sujet du chiffrement par enveloppe, et la raison pour laquelle je ne mets jamais une clé de chiffrement de données dans le même circuit que les autres secrets.

Le secret de webhook mérite aussi une attention particulière, car une rotation ratée ne fait rien tomber. Les événements sont simplement rejetés, le prestataire fait des retries pendant quelques jours, puis abandonne, et on apprend la panne par un client dont le paiement n'a jamais activé le compte.

La rotation automatique qui coupe à date fixe

Le coût est d'abord du code. Chaque point de vérification doit accepter plusieurs clés, chaque consommateur doit savoir recharger sa configuration ou être redémarré de façon ordonnée. Vient ensuite l'exploitation, avec une procédure écrite par secret, jouée au moins une fois. Chez nous, un secret n'est pas « en production » tant que sa rotation n'a pas été exécutée une fois, par quelqu'un d'autre que celui qui l'a créé.

La rotation a son propre mode de défaillance, l'automatisation qui tourne un secret sans vérifier que les consommateurs ont suivi. Une tâche planifiée qui régénère un mot de passe chaque mois et l'écrit dans le coffre fonctionne parfaitement jusqu'au jour où un consommateur ne relit pas le coffre. La panne arrive alors à date fixe, souvent la nuit, et sa cause (un changement que personne n'a fait à la main) est la dernière chose que l'astreinte ira chercher. Sans l'étape t2, une rotation automatique programme une panne.

La rotation qui ne révoque rien pose un problème voisin. Générer une nouvelle clé chez le prestataire et laisser l'ancienne active « au cas où » revient à avoir deux secrets au lieu d'un.

Les secrets qu'on ne tourne pas au calendrier

Dans certains cas, la rotation calendaire est du travail sans bénéfice.

Les secrets à durée de vie courte par construction. Un token d'accès qui expire en une heure, un identifiant émis par fédération d'identité entre votre CI et votre fournisseur cloud : il n'y a rien à tourner, et c'est la meilleure situation possible. Chaque secret statique remplacé par une identité de charge de travail est une ligne en moins dans l'inventaire.

Les mots de passe des humains. Forcer leur changement périodique produit des variantes prévisibles. Mieux vaut les changer sur signal de compromission, et mettre l'effort sur le second facteur.

Les secrets qu'on ne sait pas tourner sans coupure. Pour ceux-là, on corrige d'abord la conception avant de tourner quoi que ce soit. Une rotation ratée en production convainc l'équipe de ne plus jamais y toucher, ce qui fait perdre plus qu'on n'a gagné.

L'inventaire, secret par secret

Pour chaque secret de l'inventaire, il faut pouvoir répondre à ceci :

  • Où vit-il ? La liste des copies, vérifiée par une rotation et non déclarée de mémoire.
  • Admet-il deux valeurs valides en même temps ? Sinon, c'est le premier chantier.
  • Comment sait-on que l'ancienne valeur n'est plus utilisée ? Une requête, un log, un compteur, mais pas « on a redéployé ».
  • Quand a-t-il été tourné pour la dernière fois, et par qui ? Si la réponse est « jamais », les trois réponses précédentes ne sont pas connues non plus.

Un secret qui passe ces quatre questions peut être révoqué en dix minutes le jour où il apparaît dans les logs d'une CI.

Secrets : la rotation comme test de vérité · Deepstack