insemination : le runner verifie SA cle avant de partir
La cle d amorcage du plan est une transcription de la cle publique du runner du site. Reconstruire le runner lui donne une paire neuve ; la transcription, elle, ne bouge pas. Meme algorithme, meme commentaire, materiel different : rien ne distingue les deux a l oeil. La garde ne demande rien au reseau — le runner du site EST la machine qui insemine, sa cle est sous sa main. Elle ne compare que si les deux cles nomment le meme hote : lancee depuis le poste d un exploitant, elle se tait. Elle dit aussi ce que la correction seule ne suffit pas a reparer : une machine deja creee porte la cle perimee, il faut la RECREER. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
4b759110bf
commit
db1cb652b7
3 changed files with 86 additions and 2 deletions
1
Makefile
1
Makefile
|
|
@ -354,6 +354,7 @@ inseminer: ansible-runtime ## Le SITE amorce le runner d'un tenant — TENANT=<d
|
|||
printf '%s\n' "Refus: plusieurs runners declares ($$hote). Nommer lequel avec HOTE=."; \
|
||||
exit 2; \
|
||||
fi; \
|
||||
python3 scripts/verifier_cle_amorcage.py || exit 2; \
|
||||
printf 'Insemination de %s chez %s — couches sans secret : %s\n\n' "$$hote" "$(TENANT)" "$(COUCHES_INSEMINATION)"; \
|
||||
for groupe in $(COUCHES_INSEMINATION); do \
|
||||
printf '=== %s ===\n' "$$groupe"; \
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 4 pool(s) Proxmox, 40 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 63 scripts expliques et atteignables, 121 cibles make documentees, 68 roles avec README. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 64 scripts expliques et atteignables, 121 cibles make documentees, 68 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (146 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 45 document(s) declarent leur lecteur (39 genere(s) exempte(s)). |
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
|
||||
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 59 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 60 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
|
||||
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 153 regle(s) du site. |
|
||||
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
|
||||
|
|
|
|||
83
scripts/verifier_cle_amorcage.py
Executable file
83
scripts/verifier_cle_amorcage.py
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env python3
|
||||
"""La cle d'amorcage inscrite au plan du SITE est-elle CELLE de ce runner ?
|
||||
|
||||
LE PLAN PORTE UNE TRANSCRIPTION, ET UNE TRANSCRIPTION PREND DU RETARD (2026-09-13).
|
||||
|
||||
`runner_cle_publique`, dans `<SITE>/plan/10-intrants.yml`, recopie la cle publique du
|
||||
runner du site. Elle est deposee par cloud-init sur le SEUL hote d'un tenant qui porte
|
||||
`serveur_ops_tenant` — c'est ce qui rend l'insemination possible.
|
||||
|
||||
En reconstruisant `site-ops-01`, `setops` a recu une paire NEUVE. Cette ligne, elle, n'a
|
||||
pas bouge. Les deux se lisent pareil : meme algorithme, meme commentaire final
|
||||
`setops@site-ops-01.genese.internal`. Seul le materiel differe, et aucun coup d'oeil ne
|
||||
les distingue.
|
||||
|
||||
CE QUE CA A COUTE : quatorze machines clonees, puis `Permission denied (publickey)` sur
|
||||
la seule que le site devait amorcer. Le message accusait une cle absente ; elle etait la,
|
||||
au mauvais materiel — et le clone etant deja fait, la correction du plan ne suffit plus :
|
||||
il faut RECREER la machine, puisque la cle se pose a la naissance.
|
||||
|
||||
CETTE GARDE NE DEMANDE RIEN AU RESEAU. Le runner du site EST la machine qui insemine :
|
||||
sa cle est sous sa main. Deux secondes ici valent la materialisation d'une flotte
|
||||
entiere la-bas.
|
||||
|
||||
ELLE NE S'APPLIQUE QUE LORSQUE LES DEUX CLES NOMMENT LE MEME HOTE. Lancee depuis le poste
|
||||
d'un exploitant, la cle locale est la SIENNE : comparer la declarerait fausse. Degrader,
|
||||
jamais deviner — on se tait et on laisse passer.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
|
||||
def materiel_et_hote(ligne: str) -> tuple[str, str]:
|
||||
"""(materiel de la cle, hote du commentaire) — ('', '') si la ligne n'est pas une cle."""
|
||||
parts = ligne.split()
|
||||
return (parts[1], parts[-1]) if len(parts) > 2 else ("", "")
|
||||
|
||||
|
||||
def verifier() -> tuple[bool, str]:
|
||||
try:
|
||||
import underlay
|
||||
plan = str((underlay.lire_plan_site("10-intrants.yml") or {})
|
||||
.get("runner_cle_publique") or "").strip()
|
||||
except Exception as e: # pas de site monte
|
||||
return True, f"aucun plan de site lisible ({e.__class__.__name__}) — rien a comparer."
|
||||
if not plan:
|
||||
return True, "le plan du site ne declare aucune cle d'amorcage — rien a comparer."
|
||||
|
||||
pub = Path.home() / ".ssh" / "id_ed25519.pub"
|
||||
if not pub.is_file():
|
||||
return True, f"{pub} absent — cette machine n'est pas le runner du site."
|
||||
|
||||
mat_plan, hote_plan = materiel_et_hote(plan)
|
||||
mat_local, hote_local = materiel_et_hote(pub.read_text(encoding="utf-8").strip())
|
||||
|
||||
if not (hote_plan and hote_plan == hote_local):
|
||||
return True, f"cle locale de `{hote_local or 'inconnu'}`, plan de `{hote_plan or 'inconnu'}` — pas le meme hote, aucune comparaison."
|
||||
|
||||
if mat_plan != mat_local:
|
||||
return False, (
|
||||
"la cle d'amorcage du plan n'est PAS celle de ce runner.\n"
|
||||
f" au plan ...{mat_plan[-20:]}\n"
|
||||
f" reelle ...{mat_local[-20:]}\n"
|
||||
"Les deux portent le meme commentaire ; seul le materiel differe.\n"
|
||||
"\n"
|
||||
"Toute machine DEJA creee porte la perimee : la corriger ne suffit pas,\n"
|
||||
"il faut la RECREER — la cle se pose a la naissance.\n"
|
||||
"\n"
|
||||
" 1. relever : cat ~/.ssh/id_ed25519.pub\n"
|
||||
" 2. corriger : runner_cle_publique dans <SITE>/plan/10-intrants.yml\n"
|
||||
" 3. recreer : make raser HOTE=<runner> ... puis make creer-vm HOTE=<runner>")
|
||||
|
||||
return True, f"cle d'amorcage conforme (...{mat_local[-20:]}, {hote_local})."
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ok, message = verifier()
|
||||
print(("OK : " if ok else "Refus: ") + message, file=sys.stdout if ok else sys.stderr)
|
||||
raise SystemExit(0 if ok else 2)
|
||||
Loading…
Reference in a new issue