gabarit : minimal — il etait un cache du socle, et il perimait sans le dire
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
Il portait DIX-SEPT roles : exactement ceux du socle et du durcissement, que le
deploiement rejoue a l identique. C etait donc un CACHE — et comme tout cache, il
perimait sans le dire.
MESURE : derniere recapture le 2026-08-09, et SIX de ses roles avaient change depuis
— common_packages, cloud_init, ssh_baseline, ssh_hardening, auditd,
nftables_baseline. Rien ne le signalait : le deploiement masquait la derive en
reappliquant tout, donc personne ne pouvait la voir. Aucune preuve du harnais ne
regardait sa fraicheur.
IL NE GARDE QUE CE QUI DOIT EXISTER AVANT QU ANSIBLE PUISSE AGIR :
qemu_guest_agent l agent repond AVANT SSH — P52 s en sert
cloud_init le seul chemin vers la premiere seconde
sudo_ansible la porte par ou tout entre
ssh_baseline le serveur SSH
Ce ne sont pas des choix d efficacite, ce sont des conditions d existence.
CE QUE CA COUTE, ET QUI EST COUVERT : une VM neuve n est plus durcie a la naissance.
Elle nait cependant DERRIERE LE PARE-FEU DE L HYPERVISEUR, policy_in=REJECT arme au
clonage — verifie sur obs-01. La fenetre d exposition est fermee par la fabric, pas
par le gabarit. Mon objection initiale tombait devant la mesure.
P56 GARDE LES DEUX MOITIES. Qu il ne REGROSSISSE pas : un role ajoute recree le
cache, donc la peremption invisible. Et que rien de retire ne soit PERDU : un role
absent du gabarit ET du socle disparaitrait de toutes les machines neuves, sans
erreur ni trace, et la panne arriverait des mois plus tard sur une machine qu on
croyait durcie. Verifie : 14 retires, 14 repris, zero orphelin. Deux controles
negatifs.
make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
0b653170fa
commit
2f2323bd3e
4 changed files with 162 additions and 34 deletions
52
CHANGELOG.md
52
CHANGELOG.md
|
|
@ -1,5 +1,57 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-01 — Le gabarit etait un cache du socle, et il perimait sans le dire
|
||||
|
||||
**56 preuves.** Question de l'exploitant : le gabarit dore et cloud-init gagnent-ils leur
|
||||
place, vu tout ce que Set-OPS fixe par ailleurs ? La mesure a repondu avant le
|
||||
raisonnement.
|
||||
|
||||
**Le gabarit portait dix-sept roles — exactement ceux du socle et du durcissement**, que
|
||||
le deploiement rejoue ensuite a l'identique. C'etait un **cache**, et comme tout cache il
|
||||
perimait :
|
||||
|
||||
```
|
||||
derniere recapture 2026-08-09
|
||||
roles changes depuis common_packages · cloud_init · ssh_baseline
|
||||
ssh_hardening · auditd · nftables_baseline
|
||||
```
|
||||
|
||||
**Rien ne le signalait.** Aucune preuve du harnais ne regardait sa fraicheur, et le
|
||||
deploiement masquait la derive en reappliquant tout — *donc personne ne pouvait la voir*.
|
||||
C'est le defaut que D-81 a corrige pour la forge du genome, jamais traite ici.
|
||||
|
||||
### Il ne garde que ce qui doit exister avant qu'Ansible puisse agir
|
||||
|
||||
```
|
||||
qemu_guest_agent l'agent repond AVANT SSH — c'est par lui que P52 confirme
|
||||
cloud_init le seul chemin vers la premiere seconde : adresse, nom, cles
|
||||
sudo_ansible le compte technique et son sudo — la porte par ou tout entre
|
||||
ssh_baseline le serveur SSH, meme raison un cran plus bas
|
||||
```
|
||||
|
||||
*Ce ne sont pas des choix d'efficacite, ce sont des conditions d'existence.*
|
||||
|
||||
**cloud-init, lui, ne peut pas etre retire** : sans lui une VM neuve n'a ni adresse ni cle,
|
||||
donc personne ne peut l'atteindre pour lui en donner une. Il reste le seul chemin vers la
|
||||
premiere seconde — au prix connu de reecrire `/etc/hosts` a chaque demarrage, ce que le
|
||||
depot a deja du contourner.
|
||||
|
||||
### Ce que la reduction coute, et qui le couvre
|
||||
|
||||
Une VM neuve n'est plus durcie a la naissance. **Elle nait cependant derriere le pare-feu
|
||||
de l'hyperviseur** — `policy_in=REJECT`, arme au clonage (verifie sur `obs-01`) : seuls
|
||||
les flux declares l'atteignent, avant meme son premier paquet. *La fenetre d'exposition est
|
||||
fermee par la fabric, pas par le gabarit* — mon objection initiale tombait devant la mesure.
|
||||
|
||||
### P56 garde les deux moities
|
||||
|
||||
Qu'il ne **regrossisse** pas : un role ajoute recree le cache, donc la peremption
|
||||
invisible. Et que rien de retire ne soit **perdu** : un role absent du gabarit *et* du
|
||||
socle disparaitrait de toutes les machines neuves — sans erreur, sans trace, et la panne
|
||||
arriverait des mois plus tard sur une machine qu'on croyait durcie.
|
||||
|
||||
*Verifie : 14 retires, 14 repris, zero orphelin. Deux controles negatifs.*
|
||||
|
||||
## 2026-09-01 — `make emancipation-prouver` : couper, pas sonder
|
||||
|
||||
**55 preuves.** `docs/filiation-emancipation.md` decrivait quatre temps et n'en outillait
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (55 OK · 0 echec · 0 saute)
|
||||
- **Verdict** : ✅ CONFORME (56 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -68,6 +68,7 @@
|
|||
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
|
||||
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. |
|
||||
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 15 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 15. |
|
||||
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -21,41 +21,39 @@
|
|||
msg: "Attention : Debian {{ ansible_facts.distribution_version }} détecté. Le modèle cible est Debian 13."
|
||||
when: ansible_facts.distribution_major_version != "13"
|
||||
|
||||
# ─── LE GABARIT EST MINIMAL, ET C'EST UNE DECISION (2026-09-01) ──────────────
|
||||
#
|
||||
# Il portait DIX-SEPT roles : exactement ceux du socle et du durcissement, que le
|
||||
# deploiement rejoue ensuite a l'identique. Le gabarit etait donc un CACHE du socle —
|
||||
# et comme tout cache, il perimait sans le dire.
|
||||
#
|
||||
# Mesure du 2026-09-01 : derniere recapture le 2026-08-09, et SIX de ses roles avaient
|
||||
# change depuis (common_packages, cloud_init, ssh_baseline, ssh_hardening, auditd,
|
||||
# nftables_baseline). Rien ne le signalait — le deploiement masquait la derive en
|
||||
# reappliquant tout, donc personne ne pouvait la voir.
|
||||
#
|
||||
# CE QUI RESTE ICI N'EST PAS UN CHOIX D'EFFICACITE, C'EST UNE CONDITION D'EXISTENCE :
|
||||
# chacun doit etre en place AVANT qu'Ansible puisse agir. Tout le reste appartient au
|
||||
# socle, qui l'applique de toute facon — et P56 refuse qu'un role retire ne soit repris
|
||||
# par personne.
|
||||
#
|
||||
# `qemu_guest_agent` l'agent invite repond AVANT SSH. C'est par lui que `creer-vm`
|
||||
# confirme la materialisation sans entrer chez le tenant (P52).
|
||||
# `cloud_init` le seul chemin vers la premiere seconde — adresse, nom d'hote,
|
||||
# cles de naissance. Sans lui, personne ne peut joindre la VM.
|
||||
# `sudo_ansible` le compte technique et son sudo : la porte par ou tout entre.
|
||||
# `ssh_baseline` le serveur SSH. Meme raison, un cran plus bas.
|
||||
#
|
||||
# CE QUE LA REDUCTION COUTE, ET QUI EST COUVERT : une VM neuve n'est plus durcie a la
|
||||
# naissance. Elle nait cependant DERRIERE LE PARE-FEU DE L'HYPERVISEUR,
|
||||
# `policy_in=REJECT` arme au clonage (verifie le 2026-09-01 sur `obs-01`) : seuls les
|
||||
# flux declares l'atteignent, avant meme son premier paquet. La fenetre d'exposition est
|
||||
# fermee par la fabric, pas par le gabarit.
|
||||
#
|
||||
# CE QU'ELLE RAPPORTE : un gabarit qui ne contient presque rien ne peut presque pas
|
||||
# perimer.
|
||||
roles:
|
||||
- common_packages
|
||||
- qemu_guest_agent
|
||||
- cloud_init
|
||||
- sudo_ansible
|
||||
- chrony
|
||||
- ssh_baseline
|
||||
- systemd_ssh_auto
|
||||
- motd
|
||||
- hardening_packages
|
||||
- sysctl_hardening
|
||||
- core_dumps
|
||||
- unattended_upgrades
|
||||
- apparmor
|
||||
- auditd
|
||||
- fail2ban_ssh
|
||||
- journald
|
||||
- ssh_hardening
|
||||
- nftables_baseline
|
||||
|
||||
post_tasks:
|
||||
- name: Afficher le statut cloud-init
|
||||
ansible.builtin.command: cloud-init status --long
|
||||
register: cloud_init_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Résultat cloud-init
|
||||
ansible.builtin.debug:
|
||||
var: cloud_init_status.stdout_lines
|
||||
|
||||
- name: Message final
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Préparation terminée."
|
||||
- "Redémarrer si GRUB a changé."
|
||||
- "Ne pas convertir avant le playbook de nettoyage final."
|
||||
- "nftables est installé mais désactivé par défaut."
|
||||
|
|
|
|||
|
|
@ -1396,6 +1396,81 @@ def preuve_carte_dit_vrai() -> tuple[bool, str]:
|
|||
f"{len(_carte_chiffres_mesures())} chiffres correspondent a la mesure.")
|
||||
|
||||
|
||||
def preuve_gabarit_minimal_et_repris() -> tuple[bool, str]:
|
||||
"""Le gabarit ne porte que l'indispensable, et rien de retire n'est perdu.
|
||||
|
||||
LE GABARIT ETAIT UN CACHE DU SOCLE, ET IL PERIMAIT SANS LE DIRE (2026-09-01). Il
|
||||
portait dix-sept roles — exactement ceux que le deploiement rejoue. Derniere recapture
|
||||
le 2026-08-09, et six d'entre eux avaient change depuis. Le deploiement masquait la
|
||||
derive en reappliquant tout : personne ne pouvait la voir.
|
||||
|
||||
Il est desormais MINIMAL — seulement ce qui doit exister AVANT qu'Ansible puisse agir.
|
||||
Cette preuve garde les deux moities de cette decision :
|
||||
|
||||
1. il ne REGROSSIT pas. Un role ajoute ici recree le cache, donc la peremption
|
||||
invisible. La liste est courte et chaque entree se justifie par une condition
|
||||
d'existence, jamais par la vitesse.
|
||||
|
||||
2. rien de ce qu'il ne porte plus n'est PERDU. Un role retire du gabarit et absent
|
||||
du socle disparaitrait de toutes les machines neuves — sans erreur, sans trace,
|
||||
et la panne arriverait des mois plus tard sur une machine qu'on croyait durcie.
|
||||
C'est le mode de defaillance le plus couteux de cette famille.
|
||||
"""
|
||||
def roles_de(chemin: str) -> list[str]:
|
||||
data = yaml.safe_load((RACINE / chemin).read_text(encoding="utf-8")) or []
|
||||
out: list[str] = []
|
||||
for play in data:
|
||||
for r in (play.get("roles") or []):
|
||||
nom = r if isinstance(r, str) else (r or {}).get("role")
|
||||
if nom:
|
||||
out.append(str(nom))
|
||||
return out
|
||||
|
||||
gabarit = roles_de("playbooks/modeles_vm/debian13_proxmox_preparer.yml")
|
||||
couverture = set(roles_de("playbooks/groupes/serveur_debian.yml")) | set(
|
||||
roles_de("playbooks/groupes/serveur_durci.yml"))
|
||||
|
||||
# CE QUI DOIT EXISTER AVANT ANSIBLE, et qui ne peut donc pas venir de lui.
|
||||
# Elargir cette liste est une DECISION, pas un ajustement : elle doit passer par
|
||||
# cette preuve, et par la question « pourquoi Ansible ne peut-il pas le faire ? ».
|
||||
indispensables = {
|
||||
"qemu_guest_agent": "l'agent invite repond avant SSH — P52 s'en sert pour "
|
||||
"confirmer la materialisation sans entrer chez le tenant",
|
||||
"cloud_init": "le seul chemin vers la premiere seconde : adresse, nom, cles",
|
||||
"sudo_ansible": "le compte technique et son sudo — la porte par ou tout entre",
|
||||
"ssh_baseline": "le serveur SSH, meme raison un cran plus bas",
|
||||
}
|
||||
|
||||
fautes: list[str] = []
|
||||
for r in gabarit:
|
||||
if r not in indispensables:
|
||||
fautes.append(f"`{r}` est dans le gabarit sans etre indispensable au premier "
|
||||
f"demarrage — le gabarit redevient un cache du socle, qui "
|
||||
f"perime sans le dire")
|
||||
manquants = [r for r in indispensables if r not in gabarit]
|
||||
if manquants:
|
||||
fautes.append("le gabarit ne porte plus " + ", ".join(f"`{m}`" for m in manquants)
|
||||
+ " — une VM neuve serait injoignable ou muette")
|
||||
|
||||
# LE CONTRAT AVEC LE SOCLE : ce que le gabarit ne fait plus, quelqu'un le fait.
|
||||
ancien_gabarit = {
|
||||
"common_packages", "chrony", "systemd_ssh_auto", "motd", "hardening_packages",
|
||||
"sysctl_hardening", "core_dumps", "unattended_upgrades", "apparmor", "auditd",
|
||||
"fail2ban_ssh", "journald", "ssh_hardening", "nftables_baseline",
|
||||
}
|
||||
perdus = sorted(r for r in ancien_gabarit if r not in couverture and r not in gabarit)
|
||||
if perdus:
|
||||
fautes.append("retire(s) du gabarit et repris par PERSONNE : "
|
||||
+ ", ".join(f"`{r}`" for r in perdus)
|
||||
+ " — les machines neuves ne l'auraient plus, sans erreur ni trace")
|
||||
|
||||
if fautes:
|
||||
return False, "Gabarit :\n - " + "\n - ".join(fautes)
|
||||
return True, (f"Gabarit minimal : {len(gabarit)} role(s), tous indispensables au "
|
||||
f"premier demarrage ; {len(ancien_gabarit)} role(s) retire(s), tous "
|
||||
f"repris par le socle ou le durcissement.")
|
||||
|
||||
|
||||
def preuve_cle_du_site_bornee_au_runner() -> tuple[bool, str]:
|
||||
"""La cle du SITE ne nait que sur le runner d'un tenant. Celle du tenant, partout chez lui.
|
||||
|
||||
|
|
@ -2215,6 +2290,8 @@ PREUVES: list[dict] = [
|
|||
"refs": [], "func": preuve_insemination_sans_secret},
|
||||
{"id": "P55", "titre": "La cle du SITE ne nait que sur le runner d'un tenant",
|
||||
"refs": [], "func": preuve_cle_du_site_bornee_au_runner},
|
||||
{"id": "P56", "titre": "Gabarit minimal, et rien de retire n'est perdu",
|
||||
"refs": [], "func": preuve_gabarit_minimal_et_repris},
|
||||
{"id": "P43", "titre": "Frontiere : le devis voit les machines du site", "refs": [],
|
||||
"func": preuve_devis_frontiere_du_site},
|
||||
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
||||
|
|
|
|||
Loading…
Reference in a new issue