une source vide n est pas « tout le monde »

Les journaux de la fabric, et le defaut de classe que leur mise en place a
revele. 10 hotes dans Loki (7 VM du site + 3 hyperviseurs), site a 67
services tous OK contre 51 avant.

METRIQUES TIREES, JOURNAUX POUSSES. Un scrape part du collecteur et doit
REVENIR : il exige un chemin symetrique, que la route par defaut gelee
interdit. Un push part de la source et n attend qu un accuse.

Trois trous dans les generateurs. Le devis de la frontiere ne connaissait
fabric qu en DESTINATION - un flux entrant tombait dans « rien d autre n
entre » sans rien dire. La regle etait posee sur la patte de la destination
alors que D-61 raisonne en ARRIVEE, et opt7 manquait a la table des zones.

Et le plus grave : fabric etait un mot RECONNU mais NON RESOLU. Source
vide, donc regle sans saddr - tcp dport 3100 accept, ouvert a tous. Un mot
reconnu mais non resolu est pire qu un mot inconnu : celui-ci serait refuse
a la validation, celui-la produit une porte grande ouverte qui a l air d un
flux precis.

LA CLASSE ENTIERE EST REFERMEE. Le defaut n etait pas propre a fabric :
toute paire nommant un ensemble et ne resolvant rien ouvrait le port.
Trouve en lisant les fichiers generes, l API de supervision d un tenant
etait ouverte a tous parce que pair: serveur_backup ne resout rien - cet
ecosysteme depose chez le site. expositions et externe, eux, veulent bien
dire tout le monde : c est une intention. Ailleurs, pas de source, pas de
regle, et le generateur le DIT.

Trois regles se referment, chacune verifiee AVANT d appliquer. Deux etaient
sans effet ; la troisieme portait Grafana, qui ecoute bien sur 3000. Sa
declaration disait pair: edge - juste chez un tenant, faux au site qui n a
pas d edge. Ajout de admin : ce qui etait accidentel devient explicite.

Et la mesure a montre autre chose : Grafana etait DEJA injoignable depuis
le poste, la frontiere ne laissant pas passer le 3000. Le site a une
console deployee et sans chemin d acces. Ce n est pas corrige ici, mais
c est dit.

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-10 19:14:23 -04:00
parent 5abf20102d
commit 0778979b89
8 changed files with 231 additions and 10 deletions

View file

@ -1,5 +1,75 @@
# CHANGELOG — Set-OPS
## 2026-09-10 (15) — Une source vide n'est pas « tout le monde »
Les journaux de la fabric, et le defaut de classe que leur mise en place a revele.
journaux 10 hotes dans Loki (7 VM du site + 3 hyperviseurs)
site 67 services, tous OK (51 avant)
### Metriques TIREES, journaux POUSSES — et ce n'est pas un caprice
Un scrape part du collecteur et doit REVENIR : il exige un chemin symetrique, que la route
par defaut gelee (D-57) interdit. Un push part de la source et n'attend qu'un accuse : la
route SPECIFIQUE vers les zones du site suffit — celle qu'on venait justement de completer.
### Trois trous dans les generateurs, du meme jour
**Le devis de la frontiere ne connaissait `fabric` qu'en DESTINATION.** Un flux entrant
depuis la fabric tombait dans « rien d'autre n'entre » et n'emettait AUCUNE regle, sans
rien dire. Meme forme que le trou des integrations universelles, trouve le matin meme.
**La regle etait sur la mauvaise patte.** D-61 fait raisonner le devis en ARRIVEE ; la
regle etait posee sur l'interface de la DESTINATION. `opt7` — la patte face a la fabric,
libellee PROXMOX — manquait a la table des zones, qui ne recensait que le site. Elle y est,
et l'interface se derive desormais du reseau ou vivent les hyperviseurs.
**Et le plus grave : `fabric` etait un mot RECONNU mais NON RESOLU.** Le generateur de
pare-feu d'hote l'acceptait comme valide, ne trouvait aucune adresse, et une source vide
produit une regle sans `saddr` :
tcp dport 3100 accept # ouvert a tout le monde
*Un mot reconnu mais non resolu est pire qu'un mot inconnu* : celui-ci serait refuse a la
validation, celui-la produit une porte grande ouverte qui a l'air d'un flux precis.
### La classe entiere, refermee
Le defaut n'etait pas propre a `fabric`. **Toute** paire nommant un ensemble et ne
resolvant rien ouvrait le port. Trouve en lisant les fichiers generes :
tcp dport 5665 accept # serveur_icinga, sur le mon-01 d'un TENANT
L'API de supervision ouverte a tous, parce que `pair: serveur_backup` ne resout rien — cet
ecosysteme depose son etat chez le site et n'a pas de depot a lui.
`expositions` et `externe`, EUX, veulent bien dire « tout le monde » : ce sont des services
publies, et leur ouverture est une INTENTION. Ailleurs : **pas de source, pas de regle** —
et le generateur le DIT, parce qu'un flux tu en silence est une porte qu'on croit fermee.
Trois regles se sont refermees. Chacune verifiee AVANT d'appliquer :
| regle | verdict |
|---|---|
| `site-forge-01:3000` | rien n'ecoute — la forge sert 443 |
| `mon-01:5665` (tenant) | deux autres regles couvrent les pousseurs |
| `site-mon-01:3000` | **Grafana ecoute** — a corrige avant de fermer |
### Grafana : `edge` ET `admin`
Chez un tenant, le nginx d'edge termine le TLS : `edge` resout, c'est le seul chemin. Le
SITE n'a PAS d'edge — chaque service s'y sert lui-meme. `edge` n'y resolvait donc rien, et
la console etait ouverte a l'Internet PAR ACCIDENT, sous un commentaire qui parlait d'un
edge inexistant.
Ajouter `admin` rend explicite ce qui etait accidentel. Et la mesure a montre autre chose :
**Grafana etait deja injoignable depuis le poste** — la frontiere ne laisse pas passer le
3000. La regle ouverte ne servait a rien, sauf a rester ouverte si la frontiere s'ouvrait
un jour.
*Le site a donc une console deployee et sans chemin d'acces.* Ce n'est pas corrige ici,
mais c'est dit.
## 2026-09-10 (14) — Le site surveille enfin sa fabric
La supervision du site voyait ses sept VM et rien d'autre. Mesure de depart, depuis

View file

@ -21,7 +21,7 @@
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check), 1 nom(s) surveille(s) sans reference orpheline. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 41 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 105 flux, schéma + matrice OK. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 106 flux, schéma + matrice OK. |
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | serveur_resolveur_site |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
@ -37,7 +37,7 @@
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 13 reseau(x), aucune collision avec la plage tenant. |
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 49 groupe(s), 81 regle(s). |
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 49 groupe(s), 85 regle(s). |
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). |
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. |
@ -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 : 55 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 |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 153 regle(s) du site. |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 154 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 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 (123 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 215 regles `pass`), tous non consignes et tous motives. |
| 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 (124 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 216 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 — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |

View file

@ -43,7 +43,7 @@
| `serveur_forgejo` | egress | 25 | tcp | serveur_postfix | starttls | Notifications courriel (relais via le MTA Postfix). |
| `serveur_forgejo` | egress | 443 | tcp | edge | tls-requis | Découverte OIDC et jetons auprès de Keycloak (via son FQDN publié à l'edge). |
| `serveur_forgejo` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Base de données Forgejo (verify-full). |
| `serveur_grafana` | ingress | 3000 | tcp | edge | clair | Interface web servie via l'edge (TLS terminé à l'edge). |
| `serveur_grafana` | ingress | 3000 | tcp | edge, admin | clair | Interface web : servie via l'edge la ou il y en a un, joignable depuis le plan d'administration partout — sans quoi un deploiement sans edge n'a plus de console. |
| `serveur_grafana` | egress | 443 | tcp | edge | tls-requis | Authentification OIDC auprès de Keycloak (via son FQDN publié à l'edge). |
| `serveur_icinga` | ingress | 5665 | tcp | localhost | clair | API Icinga 2 consommée en local par Icinga Web 2 co-localisé. |
| `serveur_icinga` | ingress | 5665 | tcp | client_backup | tls-requis | Rapport passif de chaque detenteur d'etat sur SON depot distant : le depot du site heberge du chiffre et ne peut pas le juger. |
@ -60,6 +60,7 @@
| `serveur_keycloak` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Persistance Keycloak dans PostgreSQL (verify-full). |
| `serveur_loki` | ingress | 3100 | tcp | client_journal | tls | Réception des journaux poussés par Alloy (client_journal) en HTTPS (http_tls_config, cert step-ca). |
| `serveur_loki` | ingress | 3100 | tcp | localhost | clair | Requêtes de Grafana co-localisé (datasource Loki en localhost). |
| `serveur_loki` | ingress | 3100 | tcp | fabric | tls | Journaux des hyperviseurs. La machine qui PORTE les VM ecrivait ses journaux nulle part ailleurs que sur son propre disque — invisibles le jour ou c'est elle qui casse. |
| `serveur_nextcloud` | ingress | 80 | tcp | edge | clair | Interface web servie via l'edge (TLS terminé à l'edge). |
| `serveur_nextcloud` | egress | 443 | tcp | edge | tls-requis | Découverte OIDC auprès de Keycloak (via son FQDN publié à l'edge). |
| `serveur_nextcloud` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Base de données Nextcloud (verify-full). |
@ -118,6 +119,6 @@
- **n-a** : 6 flux
- **ssh** : 8 flux
- **starttls** : 6 flux
- **tls** : 10 flux
- **tls** : 11 flux
- **tls-cible** : 2 flux
- **tls-requis** : 35 flux

View file

@ -2,12 +2,27 @@
# Flux réseau de Grafana (tableaux de bord). Voir docs/flux-conception.md.
# Prometheus et Loki sont co-localisés (datasources en localhost) : aucun flux inter-nœud.
flux:
# `edge` ET `admin` — PARCE QUE TOUS LES DEPLOIEMENTS N'ONT PAS D'EDGE (2026-09-10).
#
# Chez un tenant, le nginx d'edge termine le TLS et parle en clair a Grafana : `edge`
# resout, et c'est le seul chemin. Le SITE, lui, n'a PAS d'edge — chaque service s'y
# sert lui-meme. `edge` n'y resolvait donc rien, et une source vide produisait une regle
# SANS `saddr` : la console ouverte a l'Internet, par accident, avec un commentaire qui
# parlait d'un edge inexistant.
#
# Le jour ou le generateur a cesse d'ouvrir sur une source vide, la regle a disparu et
# l'acces avec elle — mesure : `ss` ne montre que `*:3000`, rien sur 443. Ajouter `admin`
# rend explicite ce qui etait accidentel : la console se joint depuis le plan
# d'ADMINISTRATION, qui est d'ou l'exploitant la regarde. Chez un tenant, la ligne ne
# retire rien — elle ajoute l'operateur a l'edge.
- sens: ingress
port: 3000
protocole: tcp
pair: edge
pair: [edge, admin]
chiffrement: clair
raison: "Interface web servie via l'edge (TLS terminé à l'edge)."
raison: >-
Interface web : servie via l'edge la ou il y en a un, joignable depuis le plan
d'administration partout — sans quoi un deploiement sans edge n'a plus de console.
- sens: egress
port: 443
protocole: tcp

View file

@ -13,3 +13,22 @@ flux:
pair: localhost
chiffrement: clair
raison: "Requêtes de Grafana co-localisé (datasource Loki en localhost)."
# LA FABRIC JOURNALISE AUSSI, ET ELLE, ELLE PEUT POUSSER (2026-09-10).
#
# Les metriques des hyperviseurs sont TIREES : leur route par defaut est gelee (D-57) et
# le porteur de sante y expirait. Les journaux, eux, passent — parce que les routes
# SPECIFIQUES vers les zones du site ont ete completees le meme jour (`10.0.35.0/24` et
# `10.0.36.0/24` manquaient). Alloy pousse donc vers Loki comme n'importe quelle machine.
#
# LA DIFFERENCE AVEC LES METRIQUES N'EST PAS UN CAPRICE : un scrape part du collecteur et
# doit REVENIR, ce qui exige un chemin symetrique ; un push part de la source et n'attend
# qu'un accuse. La route completee suffit au second, pas au premier.
- sens: ingress
port: 3100
protocole: tcp
pair: fabric
chiffrement: tls
raison: >-
Journaux des hyperviseurs. La machine qui PORTE les VM ecrivait ses journaux nulle
part ailleurs que sur son propre disque — invisibles le jour ou c'est elle qui casse.

View file

@ -787,6 +787,38 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
# jamais la zone entiere.
_portees = [("SETOPS_SITE", z) for z in
sorted({_if_de(m.get("reseau")) for m in _machines_site})]
elif _entrant and "fabric" in _pairs_fl:
# LA FABRIC PARLE AUSSI AU SITE (2026-09-10).
#
# `fabric` n'etait connu qu'en DESTINATION — « le runner du site pilote
# les hyperviseurs ». Un flux ENTRANT depuis la fabric tombait donc dans
# la branche « rien d'autre n'entre » et n'emettait AUCUNE regle, sans
# rien dire. Meme forme que le trou des integrations universelles,
# trouve le matin meme : le generateur ne voyait qu'une moitie du monde.
#
# Le cas concret : les hyperviseurs POUSSENT leurs journaux vers Loki.
# Ils ne peuvent pas etre scrutes (route par defaut gelee, D-57) mais ils
# peuvent emettre — un push n'attend qu'un accuse, la ou un scrape exige
# un chemin symetrique.
#
# Une regle par ZONE du site ou vit le role vise, comme pour `flotte` :
# le paquet penetre le pare-feu par la patte de la zone DESTINATAIRE.
# L'INTERFACE EST CELLE PAR OU LE PAQUET ARRIVE (D-61), donc celle
# qui fait face a la FABRIC — pas celle de la destination. Derivee du
# reseau ou vivent les hyperviseurs, jamais ecrite : le jour ou la
# fabric change de patte, la regle suit.
# SEULEMENT LES RESEAUX DONT ON CONNAIT LA PATTE. Un hyperviseur a
# trois adresses (gestion, transport, transit) ; deux de ces reseaux ne
# touchent pas la frontiere et n'ont pas d'interface declaree. Sans ce
# filtre, `_if_de` retombe sur la patte par DEFAUT et pose une regle sur
# une interface que ce trafic n'emprunte jamais — une regle qui a l'air
# d'ouvrir un flux et ne correspond a rien.
_res_fabric = sorted({str(h.get("reseau")) for h in underlay_mod.hotes(_u)
if h.get("role") == "hyperviseur"
and str(h.get("reseau")) in if_zones})
_portees = [("SETOPS_FABRIC", _if_de(r)) for r in _res_fabric]
if not _portees:
continue
elif _entrant and "22" in _ports(_fl) and _admin_site:
# Le SSH de gestion arrive par la patte d'ADMINISTRATION de la
# frontiere, pas par celle du site : c'est la que le paquet penetre

View file

@ -223,6 +223,28 @@ def _ip_par_hote(data: dict) -> dict[str, str]:
return table
def _adresses_fabric() -> list[str]:
"""Les adresses du materiel de l'hebergeur : hyperviseurs et frontiere.
MEME SOURCE QUE L'ALIAS `SETOPS_FABRIC` du devis de la frontiere — la carte de
l'hebergeur. Ecrire ces adresses ailleurs les ferait mentir le jour ou un noeud
change de patte, ce qui est deja arrive : la frontiere a porte `.1` puis `.17`, et
les hyperviseurs ont continue de renvoyer vers une adresse que plus personne ne
portait, « sans qu'aucun journal ne le dise ».
Rend [] quand aucun underlay n'est monte : degrader, jamais deviner.
"""
try:
import underlay as _u
carte = _u.charger()
if not carte:
return []
return sorted({str(h["ip"]) for h in _u.hotes(carte)
if h.get("role") in ("hyperviseur", "frontiere") and h.get("ip")})
except Exception:
return []
def _resoudre_sources(data: dict, pair, actifs_noms: set[str], ip_par_hote: dict[str, str],
site: bool = False) -> list[str]:
"""pair -> IP sources concretes (pour une regle ingress). [] = pas de regle inter-noeud."""
@ -236,6 +258,22 @@ def _resoudre_sources(data: dict, pair, actifs_noms: set[str], ip_par_hote: dict
# `nftables_admin_ssh` ; `voisins_site` des supernets des autres tenants
# federes ; `runner_site` du plan du SITE. Traites a part, plus bas.
continue
if p == "fabric":
# LA FABRIC N'EST PAS UN GROUPE D'HOTES, ELLE EST DANS LA CARTE (2026-09-10).
#
# `fabric` figurait dans `MOTS_PAIR` — donc accepte comme mot valide — sans
# aucune branche pour le resoudre. Il tombait dans le cas general, ne trouvait
# aucun hote, et une source VIDE produit une regle SANS `saddr` :
#
# tcp dport 3100 accept # ouvert a tout le monde
#
# Un mot reconnu mais non resolu est pire qu'un mot inconnu : celui-ci serait
# refuse a la validation, celui-la produit une porte grande ouverte qui a
# l'air d'un flux precis.
#
# Les adresses viennent de l'underlay, comme celles du devis de la frontiere :
# hyperviseurs et frontiere, le materiel qu'aucun agent n'habite.
continue
if p == "flotte":
noms |= actifs_noms
else:
@ -245,6 +283,8 @@ def _resoudre_sources(data: dict, pair, actifs_noms: set[str], ip_par_hote: dict
demandes = pair if isinstance(pair, list) else [pair]
if "admin" in demandes:
srcs |= set(_sources_admin_ssh_site() if site else _sources_admin_ssh())
if "fabric" in demandes:
srcs |= set(_adresses_fabric())
if "voisins_site" in demandes:
# DU COTE DU SITE, « VOISIN » N'EXCLUT PERSONNE (mesure du 2026-09-02).
#
@ -572,7 +612,36 @@ def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) -
ports = reels
srcs = _resoudre_sources(data, fl.get("pair"), actifs_noms, ip_par_hote, site=site)
saddr = "" if not srcs else "ip saddr { " + ", ".join(srcs) + " } "
local = "localhost" in (fl.get("pair") if isinstance(fl.get("pair"), list) else [fl.get("pair")])
_pairs_fl = fl.get("pair") if isinstance(fl.get("pair"), list) else [fl.get("pair")]
local = "localhost" in _pairs_fl
# UNE SOURCE VIDE N'EST PAS « TOUT LE MONDE » (mesure du 2026-09-10).
#
# Une paire qui NOMME un ensemble — un role, `flotte`, `fabric`, `edge` —
# et qui ne resout rien decrivait jusqu'ici une regle SANS `saddr`, donc
# ouverte a l'Internet. Le silence de la resolution devenait une porte.
#
# CE QUE CA A COUTE, trouve en lisant les fichiers generes : sur le
# `mon-01` d'un tenant,
#
# tcp dport 5665 accept # serveur_icinga
#
# L'API de supervision ouverte a tous, parce que `pair: serveur_backup` ne
# resout rien — cet ecosysteme depose son etat chez le site et n'a pas de
# depot a lui. La regle avait l'air d'un flux precis.
#
# `expositions` et `externe`, EUX, veulent bien dire « tout le monde » :
# ce sont des services publies, et leur ouverture est une intention, pas un
# accident. On ne les touche pas.
#
# Ailleurs : pas de source, pas de regle — et on le DIT, parce qu'un flux
# tu en silence est une porte qu'on croit ouverte (ou fermee).
_public = {"expositions", "externe"} & set(_pairs_fl)
if not srcs and not local and not _public:
print(f"note : {g} declare un flux entrant depuis "
f"`{'/'.join(str(x) for x in _pairs_fl)}` qui ne resout aucune "
f"source — aucune regle emise pour {hote} (une source vide "
f"ouvrirait le port a tous).", file=sys.stderr)
continue
for port in ports:
if local and not srcs:
continue # lo deja accepte

View file

@ -539,6 +539,12 @@ def inventaire() -> dict:
_ip_icinga = str(_s.get("ip") or "")
break
_ip_loki = ""
for _n_app, _a_app in applications.items():
if str(_a_app.get("groupe")) == "serveur_loki":
_ip_loki = str((serveurs.get(_a_app.get("hote")) or {}).get("ip") or "")
break
_hyperviseurs = {}
for _h in U.hotes(u):
if str(_h.get("role")) != "hyperviseur":
@ -571,6 +577,10 @@ def inventaire() -> dict:
# de noeud du cluster. Mesure : `curl: (6) Could not resolve host` sur les trois.
**({"client_sante_icinga_url": f"https://{_ip_icinga}:5665"}
if _ip_icinga else {}),
# LES JOURNAUX, EUX, PARTENT D'ICI — et par une ADRESSE, meme raison que
# ci-dessus : pas de plancher `/etc/hosts` sur un hyperviseur.
**({"client_journal_loki_url":
f"http://{_ip_loki}:3100/loki/api/v1/push"} if _ip_loki else {}),
**communes,
}
groupes.setdefault("hyperviseurs", []).append(_nom)
@ -596,6 +606,11 @@ def inventaire() -> dict:
# Prometheus le remplace par sa propre cible `down`, que la sonde `collecte`
# rapporte deja.
groupes.setdefault("client_metrique", []).append(_nom)
# LES JOURNAUX SE POUSSENT, EUX. La route specifique vers les zones du site — celle
# qui manquait pour 35 et 36 — suffit a un push : il part de la source et n'attend
# qu'un accuse. Un scrape, lui, exige que la reponse revienne par le meme chemin,
# ce que la route par defaut gelee empeche. D'ou metriques TIREES, journaux POUSSES.
groupes.setdefault("client_journal", []).append(_nom)
out: dict = {g: {"hosts": sorted(set(h))} for g, h in groupes.items()}
out["site"] = {"hosts": sorted(hostvars)}