diff --git a/docs/audit/preuve-2026-08-14.md b/docs/audit/preuve-2026-08-14.md new file mode 100644 index 0000000..6454189 --- /dev/null +++ b/docs/audit/preuve-2026-08-14.md @@ -0,0 +1,73 @@ +# Preuve de conformite — Set-OPS — 2026-08-14 + +> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer +> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ; +> aucune validation n'est reimplementee ici. Voir le mode d'emploi : +> [`docs/audit/README.md`](README.md), et le registre trace : +> [`docs/audit/affirmations.md`](affirmations.md). + +- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml` +- **Verdict** : ✅ CONFORME (37 OK · 0 echec · 0 saute) + +## Preuves + +| # | Preuve | Affirmations | Statut | Detail | +|---|---|---|---|---| +| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  | +| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee | +| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 3 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre : plan et inventaire applique coincident. | +| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | | +| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | | +| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. | +| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). | +| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. | +| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 77 flux, schéma + matrice OK. | +| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). | +| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml | +| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. | +| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. | +| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. | +| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | +| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 31 groupes (inventaire dechiffre et parse). | +| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | +| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 26 secret(s) exige(s), tous presents. Voute reelle : 29 cle(s), aucun manque. | +| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | +| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. | +| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. | +| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (20 sections). | +| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 3 reseau(x), aucune collision avec la plage tenant. | +| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 15 regles, 6 routes, admin=10.0.0.0/24,192.168.254.2/32,192.168.255.2/32. | +| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 42 groupe(s), 68 regle(s). | +| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 4 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). | +| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 0 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. | +| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. | +| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, 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, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision ; /!\ aucun noeud de sortie declare (VRF sans chemin vers l'exterieur). | +| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 45 scripts expliques et atteignables, 93 cibles make documentees, 54 roles avec README. | +| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 35 exigence(s) de role, toutes satisfaites (126 cle(s) declaree(s) par l'instance). | +| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 32 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). | +| P34 | Chaque document declare son lecteur | — | ✅ OK | 40 document(s) declarent leur lecteur (16 genere(s) exempte(s)). | +| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | +| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). | +| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (OPS-Technolibre) : — tous offerts. | + +## Couverture des affirmations ✅ du registre + +Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus. +Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005 +`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite +d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ; +elles restent hors du harnais recurrent (rien d'executable a rejouer). + +## Declarations d'intention (⚪ invérifiables localement — assumees) + +Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees** +comme declarations d'intention, non comme preuves : + +- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement + contre une flotte vivante. +- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles. +- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee. +- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume. + +_Rapport genere le 2026-08-14._ diff --git a/docs/implanter-un-tenant-sur-un-site.md b/docs/implanter-un-tenant-sur-un-site.md index 9b49a88..1966baa 100644 --- a/docs/implanter-un-tenant-sur-un-site.md +++ b/docs/implanter-un-tenant-sur-un-site.md @@ -203,7 +203,13 @@ make frontiere-plan make sdn-plan make placement-plan ```yaml --- underlay: + # LE SEED DU SITE — le même que celui de son tenant, et la clé qu'on oublie. + # C'est elle qui dit au validateur que 10..0.0/16 est SON supernet, donc que + # la bande basse lui appartient. Sans elle, `make underlay` refuse le réseau de + # gestion en le prenant pour celui d'un AUTRE site — message déroutant, cause triviale. + index: routeur: # racine du spanning-tree, pas un routeur + # `stp` exige `routeur` : ne pas le déclarer tant qu'aucun commutateur ne l'est. routage_tenants: sdn # le routage inter-zone vit sur l'hyperviseur mtu_overlay: 1450 # 1450 + 50 de VXLAN = 1500 de transport acl_inter_tenant: false # true seulement si le matériel sait lier une ACL à un SVI diff --git a/scripts/devis_opnsense.py b/scripts/devis_opnsense.py index e8909bf..686df8f 100644 --- a/scripts/devis_opnsense.py +++ b/scripts/devis_opnsense.py @@ -796,6 +796,24 @@ def main(argv: list[str]) -> int: if not tenants: print("Aucune instance federee trouvee (../*/plan/nomenclature.yml).", file=sys.stderr) return 1 + + # UNE FRONTIERE NE POLICE QUE LES TENANTS DE SON SITE. `decouvrir()` rend toute la + # federation ; l'hebergeur declare dans SON underlay ceux qu'il porte reellement. + # Sans ce filtre, la frontiere du second site recevait les regles et les routes du + # premier — acceptees par le boitier, jamais correspondantes, jamais signalees. + portes = underlay_mod.tenants_du_site(underlay_mod.charger()) + if portes is not None: + connus = {nom for nom, _, _ in tenants} + for nom in portes: + if nom not in connus: + print(f"ATTENTION : `tenants` de l'underlay nomme « {nom} », " + f"qu'aucun dossier frere ne fournit.", file=sys.stderr) + tenants = [t for t in tenants if t[0] in set(portes)] + if not tenants: + print("Aucun tenant heberge sur ce site : `underlay.tenants` n'en retient " + "aucun de connu.", file=sys.stderr) + return 1 + devis = construire(tenants) if args.verifier: diff --git a/scripts/underlay.py b/scripts/underlay.py index 308e169..cdcc42b 100644 --- a/scripts/underlay.py +++ b/scripts/underlay.py @@ -131,6 +131,29 @@ def mtu_overlay(underlay: dict | None) -> int: return int(v) if isinstance(v, int) else MTU_OVERLAY_DEFAUT +def tenants_du_site(underlay: dict | None) -> list[str] | None: + """Noms de dossier des tenants HEBERGES sur ce site. None = non declare. + + POURQUOI CETTE CLE EXISTE (mesure du 2026-08-13, sur le second site). + Les devis partaient de `devis_reseau.decouvrir()`, qui liste TOUTE la federation : + tout dossier frere portant un `plan/nomenclature.yml` avec un index. C'etait juste + tant qu'il n'y avait qu'un site — l'hebergeur unique portait bien tous les tenants. + + Des le second site, c'est faux et dangereux. Le devis de la frontiere du site B + voulait y poser les regles ET les routes du tenant du site A : trente regles et six + routes vers des sous-reseaux qui n'existent pas la-bas. Elles auraient ete acceptees + par le boitier, elles n'auraient jamais correspondu a un paquet, et rien ne l'aurait + signale — une politique qui a l'air complete et ne protege rien. + + Absente, on garde l'ancien comportement (toute la federation) : un site unique n'a + rien a declarer. C'est le second site qui doit se nommer. + """ + v = (underlay or {}).get("underlay", underlay or {}).get("tenants") + if v is None: + return None + return [str(x) for x in v] if isinstance(v, list) else [str(v)] + + def routage_tenants(underlay: dict | None) -> str: """Qui route entre les zones d'un tenant : `switch` (SVI) ou `sdn` (EVPN).