tunnel d'administration : le SSH du site et la console de la frontiere manquaient

Eprouve en production : les locataires repondaient, le site non — son SSH vient de `flotte`
et vivait du rebond par la frontiere. Et rien ne designait la frontiere elle-meme, si bien
que monter le tunnel faisait perdre le moyen de le corriger. Une regle par port : OPNsense
refuse deux ports dans un champ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-17 15:26:23 -04:00
parent d90513ca91
commit 2a276fb6b4
4 changed files with 83 additions and 2 deletions

View file

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

View file

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

View file

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

View file

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