frontiere : un type ICMP n est pas un port symbolique

LA CAUSE EXACTE. devis_opnsense traitait tout `port` non numerique comme
SYMBOLIQUE, a resoudre par le plan. Vrai pour `derive`, le port d un
service que seul le plan connait. FAUX pour l ICMP : echo-request et
frag-needed sont des litteraux. Ils tombaient dans la branche « le plan ne
resout pas » et aucune regle n etait emise. Une ligne de condition, et le
silence de toute une flotte.

CE QUE LA CORRECTION A REVELE EN PLUS. Huit regles a creer, zero a
retirer — et SIX sont du PMTUD entre zones du site, absent pour la meme
raison. Le depot dit de ces messages : bloques, la connexion s etablit,
les petites requetes passent et les grosses reponses restent suspendues —
la panne la plus couteuse a diagnostiquer. Elle etait la, silencieuse.

CE QUE LA REGLE AUTORISE, EXACTEMENT. appliquer_opnsense n envoie
destination_port que pour TCP et UDP : une regle ICMP ouvre le PROTOCOLE
entre deux pairs, pas le seul type. Le type reste porte par la regle d
HOTE, que resoudre_flux emet precisement. La frontiere dit qui peut parler
a qui, l hote dit ce qu il accepte d entendre. Emettre un icmptype a la
frontiere aurait demande de traduire frag-needed dans le vocabulaire de
pf, au risque de casser le PMTUD pour gagner de la precision sur une
barriere qui n est pas la derniere.

MESURE APRES APPLICATION :
  ping site-mon-01 -> les six autres zones : 6/6 repondent
  hotes UP : 1/7 -> 7/7 | ping4 : 1/7 -> 7/7 | certificat 7/7 | sante 7/7
  tenant inchange : 14/14

make prouver : CONFORME, 64 OK, 0 echec, 0 saute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
Daniel Allaire 2026-09-09 22:51:13 -04:00
parent a070c339ee
commit 843d39e7aa
3 changed files with 56 additions and 9 deletions

View file

@ -45,14 +45,36 @@ en ingress) — et le registre a refuse la premiere moitie seule, ce qui est exa
raison d'etre. `CODES_ICMP` apprend `echo-request` (un TYPE, pas un code de raison d'etre. `CODES_ICMP` apprend `echo-request` (un TYPE, pas un code de
destination-unreachable). Les regles d'hote sont posees sur les 21 machines. destination-unreachable). Les regles d'hote sont posees sur les 21 machines.
**Ce qui reste ouvert, et je le dis parce que le probleme n'est pas resolu :** le ### Le generateur de la frontiere : corrige, et il cachait un second manque
generateur de la frontiere ne sait pas traduire un TYPE ICMP pour un pair INTERNE. Il le
note et n'emet rien :
note : serveur_icinga declare un port `echo-request` que le plan du site ne resout pas **La cause exacte.** `devis_opnsense.py` traitait tout `port` non numerique comme
SYMBOLIQUE — a resoudre par le plan. C'est vrai pour `derive`, le port d'un service que
seul le plan connait. C'est FAUX pour l'ICMP : `echo-request` et `frag-needed` sont des
litteraux, pas des inconnues. Ils tombaient dans la branche « le plan ne resout pas », et
aucune regle n'etait emise.
Le site reste donc a 1/7. Corriger `devis_opnsense.py` pour l'ICMP interne est le prochain Une ligne de condition, et le silence de toute une flotte.
geste — et c'est un geste sur le generateur de la frontiere, pas une retouche.
**Ce que la correction a revele en plus.** Huit regles a creer, zero a retirer — et SIX
d'entre elles sont du **PMTUD entre zones du site**, absent lui aussi pour la meme raison.
Le depot dit de ces deux messages ICMP : *« Bloques, la connexion s'etablit, les petites
requetes passent et les grosses reponses restent suspendues — la panne la plus couteuse a
diagnostiquer. »* Elle etait la, silencieuse, sur les sept machines de l'hebergeur.
**Ce que la regle emise autorise, exactement — et pourquoi on ne pretend pas mieux.**
`appliquer_opnsense` n'envoie `destination_port` que pour TCP et UDP : une regle ICMP ouvre
le PROTOCOLE entre deux pairs, pas le seul type declare. Le type reste porte par la regle
d'HOTE, que `resoudre_flux` emet precisement (`icmp type echo-request accept`). La
frontiere dit qui peut parler a qui ; l'hote dit ce qu'il accepte d'entendre. Emettre un
`icmptype` a la frontiere aurait ete plus fin — et aurait demande de traduire `frag-needed`
(un CODE de `destination-unreachable`) dans le vocabulaire de pf, au risque de casser le
PMTUD pour gagner de la precision sur une barriere qui n'est pas la derniere.
**Mesure apres application :**
ping site-mon-01 -> les six autres zones : 6/6 repondent
hotes UP : 1/7 -> 7/7
ping4 : 1/7 -> 7/7 certificat 7/7, sante 7/7
## 2026-09-09 (7) — Le contrat des sondes ETAIT celui de Nagios, sans le savoir ## 2026-09-09 (7) — Le contrat des sondes ETAIT celui de Nagios, sans le savoir

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. | | 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 : 54 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | | P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 54 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product | | P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 110 regle(s) du site. | | P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 118 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. | | 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). | | 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 | | 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). | | 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 89 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. | | P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 89 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 (121 lignes). | | 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 (121 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 175 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 183 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 | | 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. | | 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 — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b | | P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |

View file

@ -800,7 +800,32 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
if "serveur_ops_tenant" in _pairs_fl and not _runners_tenants: if "serveur_ops_tenant" in _pairs_fl and not _runners_tenants:
continue continue
_p = _ports(_fl) _p = _ports(_fl)
if any(not str(x).isdigit() for x in _p): # L'ICMP N'A PAS DE PORT, ET SON `port` N'EST PAS SYMBOLIQUE (2026-09-09).
#
# Le test ci-dessous dit « non numerique = a resoudre par le plan », ce qui
# est vrai pour `derive` — le port d'un service que seul le plan connait.
# C'est FAUX pour un flux ICMP : `echo-request` et `frag-needed` sont des
# litteraux, pas des inconnues. Ils tombaient donc dans la branche
# « le plan ne resout pas » et AUCUNE regle n'etait emise.
#
# CE QUE CA A COUTE : l'Icinga du site tenait 6 de ses 7 machines pour
# MORTES. `hostalive` est un ping ; le site est decoupe en zones ; la
# frontiere avalait l'ICMP inter-zones faute de regle. Et Icinga SUPPRIME
# les notifications des services d'un hote DOWN — la supervision n'alertait
# plus de rien tout en ayant l'air de fonctionner. Le meme silence valait
# pour PMTUD entre zones du site.
#
# CE QUE LA REGLE EMISE AUTORISE, EXACTEMENT. `appliquer_opnsense` n'envoie
# `destination_port` que pour TCP et UDP : une regle ICMP ouvre le
# PROTOCOLE entre les deux pairs, pas le seul type declare. Le type reste
# porte par la regle d'HOTE, que `resoudre_flux` emet precisement
# (`icmp type echo-request accept`). La frontiere dit qui peut parler a
# qui ; l'hote dit ce qu'il accepte d'entendre. On ne pretend donc pas ici
# une finesse qu'on n'applique pas — c'est le sens de la ligne « (type
# filtre a l'hote) » ajoutee au devis.
if str(_fl.get("protocole", "")).lower() in ("icmp", "icmpv6"):
pass
elif any(not str(x).isdigit() for x in _p):
_reel = _ports_site.get(_role) _reel = _ports_site.get(_role)
if not _reel: if not _reel:
# SUR STDERR, JAMAIS STDOUT : ce script rend du JSON, et une # SUR STDERR, JAMAIS STDOUT : ce script rend du JSON, et une