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:
parent
8bc8589163
commit
7dc7720b4b
6 changed files with 66 additions and 13 deletions
26
CHANGELOG.md
26
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
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue