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 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-17 16:08:39 -04:00
parent be8c3917e5
commit 05b85ebd27
3 changed files with 22 additions and 4 deletions

View file

@ -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

View file

@ -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 |

View file

@ -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():