client_pki : l'empreinte du root CA n'est pas un secret de voûte

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 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-01 22:37:27 -04:00
parent 8bc8589163
commit 7dc7720b4b
6 changed files with 66 additions and 13 deletions

View file

@ -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

View file

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

View file

@ -55,11 +55,19 @@ journald (rétention) · core_dumps · systemd_ssh_auto.
> (`inventories/<env>/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`

View file

@ -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 <root_ca.crt>`).
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 |

View file

@ -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).

View file

@ -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.
"""