7.6 KiB
Coffre Secrets ObsiGate — Sauvegarde et restauration (Phase 2, issue #20)
Ce document décrit la seule procédure supportée pour sauvegarder et restaurer le coffre Secrets, ainsi que le cas de perte de la KEK. Il complète la spécification du coffre (§13, §14) ; il ne décrit aucune valeur réelle — les exemples utilisent des valeurs fictives.
1. Ce qui constitue une sauvegarde
Deux fichiers, et seulement ces deux fichiers, portent le coffre :
| Fichier | Contenu |
|---|---|
data/coffres.json |
Coffres (métadonnées + DEK chiffrées), consommateurs (hashes de jetons seulement), octrois, métadonnées des secrets. |
data/coffres_valeurs.json |
Versions chiffrées des secrets (AES-256-GCM). |
Ces deux fichiers forment une sauvegarde ordinaire chiffrée : copiés seuls, ils ne donnent accès à aucune valeur — chaque DEK est chiffrée par la KEK, chaque valeur est chiffrée par la DEK de son coffre. Ils peuvent donc transiter par la sauvegarde habituelle du serveur, avec les mêmes permissions restrictives que sur l'instance (0600). Ils ne doivent jamais être déposés dans le dépôt Git, ni dans une note ObsiGate, ni dans un journal.
La KEK ne fait pas partie de la sauvegarde : elle est copiée à part, hors serveur (voir §2). Sauvegarder les deux fichiers et la KEK au même endroit annulerait la protection du chiffrement.
2. Sauvegarder la KEK (copie externe, hors serveur)
La KEK est une clé maître 256 bits (base64 de 32 octets), générée une
seule fois via POST /api/coffres/admin/generer-kek (admin, coffres
activés) et jamais stockée par ObsiGate. Au moment de sa
génération :
- Copiez-la immédiatement dans un gestionnaire de mots de passe ou sur un support hors ligne, hors du serveur ObsiGate.
- Ne la collez jamais dans le dépôt, jamais dans une note ObsiGate,
jamais dans le même dossier que
data/, jamais dans un journal. - Vérifiez la copie (relisez-la depuis le gestionnaire, pas depuis l'écran qui ne l'affichera plus jamais).
Variables d'environnement (documentées aussi dans .env.example,
sans aucune valeur) :
| Variable | Rôle | Défaut |
|---|---|---|
OBSIGATE_SECRETS_ENABLED |
Activation du coffre. Toute route répond 503 tant qu'elle n'est pas activée. | désactivé |
OBSIGATE_KEK |
KEK, base64 de 32 octets (ou hex 64 caractères). Prioritaire sur le fichier. | absente |
OBSIGATE_KEK_FILE |
Chemin d'un fichier 0600, hors dépôt et hors arborescence des vaults, contenant la KEK. Un fichier trop permissif est refusé. | absent |
OBSIGATE_DATA_DIR |
Répertoire des données (data/ par défaut), donc des deux fichiers du coffre et de api_keys.json. |
data |
Aucune autre variable ne doit contenir une valeur de secret du
coffre. En particulier, ne créez jamais de variable du type
COFFRE_<nom> avec une valeur : la seule valeur externe autorisée est
la KEK, et elle n'est pas une valeur de secret du coffre.
3. Restauration sur une instance (y compris vierge)
Prérequis : une instance ObsiGate avec le coffre activé
(OBSIGATE_SECRETS_ENABLED), et la même KEK que l'instance
d'origine.
- Arrêtez l'instance cible (ou, à défaut, assurez-vous qu'aucune écriture n'a lieu pendant la copie).
- Restaurez les deux fichiers
coffres.jsonetcoffres_valeurs.jsondans le répertoire de données cible, en permissions 0600. - Re-présentez la même KEK, via
OBSIGATE_KEKou viaOBSIGATE_KEK_FILE(fichier 0600, hors dépôt). - Redémarrez l'instance.
- Vérifiez, sans jamais afficher ni journaliser de valeur :
- la vue Secrets liste les coffres et les secrets avec
has_value = true; - une distribution de test via un consommateur existant (ou un consommateur de test, révoqué ensuite) aboutit — côté serveur, le succès seul est vérifié, la valeur n'est ni affichée ni consignée ;
- le journal d'audit contient
distribution_okpour cette référence.
- la vue Secrets liste les coffres et les secrets avec
Test de restauration obligatoire avant la mise en service réelle
(issue #20) : la procédure ci-dessus a été automatisée dans la
suite de tests (tests/test_coffre_secrets.py, classe
TestRestaurationVierge) — copie des deux fichiers vers un
répertoire vierge, même KEK, distributions fonctionnelles — et doit
aussi être rejouée une fois sur une instance réelle par l'opérateur
avant de considérer la sauvegarde comme valide.
4. Perte de la KEK : perte d'accès assumée, par construction
Si la KEK est perdue, il n'existe aucun moyen de récupérer les valeurs : les DEK ne sont stockées que chiffrées par la KEK, et les valeurs ne sont stockées que chiffrées par les DEK (AES-256-GCM, authentifié). Il n'y a ni clé de secours, ni dérivation depuis le secret d'application, ni porte dérobée.
Procédure en cas de perte :
- Considérez toutes les valeurs du coffre comme perdues.
- Créez de nouveaux coffres avec une nouvelle KEK.
- Pour chaque secret perdu, régénérez la valeur côté fournisseur (nouvelle clé API, nouveau jeton) et créez un nouveau secret avec cette nouvelle valeur ; révoquez les anciennes valeurs côté fournisseur, car elles restent valides hors d'ObsiGate tant qu'elles ne sont pas révoquées là-bas.
- Recréez les consommateurs et les octrois (les anciens octrois pointent vers des secrets qui n'existent plus dans les nouveaux coffres).
- Ne tentez pas de « réparer » les anciens fichiers avec une nouvelle KEK : le déchiffrement échouera (tag GCM invalide), ce qui est le comportement attendu, pas un bug.
La rotation KEK (POST /api/coffres/admin/rotation-kek) n'est pas
une récupération : elle exige l'ancienne KEK encore valide pour
re-chiffrer les DEK, puis le remplacement de la KEK dans
l'environnement ou le fichier KEK.
5. Migration api_keys.json (Phase 2) — avertissement important
Le bouton « Importer l'existant » de la vue Secrets (admin seulement)
copie les clés IA et clés d'outils de data/api_keys.json vers le
coffre d'instance « ObsiGate interne » (slug obsigate-interne),
en deux temps :
- Import : les entrées avec valeur sont chiffrées dans le coffre
(un secret déjà présent n'est jamais écrasé) ;
api_keys.jsonn'est pas modifié. - Purge : après confirmation explicite, et uniquement pour les noms migrés, les entrées sont supprimées du clair. Sans confirmation, rien n'est supprimé — jamais automatiquement.
Avertissement : les lecteurs actuels d'ObsiGate
(backend/ai.py, backend/tools/secrets.py) lisent encore
api_keys.json directement ; ils ne sont pas encore branchés sur le
coffre. Purger le clair prive donc ces lecteurs de la clé tant que
leur lecteur n'est pas branché sur une distribution depuis le coffre.
Ne purgez que si vous avez vérifié que le consommateur concerné
distribue depuis le coffre, ou si vous acceptez cette interruption.
Ce branchement des lecteurs internes est un reste à faire explicite
(voir §6).
6. Restes à faire (après cette Phase 2)
- Brancher les lecteurs internes (
ai.py,tools/secrets.py) sur une distribution depuis le coffre d'instance, pour que la purge du clair ne coupe pas l'IA ni les outils. - Rejouer le test de restauration sur une instance réelle (la suite automatisée simule une instance vierge, elle ne remplace pas un essai sur l'instance de production).
- Endpoint de distribution par session utilisateur (copie manuelle sans consommateur temporaire) : non ajouté ici, car il contredirait la règle Phase 1b (distribution réservée aux consommateurs) et exigerait une ré-authentification qui n'existe pas encore — décision à prendre avec l'opérateur avant toute implémentation.