site : journaux allégés, porteur de santé retiré des hyperviseurs
- client_sante : tasks/retirer.yml, joué sur hyperviseurs:!client_sante ; le minuteur resté depuis le 2026-09-10 visait 10.0.36.11 et échouait toutes les 15 min (une unité en échec permanente par hyperviseur) - site_inventaire : plus de client_sante_icinga_url pour les hyperviseurs - client_journal : loki.process jette le bruit de la sonde connectivite, phrase exacte et seulement depuis une machine de client_sante - serveur_loki : log_level warn (Loki réingérait ses propres requêtes) - auditd : règle never pour adjtimex de node_exporter (35 % de l'audit) ; nouveau handler augenrules --load, un restart d'auditd ne rechargeait pas les règles sur Debian 13 - audit : rapport de preuves du 2026-10-03, carte à 51 pièces Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8b008a8cf4
commit
8d40c687e1
15 changed files with 361 additions and 20 deletions
64
CHANGELOG.md
64
CHANGELOG.md
|
|
@ -1,5 +1,69 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-10-03 (62) — Le site parlait trop dans ses journaux, et trois hyperviseurs échouaient en silence
|
||||
|
||||
**Demande de l'exploitant** : analyser les journaux centralisés (Loki) et les événements
|
||||
Icinga du site, en faire un rapport d'anomalies, puis corriger quatre points de ce rapport.
|
||||
|
||||
**1. Un porteur de santé resté sur les trois hyperviseurs.** `client_sante` y avait été posé
|
||||
le 2026-09-10, puis retiré du groupe le jour même : la route par défaut gelée (D-57) empêche un
|
||||
hyperviseur de joindre Icinga, et ses métriques sont depuis TIRÉES. Mais personne ne défaisait
|
||||
ce qui avait été posé. Le minuteur visait encore `10.0.36.11` (l'adresse de `site-mon-01`
|
||||
d'avant la renumérotation) et échouait toutes les 15 min depuis au moins sept jours, soit
|
||||
environ 2 050 échecs par hyperviseur. `node_exporter` publiait donc sur chacun une unité en
|
||||
échec permanente : le jour où une vraie unité tombait, rien ne la distinguait.
|
||||
**Fait** : `client_sante/tasks/retirer.yml` (minuteurs arrêtés, unités, script, mot de passe
|
||||
d'API et AC retirés, `reset-failed`), joué par un 3ᵉ play `hyperviseurs:!client_sante` du
|
||||
playbook de groupe ; `client_sante_unites_posees` nomme ce que le rôle pose.
|
||||
`site_inventaire.py` ne dérive plus `client_sante_icinga_url` pour les hyperviseurs : la valeur
|
||||
faisait croire qu'ils poussaient. Relu : 0 unité `setops-sante*`, 0 unité en échec sur les
|
||||
trois, et aucune unité en échec dans Prometheus.
|
||||
|
||||
**2. Le bruit de la sonde `connectivite`, filtré à la collecte.** Depuis le 2026-10-01, chaque
|
||||
machine ouvre puis referme chaque minute une connexion vers les ports promis : `sshd`
|
||||
(« Connection closed by… ») et les serveurs Go — Forgejo, step-ca — (« TLS handshake error…
|
||||
EOF ») le notent. Le journal de chaque VM du site est passé de 2 000 à 14 000 lignes par jour,
|
||||
et ces deux phrases faisaient 98 % des « erreurs » de la forge et de l'autorité. Rien à corriger
|
||||
à la source : `sshd` journalise toute connexion fermée avant l'échange de clés.
|
||||
**Fait** : `loki.process "sondes"` dans le gabarit Alloy, avec deux `stage.drop` : la phrase
|
||||
exacte, et seulement depuis une machine de `client_sante` (`client_journal_sondeurs`, dérivé
|
||||
de l'inventaire). La même phrase venue d'ailleurs passe ; les sessions d'administration par
|
||||
rebond (pattes `10.37.x.1` de la frontière) passent, et c'est vérifié. Compte de ce qui est jeté :
|
||||
`loki_process_dropped_lines_total{reason="sonde_connectivite"}`, distinct des pertes que
|
||||
surveille la sonde `journaux`. Éprouvé avant d'écrire : `alloy validate` (v1.19.2) sur la
|
||||
config réelle. Appliqué aux 9 VM seulement — sur les hyperviseurs, le même rôle aurait aussi
|
||||
changé leurs sources apt (Grafana par le cache du site), un écart antérieur laissé à part.
|
||||
|
||||
**3. Loki se relisait lui-même.** À `info`, Loki écrit quatre lignes par requête, qu'Alloy lui
|
||||
renvoyait : 913 000 lignes en sept jours sur `site-mon-01`, et une recherche par motif
|
||||
retrouvait ses propres requêtes (« segfault » : 1 787 faux positifs). **Fait** :
|
||||
`serveur_loki_log_level: warn`. Relu : 55 lignes du service en dix minutes après le
|
||||
redémarrage, des erreurs seulement ; ce qui reste à `info` vient de Grafana.
|
||||
|
||||
**4. L'audit enregistrait une lecture d'horloge toutes les 15 s.** Le collecteur `timex` de
|
||||
`node_exporter` appelle `adjtimex` en lecture ; la règle `time-change` ne peut pas distinguer
|
||||
une lecture d'un réglage. 4 événements par minute, 35 % de la piste d'audit d'une VM. **Fait** :
|
||||
une règle `never` étroite, placée DEVANT (seul `adjtimex`, seul ce binaire ; chrony et
|
||||
`settimeofday`/`clock_settime` restent audités). Éprouvé à la main : 4 événements/min sans la
|
||||
règle, 0 avec ; `auditctl` accepte un `exe=` absent.
|
||||
|
||||
**Trouvé en relisant : redémarrer `auditd` ne recharge pas les règles.** Sur Debian 13, elles
|
||||
sont chargées par `audit-rules.service` (`augenrules --load`), qu'un redémarrage d'`auditd` ne
|
||||
rejoue pas : le handler laissait le fichier juste et le noyau sur les règles du dernier
|
||||
démarrage. Nouveau handler `Recharger les regles auditd`, sans `failed_when: false`. Relu : la
|
||||
règle `never` est armée dans le noyau sur les 9 VM.
|
||||
|
||||
**Validation** : `--syntax-check` des 4 playbooks, `ansible-lint` (profil production, 0
|
||||
violation), `make verifier` conforme (83/83, carte à 51 pièces d'audit après le rapport du jour).
|
||||
Au site : `--check --diff` des quatre groupes, puis application, 0 échec. Icinga revient à une
|
||||
seule alerte, l'ancienne : secteurs défaillants sur `gandalf`. Le passage a aussi aligné le site
|
||||
sur des modes déjà décidés par le dépôt (`/etc/setops` 0700, `/var/lib/setops` 0755).
|
||||
|
||||
**Reste du rapport, non traité ici** : aucune notification Icinga n'a jamais été émise au site
|
||||
(0 objet `Notification` : les règles d'exemple exigent `host.vars.notification.mail`, que rien
|
||||
ne pose) ; disque `sda` de `gandalf` (osd.4) qui se dégrade ; lien SATA instable du SSD système
|
||||
de `vishnu` (CRC 16), que smartmon ne remonte pas.
|
||||
|
||||
## 2026-10-01 (61) — Une donnée restaurée l'emportait sur une règle du plan (Nextcloud)
|
||||
|
||||
**Constat de l'exploitant** : dans Nextcloud, `sysadmin` n'a pas les mêmes droits chez les
|
||||
|
|
|
|||
120
docs/audit/preuve-2026-10-03.md
Normal file
120
docs/audit/preuve-2026-10-03.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Preuve de conformite — Set-OPS — 2026-10-03
|
||||
|
||||
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ❌ NON CONFORME (81 OK · 1 echec · 1 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
| # | Preuve | Affirmations | Statut | Detail |
|
||||
|---|---|---|---|---|
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | [0m[0m |
|
||||
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
|
||||
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre : plan et inventaire applique coincident. |
|
||||
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
|
||||
| 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 : 42 groupes classes, aucun cycle, aucune arete en arriere ; playbooks/site.yml a jour. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 121 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. |
|
||||
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
|
||||
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
|
||||
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
|
||||
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
|
||||
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 32 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 30 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) et 7 zone(s) de site : adressage 100% derive du seed index. |
|
||||
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 5 instance(s) federee(s), aucun index en collision. |
|
||||
| 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 : 14 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, 2 tenant(s), 40 groupe(s), 108 regle(s). |
|
||||
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 13 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, 35 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 34 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 23, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 77 scripts expliques et atteignables, 146 cibles make documentees, 69 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (142 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 44 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 49 document(s) declarent leur lecteur (46 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 7 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (8 groupe(s) au catalogue). |
|
||||
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
|
||||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 42 role(s) serveur/client tous nommes, 42 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 97 page(s) de wiki toutes atteignables. |
|
||||
| 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 : 73 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 (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, 202 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 | — | ❌ ECHEC | La carte d'orientation ne dit plus vrai :
|
||||
- « pieces d'audit » : la carte annonce 50, le depot en compte 51 |
|
||||
| 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 (139 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 309 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 : WAN muet, interfa |
|
||||
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
|
||||
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
|
||||
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
|
||||
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (83 preuves, 69 roles, 42 groupes). |
|
||||
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
|
||||
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
|
||||
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `3fe7e3c` (publie le 2026-09-29). |
|
||||
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 56 champ(s) sur 7 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:9, 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 |
|
||||
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 47 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, client_s |
|
||||
| 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. |
|
||||
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (7 exposition(s)). |
|
||||
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 14 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
|
||||
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
|
||||
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
|
||||
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
|
||||
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
|
||||
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
|
||||
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 10 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
|
||||
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
|
||||
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
|
||||
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 185 gabarits de role : tous se rendent. |
|
||||
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
|
||||
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
|
||||
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ✅ OK | 5 ecosysteme(s) (instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, SITE-Chezlepro) : pattes, edges, certificats, rechargements, jumeaux d' |
|
||||
| P80 | Remise au client : inscrite, nommee, et son second temps a l'heure | — | ✅ OK | Aucun ecosysteme remis a un client : rien a tenir. |
|
||||
| P81 | La console dit sa portee, et ne sert pas un inventaire vide en silence | — | ✅ OK | Portee `poste` derivee des voutes portees, source d'inventaire `instance/inventories/principal/hosts.yml`, et 15 route(s) POST exigent toutes un pouvoir. |
|
||||
| P82 | DNS public : les zones publiees sont servies, signees avant d'etre exposees | — | ✅ OK | 2 zone(s) publique(s) declaree(s), toutes servies par le site ; 2 replication(s) declaree(s) des deux cotes ; transfert ouvert a la cle seule ; aucune expositio |
|
||||
| P83 | Assistants : le registre des runbooks ne prend pas de retard sur le Makefile | — | ✅ OK | 17 runbooks, 139 etapes, 145 cibles documentees : chacune portee par un assistant ou exemptee avec son motif. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||
|
||||
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||
|
||||
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||
comme declarations d'intention, non comme preuves :
|
||||
|
||||
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||
contre une flotte vivante.
|
||||
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||
|
||||
_Rapport genere le 2026-10-03._
|
||||
|
|
@ -26,7 +26,7 @@ README de rôles). Cette page comble ces deux trous.
|
|||
| rôles | 69 | `roles/*/` |
|
||||
| README de rôles | 69 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
|
||||
| documents | 45 | `docs/*.md` |
|
||||
| pièces d'audit | 50 | `docs/audit/*` |
|
||||
| pièces d'audit | 51 | `docs/audit/*` |
|
||||
| unités de wiki | 27 | `wiki/*.md` |
|
||||
| décisions en vigueur | 85 | lignes `\| **D-nn** \|` de `decisions-architecture.md` |
|
||||
| décisions renversées | 3 | lignes `\| **D-nn** —` du même document |
|
||||
|
|
|
|||
|
|
@ -37,3 +37,20 @@
|
|||
pre_tasks: *pre_sante
|
||||
roles:
|
||||
- client_sante
|
||||
|
||||
# LE RETRAIT, SUR LES HOTES SORTIS DU GROUPE (2026-10-03). Un role qu'on cesse d'appliquer
|
||||
# laisse son minuteur derriere lui : les hyperviseurs du site ont pousse vers une adresse
|
||||
# perimee pendant des semaines (voir `roles/client_sante/tasks/retirer.yml`).
|
||||
#
|
||||
# LE GROUPE EST NOMME, pas deduit (`all:!client_sante`) : un hyperviseur n'est pas une VM
|
||||
# de la flotte, et un play qui le touche doit le dire. Chez un tenant, `hyperviseurs`
|
||||
# n'existe pas et ce play ne trouve personne.
|
||||
- name: Intégration client_sante — retrait sur les hyperviseurs qui n'y sont plus
|
||||
hosts: hyperviseurs:!client_sante
|
||||
become: true
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Retirer le porteur de santé
|
||||
ansible.builtin.include_role:
|
||||
name: client_sante
|
||||
tasks_from: retirer.yml
|
||||
|
|
|
|||
|
|
@ -4,3 +4,15 @@
|
|||
name: auditd
|
||||
state: restarted
|
||||
failed_when: false
|
||||
|
||||
# REDEMARRER `auditd` NE RECHARGE PAS LES REGLES (mesure du 2026-10-03, Debian 13).
|
||||
#
|
||||
# Elles sont chargees par `audit-rules.service` (`augenrules --load`), une unite a part
|
||||
# dont `auditd` depend — et qu'un redemarrage d'`auditd` ne rejoue pas. Le handler
|
||||
# ci-dessus laissait donc le fichier juste et le noyau sur les regles du dernier demarrage.
|
||||
#
|
||||
# SANS `failed_when: false` : une regle que le noyau refuse doit se voir ici, pas au
|
||||
# prochain redemarrage de la machine.
|
||||
- name: Recharger les regles auditd
|
||||
ansible.builtin.command: augenrules --load
|
||||
changed_when: true
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
path: "/etc/audit/rules.d/{{ item }}"
|
||||
state: absent
|
||||
loop: "{{ auditd_fichiers_perimes }}"
|
||||
notify: Restart auditd
|
||||
notify:
|
||||
- Restart auditd
|
||||
- Recharger les regles auditd
|
||||
when: auditd_enabled | bool
|
||||
|
||||
# LA RETENTION EST UN CHOIX, DONC ELLE S'ECRIT (2026-09-11). Voir defaults/main.yml pour
|
||||
|
|
@ -48,7 +50,9 @@
|
|||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
notify: Restart auditd
|
||||
notify:
|
||||
- Restart auditd
|
||||
- Recharger les regles auditd
|
||||
when: auditd_enabled | bool
|
||||
|
||||
# `failed_when: false` A CACHE LA PANNE PENDANT TOUT UN DEPLOIEMENT (2026-08-30).
|
||||
|
|
|
|||
|
|
@ -36,6 +36,20 @@
|
|||
-w /etc/step-ca/ -p wa -k pki-autorite
|
||||
{% endif %}
|
||||
|
||||
# L'EXPORTATEUR DE METRIQUES LIT L'HORLOGE, IL NE LA REGLE PAS (2026-10-03).
|
||||
#
|
||||
# `adjtimex` sert aux deux : regler ET lire. Le collecteur `timex` de `node_exporter` le lit
|
||||
# a chaque collecte, et la regle `time-change` ci-dessous ne peut pas distinguer — le mode
|
||||
# est dans une structure pointee, hors de portee d'un filtre `-F`. Mesure au site : 4
|
||||
# evenements par minute, 35 % de toute la piste d'audit d'une VM, pour un appel en lecture.
|
||||
#
|
||||
# UNE REGLE `never` ETROITE, ET DEVANT : l'audit retient la PREMIERE regle qui correspond.
|
||||
# Elle ne vise que `adjtimex`, que ce binaire-la : `settimeofday`/`clock_settime` restent
|
||||
# audites pour lui aussi, et chrony — qui regle vraiment — reste entierement audite.
|
||||
# Eprouve avant d'ecrire : 4 evenements/min sans elle, 0 avec ; et `auditctl` accepte un
|
||||
# `exe=` absent, donc la ligne ne casse pas le chargement d'une machine sans exportateur
|
||||
# (ce que ferait un `-w` vers un chemin absent, voir plus haut).
|
||||
-a never,exit -F arch=b64 -S adjtimex -F exe=/usr/bin/prometheus-node-exporter
|
||||
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change
|
||||
-a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale
|
||||
|
||||
|
|
|
|||
|
|
@ -88,3 +88,11 @@ client_journal_syslog_emetteur: "frontiere"
|
|||
# une liste de plus qui suit une autre — et qui prendrait du retard.
|
||||
client_journal_audit_actif: "{{ 'serveur_durci' in group_names }}"
|
||||
client_journal_audit_chemin: "/var/log/audit/audit.log"
|
||||
|
||||
# LES MACHINES QUI SONDENT — celles qui portent `client_sante`, donc qui jouent la sonde
|
||||
# « connectivite » chaque minute. Leurs connexions de sonde ne sont pas expediees (voir le
|
||||
# gabarit). Derive de l'inventaire : une machine ajoutee y entre sans qu'on l'ecrive.
|
||||
# Vide = aucun filtre.
|
||||
client_journal_sondeurs: >-
|
||||
{{ groups.get('client_sante', []) | map('extract', hostvars)
|
||||
| map(attribute='ansible_host', default='') | select('match', '^[0-9.]+$') | list }}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,41 @@ loki.write "loki" {
|
|||
}
|
||||
}
|
||||
|
||||
{% set sondeurs = client_journal_sondeurs | map('regex_escape') | join('|') | replace('\\', '\\\\') %}
|
||||
{% if sondeurs %}
|
||||
// --- LE BRUIT DE LA SONDE « connectivite » RESTE SUR LA MACHINE (2026-10-03) ---
|
||||
//
|
||||
// Chaque minute, chaque machine ouvre une connexion TCP vers les ports que le registre des
|
||||
// flux lui promet, puis la referme sans rien dire (`nftables_baseline`,
|
||||
// `sonde-connectivite.sh`). Ceux qui ecoutent le notent : `sshd` (« Connection closed by
|
||||
// <pair> port <n> ») et les serveurs Go — Forgejo, step-ca — (« TLS handshake error from
|
||||
// <pair>:<n>: EOF »). Mesure au site : le journal de chaque VM est passe de 2 000 a
|
||||
// 14 000 lignes par jour le 2026-10-01, et ces deux phrases faisaient 98 % des « erreurs »
|
||||
// de la forge et de l'autorite. Rien a corriger a la source : `sshd` journalise toute
|
||||
// connexion fermee avant l'echange de cles, quel que soit le client.
|
||||
//
|
||||
// CE QUI EST JETE EST ETROIT : la phrase EXACTE, et seulement depuis une machine qui porte
|
||||
// la sonde (`client_sante`, derive de l'inventaire). Le meme message venu d'ailleurs — un
|
||||
// balayage, une machine inconnue — passe. Les lignes restent dans le journal local ; le
|
||||
// compte de ce qui est jete est `loki_process_dropped_lines_total{reason="sonde_connectivite"}`,
|
||||
// distinct des pertes que surveille la sonde « journaux ».
|
||||
loki.process "sondes" {
|
||||
forward_to = [loki.write.loki.receiver]
|
||||
|
||||
stage.drop {
|
||||
expression = "^Connection closed by ({{ sondeurs }}) port [0-9]+$"
|
||||
drop_counter_reason = "sonde_connectivite"
|
||||
}
|
||||
|
||||
stage.drop {
|
||||
expression = "TLS handshake error from ({{ sondeurs }}):[0-9]+: EOF$"
|
||||
drop_counter_reason = "sonde_connectivite"
|
||||
}
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
loki.source.journal "journal" {
|
||||
forward_to = [loki.write.loki.receiver]
|
||||
forward_to = [{{ 'loki.process.sondes.receiver' if sondeurs else 'loki.write.loki.receiver' }}]
|
||||
max_age = "12h"
|
||||
labels = {
|
||||
job = "systemd-journal",
|
||||
|
|
|
|||
|
|
@ -78,3 +78,11 @@ client_sante_disque_crit: 90
|
|||
# Le plancher que la sonde « plancher » compare a l'autoritatif. Mise en defaut PAR
|
||||
# PARAMETRE : un fichier vide ou absent — la sonde doit alors voir tous les noms manquer.
|
||||
client_sante_plancher_fichier: "/etc/hosts"
|
||||
|
||||
# LES UNITES QUE CE ROLE POSE — la liste que `tasks/retirer.yml` defait sur un hote sorti du
|
||||
# groupe. Ecrite a cote des gabarits qu'elle nomme : une unite ajoutee au role s'ajoute ici.
|
||||
client_sante_unites_posees:
|
||||
- setops-sante.timer
|
||||
- setops-sante.service
|
||||
- setops-sondes-minute.timer
|
||||
- setops-sondes-minute.service
|
||||
|
|
|
|||
|
|
@ -2,3 +2,12 @@
|
|||
- name: Recharger systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
# UNE UNITE RETIREE PEUT RESTER « failed » DANS LA MEMOIRE DE systemd, et c'est cet etat
|
||||
# que lit `node_exporter`. Recharger ne l'efface pas : il faut `reset-failed`, faute de
|
||||
# quoi le retrait se verrait sur disque et pas dans les metriques (`tasks/retirer.yml`).
|
||||
- name: Oublier l'echec du porteur retire
|
||||
ansible.builtin.shell: |
|
||||
systemctl reset-failed setops-sante.service setops-sondes-minute.service 2>/dev/null || true
|
||||
systemctl daemon-reload
|
||||
changed_when: true
|
||||
|
|
|
|||
48
roles/client_sante/tasks/retirer.yml
Normal file
48
roles/client_sante/tasks/retirer.yml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
# DEFAIRE CE QUE CE ROLE A POSE, SUR UN HOTE QUI N'EST PLUS DANS SON GROUPE (2026-10-03).
|
||||
#
|
||||
# Le cas qui l'a fait ecrire : les trois hyperviseurs du site. Ils ont recu `client_sante`
|
||||
# le 2026-09-10, puis en sont sortis le jour meme — la route par defaut gelee (D-57)
|
||||
# empeche un hyperviseur de joindre Icinga, et leurs metriques sont depuis TIREES par
|
||||
# Prometheus (voir `scripts/site_inventaire.py`). Sortis du groupe, plus aucun play ne les
|
||||
# touchait : le minuteur est reste, pointe sur `10.0.36.11`, l'adresse de `site-mon-01`
|
||||
# d'avant la renumerotation.
|
||||
#
|
||||
# Mesure du 2026-10-03 : un echec toutes les 15 min depuis au moins sept jours, environ
|
||||
# 2 050 par hyperviseur. Et `node_exporter` publiait sur chacun UNE unite en echec
|
||||
# permanente — exactement ce que ce role refuse ailleurs (`client_sante_unites_tolerees`) :
|
||||
# le jour ou une vraie unite tombe, le compte passe de 1 a 2 et personne ne le voit.
|
||||
#
|
||||
# ON NE RETIRE QUE CE QUE CE ROLE POSE. Les sondes de `client_journal` et de
|
||||
# `client_metrique` restent dans `/usr/local/lib/setops/sondes/` : ces roles s'appliquent
|
||||
# encore, ils les redeposeraient au prochain passage, et sans porteur elles sont inertes.
|
||||
- name: Les unites du porteur sont-elles encore la ?
|
||||
ansible.builtin.stat:
|
||||
path: "/etc/systemd/system/{{ item }}"
|
||||
loop: "{{ client_sante_unites_posees }}"
|
||||
register: client_sante_unites_restantes
|
||||
|
||||
- name: Arreter et desactiver les minuteurs restes en place
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item.item }}"
|
||||
state: stopped
|
||||
enabled: false
|
||||
loop: "{{ client_sante_unites_restantes.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when:
|
||||
- item.stat.exists
|
||||
- item.item is search('\.timer$')
|
||||
|
||||
- name: Retirer les unites, le script et les secrets du rapport passif
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop: >-
|
||||
{{ client_sante_unites_posees | map('regex_replace', '^', '/etc/systemd/system/') | list
|
||||
+ ['/usr/local/sbin/setops-sante.sh',
|
||||
'/etc/setops/icinga-api.pass',
|
||||
client_sante_ca_verification,
|
||||
'/usr/local/lib/setops/sondes/disque.sh',
|
||||
'/usr/local/lib/setops/sondes/plancher.sh'] }}
|
||||
notify: Oublier l'echec du porteur retire
|
||||
|
|
@ -8,6 +8,16 @@ serveur_loki_port: 3100
|
|||
serveur_loki_chemin: "/var/lib/loki"
|
||||
serveur_loki_retention: "744h" # 31 jours
|
||||
|
||||
# `warn`, PAS LE `info` AMONT (2026-10-03). A `info`, Loki ecrit QUATRE lignes par requete
|
||||
# (`executing query`, `metrics.go`, `get or create table`...) dans le journal de sa propre
|
||||
# machine — qu'Alloy lui renvoie aussitot. Mesure au site : 913 000 lignes en sept jours sur
|
||||
# `site-mon-01`, cinq fois le journal de toutes les autres VM reunies, et un tableau ouvert
|
||||
# suffisait a l'emballer. Pire que le volume : une recherche par motif SE TROUVAIT
|
||||
# ELLE-MEME (« segfault » : 1 787 occurrences, toutes des requetes).
|
||||
#
|
||||
# Les erreurs et les avertissements restent : c'est eux qui disent qu'un composant souffre.
|
||||
serveur_loki_log_level: "warn"
|
||||
|
||||
# Depot apt officiel Grafana (partage avec serveur_grafana).
|
||||
serveur_loki_depot_cle_url: "{{ serveur_loki_depot_schema }}://apt.grafana.com/gpg-full.key"
|
||||
serveur_loki_depot_cle_fichier: "/etc/apt/keyrings/grafana.asc"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ auth_enabled: false
|
|||
server:
|
||||
http_listen_port: {{ serveur_loki_port }}
|
||||
grpc_listen_port: 9096
|
||||
log_level: {{ serveur_loki_log_level }}
|
||||
{% if serveur_loki_tls_actif | default(false) %}
|
||||
http_tls_config:
|
||||
cert_file: {{ serveur_loki_tls_dir }}/loki.crt
|
||||
|
|
|
|||
|
|
@ -919,14 +919,6 @@ def inventaire() -> dict:
|
|||
# Ce que ca coute a dire : un groupe qui recoit des roles doit etre nomme a chaque
|
||||
# fois. C'est le prix d'un perimetre explicite, et il est moins cher qu'un
|
||||
# durcissement applique par megarde a un hyperviseur.
|
||||
# L'adresse du temoin, derivee comme tout le reste : l'hote qui porte `serveur_icinga`.
|
||||
_ip_icinga = ""
|
||||
for _n_app, _a_app in applications.items():
|
||||
if str(_a_app.get("groupe")) == "serveur_icinga":
|
||||
_s = serveurs.get(_a_app.get("hote")) or {}
|
||||
_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":
|
||||
|
|
@ -959,14 +951,15 @@ def inventaire() -> dict:
|
|||
# PAS DE PARE-FEU DERIVE : aucun `nftables_baseline_ruleset_genere`. Un
|
||||
# hyperviseur porte son propre filtrage, pose par Proxmox et par l'exploitant.
|
||||
#
|
||||
# LE TEMOIN SE JOINT PAR SON ADRESSE, PAS PAR SON NOM. Le porteur de sante vise
|
||||
# `<hote>.<domaine>`, ce qui suppose le plancher `/etc/hosts` du socle — que ces
|
||||
# machines n'ont pas, et ne doivent pas avoir : Proxmox s'en sert pour l'identite
|
||||
# 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.
|
||||
# PAS DE `client_sante_icinga_url` (retire le 2026-10-03). Il survivait a la
|
||||
# decision ci-dessous : un hyperviseur ne POUSSE pas vers Icinga, il n'est pas
|
||||
# dans `client_sante`. La valeur ne servait plus qu'a faire croire le contraire ;
|
||||
# le minuteur reste en place la visait encore (`10.0.36.11`, d'avant la
|
||||
# renumerotation) — voir `roles/client_sante/tasks/retirer.yml`.
|
||||
#
|
||||
# LES JOURNAUX, EUX, PARTENT D'ICI — et par une ADRESSE, pas par un nom : le
|
||||
# plancher `/etc/hosts` du socle n'est pas pose sur un hyperviseur, Proxmox s'en
|
||||
# sert pour l'identite de noeud du cluster.
|
||||
**({"client_journal_loki_url":
|
||||
f"http://{_ip_loki}:3100/loki/api/v1/push"} if _ip_loki else {}),
|
||||
**communes,
|
||||
|
|
|
|||
Loading…
Reference in a new issue