Files
ObsiGate/docs/COFFRE-deploiement-mise-en-service.md
T
bruno 0a7bed10bf
CI / lint (pull_request) Failing after 1s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
CI / security (pull_request) Failing after 0s
Phase 5 (issue #23) : correction numerotation etape lecteurs internes (4, pas 5)
2026-10-28 00:11:20 -04:00

20 KiB
Raw Blame History

Coffre Secrets ObsiGate — Déploiement et mise en service (Phase 5, issue #23)

Document de déploiement du coffre Secrets, issu des Phases 1a/1b (issues #17, #18), 2 (issue #20), 3 (issue #21, côté agent-commun) et 4 (issue #22, côté TikTak, Flowdeck, Kanbante).

Règles de gouvernance (non négociables) : travail en branche + pull request, jamais sur main, jamais de merge, de release, de tag, de fermeture d'issue ni de changement de label sans le go explicite de Bruno. Ce document ne décrit aucune valeur réelle : les exemples utilisent des valeurs fictives (TEST-VALUE-XXXX) et aucune KEK n'est jamais écrite ici, ni dans un journal, ni dans une note, ni dans le dépôt.

Documents compagnons (même branche de référence, Phase 2) :

  • docs/COFFRE-contrat-distribution.md — contrat POST /api/coffres/distribuer pour Spark, TikTak, Flowdeck, Kanbante (authentification par consommateur, contextes, codes d'erreur stables).
  • docs/COFFRE-sauvegarde-restauration.md — sauvegarde (data/coffres.json + data/coffres_valeurs.json, 0600) et restauration sur instance vierge, perte de KEK.

1. Prérequis et variables d'environnement

1.1 Dépendance

  • cryptography>=44.0 dans backend/requirements.txt (dépendance Phase 1a). Sans elle, les opérations de chiffrement répondent 503 crypto_indisponible : c'est un mode dégradé explicite, pas un repli en clair. Il n'y a jamais de stockage ni de distribution de valeur en clair.
  • Chiffrement par enveloppe AES-256-GCM : KEK (256 bits) → DEK par coffre → valeur du secret. Un tag GCM invalide refuse le déchiffrement (testé : tests/test_coffre_secrets.py, test_tag_invalide_refuse).

1.2 Variables

Variable Rôle Défaut
OBSIGATE_SECRETS_ENABLED Activation du coffre. Tant qu'elle n'est pas à true, toutes les routes coffre répondent 503 secrets_desactives. désactivé
OBSIGATE_KEK KEK, base64 de 32 octets (ou hex 64 caractères). Prioritaire sur le fichier si les deux sont définis. absente
OBSIGATE_KEK_FILE Chemin d'un fichier contenant la KEK, en 0600, hors dépôt et hors arborescence des vaults. Un fichier trop permissif (droits groupe/autre) est refusé explicitement (KEKInvalideError). absent
OBSIGATE_DATA_DIR Répertoire des données (data/ par défaut) : coffres.json, coffres_valeurs.json et l'historique api_keys.json. data

La KEK est générée une seule fois (admin, coffres activés) via POST /api/coffres/admin/generer-kek ; ObsiGate ne la stocke jamais. Au moment de sa génération : la copier immédiatement dans un gestionnaire de mots de passe ou sur un support hors ligne, hors du serveur ; vérifier la copie en la relisant depuis le gestionnaire, jamais depuis l'écran qui ne l'affichera plus. Ne jamais la coller dans le dépôt, dans une note ObsiGate, dans le même dossier que data/, ni dans un journal.

Recommandation de déploiement : préférer OBSIGATE_KEK_FILE (fichier 0600) à OBSIGATE_KEK en variable d'environnement, pour éviter que la KEK apparaisse dans un historique de processus ou un dump d'environnement. Si les deux sont définis, OBSIGATE_KEK l'emporte — vérifier qu'aucune ancienne valeur ne traîne dans l'environnement au moment de basculer sur le fichier.

1.3 Ce que la sauvegarde contient (rappel)

Deux fichiers seulement portent le coffre, tous deux en 0600 :

  • data/coffres.json : métadonnées des coffres, DEK chiffrées, consommateurs (hashes SHA-256 des jetons seulement, jamais le jeton en clair), octrois, métadonnées des secrets ;
  • data/coffres_valeurs.json : versions chiffrées des secrets.

Copiés seuls, ils ne donnent accès à aucune valeur. La KEK ne fait pas partie de la sauvegarde : elle est copiée à part, hors serveur. Sauvegarder les deux fichiers et la KEK au même endroit annulerait la protection du chiffrement.

2. Décisions ouvertes de la spec — tranchées par défaut

Les décisions ouvertes de la spec (§15, §16), rappelées dans l'issue #23, sont confirmées comme tranchées selon les défauts recommandés, en attendant une décision contraire explicite de Bruno. Aucune d'elles ne nécessite de changement de code pour la mise en service décrite ci-dessous :

Décision Choix confirmé par défaut
Glyphe du nœud Secrets Cadenas U+1F510 (🔐), distinct de la maison du Home et du diamant du Partage. Le nœud est un nœud virtuel sous le Home de l'utilisateur courant seulement (voir §4).
Stockage de la KEK Par fichier 0600, hors dépôt (OBSIGATE_KEK_FILE) ; OBSIGATE_KEK reste supportée comme solution de secours.
Expiration des octrois 90 jours par défaut à la création, renouvelable (création d'un nouvel octroi). Un octroi expiré refuse la distribution (octroi_expire, 403) ; un octroi révoqué refuse dès l'appel suivant (octroi_revoque, 403).
Copie manuelle Presse-papiers seul, sans affichage de la valeur. Patron supporté : consommateur temporaire créé par un admin puis révoqué immédiatement, auditée comme distribution de contexte copie_manuelle. Aucun endpoint dédié de copie par session n'est ajouté : il contredirait la règle « distribution réservée aux consommateurs » et exigerait une ré-authentification qui n'existe pas encore — décision volontaire, à trancher avec Bruno avant toute implémentation.
Coffres partagés Phase 2 ultérieure. En mise en service : coffres personnels d'abord, plus le coffre d'instance « ObsiGate interne » (slug obsigate-interne, admin seulement) issu de l'import.

3. Mise en service pas à pas

Ordre impératif. Chaque étape est validée avant de passer à la suivante. En particulier : les lecteurs internes sont branchés sur le coffre AVANT toute purge du clair (étape 4 avant étape 7), et la purge n'a lieu qu'après validation complète (étape 6).

Étape 0 — Préparer, coffre encore désactivé

  1. Déployer le code coffre (Phases 1–2) sur l'instance, sans activer le flag : OBSIGATE_SECRETS_ENABLED reste absent ou à false. Vérifier que toutes les routes /api/coffres/* répondent 503.
  2. Sauvegarder l'état actuel : copie de data/ (dont api_keys.json) et de la configuration, hors serveur.
  3. Générer la KEK (voir §1.2) et la copier hors serveur. Ne pas encore la référencer dans la configuration de l'instance si l'étape 1 n'est pas terminée — une KEK présente sans flag actif ne distribue rien, mais sa présence doit rester traçable.

Étape 1 — Créer les consommateurs et les octrois

Avant d'activer le flag, préparer les consommateurs nommés, un par application cliente (convention de nommage, non imposée par le code — l'autorisation porte sur le jeton, pas sur le nom) :

Consommateur Usage Contexte de distribution
spark Agent Spark (agent-commun, Phase 3) contexte_type = "mission", contexte_id = id de mission (forme imbriquée contexte: {type, id} acceptée)
tiktak Synchronisation TikTak (Phase 4) contexte_type = "tiktak_sync", contexte_id = id utilisateur
flowdeck Connecteurs Flowdeck (Phase 4) Contexte workspace, vérifié côté Flowdeck (voir §5)
kanbante Webhooks Kanbante (Phase 4) Contexte webhook, distribution à chaque appel puis oubli

Création en une seule opération avec les octrois :

POST /api/coffres/consommateurs/avec-octrois
{"nom": "tiktak", "octrois": [{"coffre_slug": "...", "secret_nom": "..."}]}
  • secret_nom absent = tout le coffre (octroi coffre-wide).
  • Le jeton (csk_…) n'est rendu qu'une seule fois, à la création. Le noter immédiatement dans le coffre de configuration de l'application cliente concernée, jamais dans une note ObsiGate ni dans le dépôt. Il n'est jamais relisté : GET /api/coffres/consommateurs ne renvoie que id, nom, jeton_prefixe, statut, cree_par, cree_le.
  • La révocation est immédiate : DELETE /api/coffres/consommateurs/{id} révoque toutes les distributions de ce consommateur, sans toucher aux autres. Une mission Spark déjà démarrée conserve son environnement jusqu'à sa fin ; la mission suivante repasse en acces_requis.

À ce stade, les consommateurs et octrois peuvent être créés alors que les secrets n'existent pas encore : les octrois pointent vers des coffres/secrets à créer aux étapes suivantes. Vérifier la liste des consommateurs et des octrois avant d'activer le flag.

Étape 2 — Activer le flag

  1. Définir OBSIGATE_KEK_FILE (fichier 0600, hors dépôt) puis OBSIGATE_SECRETS_ENABLED=true.
  2. Redémarrer l'instance.
  3. Vérifier : la vue Secrets s'ouvre ; GET /api/coffres répond 200 (liste vide ou coffres existants) ; sans KEK valide, les routes répondent 503 kek_absente — dans ce cas, corriger la KEK avant toute création de secret.

Étape 3 — Importer l'existant (admin seulement)

Bouton « Importer l'existant » de la vue Secrets (admin seulement), en deux temps séparés :

  1. Aperçu : GET /api/coffres/migration/api-keys liste les candidats de data/api_keys.json (clés IA et clés d'outils).
  2. Import : POST /api/coffres/migration/api-keys/import copie les entrées avec valeur vers le coffre d'instance « ObsiGate interne » (slug obsigate-interne), chiffrées. Un secret déjà présent n'est jamais écrasé. api_keys.json n'est pas modifié par l'import.

Vérifier après import : les secrets importés apparaissent dans la vue Secrets avec has_value = true, et aucune valeur n'est visible (métadonnées seules : nom, type, version, dates, octrois actifs — jamais de valeur, jamais de texte chiffré, jamais de nonce).

Étape 4 — Brancher les lecteurs internes AVANT toute purge

Point de vigilance principal de la mise en service. Les lecteurs actuels d'ObsiGate (backend/ai.py, backend/tools/secrets.py) lisent encore data/api_keys.json directement ; ils ne sont pas encore branchés sur le coffre (reste à faire explicite des Phases 2–5). Tant que ce branchement n'est pas livré et validé, purger le clair priverait l'IA et les outils de leurs clés.

Ordre à respecter :

  1. Ne pas purger.
  2. Livrer (en branche + PR, jamais mergé sans go) le branchement des lecteurs internes sur une distribution depuis le coffre d'instance, puis le valider sur l'instance : l'IA et les outils fonctionnent avec api_keys.json rendu temporairement inaccessible en lecture (test de coupure contrôlée), ou à défaut avec un secret de test distribué depuis le coffre.
  3. Seulement après cette validation, passer à l'étape 7.

Si Bruno accepte une interruption temporaire de l'IA/outils, le documenter explicitement avant la purge ; sinon, la purge attend le branchement.

Étape 5 — Brancher les applications clientes

Pour chaque application (branches dédiées, jamais mergées sans go), appliquer le patron commun distribuer → utiliser → oublier : le serveur distribue à chaque opération, utilise la valeur en mémoire, puis l'oublie ; aucune valeur en cache, aucune persistance. Si ObsiGate est indisponible, l'opération échoue proprement, sans repli sur une ancienne valeur.

  • Spark (agent-commun, Phase 3) : identités référencées coffre:<slug>/<nom> (slug 3–64, nom 3–128, minuscules / chiffres / - / _), sans colonne valeur ; le LLM ne voit que la référence et un accès oui/non. Sans octroi, la mission passe en acces_requis en nommant l'accès manquant ; avec octroi et mission approuvée (enveloppe budgétaire), distribution puis injection en variable d'environnement de l'exécuteur, jamais dans le prompt, jamais dans les étapes d'activité. Fin de mission = environnement détruit.
  • TikTak (Phase 4) : référence seule stockée ; distribution serveur à chaque synchro ; masquage côté Android (ni référence complète ni valeur dans les réponses vues par une clé tck_). Les configurations existantes sont marquées a_resaisir = 1 (migration 019 : colonne en clair supprimée, référence vide) : il n'y a pas de conversion automatique — chaque configuration doit être re-saisie par référence.
  • Flowdeck (Phase 4) : workspace_id + référence sur les connecteurs ; distribution en mémoire ; isolation A vs B testée (un workspace ne distribue pas avec l'octroi d'un autre). Les connecteurs natifs Gitea/GitHub et les clés LLM ne sont pas basculés ; pas de sélecteur UI (API seulement).
  • Kanbante (Phase 4) : secret webhook migré en référence, distribuée à chaque webhook puis oubliée ; client générique réutilisable. Les jetons forge chiffrés Fernet ne sont pas migrés (distribution par appel requise, incompatible avec le cache ForgeRegistry) ; les secrets webhook historiques en clair restent acceptés par compatibilité, sans migration automatique.

Étape 6 — Valider (avant purge)

Exécuter la checklist de validation de l'instance réelle (§4). Tant qu'un point échoue, ne pas purger et ne pas considérer la mise en service comme terminée.

Étape 7 — Purger, seulement après validation

POST /api/coffres/migration/api-keys/purger, après confirmation explicite (confirmer = true), et uniquement pour les noms migrés. Sans confirmation, rien n'est supprimé — jamais automatiquement.

Après purge, vérifier :

  • api_keys.json ne contient plus les noms purgés ;
  • l'IA et les outils fonctionnent toujours (lecteurs branchés, étape 4) ;
  • une distribution de test via un consommateur existant aboutit (succès seul vérifié, valeur ni affichée ni consignée).

Étape 8 — Sauvegarder et tester la restauration

  1. Sauvegarder data/coffres.json + data/coffres_valeurs.json (0600) par la sauvegarde habituelle du serveur, et la KEK à part, hors serveur (§1.3).
  2. Rejouer la restauration sur une instance vierge (procédure docs/COFFRE-sauvegarde-restauration.md §3 : arrêt de la cible, copie des deux fichiers en 0600, même KEK, redémarrage, vérification has_value = true + distribution de test + distribution_ok dans le journal). La suite automatisée (tests/test_coffre_secrets.py, classe TestRestaurationVierge) simule cette restauration ; elle ne remplace pas un essai sur instance réelle.
  3. En cas de perte de KEK : perte d'accès assumée, par construction — aucun moyen de récupérer les valeurs. Recréer les coffres avec une nouvelle KEK, régénérer les valeurs côté fournisseur, révoquer les anciennes côté fournisseur, recréer consommateurs et octrois. Ne jamais tenter de « réparer » les anciens fichiers avec une nouvelle KEK (le déchiffrement échouera sur tag GCM invalide, ce qui est attendu).

4. Checklist de validation par Bruno sur instance réelle

Validation exigée par l'issue #23 avant tout usage réel. Chaque point est validé sur capture, comme pour les autres livraisons :

  • Sidebar : le nœud « Secrets » (cadenas U+1F510) apparaît sous le Home de l'utilisateur courant seulement, et pas sous les autres vaults ; cliquer dessus ouvre la vue Secrets sans naviguer dans les fichiers.
  • Secret de test : création d'un secret de test (valeur fictive, ex. TEST-VALUE-XXXX) dans un coffre personnel, puis lecture : métadonnées seules, aucune valeur affichée, aucun texte chiffré, aucun nonce.
  • Journal d'audit : après une distribution de test, le journal contient distribution_ok pour la référence, et un grep du journal sur la valeur de test ne renvoie rien (ni valeur, ni texte chiffré, ni nonce, ni jeton).
  • Autorisations : un admin ne voit pas le coffre personnel d'autrui ; une ACL * sur un vault ne donne aucun droit sur un coffre ; une session utilisateur, même admin, ne distribue pas (401 consommateur_invalide).
  • Distribution : contexte manquant refusé (400 contexte_manquant) ; octroi révoqué refusé dès l'appel suivant (403 octroi_revoque) ; octroi expiré refusé (403 octroi_expire) ; secret expiré refusé (410 secret_expire).
  • Import / purge : l'import ne modifie pas api_keys.json et n'écrase pas un secret existant ; la purge sans confirmation explicite ne supprime rien.
  • Restauration : sauvegarde + KEK externe restaurées sur une instance vierge, distributions fonctionnelles.
  • Intégrations : mission Spark en acces_requis sans octroi, puis injection exécuteur sans trace dans les étapes ; synchro TikTak sans valeur côté Android ; isolation workspace Flowdeck (A ne distribue pas avec l'octroi de B).
  • Modale : la modale de création de secret n'est plus coupée en haut (.obsigate-modal bornée au viewport, corps seul défilant, en-tête et pied toujours visibles).

5. Limites connues à la mise en service

À lire avant de déclarer la mise en service terminée :

  • Lecteurs internes non branchés (ai.py, tools/secrets.py lisent encore api_keys.json en clair) : purger avant de les brancher priverait l'IA/outils de leurs clés (§3, étape 4).
  • Endpoint dédié de copie manuelle par session non ajouté (décision volontaire, §2) : la copie manuelle passe par un consommateur temporaire admin, presse-papiers seul.
  • Isolation workspace Flowdeck non contrôlée côté ObsiGate : /distribuer n'autorise aujourd'hui que les octrois de type consommateur ; l'isolation workspace repose sur le serveur Flowdeck lui-même (il ne demande que les références de son workspace, avec son propre jeton). Un contrôle serveur strict reste à spécifier si Bruno l'exige.
  • Identités agent-commun : détenteur libre hors coffre conservé (historique) ; révocation Identités non propagée vers ObsiGate (pas d'endpoint) ; exécuteurs CLI synchrones uniquement.
  • TikTak : « sélecteur » = saisie validée, pas de liste déroulante (aucune API de listage côté ObsiGate) ; configurations existantes à re-saisir, pas de conversion automatique.
  • Flowdeck : natifs Gitea/GitHub et clés LLM non basculés ; pas de sélecteur UI (API seulement) ; workspace_id pas encore branché depuis AgentEngine.
  • Kanbante : jetons forge chiffrés Fernet non migrés ; pas de migration automatique des secrets webhook historiques.
  • Recherche / graphe / index : par construction, un coffre n'est pas un vault de notes — il ne passe ni par l'indexeur, ni par la recherche, ni par le graphe, et aucun outil MCP de secret n'existe. Aucune métadonnée de secret ne doit apparaître dans ces index (vérification par revue de code, pas de suite automatisée dédiée — voir le bilan Phase 5).

6. Repli (rollback)

  • Désactiver le flag (OBSIGATE_SECRETS_ENABLED absent ou false) : toutes les routes coffre répondent 503, les fichiers coffres.json / coffres_valeurs.json restent intacts et re-distribueront dès réactivation avec la même KEK.
  • Révoquer un consommateur (DELETE /api/coffres/consommateurs/{id}) coupe immédiatement ses distributions, sans toucher aux autres consommateurs ni aux secrets.
  • Ne jamais « réparer » des fichiers chiffrés avec une nouvelle KEK, ne jamais restaurer coffres.json sans coffres_valeurs.json (et inversement), ne jamais copier la KEK au même endroit que la sauvegarde.

7. Références

  • Spec « Coffre de secrets ObsiGate » §6 (autorisations), §7/§10/§11 (API), §13/§14 (sauvegarde/restauration), §15/§16 (tests, déploiement).
  • Issues ObsiGate : #17 (Phase 1a), #18 (Phase 1b), #19 (Phase 1c UI), #20 (Phase 2), #21 (Phase 3, Spark), #22 (Phase 4, TikTak / Flowdeck / Kanbante), #23 (Phase 5, ce document).
  • Suites de tests : tests/test_coffre_secrets.py (ObsiGate, 35 tests), tests/test_identites_coffre_phase3.py (agent-commun, 16 tests), tests/test_coffre_reference.py (TikTak, 17 fonctions / 26 cas), tests/test_coffre_reference_flowdeck.py (Flowdeck, 16 tests), tests/test_coffre_client.py (Kanbante, 11 fonctions / 25 cas). Voir le bilan Phase 5 pour la matrice de couverture complète et les manques.