Set-OPS-Public/scripts/verifier_cle_amorcage.py
Daniel Allaire db1cb652b7 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
2026-09-13 22:04:41 -04:00

83 lines
3.8 KiB
Python
Executable file

#!/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)