From b526160cbc6243e3faacc7aae8d8386d04694051 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Thu, 6 Aug 2026 13:59:11 -0400 Subject: [PATCH] =?UTF-8?q?flux=20:=20r=C3=A9seau=20d'administration=20=3D?= =?UTF-8?q?=20VLAN=2010,=20et=20make=20flux=20r=C3=A9par=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `nftables_admin_ssh` est la source unique du « d'où administre-t-on » : il alimente les alias de la frontière, le devis du commutateur et le jeu nftables de chaque VM. Chezlepro déclarait encore l'ancien 192.168.255.0/24. `make flux` échouait par ailleurs sur un tri mêlant ports numériques et symboliques — défaut latent réveillé par les deux flux ICMP frag-needed, qui sont dans les deux sens. `_cle_port()` rend la clé homogène. Aperçus régénérés : les 14 hôtes actifs de Chezlepro portent 10.0.0.0/24. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ docs/registre-flux.md | 4 +++- scripts/resoudre_flux.py | 18 +++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f28bf90..651132f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ ## 2026-08-06 — le chemin nord-sud devient dérivable +### Le réseau d'administration est le VLAN 10, partout + +`nftables_admin_ssh` déclarait encore `192.168.255.0/24` chez Chezlepro — l'ancien monde. +Cet intrant a **trois** consommateurs : les alias `SETOPS_ADMIN_*` de la frontière, le +devis du commutateur, et le jeu nftables de chaque VM. Il est donc devenu la source unique +du « d'où administre-t-on ». + +- **Chezlepro** : `10.0.0.0/24`. Le VLAN 10 *est* son réseau d'administration (D-54). +- **Technolibre** : ses deux réseaux **plus** `10.0.0.0/24`. Un tenant doit accepter son + propre admin **et** celui de qui l'héberge — c'est de la VLAN 10 de l'hébergeur + qu'Ansible se connecte. Sans le second, aucun déploiement ne joindrait ses VM. + +Propagé : alias mis à jour sur la frontière, 14 aperçus nftables régénérés. + +**Ce que ça évitait.** `flux-genere/infra-pki-01.nft` était figé avec l'ancienne valeur, et +il **prime** sur le gabarit plat quand il existe. Un `make deployer` aurait posé un jeu +n'autorisant SSH que depuis `192.168.255.0/24`, sur un hôte joint depuis `10.0.0.x` — la +machine se serait fermée derrière l'outil qui venait de la configurer. Les treize autres +n'avaient pas d'aperçu et seraient tombées sur le gabarit plat : SSH ouvert à tous, pas de +blocage mais aucune restriction non plus. Les quatorze portent maintenant la bonne source. + +### `make flux` était cassé par un port non numérique + +Le tri du registre comparait les ports directement, donc `int` contre `str` dès que deux +types se croisaient dans un même sens pour un même rôle. Le défaut était **latent** : +`serveur_nginx` a `derive` depuis longtemps, mais son voisin est dans l'autre sens et le +tuple tranche sur le rang avant d'atteindre le port. Les deux flux ICMP `frag-needed` de +`serveur_debian`, eux, sont dans les deux sens — ils ont rendu la comparaison inévitable. + +`_cle_port()` rend la clé homogène : les numériques d'abord, les symboliques ensuite par +ordre alphabétique. Le registre se régénère. + + ### L'EVPN tourne Les commutateurs configurés, le trunk vérifié : `10.0.5.41` joint `.43` et `.47`. Le SDN diff --git a/docs/registre-flux.md b/docs/registre-flux.md index 65cf5ce..121cb16 100644 --- a/docs/registre-flux.md +++ b/docs/registre-flux.md @@ -19,9 +19,11 @@ | `serveur_collabora` | ingress | 9980 | tcp | edge | clair | Éditeur servi au navigateur via l'edge (WebSocket WOPI ; TLS terminé à l'edge). | | `serveur_collabora` | ingress | 9980 | tcp | localhost | clair | Vérifications WOPI serveur→Collabora depuis Nextcloud co-localisé. | | `serveur_debian` | ingress | 22 | tcp | flotte, externe | ssh | Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). | +| `serveur_debian` | ingress | frag-needed | icmp | externe | n-a | ICMP « fragmentation nécessaire » entrant : sans lui, un distant ne peut pas nous demander de réduire nos paquets — les transferts se figent. | | `serveur_debian` | egress | 80 | tcp | externe | clair | Dépôts apt en clair et redirections HTTP des miroirs (l'intégrité vient de la signature des paquets, pas du transport). | | `serveur_debian` | egress | 123 | udp | externe | n-a | Synchronisation d'horloge (NTP). Une dérive fait échouer la validation des certificats step-ca et l'authentification SSO. | | `serveur_debian` | egress | 443 | tcp | externe | tls-requis | Dépôts apt en HTTPS (Debian, Smallstep, Grafana, Icinga, Forgejo, Nextcloud) — sans quoi aucun correctif de sécurité n'entre. | +| `serveur_debian` | egress | frag-needed | icmp | externe | n-a | ICMP « fragmentation nécessaire » sortant : c'est ainsi que nos hôtes signalent l'overlay à 1450 aux correspondants distants. | | `serveur_dovecot` | ingress | 24 | tcp | serveur_postfix | tls-requis | Remise LMTP depuis Postfix (edge-mta -> mailstore), en TLS vérifié (lmtp_tls_security_level=verify). | | `serveur_dovecot` | ingress | 993 | tcp | externe | tls-requis | Accès courriel des utilisateurs (IMAPS). Frontière publique gérée à l'OPNsense. | | `serveur_dovecot` | ingress | 12345 | tcp | serveur_postfix | tls | Authentification SASL déléguée : Postfix valide les identifiants de soumission contre Dovecot. | @@ -78,7 +80,7 @@ ## Synthèse chiffrement - **clair** : 26 flux -- **n-a** : 1 flux +- **n-a** : 3 flux - **ssh** : 3 flux - **starttls** : 6 flux - **tls** : 8 flux diff --git a/scripts/resoudre_flux.py b/scripts/resoudre_flux.py index daf0002..e811e4b 100644 --- a/scripts/resoudre_flux.py +++ b/scripts/resoudre_flux.py @@ -103,6 +103,22 @@ def valider(flux_par_role: dict[str, list[dict]]) -> tuple[int, int]: # --- Registre d'audit (niveau moteur, sans instance) --------------------------- +def _cle_port(port) -> tuple: + """Cle de tri stable pour un port, qu'il soit numerique ou symbolique. + + Tous les ports ne sont pas des nombres : `derive` (serveur_nginx) et + `frag-needed` (ICMP, serveur_debian) en sont. Comparer directement + laisserait `int < str` lever une TypeError des que deux ports de types + differents se retrouvent dans le meme sens pour un meme role. + """ + if isinstance(port, list): + port = port[0] if port else 0 + try: + return (0, int(port), "") + except (TypeError, ValueError): + return (1, 0, str(port)) + + def generer_registre(flux_par_role: dict[str, list[dict]]) -> str: lignes = [ "# Registre des flux réseau — matrice d'audit (GÉNÉRÉ)", @@ -116,7 +132,7 @@ def generer_registre(flux_par_role: dict[str, list[dict]]) -> str: ] rangs = {s: i for i, s in enumerate(("ingress", "egress"))} for role in sorted(flux_par_role): - for fl in sorted(flux_par_role[role], key=lambda f: (rangs.get(f.get("sens"), 9), f.get("port") or 0)): + for fl in sorted(flux_par_role[role], key=lambda f: (rangs.get(f.get("sens"), 9), _cle_port(f.get("port")))): pair = ", ".join(str(p) for p in _pairs(fl)) port = fl.get("port") port = ", ".join(str(x) for x in port) if isinstance(port, list) else str(port)