From 7dc7720b4b0af30d8cb89da9def3ebb6b1f42ebf Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 1 Aug 2026 22:37:27 -0400 Subject: [PATCH] =?UTF-8?q?client=5Fpki=20:=20l'empreinte=20du=20root=20CA?= =?UTF-8?q?=20n'est=20pas=20un=20secret=20de=20vo=C3=BBte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le rôle dérive l'empreinte à chaud depuis l'autorité, parce qu'un from-zero régénère l'AC avec une empreinte neuve. Figée en voûte, elle serait périmée dès la première reconstruction — et une empreinte périmée fait échouer le bootstrap de chaque hôte. Or `defaults/main.yml` portait encore un défaut lisant `vault_step_ca_fingerprint`. Ce défaut était mort : la tâche suivante écrase le fait sans condition, donc la valeur de la voûte n'avait aucun effet. Le recensement de voute.py s'y laissait prendre — il cherche la chaîne `vault_*` sans pouvoir savoir qu'un défaut n'est jamais lu. La « source unique » avait hérité de l'erreur, et le panneau réclamait un secret impossible à fournir avant que l'AC n'existe. Défaut retiré : la clé disparaît du recensement (25 -> 24 exigés), du gabarit et du panneau. `client_pki_ca_fingerprint_override` reste le moyen d'épingler une empreinte. `docs/intrants-communs.md` §H était une troisième copie manuelle de la liste des secrets, avec les deux mêmes erreurs ; elle renvoie maintenant à `voute.py lister` et à la preuve P18. Preuves : 24 OK, 0 échec ; voute.py verifier --strict passe. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ docs/audit/preuve-2026-08-01.md | 2 +- docs/intrants-communs.md | 18 +++++++++++++----- roles/client_pki/README.md | 17 +++++++++++++++-- roles/client_pki/defaults/main.yml | 9 +++++++-- scripts/inventory_gui.py | 7 ++++--- 6 files changed, 66 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6e5dd..86b2ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # CHANGELOG — Set-OPS +## 2026-08-01 (suite 14) — l'empreinte du root CA n'est pas un secret de voûte + +`client_pki` **dérive l'empreinte à chaud** depuis l'autorité (`step certificate +fingerprint` en `delegate_to` sur `serveur_step_ca`) — précisément parce qu'un `from-zero` +régénère l'AC avec une empreinte neuve. Une empreinte figée en voûte serait périmée dès la +première reconstruction, et une empreinte périmée fait échouer le `bootstrap` de chaque hôte. + +Or `defaults/main.yml` portait encore `client_pki_ca_fingerprint: "{{ +vault_step_ca_fingerprint | default('') }}"`. Ce défaut était **mort** : la tâche suivante +écrase le fait sans condition. La valeur de la voûte n'avait aucun effet, quel qu'en soit le +contenu. + +Le recensement de `voute.py` s'y laissait prendre — il cherche la chaîne `vault_*` dans les +fichiers, sans pouvoir savoir qu'un défaut n'est jamais lu. La « source unique » avait donc +hérité de l'erreur, et le panneau réclamait un secret impossible à fournir avant que l'AC +n'existe. + +Le défaut mort est retiré, et la clé disparaît d'elle-même du recensement (25 → 24 exigés), +du gabarit et du panneau. `client_pki_ca_fingerprint_override` reste le moyen documenté +d'épingler une empreinte (AC externe, migration). + +### Corrigé — une troisième copie manuelle de la liste des secrets +`docs/intrants-communs.md` §H énumérait les secrets à la main, avec les deux mêmes erreurs. +Elle renvoie désormais à `scripts/voute.py lister` et à la preuve P18 plutôt que d'entretenir +une copie de plus. + ## 2026-08-01 (suite 13) — le rappel des secrets dérive de `voute.py` `SECRETS_ATTENDUS` était une liste écrite à la main dans `inventory_gui.py`, en parallèle du diff --git a/docs/audit/preuve-2026-08-01.md b/docs/audit/preuve-2026-08-01.md index 50e75a4..06c18e2 100644 --- a/docs/audit/preuve-2026-08-01.md +++ b/docs/audit/preuve-2026-08-01.md @@ -30,7 +30,7 @@ | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | | P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 32 groupes (inventaire dechiffre et parse). | | P17 | Tous les modeles d'instance valident | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | -| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 25 secret(s) exige(s), tous presents. | +| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 24 secret(s) exige(s), tous presents. | | P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | | P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. | | P21 | Federation : aucun index en collision | AFF-001 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. | diff --git a/docs/intrants-communs.md b/docs/intrants-communs.md index 389140e..2d2ae39 100644 --- a/docs/intrants-communs.md +++ b/docs/intrants-communs.md @@ -55,11 +55,19 @@ journald (rétention) · core_dumps · systemd_ssh_auto. > (`inventories//group_vars/all/vault.yml`), gabarit > [`exemples/vault.exemple.yml`](../exemples/vault.exemple.yml). Un mot de passe, un > endroit. Édité via `ansible-vault edit` (jamais par le GUI). -`proxmox_api_token_id/secret`, `vault_step_ca_password`, `vault_step_ca_fingerprint`, -`vault_step_ca_provisioner_password`, `vault_openldap_admin`, `vault_ldap_sssd`, -`vault_keycloak_admin`, `vault_postgresql_keycloak`, `vault_bd_keycloak`, -`vault_forgejo_admin`, `vault_forgejo_secret_key`, `vault_forgejo_internal_token`, -`vault_bd_forgejo`, `vault_grafana_admin`, `vault_redis`, `vault_bd_icingadb`. +**La liste ne s'écrit pas ici.** Elle se *recense*, depuis le plan, les rôles des groupes +actifs et les `group_vars` : + +```sh +python3 scripts/voute.py lister # chaque secret exigé + d'où vient l'exigence +make prouver # P18 : le gabarit les couvre-t-il tous ? +``` + +C'est la même source que le rappel « Secrets attendus » du panneau d'intrants. Trois copies +manuelles de cette liste ont existé et **toutes ont divergé** — elles annonçaient +`vault_ldap_sssd`, qu'aucun rôle ne consomme, et `vault_step_ca_fingerprint`, que +`client_pki` dérive à chaud depuis l'AC plutôt que de la lire. + Mot de passe du Vault lui-même : saisi au déploiement. ### I. Exposition publique — `plan/domaines.yml` diff --git a/roles/client_pki/README.md b/roles/client_pki/README.md index 042eeee..05acfd3 100644 --- a/roles/client_pki/README.md +++ b/roles/client_pki/README.md @@ -19,10 +19,23 @@ du TLS / mTLS interne. ## Secrets requis (Vault) ```yaml -client_pki_ca_fingerprint: "{{ vault_step_ca_fingerprint }}" # empreinte racine de l'AC client_pki_provisioner_password: "{{ vault_step_ca_provisioner_password }}" # partage avec serveur_step_ca ``` -L'empreinte racine s'obtient sur l'AC (`step certificate fingerprint `). +Un seul — et l'empreinte racine n'en fait **pas** partie. + +## L'empreinte du root CA n'est pas un intrant +Elle est **dérivée à chaud** depuis l'autorité (`step certificate fingerprint` exécuté sur +`serveur_step_ca`, en `delegate_to`), et non lue depuis la voûte. La raison : un `from-zero` +régénère l'AC, donc son empreinte change. Une empreinte figée en voûte serait périmée dès la +première reconstruction — et une empreinte périmée fait échouer le `bootstrap` de chaque +hôte, sans que rien n'indique pourquoi. + +Si l'AC est injoignable ou pas encore déployée, un `assert` échoue avec un message clair +plutôt que de laisser passer une empreinte vide — ce qui reviendrait à faire confiance à +n'importe quelle autorité au premier contact. + +Pour épingler explicitement une empreinte (AC externe, migration) : +`client_pki_ca_fingerprint_override`. ## Variables principales | Variable | Défaut | Rôle | diff --git a/roles/client_pki/defaults/main.yml b/roles/client_pki/defaults/main.yml index 8599f22..a858b00 100644 --- a/roles/client_pki/defaults/main.yml +++ b/roles/client_pki/defaults/main.yml @@ -20,8 +20,13 @@ client_pki_sans: - "{{ ansible_hostname | default('') }}" - "{{ ansible_host | default('') }}" -# Secrets OBLIGATOIRES (Ansible Vault). -client_pki_ca_fingerprint: "{{ vault_step_ca_fingerprint | default('') }}" # rempli depuis la voute (vault_step_ca_fingerprint) +# L'empreinte du root CA n'est PAS un intrant : elle est DERIVEE a chaud depuis l'AC +# (tasks/main.yml), parce qu'un from-zero regenere l'autorite avec une empreinte neuve. +# La stocker en voute donnerait une valeur perimee des la premiere reconstruction. +# Pour epingler explicitement une empreinte : `client_pki_ca_fingerprint_override`. +client_pki_ca_fingerprint: "" + +# Secret OBLIGATOIRE (Ansible Vault). client_pki_provisioner_password: "{{ vault_step_ca_provisioner_password | default('') }}" # rempli depuis la voute (vault_step_ca_provisioner_password) # Depot apt officiel Smallstep (partage avec serveur_step_ca). diff --git a/scripts/inventory_gui.py b/scripts/inventory_gui.py index 60bbaaf..f4b3710 100644 --- a/scripts/inventory_gui.py +++ b/scripts/inventory_gui.py @@ -153,9 +153,10 @@ def secrets_attendus() -> list[str]: pas le prefixe `vault_` (les jetons Proxmox). C'est la MEME source que la preuve P18. Une liste ecrite a la main vivait ici et avait diverge : elle annoncait - `vault_ldap_sssd`, que rien ne consomme, et omettait `vault_step_ca_fingerprint`, - reellement lu par `client_pki`. Un operateur qui suivait le panneau creait donc un - secret inutile et en oubliait un necessaire. + `vault_ldap_sssd`, qu'aucun role ne consomme. Un operateur qui suivait le panneau + creait donc un secret inutile. Le recensement par motif textuel a lui aussi sa + limite : il a un temps reclame `vault_step_ca_fingerprint` a cause d'un defaut mort + dans `client_pki`, qui derive en realite l'empreinte a chaud depuis l'AC. Depot public nu (aucune instance) : liste vide plutot qu'une erreur d'affichage. """