From 05b85ebd27de44d178fe8e8e36c876ba7cba9d0c Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Thu, 17 Sep 2026 16:08:39 -0400 Subject: [PATCH] tunnel d'administration : TechnoLibre servi, et l'oeuf et la poule du premier pair `pair-nouveau --locataire` refusait de proposer une adresse a un ecosysteme sans aucun pair, soit exactement le premier. Le kit remis au client derive tout ce qu'il affiche. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 15 +++++++++++++++ docs/audit/preuve-2026-09-17.md | 2 +- scripts/vpn_admin.py | 9 ++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c6718..05ce7e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # CHANGELOG — Set-OPS +## 2026-09-17 (6) — TechnoLibre a son tunnel, et son kit de mise en service + +- Instance `admins-technolibre` (`wg23`, port 52023, `10.23.29.0/24`), pair declare dans le + plan de TechnoLibre ; deux regles bornees a `SETOPS_TENANT_TECH23` ; 13 machines + redeployees (0 failed). +- **Defaut d'ergonomie corrige avant de servir** : `pair-nouveau --locataire` refusait de + proposer une adresse a un ecosysteme qui n'a encore AUCUN pair — c'est-a-dire exactement le + premier. L'oeuf et la poule, sur le geste d'ouverture. +- **Kit remis au client** (`livraisons/generer-kit-vpn.py`, hors depot) : reseau, port, cle + publique du serveur et adresses des machines sont DERIVES ou LUS, jamais recopies. Le + document dit lui-meme que la premiere cle privee a voyage et comment la remplacer par une + cle tiree sur l'appareil. + +`make prouver` : CONFORME, 82 OK. + ## 2026-09-17 (5) — Un tunnel d'administration par locataire : il declare, le site sert - **Nouveau registre de plan** : `plan/acces.yml` (`acces_admin_vpn`), une entree par personne diff --git a/docs/audit/preuve-2026-09-17.md b/docs/audit/preuve-2026-09-17.md index d194e2c..485c997 100644 --- a/docs/audit/preuve-2026-09-17.md +++ b/docs/audit/preuve-2026-09-17.md @@ -62,7 +62,7 @@ | P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). | | P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 101 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. | | 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 (138 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 305 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 308 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 | | 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. | | P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b | diff --git a/scripts/vpn_admin.py b/scripts/vpn_admin.py index a6f18f9..45207fa 100644 --- a/scripts/vpn_admin.py +++ b/scripts/vpn_admin.py @@ -41,7 +41,7 @@ import devis_opnsense as devis_mod # noqa: E402 import underlay as underlay_mod # noqa: E402 -def tunnels() -> list[dict]: +def tunnels(inclure_vides: bool = False) -> list[dict]: """Tous les tunnels a tenir : celui du SITE, puis un par locataire qui declare des pairs. DEUX DECLARANTS, DEUX PORTEES. Le site declare le sien dans son plan (`acces_admin_vpn`). @@ -64,7 +64,10 @@ def tunnels() -> list[dict]: base = RACINE.parent / nom_depot registre = charger_acces(base / "plan" / "acces.yml") index = charger_nomenclature(base / "plan" / "nomenclature.yml").get("index") - if index is None or not (registre.get("acces_admin_vpn") or {}): + # UN LOCATAIRE SANS PAIR N'A PAS DE TUNNEL — sauf quand on vient justement lui en + # proposer un (`pair-nouveau`) : refuser la` aurait fait de la premiere cle un + # probleme d'oeuf et de poule, et c'est ce que le premier essai a montre. + if index is None or (not (registre.get("acces_admin_vpn") or {}) and not inclure_vides): continue valider_acces(registre, index) slug = nom_depot.lower().removeprefix("ops-") @@ -312,7 +315,7 @@ def cmd_plan(args) -> int: def _tunnel_vise(portee: str | None) -> dict: """Le tunnel du site par defaut ; celui d'un locataire avec `--locataire OPS-X`.""" - tous = tunnels() + tous = tunnels(inclure_vides=True) if portee: for t in tous: if t["portee"].lower() == portee.lower():