creer-vm : prouver la materialisation sans entrer chez le tenant
`creer-vm` confirmait son succes en attendant une reponse SSH. Le runner du SITE
materialise le terrain de TOUS les tenants, mais la frontiere lui refuse d'entrer
chez eux — c'est le sens meme de leur isolation. La premiere VM qu'il a creee a
donc ete declaree en echec apres 600 secondes alors qu'elle tournait, avec
l'adresse exacte que le plan lui destinait :
Attente de SSH sur ops-01 ............ ECHEC: injoignable apres 600s.
LA TENTATION ETAIT D'OUVRIR LE SSH du runner vers tous les tenants. Ca aurait
repare la mesure en detruisant ce qu'elle protege : une machine capable d'entrer
chez chaque locataire est precisement ce que cette architecture refuse d'avoir.
L'agent invite repond sans rien ouvrir — l'API des hyperviseurs est deja le flux
par lequel la VM vient d'etre creee, donc qui peut la creer peut la voir naitre —
et il PROUVE DAVANTAGE. « Quelque chose ecoute sur le port 22 » ne dit ni quel
systeme a demarre, ni si cloud-init a pose la bonne adresse. Sur ops-01 :
10.17.19.41 portee sur asgard — Debian GNU/Linux 13 (trixie) 6.12.101
Ses echecs distinguent deux causes tres differentes : « la machine demarre mais
rapporte une AUTRE adresse — cloud-init, ou le pont sur lequel elle est posee »,
et « introuvable sur la fabric ».
ATTENDRE LA DISPONIBILITE A CHANGE DE MAIN. Guetter cloud-init et la liberation de
dpkg appartient a qui va CONFIGURER : `deployer` le fait desormais, la ou il se
contentait d'un `ping` unique. Il y gagne ce qu'il n'avait pas — attendre le verrou
APT, faute de quoi la premiere couche echouait dessus. Contrepartie assumee : un
nom d'hote errone patiente au lieu d'echouer vite ; echouer vite interdirait de
chainer creation et deploiement, le geste central d'une reconstruction.
P52 garde le couplage ferme. Deux controles negatifs verifies.
make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5d0f82792a
commit
6fe62f74e1
4 changed files with 201 additions and 8 deletions
35
Makefile
35
Makefile
|
|
@ -355,6 +355,17 @@ appliquer: ansible-runtime ## Applique un groupe a la flotte — GROUPE=<groupe>
|
||||||
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-groupe --groupe $(GROUPE)
|
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-groupe --groupe $(GROUPE)
|
||||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'
|
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'
|
||||||
|
|
||||||
|
# ATTENDRE PLUTOT QUE CONSTATER (2026-08-27). `deployer` faisait un `ping` unique
|
||||||
|
# (`_verifier-acces-hote`). Depuis que `creer-vm` ne guette plus le SSH — il prouve la
|
||||||
|
# materialisation par l'agent invite, seul geste que le runner du SITE ait le droit de
|
||||||
|
# faire chez un tenant — c'est ICI qu'il faut attendre : enchainer `creer-vm` puis
|
||||||
|
# `deployer` tomberait sinon sur une machine encore en cloud-init. On y gagne au passage
|
||||||
|
# ce que le `ping` unique ne faisait pas : attendre que `dpkg` se libere, faute de quoi
|
||||||
|
# la premiere couche echoue sur un verrou APT.
|
||||||
|
#
|
||||||
|
# CONTREPARTIE ASSUMEE : un nom d'hote errone ne fait plus echouer tout de suite, il
|
||||||
|
# patiente ATTENTE_HOTE secondes. Echouer vite sur l'injoignable interdirait de chainer
|
||||||
|
# la creation au deploiement — le geste central d'une reconstruction.
|
||||||
deployer: _instance-requise ## Deploie un hote, couche par couche, dans l'ordre du graphe — HOTE=<nom>
|
deployer: _instance-requise ## Deploie un hote, couche par couche, dans l'ordre du graphe — HOTE=<nom>
|
||||||
@set -e; \
|
@set -e; \
|
||||||
if [[ -z "$(HOTE)" ]]; then \
|
if [[ -z "$(HOTE)" ]]; then \
|
||||||
|
|
@ -369,7 +380,7 @@ deployer: _instance-requise ## Deploie un hote, couche par couche, dans l'ordre
|
||||||
exit 2; \
|
exit 2; \
|
||||||
fi; \
|
fi; \
|
||||||
$(VAULT_UNE_FOIS) \
|
$(VAULT_UNE_FOIS) \
|
||||||
$(MAKE) _verifier-acces-hote LIMITE="$(HOTE)"; \
|
$(MAKE) --no-print-directory _attendre-hote LIMITE="$(HOTE)"; \
|
||||||
$(MAKE) _verifier-privileges-hote LIMITE="$(HOTE)"; \
|
$(MAKE) _verifier-privileges-hote LIMITE="$(HOTE)"; \
|
||||||
for playbook in $$playbooks; do \
|
for playbook in $$playbooks; do \
|
||||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)"; \
|
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)"; \
|
||||||
|
|
@ -1018,11 +1029,25 @@ creer-vm: _instance-requise ## Cree une VM et attend qu'elle soit joignable —
|
||||||
CLE_SSH_PUBLIQUE="$(CLE_SSH_PUBLIQUE)" \
|
CLE_SSH_PUBLIQUE="$(CLE_SSH_PUBLIQUE)" \
|
||||||
DEMARRER="$(DEMARRER)" \
|
DEMARRER="$(DEMARRER)" \
|
||||||
CLONE_COMPLET="$(CLONE_COMPLET)"
|
CLONE_COMPLET="$(CLONE_COMPLET)"
|
||||||
@# `creer-vm` rend une VM PRETE, pas seulement demarree : sans cette attente,
|
@# `creer-vm` CONFIRME LA MATERIALISATION, PAS LA JOIGNABILITE (2026-08-27). Il
|
||||||
@# enchainer `creer-vm` puis `deployer` echoue presque toujours sur une machine
|
@# attendait une reponse SSH — un geste que le runner du SITE ne peut pas faire : la
|
||||||
@# neuve. C'est ce qui separe une suite de commandes d'une reconstruction.
|
@# frontiere lui refuse d'entrer chez les tenants, et c'est le sens meme de leur
|
||||||
|
@# isolation. La premiere VM creee par le runner a donc ete declaree en echec apres
|
||||||
|
@# 600 secondes alors qu'elle tournait, avec la bonne adresse.
|
||||||
|
@#
|
||||||
|
@# L'agent invite repond a cette question sans rien ouvrir — l'API des hyperviseurs
|
||||||
|
@# est deja le flux par lequel la VM vient d'etre creee — et il PROUVE DAVANTAGE :
|
||||||
|
@# il rapporte le systeme, le noyau et l'adresse effectivement portee, la ou SSH ne
|
||||||
|
@# disait que « quelque chose ecoute sur le port 22 ».
|
||||||
|
@#
|
||||||
|
@# ATTENDRE LA DISPONIBILITE est un autre geste, deplace chez `deployer` : cloud-init
|
||||||
|
@# et dpkg se guettent juste avant de configurer, par celui qui va configurer.
|
||||||
@if [[ "$(ATTENDRE)" != "false" ]]; then \
|
@if [[ "$(ATTENDRE)" != "false" ]]; then \
|
||||||
$(MAKE) --no-print-directory _attendre-hote LIMITE="$(HOTE)"; \
|
params="$$(python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) parametres-proxmox --hote $(HOTE))"; \
|
||||||
|
eval "$$params"; \
|
||||||
|
python3 scripts/attendre_materialisation.py \
|
||||||
|
--vmid "$$SETOPS_VMID" --ip "$$SETOPS_IP" --hote "$(HOTE)" \
|
||||||
|
--delai $(ATTENTE_HOTE); \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
inventaire-verifier: ansible-runtime _instance-requise ## Verifie que l'inventaire se parse (voute dechiffree)
|
inventaire-verifier: ansible-runtime _instance-requise ## Verifie que l'inventaire se parse (voute dechiffree)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||||
|
|
||||||
- **Instance** : `instance` — inventaire `instance/inventories/production/hosts.yml`
|
- **Instance** : `instance` — inventaire `instance/inventories/production/hosts.yml`
|
||||||
- **Verdict** : ✅ CONFORME (51 OK · 0 echec · 0 saute)
|
- **Verdict** : ✅ CONFORME (52 OK · 0 echec · 0 saute)
|
||||||
|
|
||||||
## Preuves
|
## Preuves
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
|
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. |
|
||||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 29 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 18, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 29 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 18, 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. |
|
| 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 | 50 scripts expliques et atteignables, 104 cibles make documentees, 61 roles avec README. |
|
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 51 scripts expliques et atteignables, 104 cibles make documentees, 61 roles avec README. |
|
||||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 14 exigence(s) de role, toutes satisfaites (39 cle(s) declaree(s) par l'instance). |
|
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 14 exigence(s) de role, toutes satisfaites (39 cle(s) declaree(s) par l'instance). |
|
||||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (16 groupes). |
|
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (16 groupes). |
|
||||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (25 genere(s) exempte(s)). |
|
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (25 genere(s) exempte(s)). |
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 36 role(s) serveur/client tous nommes, 37 groupe(s) cite(s) en table existent tous. |
|
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 36 role(s) serveur/client tous nommes, 37 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. |
|
| 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. |
|
| 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 : 46 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 47 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 |
|
| 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 : 5 machine(s) du plan retrouvees, 56 regle(s) du site. |
|
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 56 regle(s) du site. |
|
||||||
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
|
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
|
||||||
|
|
@ -64,6 +64,7 @@
|
||||||
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (110 lignes). |
|
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (110 lignes). |
|
||||||
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 121 regles `pass`), tous non consignes et tous motives. |
|
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 121 regles `pass`), tous non consignes et tous motives. |
|
||||||
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
|
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
|
||||||
|
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
|
||||||
|
|
||||||
## Couverture des affirmations ✅ du registre
|
## Couverture des affirmations ✅ du registre
|
||||||
|
|
||||||
|
|
|
||||||
119
scripts/attendre_materialisation.py
Normal file
119
scripts/attendre_materialisation.py
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Attend qu'une VM soit MATERIALISEE : demarree, et portant l'adresse que le plan lui destine.
|
||||||
|
|
||||||
|
POURQUOI PAS SSH (mesure du 2026-08-27). `creer-vm` confirmait son succes en attendant
|
||||||
|
une reponse SSH. Deux choses clochaient.
|
||||||
|
|
||||||
|
La premiere est une question de PERIMETRE. Le runner du SITE materialise le terrain de
|
||||||
|
tous les tenants, mais la frontiere lui refuse — deliberement — d'entrer chez eux : c'est
|
||||||
|
le sens meme de l'isolation inter-tenant. La premiere VM creee par le runner a donc ete
|
||||||
|
declaree en echec apres 600 secondes alors qu'elle tournait parfaitement. Ouvrir le SSH
|
||||||
|
du runner vers tous les tenants aurait « repare » la mesure en detruisant ce qu'elle
|
||||||
|
mesurait : une machine capable d'entrer chez chaque locataire est exactement ce que
|
||||||
|
l'architecture cherche a ne pas avoir.
|
||||||
|
|
||||||
|
La seconde est que SSH PROUVE MOINS. « Quelque chose ecoute sur le port 22 » ne dit ni
|
||||||
|
quel systeme a demarre, ni si cloud-init a pose la bonne adresse. L'agent invite, lui,
|
||||||
|
rapporte le systeme, le noyau et l'adresse EFFECTIVEMENT portee par l'interface — on
|
||||||
|
verifie donc que la machine est celle que le plan decrivait, pas seulement qu'une machine
|
||||||
|
repond.
|
||||||
|
|
||||||
|
Et il n'ouvre rien : l'API des hyperviseurs est deja le flux par lequel on a cree la VM.
|
||||||
|
Qui peut la creer peut la voir naitre.
|
||||||
|
|
||||||
|
ATTENDRE LA DISPONIBILITE EST UN AUTRE GESTE, qui appartient a qui va CONFIGURER la
|
||||||
|
machine : `make deployer` attend desormais SSH, cloud-init et la liberation de dpkg,
|
||||||
|
juste avant d'en avoir besoin. Materialiser et configurer sont deux metiers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
|
||||||
|
from proxmox_api import Cluster # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _noeud_de(api: Cluster, vmid: str) -> str | None:
|
||||||
|
"""Sur quel noeud vit ce VMID. Le plan ne le dit pas toujours — la fabric, si."""
|
||||||
|
noeuds = api("/nodes")
|
||||||
|
if not isinstance(noeuds, list):
|
||||||
|
return None
|
||||||
|
for n in noeuds:
|
||||||
|
nom = n.get("node")
|
||||||
|
vms = api(f"/nodes/{nom}/qemu")
|
||||||
|
if isinstance(vms, list) and any(str(v.get("vmid")) == str(vmid) for v in vms):
|
||||||
|
return nom
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _adresses(api: Cluster, noeud: str, vmid: str) -> list[str]:
|
||||||
|
"""Les IPv4 que l'agent invite declare, hors boucle locale.
|
||||||
|
|
||||||
|
Un `_erreur` n'est pas un echec : tant que la machine demarre, l'agent ne repond pas
|
||||||
|
encore. C'est la difference entre « pas encore » et « pas du tout », et seule
|
||||||
|
l'expiration du delai tranche.
|
||||||
|
"""
|
||||||
|
rep = api(f"/nodes/{noeud}/qemu/{vmid}/agent/network-get-interfaces")
|
||||||
|
if not isinstance(rep, dict) or "result" not in rep:
|
||||||
|
return []
|
||||||
|
out = []
|
||||||
|
for i in rep["result"]:
|
||||||
|
if i.get("name") == "lo":
|
||||||
|
continue
|
||||||
|
for a in i.get("ip-addresses") or []:
|
||||||
|
if a.get("ip-address-type") == "ipv4":
|
||||||
|
out.append(str(a.get("ip-address")))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str]) -> int:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
|
ap.add_argument("--vmid", required=True)
|
||||||
|
ap.add_argument("--ip", required=True, help="l'adresse que le plan destine a cette VM")
|
||||||
|
ap.add_argument("--hote", default="", help="son nom, pour les messages")
|
||||||
|
ap.add_argument("--delai", type=int, default=600)
|
||||||
|
ap.add_argument("--noeud", default="")
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
|
||||||
|
api, _ = Cluster.depuis_hebergeur()
|
||||||
|
nom = args.hote or f"VMID {args.vmid}"
|
||||||
|
|
||||||
|
noeud = args.noeud or _noeud_de(api, args.vmid)
|
||||||
|
if not noeud:
|
||||||
|
print(f"ECHEC: VMID {args.vmid} introuvable sur la fabric.", file=sys.stderr)
|
||||||
|
return 4
|
||||||
|
|
||||||
|
print(f"Attente de la materialisation de {nom} ({args.ip}) ", end="", flush=True)
|
||||||
|
fin = time.time() + args.delai
|
||||||
|
vues: list[str] = []
|
||||||
|
while time.time() < fin:
|
||||||
|
vues = _adresses(api, noeud, args.vmid)
|
||||||
|
if args.ip in vues:
|
||||||
|
print(" ok")
|
||||||
|
osinfo = api(f"/nodes/{noeud}/qemu/{args.vmid}/agent/get-osinfo")
|
||||||
|
o = osinfo.get("result", {}) if isinstance(osinfo, dict) else {}
|
||||||
|
print(f" {nom} : {args.ip} portee sur {noeud} — "
|
||||||
|
f"{o.get('pretty-name') or o.get('name') or 'systeme inconnu'} "
|
||||||
|
f"{o.get('kernel-release', '')}".rstrip())
|
||||||
|
return 0
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
print(f"\nECHEC: {nom} n'a pas porte {args.ip} apres {args.delai}s.", file=sys.stderr)
|
||||||
|
if vues:
|
||||||
|
print(f" L'agent invite rapporte pourtant : {', '.join(vues)}. "
|
||||||
|
f"La machine demarre mais son adressage n'est pas celui du plan — "
|
||||||
|
f"cloud-init, ou le pont/VLAN sur lequel elle est posee.", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(" L'agent invite n'a rien rapporte : la machine ne demarre pas, ou "
|
||||||
|
"`qemu-guest-agent` est absent du gabarit dore.", file=sys.stderr)
|
||||||
|
return 4
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
||||||
|
|
@ -1396,6 +1396,52 @@ def preuve_carte_dit_vrai() -> tuple[bool, str]:
|
||||||
f"{len(_carte_chiffres_mesures())} chiffres correspondent a la mesure.")
|
f"{len(_carte_chiffres_mesures())} chiffres correspondent a la mesure.")
|
||||||
|
|
||||||
|
|
||||||
|
def preuve_materialiser_n_exige_pas_d_entrer() -> tuple[bool, str]:
|
||||||
|
"""Creer une VM ne demande pas d'entrer dans le tenant qui l'heberge.
|
||||||
|
|
||||||
|
POURQUOI (mesure du 2026-08-27). `creer-vm` confirmait son succes en attendant une
|
||||||
|
reponse SSH. Le runner du SITE materialise le terrain de TOUS les tenants, mais la
|
||||||
|
frontiere lui refuse d'entrer chez eux — c'est le sens meme de leur isolation. La
|
||||||
|
premiere VM qu'il a creee a donc ete declaree en echec apres 600 secondes alors
|
||||||
|
qu'elle tournait, avec l'adresse exacte que le plan lui destinait.
|
||||||
|
|
||||||
|
La tentation etait d'ouvrir le SSH du runner vers tous les tenants. Ca aurait
|
||||||
|
« repare » la mesure en detruisant ce qu'elle protege : une machine capable d'entrer
|
||||||
|
chez chaque locataire est precisement ce que cette architecture refuse d'avoir.
|
||||||
|
|
||||||
|
L'agent invite repond sans rien ouvrir, et prouve DAVANTAGE — le systeme, le noyau et
|
||||||
|
l'adresse effectivement portee, la ou SSH ne disait que « quelque chose ecoute sur le
|
||||||
|
port 22 ». Cette preuve garde le couplage ferme : materialiser et configurer sont deux
|
||||||
|
metiers, et seul le second exige d'entrer.
|
||||||
|
"""
|
||||||
|
mk = RACINE / "Makefile"
|
||||||
|
if not mk.is_file():
|
||||||
|
return False, "Makefile introuvable."
|
||||||
|
txt = mk.read_text(encoding="utf-8")
|
||||||
|
debut = txt.find("\ncreer-vm:")
|
||||||
|
if debut < 0:
|
||||||
|
return False, "La cible `creer-vm` a disparu du Makefile."
|
||||||
|
# La recette court jusqu'a la prochaine cible en debut de ligne.
|
||||||
|
suite = txt[debut + 1:]
|
||||||
|
fin = re.search(r"\n[A-Za-z_][A-Za-z0-9_.-]*:", suite)
|
||||||
|
recette = suite[:fin.start()] if fin else suite
|
||||||
|
|
||||||
|
outil = RACINE / "scripts" / "attendre_materialisation.py"
|
||||||
|
fautes = []
|
||||||
|
if "_attendre-hote" in recette:
|
||||||
|
fautes.append("`creer-vm` appelle `_attendre-hote`, qui exige SSH : un runner de "
|
||||||
|
"SITE ne peut pas entrer chez un tenant, et ne doit pas pouvoir.")
|
||||||
|
if not outil.is_file():
|
||||||
|
fautes.append("scripts/attendre_materialisation.py est absent.")
|
||||||
|
elif "attendre_materialisation.py" not in recette:
|
||||||
|
fautes.append("`creer-vm` ne confirme plus la materialisation : ni SSH ni agent "
|
||||||
|
"invite. Une creation qui ne verifie rien n'est pas une creation.")
|
||||||
|
if fautes:
|
||||||
|
return False, " | ".join(fautes)
|
||||||
|
return True, ("`creer-vm` confirme par l'agent invite (API des hyperviseurs, deja "
|
||||||
|
"utilisee pour creer), sans exiger d'entrer dans le tenant.")
|
||||||
|
|
||||||
|
|
||||||
def preuve_collections_declarees_et_epinglees() -> tuple[bool, str]:
|
def preuve_collections_declarees_et_epinglees() -> tuple[bool, str]:
|
||||||
"""Toute collection appelee par le moteur est DECLAREE, et a une version EPINGLEE.
|
"""Toute collection appelee par le moteur est DECLAREE, et a une version EPINGLEE.
|
||||||
|
|
||||||
|
|
@ -1958,6 +2004,8 @@ PREUVES: list[dict] = [
|
||||||
"refs": [], "func": preuve_silences_declares},
|
"refs": [], "func": preuve_silences_declares},
|
||||||
{"id": "P51", "titre": "Collections : toutes declarees, toutes epinglees",
|
{"id": "P51", "titre": "Collections : toutes declarees, toutes epinglees",
|
||||||
"refs": [], "func": preuve_collections_declarees_et_epinglees},
|
"refs": [], "func": preuve_collections_declarees_et_epinglees},
|
||||||
|
{"id": "P52", "titre": "Materialiser n'exige pas d'entrer dans le tenant",
|
||||||
|
"refs": [], "func": preuve_materialiser_n_exige_pas_d_entrer},
|
||||||
{"id": "P43", "titre": "Frontiere : le devis voit les machines du site", "refs": [],
|
{"id": "P43", "titre": "Frontiere : le devis voit les machines du site", "refs": [],
|
||||||
"func": preuve_devis_frontiere_du_site},
|
"func": preuve_devis_frontiere_du_site},
|
||||||
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue