diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd0642..a479be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # CHANGELOG — Set-OPS +## 2026-09-17 (4) — Le tunnel d'administration, eprouve en production : deux trous refermes + +Le tunnel montait, la poignee de main se faisait, une machine de LOCATAIRE repondait — et +aucune machine du SITE, ni la console de la frontiere. + +- **SSH des machines du site** : le socle declare son 22 en `pair: [flotte, externe]`, jamais + `admin`. L'acces vivait du REBOND par la frontiere (la connexion part alors du boitier, que + pf laisse sortir sans regle) ; par le tunnel, la source est exterieure et rien ne + l'autorisait. Les locataires marchaient deja : leur regle SSH nait de `nftables_admin_ssh`. +- **Console et API de la frontiere** : aucun flux ne la designait comme destination + d'administration. Monter le tunnel faisait perdre le moyen de reparer la regle manquante — + il a fallu remettre l'adresse d'avant pour appliquer le correctif. +- **Une regle par port** : OPNsense refuse « 22,443 » (« Please specify a valid portnumber, + name, alias or range »). Aucun flux jusqu'ici n'en portait deux. + +Mesure, tunnel monte et adresse d'avant retiree : site-mon-01, site-ops-01, site-dnspub-01, +les deux `infra-dns-01` des locataires et la console OPNsense (HTTP 200) repondent tous, vus +depuis `10.37.29.2`. `make prouver` : CONFORME, 82 OK. + ## 2026-09-17 (3) — L'administration entre par un tunnel nominatif, pas par le runner Question posee : « le runner ne devrait-il pas etre le rebond SSH des admins ? » Non — il diff --git a/docs/acces-administration.md b/docs/acces-administration.md index 5668c16..bd7bb24 100644 --- a/docs/acces-administration.md +++ b/docs/acces-administration.md @@ -58,6 +58,22 @@ l'appareil. Perdue, on en tire une autre ; l'ancienne se révoque par `etat: abs aussi tout pair **attaché à notre instance que le plan ne déclare plus** : un accès qui survivrait à la décision de le retirer est exactement ce qu'on ne veut pas. +## Deux trous que seul l'usage a montrés (2026-09-17, une heure après la pose) + +- **Le SSH des machines du site.** Le socle déclare son 22 en `pair: [flotte, externe]`, jamais + `admin` : la source dérivée est le site lui-même. L'exploitant y arrivait **par rebond sur la + frontière** — la connexion partait alors du boîtier, que pf laisse sortir sans règle. Par le + tunnel, il arrive comme une source extérieure, et plus rien ne l'autorisait. Les locataires, + eux, marchaient : leur règle SSH naît de `nftables_admin_ssh`, qui contient déjà le tunnel. +- **La frontière elle-même.** Aucun flux ne la désignait comme destination d'administration : + monter le tunnel faisait perdre sa console et son API — donc le moyen même de réparer la + règle manquante. *La panne se referme sur celui qui la répare* : il a fallu remettre + l'adresse d'avant pour poser le correctif. + +Les deux règles sont désormais émises par `devis_opnsense` dès que `acces_admin_vpn` est +déclaré (`ports_frontiere`, par défaut 22 et 443). Une règle **par port** : OPNsense refuse +« 22,443 » dans un champ de port, et aucun flux jusque-là n'en portait deux. + ## Ce que le tunnel donne, et ce qu'il ne donne pas `AllowedIPs` est **dérivé** de la carte : zones du site, fabric, lien de transit et supernets diff --git a/docs/audit/preuve-2026-09-17.md b/docs/audit/preuve-2026-09-17.md index 5281353..3c5a7f0 100644 --- a/docs/audit/preuve-2026-09-17.md +++ b/docs/audit/preuve-2026-09-17.md @@ -55,14 +55,14 @@ | 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 : 68 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | | P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre | -| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 9 machine(s) du plan retrouvees, 197 regle(s) du site. | +| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 9 machine(s) du plan retrouvees, 200 regle(s) du site. | | P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. | | P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). | | P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos | | 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 299 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 302 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/devis_opnsense.py b/scripts/devis_opnsense.py index 7b78b45..01c4f50 100644 --- a/scripts/devis_opnsense.py +++ b/scripts/devis_opnsense.py @@ -1419,6 +1419,52 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict: "raison": _fl_f.get("raison", ""), }) + # --- CE QUE LE TUNNEL D'ADMINISTRATION ATTEINT (2026-09-17, mesure en production) --- + # + # DEUX TROUS QUE SEUL L'USAGE A MONTRES, une heure apres la pose du tunnel : + # + # 1. LE SSH DES MACHINES DU SITE. Le socle declare son 22 en `pair: [flotte, externe]`, + # jamais `admin` : la boucle des roles du site le range donc dans « flotte », dont la + # source est le site lui-meme. L'exploitant y arrivait par REBOND sur la frontiere — + # la connexion partait alors du boitier, que pf laisse sortir sans regle. Par le + # tunnel, il arrive comme une source exterieure, et plus rien ne l'autorise. Les + # locataires, eux, marchaient : leur regle SSH nait de `nftables_admin_ssh`, qui + # contient deja le reseau du tunnel. + # + # 2. LA FRONTIERE ELLE-MEME. Aucun flux ne la designe comme destination + # d'administration : monter le tunnel revenait a perdre sa console et son API — donc + # le moyen meme de reparer la regle manquante. C'est la panne qui se referme sur + # celui qui la repare. + if _reseau_vpn: + _ports_frontiere = [str(p) for p in (_vpn_admin.get("ports_frontiere") or [22, 443])] + _cibles_vpn = [("SETOPS_SITE_SERVEUR_DEBIAN", ["22"], + "SSH d'administration vers TOUTES les machines du site, par le tunnel " + "nominatif. Le socle declare son 22 en `flotte` : sans cette regle, " + "l'acces ne survit qu'au rebond par la frontiere.")] + if _ports_frontiere: + _cibles_vpn.append(("SETOPS_FRONTIERE", _ports_frontiere, + "Console et API de la frontiere depuis le tunnel : sans elles, " + "monter le tunnel fait perdre le moyen de le corriger.")) + # UNE REGLE PAR PORT (mesure du 2026-09-17). OPNsense refuse « 22,443 » dans le champ + # `destination_port` : « Please specify a valid portnumber, name, alias or range ». + # Ailleurs, chaque flux ne porte qu'un port et le cas ne s'etait jamais presente. + for _dst_vpn, _ports_vpn, _raison_vpn in _cibles_vpn: + if _dst_vpn not in alias: + continue + for _port_vpn in _ports_vpn: + regles.append({ + "sens": "in", + "interface": if_vpn, + "protocole": "tcp", + "source": "SETOPS_ADMIN_SITE_VPN", + "destination": _dst_vpn, + "ports": [_port_vpn], + "chiffrement": "ssh" if _port_vpn == "22" else "tls", + "role": "acces_admin_vpn", + "tenant": "SITE", + "raison": _raison_vpn, + }) + # --- LA PORTE DU TUNNEL D'ADMINISTRATION (2026-09-17) -------------------------- # # Le tunnel ne peut pas s'etablir si son port n'est pas ouvert sur le WAN. C'est le SEUL