la frontiere journalise a nouveau, et une garde veille cette fois
Doctrine posee par l exploitant : des lors que le site a son Loki, la journalisation de la frontiere et des hyperviseurs doit y etre dirigee. CE QUI ETAIT CASSE. La destination syslog d OPNsense pointait 10.17.20.11:3100 - une adresse de TENANT, sur le port de Loki, en UDP, ce que Loki ne sait pas lire. La VM a ete rasee ; une cible morte a arrete TOUTE la journalisation pendant dix jours. La destination a ete eteinte pour reparer et jamais remplacee. Deux fautes en une : plus de journaux, et une cible chez un locataire, ce que D-87 refuse dans l autre sens. L intention etait juste - bonnes facilites, info et au-dessus. On l a REPRISE telle quelle et change uniquement la destination : ces choix avaient ete faits, ce n etait pas a nous de les redecider. UN TRADUCTEUR, parce que Loki ne parle pas syslog : loki.source.syslog dans l Alloy qui tourne DEJA sur le collecteur. Port 1514 et non 514, donc ecoute sans privilege - un collecteur qui aurait besoin des droits du systeme pour entendre un equipement serait un mauvais echange. UNE REGLE DE RELABEL SANS GARDE N IGNORE PAS : ELLE EFFACE. Trois quarts d heure sur un symptome absurde - la configuration deposee portait host = "bifrost-1", visible dans le fichier, et le flux arrivait sans etiquette. Sans regex, la regle correspond TOUJOURS, meme quand sa source n existe pas, et pose host = "". Loki jette une etiquette vide, et celle de l ecouteur disparaissait avec elle. Et la verification finale a corrige une seconde erreur, la mienne : label/host/values rendait une liste en CACHE. La requete directe rendait bien un flux. Un index qui ne montre pas une chose ne prouve pas qu elle n existe pas. LA GARDE, qui manquait depuis l incident. journaux-frontiere demande a LOKI ce qu il a RECU, pas a la frontiere ce qu elle croit avoir envoye : la destination est le seul juge, et un emetteur qui parle a un trou noir se porte tres bien. La fenetre est DERIVEE du debit observe - environ 1500 lignes par minute - pas choisie. Trois etats eprouves sur une copie : frontiere muette rc=2, Loki injoignable rc=3, debit normal rc=0. Le 3 compte autant que le 2 : « je ne peux rien affirmer » n est pas « c est casse ». Mesure : 46 681 lignes recues en 30 min, site a 68 services tous OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
0778979b89
commit
c733d2df1c
11 changed files with 293 additions and 8 deletions
63
CHANGELOG.md
63
CHANGELOG.md
|
|
@ -1,5 +1,68 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-09-10 (16) — La frontiere journalise a nouveau, et une garde veille cette fois
|
||||||
|
|
||||||
|
Doctrine posee par l'exploitant : *des lors que le site a son Loki, la journalisation de la
|
||||||
|
frontiere et des hyperviseurs doit y etre dirigee.*
|
||||||
|
|
||||||
|
46 681 lignes recues de la frontiere en 30 min
|
||||||
|
site : 68 services, tous OK
|
||||||
|
|
||||||
|
### Ce qui etait casse, et depuis quand
|
||||||
|
|
||||||
|
La destination syslog d'OPNsense pointait `10.17.20.11:3100` — une adresse de **TENANT**,
|
||||||
|
sur le port de Loki, en **UDP**, ce que Loki ne sait pas lire. La VM a ete rasee ; une cible
|
||||||
|
morte a arrete TOUTE la journalisation d'OPNsense pendant dix jours. La destination a ete
|
||||||
|
ETEINTE pour reparer, et jamais remplacee.
|
||||||
|
|
||||||
|
Deux fautes en une : la frontiere ne journalisait plus nulle part, **et** sa cible etait chez
|
||||||
|
un locataire — ce que D-87 refuse dans l'autre sens.
|
||||||
|
|
||||||
|
L'intention, elle, etait juste : les bonnes facilites, `info` et au-dessus. On l'a REPRISE
|
||||||
|
telle quelle et change uniquement la destination — ces choix avaient ete faits, ce n'etait
|
||||||
|
pas a nous de les redecider.
|
||||||
|
|
||||||
|
### Un traducteur, parce que Loki ne parle pas syslog
|
||||||
|
|
||||||
|
`loki.source.syslog` dans l'Alloy qui tourne DEJA sur le collecteur — un second agent
|
||||||
|
n'aurait servi qu'a tenir deux configurations en phase. Port 1514 et non 514 : au-dessus de
|
||||||
|
1024, donc ecoute sans privilege. *Un collecteur qui aurait besoin des droits du systeme
|
||||||
|
pour entendre un equipement serait un mauvais echange.*
|
||||||
|
|
||||||
|
### Une regle de relabel sans garde n'ignore pas : elle EFFACE
|
||||||
|
|
||||||
|
Trois quarts d'heure sur un symptome absurde — la configuration deposee portait
|
||||||
|
`host = "bifrost-1"`, visible a l'oeil dans le fichier, et le flux arrivait sans etiquette.
|
||||||
|
|
||||||
|
rule { source_labels = ["__syslog_connection_ip_address"]
|
||||||
|
target_label = "host" } # sans regex
|
||||||
|
|
||||||
|
Sans `regex`, la regle correspond **toujours** — meme quand sa source n'existe pas — et pose
|
||||||
|
`host = ""`. Loki jette une etiquette vide, et celle de l'ecouteur disparaissait avec elle.
|
||||||
|
Les deux autres regles etaient gardees ; celle-la ne l'etait pas.
|
||||||
|
|
||||||
|
Et la verification finale a corrige une seconde erreur, la mienne : `label/host/values`
|
||||||
|
rendait une liste en CACHE. La requete directe `{host="bifrost-1"}` rendait bien un flux.
|
||||||
|
*Un index qui ne montre pas une chose ne prouve pas qu'elle n'existe pas.*
|
||||||
|
|
||||||
|
### La garde, qui manquait depuis l'incident
|
||||||
|
|
||||||
|
`journaux-frontiere` demande a **Loki ce qu'il a RECU**, pas a la frontiere ce qu'elle croit
|
||||||
|
avoir envoye — la destination est le seul juge, et un emetteur qui parle a un trou noir se
|
||||||
|
porte tres bien.
|
||||||
|
|
||||||
|
**La fenetre est DERIVEE du debit observe**, pas choisie : ~1 500 lignes/minute mesurees.
|
||||||
|
Une frontiere qui filtre ne se tait jamais dix minutes.
|
||||||
|
|
||||||
|
Trois etats, tous eprouves sur une copie :
|
||||||
|
|
||||||
|
frontiere muette rc=2 « LA FRONTIERE NE JOURNALISE PLUS »
|
||||||
|
Loki injoignable rc=3 « la sonde ne peut rien affirmer »
|
||||||
|
debit normal rc=0 « 973 ligne(s) recue(s) en 10m »
|
||||||
|
|
||||||
|
Le `3` compte autant que le `2` : *« je ne peux rien affirmer » n'est pas « c'est casse »*.
|
||||||
|
Une sonde qui confondrait les deux accuserait la frontiere d'un silence qui serait le sien.
|
||||||
|
|
||||||
## 2026-09-10 (15) — Une source vide n'est pas « tout le monde »
|
## 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.
|
Les journaux de la fabric, et le defaut de classe que leur mise en place a revele.
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
|
| 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. |
|
| 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. |
|
| 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, 106 flux, schéma + matrice OK. |
|
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 39 rôles, 107 flux, schéma + matrice OK. |
|
||||||
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
| 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 |
|
| 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. |
|
| 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). |
|
| 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. |
|
| 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. |
|
| 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), 85 regle(s). |
|
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 49 groupe(s), 87 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). |
|
| 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. |
|
| 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. |
|
| 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. |
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 59 scripts expliques et atteignables, 116 cibles make documentees, 67 roles avec README. |
|
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 59 scripts expliques et atteignables, 116 cibles make documentees, 67 roles avec README. |
|
||||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (132 cle(s) declaree(s) par l'instance). |
|
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (132 cle(s) declaree(s) par l'instance). |
|
||||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
|
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
|
||||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (36 genere(s) exempte(s)). |
|
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (36 genere(s) exempte(s)). |
|
||||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
||||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||||
|
|
@ -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 : 55 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
| 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 |
|
| 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, 154 regle(s) du site. |
|
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 155 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 (124 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 (125 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. |
|
| 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 217 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 |
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
|
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
|
||||||
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
|
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
|
||||||
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
|
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
|
||||||
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 23 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
|
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 24 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
|
||||||
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
|
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
|
||||||
|
|
||||||
## Couverture des affirmations ✅ du registre
|
## Couverture des affirmations ✅ du registre
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
| `serveur_keycloak` | ingress | 8080 | tcp | edge | clair | Console et endpoints OIDC servis au navigateur et aux applications via l'edge (TLS terminé à l'edge). |
|
| `serveur_keycloak` | ingress | 8080 | tcp | edge | clair | Console et endpoints OIDC servis au navigateur et aux applications via l'edge (TLS terminé à l'edge). |
|
||||||
| `serveur_keycloak` | egress | 636 | tcp | serveur_openldap | tls-requis | Fédération de l'annuaire OpenLDAP (LDAPS). |
|
| `serveur_keycloak` | egress | 636 | tcp | serveur_openldap | tls-requis | Fédération de l'annuaire OpenLDAP (LDAPS). |
|
||||||
| `serveur_keycloak` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Persistance Keycloak dans PostgreSQL (verify-full). |
|
| `serveur_keycloak` | egress | 5432 | tcp | serveur_postgresql | tls-requis | Persistance Keycloak dans PostgreSQL (verify-full). |
|
||||||
|
| `serveur_loki` | ingress | 1514 | tcp | fabric | clair | Journaux de la frontiere. Elle n'accueille aucun agent et n'emet que du syslog ; sans ce flux, le seul equipement qui voit passer TOUT le trafic n'ecrit nulle part. |
|
||||||
| `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 | 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 | 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_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. |
|
||||||
|
|
@ -115,7 +116,7 @@
|
||||||
|
|
||||||
## Synthèse chiffrement
|
## Synthèse chiffrement
|
||||||
|
|
||||||
- **clair** : 38 flux
|
- **clair** : 39 flux
|
||||||
- **n-a** : 6 flux
|
- **n-a** : 6 flux
|
||||||
- **ssh** : 8 flux
|
- **ssh** : 8 flux
|
||||||
- **starttls** : 6 flux
|
- **starttls** : 6 flux
|
||||||
|
|
|
||||||
|
|
@ -57,3 +57,26 @@ client_journal_sonde_etat: "/var/lib/setops/journaux-jetees"
|
||||||
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
|
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
|
||||||
client_journal_depot_schema: >-
|
client_journal_depot_schema: >-
|
||||||
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}
|
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}
|
||||||
|
|
||||||
|
# --- RECEPTEUR SYSLOG (vide = pas de recepteur) ---------------------------------------
|
||||||
|
#
|
||||||
|
# CE QUI NE PEUT PAS PORTER D'AGENT DOIT QUAND MEME ETRE ENTENDU. La frontiere OPNsense
|
||||||
|
# n'accueille rien ; elle sait emettre du syslog. Le collecteur du site l'ecoute et le
|
||||||
|
# traduit vers Loki.
|
||||||
|
#
|
||||||
|
# CE QUE CA A COUTE DE NE PAS L'AVOIR : la destination syslog de la frontiere pointait une
|
||||||
|
# VM de TENANT, rasee depuis. Une cible morte a arrete TOUTE la journalisation d'OPNsense
|
||||||
|
# pendant dix jours, et rien ne l'a signale — c'est le meme silence qui a motive le `ttl`
|
||||||
|
# des sondes. La destination a ete eteinte pour reparer, et jamais remplacee : la frontiere
|
||||||
|
# ne journalisait plus nulle part.
|
||||||
|
#
|
||||||
|
# ET LA CIBLE ETAIT CHEZ UN LOCATAIRE, ce que D-87 refuse dans l'autre sens : les journaux
|
||||||
|
# du site ne vivent pas chez un tenant, pas plus que l'inverse. Le site a son Loki depuis
|
||||||
|
# le 2026-09-10 ; c'est la que ses equipements parlent.
|
||||||
|
#
|
||||||
|
# Vide par defaut : un tenant n'a pas de frontiere a ecouter.
|
||||||
|
client_journal_syslog_ecoute: ""
|
||||||
|
client_journal_syslog_protocole: "tcp"
|
||||||
|
# Le nom sous lequel les journaux recus apparaitront. Derive de la carte par l'inventaire
|
||||||
|
# du site — jamais fabrique ici.
|
||||||
|
client_journal_syslog_emetteur: "frontiere"
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,80 @@ loki.source.journal "journal" {
|
||||||
host = "{{ inventory_hostname }}",
|
host = "{{ inventory_hostname }}",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% if client_journal_syslog_ecoute %}
|
||||||
|
// --- RECEPTEUR SYSLOG : CE QUI NE PEUT PAS PORTER D'AGENT (2026-09-10) ---
|
||||||
|
//
|
||||||
|
// La frontiere OPNsense n'accueille aucun agent. Elle sait emettre du syslog, et rien
|
||||||
|
// d'autre. Loki ne parle pas syslog : il faut un traducteur, et Alloy en est un.
|
||||||
|
//
|
||||||
|
// POURQUOI CE RECEPTEUR VIT ICI plutot que dans un role a lui : c'est la meme machine qui
|
||||||
|
// pousse deja ses propres journaux vers le meme Loki. Un second agent ne servirait qu'a
|
||||||
|
// avoir deux configurations a garder en phase.
|
||||||
|
//
|
||||||
|
// L'ETIQUETTE `host` VIENT DE L'EMETTEUR, pas de nous : plusieurs equipements peuvent
|
||||||
|
// pointer ici, et les confondre sous le nom du recepteur rendrait les journaux
|
||||||
|
// inutilisables au moment ou l'on en a besoin.
|
||||||
|
loki.source.syslog "frontiere" {
|
||||||
|
listener {
|
||||||
|
address = "{{ client_journal_syslog_ecoute }}"
|
||||||
|
protocol = "{{ client_journal_syslog_protocole }}"
|
||||||
|
label_structured_data = true
|
||||||
|
labels = { job = "syslog", host = "{{ client_journal_syslog_emetteur }}" }
|
||||||
|
}
|
||||||
|
forward_to = [loki.relabel.syslog.receiver]
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUI A PARLE ? On veut la meme etiquette `host` que pour une machine qui pousse, sans
|
||||||
|
// quoi la requete change de forme selon la provenance et personne ne s'en souvient.
|
||||||
|
//
|
||||||
|
// DEUX SOURCES, DANS CET ORDRE, ET C'EST DELIBERE.
|
||||||
|
//
|
||||||
|
// L'en-tete RFC5424 porte un nom que l'emetteur se donne — pratique quand il est juste,
|
||||||
|
// et vide ou faux quand il ne l'est pas. Mesure du 2026-09-10 : ni les journaux reels de
|
||||||
|
// la frontiere, ni un message d'essai forge a la main avec un nom explicite n'ont produit
|
||||||
|
// d'etiquette. Le champ existe dans la specification ; il n'arrive pas ici.
|
||||||
|
//
|
||||||
|
// L'ADRESSE DE LA CONNEXION, ELLE, EST TOUJOURS LA — et elle ne s'invente pas : c'est le
|
||||||
|
// pare-feu qui l'observe, pas l'emetteur qui la declare. Un nom qu'on se donne peut
|
||||||
|
// mentir ; une adresse de connexion, non.
|
||||||
|
//
|
||||||
|
// CE QUI A FINALEMENT SERVI : l'etiquette posee sur l'ECOUTEUR, depuis la carte de
|
||||||
|
// l'hebergeur. Aucune des trois regles ci-dessous n'a produit quoi que ce soit — ni sur
|
||||||
|
// les journaux reels, ni sur un message forge a la main. On garde le relabel : le jour ou
|
||||||
|
// l'emetteur enverra un nom, il prendra le dessus. En attendant, le nom vient de la ou il
|
||||||
|
// est DECLARE, pas de la ou on l'espere.
|
||||||
|
//
|
||||||
|
// CE QUE CA SUPPOSE, ET QU'IL FAUT SAVOIR : un SEUL emetteur par ecouteur. Le site n'a
|
||||||
|
// qu'une frontiere ; le jour ou un second equipement pointe ici, il faudra un ecouteur par
|
||||||
|
// emetteur, ou faire enfin marcher le relabel. La supposition est ecrite pour qu'on la
|
||||||
|
// retrouve, plutot que decouverte devant des journaux melanges.
|
||||||
|
//
|
||||||
|
// La derniere regle qui correspond gagne : le nom l'emporte quand il existe.
|
||||||
|
loki.relabel "syslog" {
|
||||||
|
forward_to = [loki.write.loki.receiver]
|
||||||
|
// `regex` SUR LES TROIS, Y COMPRIS CELLE-CI (mesure du 2026-09-10).
|
||||||
|
//
|
||||||
|
// Sans elle, la regle correspond TOUJOURS — meme quand sa source n'existe pas — et pose
|
||||||
|
// `host = ""`. Loki jette une etiquette vide, et l'etiquette posee sur l'ecouteur juste
|
||||||
|
// au-dessus disparaissait avec elle. Le symptome : un flux sans `host`, alors que la
|
||||||
|
// configuration deposee en portait un, visible a l'oeil dans le fichier.
|
||||||
|
//
|
||||||
|
// Une regle de relabel sans garde ne « laisse pas passer » : elle EFFACE.
|
||||||
|
rule {
|
||||||
|
source_labels = ["__syslog_connection_ip_address"]
|
||||||
|
target_label = "host"
|
||||||
|
regex = "(.+)"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__syslog_connection_hostname"]
|
||||||
|
target_label = "host"
|
||||||
|
regex = "(.+)"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__syslog_message_hostname"]
|
||||||
|
target_label = "host"
|
||||||
|
regex = "(.+)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,13 @@ serveur_loki_sonde_url: >-
|
||||||
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
|
# DEGRADE, JAMAIS DEVINE : pas de cache d'amorcage declare, pas de reecriture.
|
||||||
serveur_loki_depot_schema: >-
|
serveur_loki_depot_schema: >-
|
||||||
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}
|
{{ 'http' if (artefacts_amorcage | default('') | string | length > 0) else 'https' }}
|
||||||
|
|
||||||
|
# --- Sonde « journaux-frontiere » (voir meta/supervision.yml) ---
|
||||||
|
# L'etiquette sous laquelle la frontiere apparait — la meme que celle posee sur l'ecouteur
|
||||||
|
# syslog par `client_journal`, derivee de la carte. Vide = pas de sonde a poser.
|
||||||
|
serveur_loki_sonde_frontiere_etiquette: ""
|
||||||
|
|
||||||
|
# LA FENETRE EST DERIVEE DU DEBIT OBSERVE, pas choisie. Mesure du 2026-09-10 : ~46 000
|
||||||
|
# lignes en trente minutes, soit environ 1 500 par minute. Une frontiere qui filtre ne se
|
||||||
|
# tait jamais dix minutes ; si elle le fait, c'est qu'elle ne parle plus a nous.
|
||||||
|
serveur_loki_sonde_frontiere_fenetre: "10m"
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,20 @@ flux:
|
||||||
raison: >-
|
raison: >-
|
||||||
Journaux des hyperviseurs. La machine qui PORTE les VM ecrivait ses journaux nulle
|
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.
|
part ailleurs que sur son propre disque — invisibles le jour ou c'est elle qui casse.
|
||||||
|
|
||||||
|
# LE SYSLOG DE LA FRONTIERE (2026-09-10).
|
||||||
|
#
|
||||||
|
# Port 1514 et non 514 : au-dessus de 1024, donc Alloy l'ecoute sans privilege. Un
|
||||||
|
# collecteur qui aurait besoin des droits du systeme pour entendre un equipement serait
|
||||||
|
# un mauvais echange.
|
||||||
|
#
|
||||||
|
# `fabric` couvre la frontiere. Le flux est declare COTE LOKI parce que c'est lui qui
|
||||||
|
# ouvre un port : l'emetteur, lui, n'est pas dans la flotte et ne declare rien.
|
||||||
|
- sens: ingress
|
||||||
|
port: 1514
|
||||||
|
protocole: tcp
|
||||||
|
pair: fabric
|
||||||
|
chiffrement: clair
|
||||||
|
raison: >-
|
||||||
|
Journaux de la frontiere. Elle n'accueille aucun agent et n'emet que du syslog ; sans
|
||||||
|
ce flux, le seul equipement qui voit passer TOUT le trafic n'ecrit nulle part.
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,11 @@ sondes:
|
||||||
raison: Loki est-il PRET a ingerer ? Un Loki qui ecoute mais dont l'ingesteur n'est pas pret accepte
|
raison: Loki est-il PRET a ingerer ? Un Loki qui ecoute mais dont l'ingesteur n'est pas pret accepte
|
||||||
les connexions et perd les journaux — la panne se voit des semaines plus tard, quand on cherche
|
les connexions et perd les journaux — la panne se voit des semaines plus tard, quand on cherche
|
||||||
une trace qui n'a jamais ete ecrite.
|
une trace qui n'a jamais ete ecrite.
|
||||||
|
|
||||||
|
- nom: journaux-frontiere
|
||||||
|
ttl: 5400
|
||||||
|
raison: >-
|
||||||
|
La frontiere journalise-t-elle encore ? Sa destination syslog a deja pointe une VM
|
||||||
|
rasee : toute la journalisation d'OPNsense s'est arretee dix jours sans que rien ne
|
||||||
|
le signale. Un equipement muet ne se plaint pas — on demande donc a Loki ce qu'il a
|
||||||
|
RECU, pas a la frontiere ce qu'elle croit avoir envoye.
|
||||||
|
|
|
||||||
|
|
@ -262,3 +262,20 @@
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0750"
|
mode: "0750"
|
||||||
|
|
||||||
|
# LA SONDE DE LA FRONTIERE N'EXISTE QUE LA OU ELLE EST ECOUTEE. Un tenant n'a pas de
|
||||||
|
# frontiere a lui : sans etiquette declaree, rien n'est depose et rien n'est attendu.
|
||||||
|
- name: Deposer la sonde des journaux de la frontiere
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-frontiere.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/journaux-frontiere.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
when: serveur_loki_sonde_frontiere_etiquette | length > 0
|
||||||
|
|
||||||
|
- name: Retirer la sonde de frontiere la ou aucune n'est ecoutee
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes/journaux-frontiere.sh
|
||||||
|
state: absent
|
||||||
|
when: not (serveur_loki_sonde_frontiere_etiquette | length > 0)
|
||||||
|
|
|
||||||
54
roles/serveur_loki/templates/sonde-frontiere.sh.j2
Normal file
54
roles/serveur_loki/templates/sonde-frontiere.sh.j2
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# GENERE par Set-OPS (role serveur_loki). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « journaux-frontiere » — l'equipement qui voit TOUT parle-t-il encore ?
|
||||||
|
#
|
||||||
|
# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2.
|
||||||
|
# Mise en defaut PAR PARAMETRE : `serveur_loki_sonde_frontiere_etiquette` (nom inconnu).
|
||||||
|
#
|
||||||
|
# POURQUOI CETTE SONDE EXISTE, ET POURQUOI ELLE EST LA PLUS IMPORTANTE DU LOT.
|
||||||
|
#
|
||||||
|
# La destination syslog de la frontiere a pointe une VM de TENANT, rasee depuis. Une cible
|
||||||
|
# morte a arrete TOUTE la journalisation d'OPNsense pendant DIX JOURS, et rien ne l'a
|
||||||
|
# signale : le boitier fonctionnait, le pare-feu filtrait, les regles s'appliquaient — il
|
||||||
|
# n'ecrivait simplement plus nulle part. On ne l'a su qu'en allant chercher un evenement
|
||||||
|
# qui n'avait jamais ete ecrit.
|
||||||
|
#
|
||||||
|
# Remettre la destination sans poser cette garde, ce serait reconstruire la meme panne en
|
||||||
|
# esperant s'en souvenir. Un equipement muet ne se plaint pas.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
URL={{ serveur_loki_sonde_url }}
|
||||||
|
ETIQ="{{ serveur_loki_sonde_frontiere_etiquette }}"
|
||||||
|
FENETRE={{ serveur_loki_sonde_frontiere_fenetre }}
|
||||||
|
|
||||||
|
# ON DEMANDE A LOKI CE QU'IL A RECU, pas a la frontiere ce qu'elle croit avoir envoye.
|
||||||
|
# La destination est le seul juge : un emetteur qui « envoie » vers un trou noir se porte
|
||||||
|
# tres bien, et c'est exactement ce qui s'est passe.
|
||||||
|
rep=$(curl -sk --max-time 15 -G "${URL}/loki/api/v1/query" \
|
||||||
|
--data-urlencode "query=count_over_time({host=\"${ETIQ}\", job=\"syslog\"}[${FENETRE}])" \
|
||||||
|
2>/dev/null)
|
||||||
|
[[ -n "${rep}" ]] || { echo "Loki ne repond pas sur ${URL} — la sonde ne peut rien affirmer."; exit 3; }
|
||||||
|
|
||||||
|
n=$(printf '%s' "${rep}" | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
print("-1"); raise SystemExit
|
||||||
|
r = (d.get("data") or {}).get("result") or []
|
||||||
|
print(int(sum(float(x.get("value", [0, 0])[1]) for x in r)))
|
||||||
|
' 2>/dev/null)
|
||||||
|
: "${n:=-1}"
|
||||||
|
|
||||||
|
perf="lignes=${n} fenetre=${FENETRE}"
|
||||||
|
if (( n < 0 )); then
|
||||||
|
echo "Reponse illisible de Loki — impossible de dire si la frontiere journalise. | ${perf}"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
if (( n == 0 )); then
|
||||||
|
echo "LA FRONTIERE NE JOURNALISE PLUS : aucune ligne de « ${ETIQ} » depuis ${FENETRE}. | ${perf}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "Frontiere journalise : ${n} ligne(s) recue(s) en ${FENETRE}. | ${perf}"
|
||||||
|
exit 0
|
||||||
|
|
@ -349,6 +349,11 @@ def inventaire() -> dict:
|
||||||
})
|
})
|
||||||
_fabric_surveillee.sort(key=lambda x: x["adresse"])
|
_fabric_surveillee.sort(key=lambda x: x["adresse"])
|
||||||
|
|
||||||
|
# Le nom de la frontiere, tel que la carte le donne — pour etiqueter ses journaux.
|
||||||
|
_nom_frontiere = next((str(h.get("nom")) for h in U.hotes(u)
|
||||||
|
if h.get("role") == "frontiere"
|
||||||
|
and str(h.get("etat") or "actif") == "actif"), "frontiere")
|
||||||
|
|
||||||
hostvars: dict[str, dict] = {}
|
hostvars: dict[str, dict] = {}
|
||||||
groupes: dict[str, list[str]] = {}
|
groupes: dict[str, list[str]] = {}
|
||||||
|
|
||||||
|
|
@ -478,6 +483,16 @@ def inventaire() -> dict:
|
||||||
# Le plan du site, pour les roles qui lisent des registres.
|
# Le plan du site, pour les roles qui lisent des registres.
|
||||||
"setops_plan_dir": str(plan_dir() or ""),
|
"setops_plan_dir": str(plan_dir() or ""),
|
||||||
"hosts_statiques_expositions": expositions,
|
"hosts_statiques_expositions": expositions,
|
||||||
|
# LE COLLECTEUR ECOUTE AUSSI CE QUI NE PEUT PAS POUSSER. La frontiere n'a pas
|
||||||
|
# d'agent : elle emet du syslog, et Alloy le traduit vers Loki. Active sur la
|
||||||
|
# machine qui PORTE Loki — un second recepteur ailleurs serait une seconde
|
||||||
|
# configuration a garder en phase.
|
||||||
|
**({"client_journal_syslog_ecoute": f"{srv.get('ip')}:1514",
|
||||||
|
"client_journal_syslog_emetteur": _nom_frontiere,
|
||||||
|
# La sonde interroge la MEME etiquette que celle posee sur l'ecouteur :
|
||||||
|
# une seule derivation, deux consommateurs.
|
||||||
|
"serveur_loki_sonde_frontiere_etiquette": _nom_frontiere}
|
||||||
|
if "serveur_loki" in _groupes_de(nom) else {}),
|
||||||
# LA FABRIC SURVEILLEE PAR CONTROLE ACTIF (2026-09-10).
|
# LA FABRIC SURVEILLEE PAR CONTROLE ACTIF (2026-09-10).
|
||||||
#
|
#
|
||||||
# Un pare-feu n'accueille aucun agent : Icinga doit aller VOIR. Une entree par
|
# Un pare-feu n'accueille aucun agent : Icinga doit aller VOIR. Une entree par
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue