diff --git a/CHANGELOG.md b/CHANGELOG.md index bee6bf1..e3232f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # CHANGELOG — Set-OPS +## 2026-10-11 (130) — La clé du runner du site ne transite plus par la face du locataire + +**94 preuves.** `make verifier` passe. `test_contexte.py` et `test_inventory_host.py` comptent +les nouveaux cas, et la garde de face publiée (P92) échoue si la face recommence à porter la clé. + +### Le site la posait chez lui en passant par le locataire + +Pour que le site puisse amorcer le runner d'un locataire, la clé publique du runner du site doit +être posée sur cette machine à sa naissance. `_cle_amorcage` la lisait dans le plan du site, la +face du locataire la publiait, et le site la relisait dans la face pour cloner. La clé faisait un +aller-retour, et seul le poste, qui monte les deux dépôts, pouvait calculer une face complète. +Sur le runner d'un locataire, la face recalculée perdait la clé ; l'entrée 129 avait dû cesser +de la juger là-bas. + +Le site pose maintenant sa clé lui-même en clonant (`contexte.clonage_rendu`) : sur la machine +qui porte `serveur_ops_tenant`, et sur elle seule, en tête des clés d'amorçage. C'est l'ordre +même de `_cle_amorcage`. Le locataire publie sa face sans cette clé +(`parametres_proxmox_hote(..., avec_cle_du_site=False)`). Le chemin du poste, qui clone depuis +l'instance montée, est inchangé. + +Mesuré avant de conclure : + +- pour `ops-01` des deux locataires, les paramètres que le site rend depuis la face sont + identiques, à l'octet près, à ceux de `parametres-proxmox` sur le poste ; +- `infra-dns-01` ne reçoit pas la clé du site ; +- sans le plan du site, chaque locataire calcule exactement la face qu'il a publiée. + +P93 compare désormais ce que le site clonera, clé comprise, au chemin du poste. Elle signale +aussi une face qui porterait encore la clé. Une face d'avant, qui la porte déjà, n'est pas +doublée. La console juge de nouveau la face partout : la restriction de l'entrée 129 est +retirée. + +Les deux faces ont été republiées : seule la clé d'amorçage d'`ops-01` change dans chacune. + ## 2026-10-11 (129) — Le runner d'un locataire conseillait de republier une face juste **94 preuves.** `make verifier` passe et `test_portee_console.py` compte un cas de plus. Ce diff --git a/docs/conception-contextes.md b/docs/conception-contextes.md index 2ebee46..5f75ca2 100644 --- a/docs/conception-contextes.md +++ b/docs/conception-contextes.md @@ -184,7 +184,9 @@ les publie désormais (`sources_declarees`). La frontière, en trois temps : les l'inventaire d'un locataire se génère sans son site, à l'octet près (P91). Le locataire publie sa face réseau (P92) ; les comptes de sauvegarde, le DNS public, le pare-feu Proxmox, la frontière et la découverte des locataires du site la lisent. La matérialisation aussi : la face publie les -paramètres de clonage (P93) ; `locataire-creer`, `locataire-raser` et `placement-plan TENANT=` +paramètres de clonage (P93), sauf la clé du runner du site. Le site l'ajoute lui-même en +clonant le runner du locataire (`clonage_rendu`, 2026-10-11) : elle ne transite plus par la +face, et un locataire peut calculer sa face sans le plan de son site ; `locataire-creer`, `locataire-raser` et `placement-plan TENANT=` nomment leur locataire au lieu de le monter, et visent les mêmes machines, à l'argument près de la ligne `ansible-playbook` (P94, `test_appels_locataire.py`) ; `reconstruire-locataire` les emploie. Reste : la preuve par reconstruction. diff --git a/scripts/contexte.py b/scripts/contexte.py index b53a424..e274fee 100644 --- a/scripts/contexte.py +++ b/scripts/contexte.py @@ -453,11 +453,13 @@ class Locataire(Ecosysteme): # LES PARAMETRES DE CLONAGE de chaque machine (M1 de la materialisation, 2026-10-05) : # exactement ce que `make creer-vm` evalue aujourd'hui (`parametres-proxmox`), calcule # ici par la MEME fonction. Le runner du site clonera d'apres eux, sans monter ce depot. + # SANS LA CLE DU RUNNER DU SITE (2026-10-11) : elle n'est pas au locataire, et il ne + # peut pas la lire hors du poste. Le site l'ajoute en clonant (`clonage_rendu`). from inventory_host import parametres_proxmox_hote def clonage(h: str) -> dict: try: - lignes = parametres_proxmox_hote(data, h, self.inventaire()) + lignes = parametres_proxmox_hote(data, h, self.inventaire(), avec_cle_du_site=False) except ValueError as e: return {"erreur": str(e)} out = {} @@ -1129,16 +1131,37 @@ def verifier_face_publiee(locataire: "Locataire") -> list[str]: return [] -def lignes_clonage(face: dict, hote: str) -> list[str]: - """Les lignes shell que `parametres-proxmox` rend pour cet hote — depuis la face publiee. - Meme forme, a l'octet pres : `SETOPS_X='valeur'`, une valeur pouvant tenir sur plusieurs - lignes (les cles d'amorcage). `make creer-vm TENANT=` les evalue comme les autres.""" +def clonage_rendu(machine: dict, cle_site: str) -> dict: + """Les parametres de clonage d'une machine publiee, tels que LE SITE les clone. + + LE SITE QUI CLONE POSE SA PROPRE CLE (2026-10-11). Elle passait par la face du locataire : + le poste la lisait dans le plan du site et la publiait, le site la relisait. Le runner du + locataire, sans ce plan, recalculait une face sans elle. Elle ne quitte plus le site : + sur la machine qui porte `serveur_ops_tenant`, et sur elle seule, il la met EN TETE des + cles d'amorcage — l'ordre meme de `inventory_host._cle_amorcage`, pour que les deux + chemins rendent la meme VM. Une face d'avant, qui la porte deja, n'est pas doublee. + """ + from inventory_host import GROUPE_RUNNER_TENANT + c = dict(machine.get("clonage") or {}) + if cle_site and GROUPE_RUNNER_TENANT in (machine.get("groupes") or []) and "erreur" not in c: + autres = [x for x in str(c.get("SETOPS_CLES_AMORCAGE") or "").split("\n") + if x.strip() and x.strip() != cle_site] + c["SETOPS_CLES_AMORCAGE"] = "\n".join([cle_site] + autres) + return c + + +def lignes_clonage(face: dict, hote: str, cle_site: str | None = None) -> list[str]: + """Les lignes shell que `parametres-proxmox` rend pour cet hote — depuis la face publiee, + plus la cle du runner du site monte (`clonage_rendu`). Meme forme, a l'octet pres : + `SETOPS_X='valeur'`, une valeur pouvant tenir sur plusieurs lignes (les cles + d'amorcage). `make creer-vm TENANT=` les evalue comme les autres.""" + from inventory_host import cle_runner_du_site m = (face.get("machines") or {}).get(hote) if m is None: raise SystemExit(f"Hote absent de la face reseau publiee : {hote}.") - c = m.get("clonage") or {} - if "erreur" in c: - raise SystemExit(c["erreur"]) + if "erreur" in (m.get("clonage") or {}): + raise SystemExit(m["clonage"]["erreur"]) + c = clonage_rendu(m, cle_runner_du_site() if cle_site is None else cle_site) return [f"{k}='{v}'" for k, v in c.items()] @@ -1152,6 +1175,8 @@ def verifier_clonage(locataire: "Locataire", face: dict | None = None) -> list[s `inventory_host.py parametres-proxmox` sur l'inventaire du locataire — la commande meme que `make creer-vm` evalue ? Lancee a part, pour chaque machine : un temoin independant.""" import subprocess + from inventory_host import cle_runner_du_site + cle_site = cle_runner_du_site() f = face if face is not None else (locataire.face_publiee() or {}) ecarts: list[str] = [] for h, m in sorted((f.get("machines") or {}).items()): @@ -1169,10 +1194,17 @@ def verifier_clonage(locataire: "Locataire", face: dict | None = None) -> list[s cle, _, valeur = jeton.partition("=") if cle.startswith("SETOPS_"): attendu[cle] = valeur + # CE QUE LE SITE CLONERA, pas seulement ce que la face porte : la face, plus la cle de + # son runner (`clonage_rendu`). C'est cela qui doit egaler le chemin du poste, cle + # comprise — la VM nee d'un chemin ou de l'autre doit etre la meme. publie = m.get("clonage") or {} - if publie != attendu: - diff = sorted(k for k in set(publie) | set(attendu) if publie.get(k) != attendu.get(k)) - ecarts.append(f"{h} : la face differe de parametres-proxmox ({', '.join(diff)})") + if cle_site and cle_site in str(publie.get("SETOPS_CLES_AMORCAGE") or ""): + ecarts.append(f"{h} : la face porte la cle du runner du site ; elle n'est pas au " + "locataire (le site l'ajoute en clonant) : republier la face") + rendu = clonage_rendu(m, cle_site) + if rendu != attendu: + diff = sorted(k for k in set(rendu) | set(attendu) if rendu.get(k) != attendu.get(k)) + ecarts.append(f"{h} : le clonage par la face differe de parametres-proxmox ({', '.join(diff)})") return ecarts diff --git a/scripts/inventory_gui.py b/scripts/inventory_gui.py index 22b41a7..04537fe 100644 --- a/scripts/inventory_gui.py +++ b/scripts/inventory_gui.py @@ -1007,12 +1007,12 @@ def lien_du_locataire(instance: Path) -> dict: site = loc.site() except _ctx.ContexteInvalide: site = None - # LA FACE NE SE JUGE QUE LA OU LA CARTE DU SITE EST MONTEE (mesure du 2026-10-11 sur - # le runner de Technolibre). Elle porte, pour le runner du locataire, la cle du runner - # du site (`_cle_amorcage`), lue dans le plan du SITE. Sans ce plan, la face calculee - # perd cette cle : la page disait « a republier », et republier d'ici l'aurait retiree. + # LA FACE SE JUGE PARTOUT CHEZ LE LOCATAIRE. Elle a porte un temps la cle du runner du + # site, lue dans le plan du site : le runner du locataire la recalculait sans elle et la + # disait perimee (mesure du 2026-10-11 sur Technolibre). Le site pose maintenant cette + # cle lui-meme en clonant (`contexte.clonage_rendu`) : la face ne lit plus rien du site. ecarts_face = None - if site is not None and face is not None: + if face is not None: try: ecarts_face = _ctx.verifier_face_publiee(loc) except Exception as exc: # une face incalculable ne doit pas masquer la publiee @@ -1028,7 +1028,6 @@ def lien_du_locataire(instance: Path) -> dict: "hebergeur": loc.nom_hebergeur(), "face": {"publiee": face is not None, "trace": _trace_git(loc.depot, _ctx.NOM_FICHIER_FACE_RESEAU), - "jugeable": site is not None, "ecarts": ecarts_face, "resume": resume_face(face) if face else None}, "fiche": {"presente": bool(fiche), @@ -4252,7 +4251,6 @@ let selMachine = null; // MA FACE RESEAU let etatFace; if (!F.publiee) etatFace = 'jamais publiée'; - else if (!F.jugeable) etatFace = 'lue, non jugeable d\'ici'; else if ((F.ecarts || []).length) etatFace = 'à republier'; else etatFace = 'à jour avec mon plan'; const face = !F.publiee ? `