diff --git a/CHANGELOG.md b/CHANGELOG.md index ce163eb..60dbe58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,50 @@ # CHANGELOG — Set-OPS +## 2026-08-31 — Trois unites en echec sur chaque machine, et personne ne les voyait + +**55 preuves.** Le deploiement rendait `15/15, failed=0`. Les machines portaient chacune +**trois unites systemd en echec**. *Le rapport d'Ansible n'est pas l'etat d'une machine.* + +### auditd : onze regles armees, personne pour collecter + +Deux fichiers de regles identiques cohabitaient — `99-chezlepro.rules` et +`99-setops.rules`, vestige du renommage du role. `augenrules` **concatene** tout +`rules.d/`, et le noyau refuse la seconde occurrence : + +``` +Error sending add rule data request (Rule exists) +There was an error in line 16 of /etc/audit/audit.rules +``` + +`audit-rules` echoue, et `auditd` ne demarre pas — c'est sa dependance. **`auditctl -l` +affichait pourtant onze regles**, ce qui donne toutes les apparences d'un audit qui +fonctionne. L'audit etait arme dans le noyau et personne ne l'enregistrait. + +Meme mue que `ssh_baseline`, meme registre — `auditd_fichiers_perimes`. Et +`failed_when: false` cachait le reste : *un service de securite qui ne demarre pas doit +se voir.* + +### Les timers apt-daily : un etat d'echec residuel + +Masquer le service pendant que son timer tourne lui fait perdre sa cible ; systemd le note +et le **garde**. `state: stopped` n'efface pas un etat `failed` — seul `reset-failed` le +fait. + +Ce n'est pas cosmetique. Une supervision qui compte les unites en echec compte ces deux-la +**pour toujours**, et la vraie panne s'y noiera. Meme defaut que le journal de la frontiere +noye sous 982 000 entrees : *ce qui ment le plus n'est pas ce qui se tait, c'est ce qui crie +sans raison.* + +### Un diagnostic faux, annule avant d'etre livre + +J'avais lu `masked enabled` dans `list-unit-files` comme un etat contradictoire, conclu que +masquer empechait de desactiver, et bati une reparation pour le defaire. **Ces colonnes +sont ETAT puis PRESET** : masque, avec un prereglage constructeur active, est parfaitement +normal. Mesure sans ambiguite ensuite : `is-enabled=masked`, `is-active=failed`. Le +masquage etait juste ; seule la trace restait. + +make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute. + ## 2026-08-30 — Vingt-et-un plays : le resolveur ouvre la chaine, deux defauts tombent **55 preuves.** Le deploiement depuis `ops-01` passe de **3 plays a 21**. Douze machines diff --git a/docs/audit/preuve-2026-08-31.md b/docs/audit/preuve-2026-08-31.md new file mode 100644 index 0000000..285a3a0 --- /dev/null +++ b/docs/audit/preuve-2026-08-31.md @@ -0,0 +1,91 @@ +# Preuve de conformite — Set-OPS — 2026-08-31 + +> 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** : `instance` — inventaire `instance/inventories/principal/hosts.yml` +- **Verdict** : ✅ CONFORME (55 OK · 0 echec · 0 saute) + +## Preuves + +| # | Preuve | Affirmations | Statut | Detail | +|---|---|---|---|---| +| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK |  | +| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee | +| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : 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). | +| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 39 groupes classes, aucun cycle, aucune arete en arriere. | +| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 37 rôles, 97 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 | ✅ OK | 15 hotes, 36 groupes (inventaire dechiffre et parse). | +| 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 : 23 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 28 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) : adressage 100% derive du seed index. | +| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 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 : 11 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), 51 groupe(s), 81 regle(s). | +| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 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 | 31 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 20, 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, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | +| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 53 scripts expliques et atteignables, 106 cibles make documentees, 63 roles avec README. | +| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 41 exigence(s) de role, toutes satisfaites (130 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 (38 groupes). | +| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (28 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). | +| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 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 : 38 role(s) serveur/client tous nommes, 39 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, 27 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 : 49 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 : 5 machine(s) du plan retrouvees, 66 regle(s) du site. | +| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 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 84 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 (115 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 131 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 | +| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 role(s) applique(s), aucun secret de tenant reclame. | +| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 15 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 15. | + +## 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-08-31._ diff --git a/docs/carte-set-ops.md b/docs/carte-set-ops.md index 65add7e..2deb7c3 100644 --- a/docs/carte-set-ops.md +++ b/docs/carte-set-ops.md @@ -26,7 +26,7 @@ README de rôles). Cette page comble ces deux trous. | rôles | 63 | `roles/*/` | | README de rôles | 63 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette | | documents | 38 | `docs/*.md` | -| pièces d'audit | 30 | `docs/audit/*` | +| pièces d'audit | 31 | `docs/audit/*` | | unités de wiki | 27 | `wiki/*.md` | | décisions en vigueur | 78 | lignes `\| **D-nn** \|` de `decisions-architecture.md` | | décisions renversées | 3 | lignes `\| **D-nn** —` du même document | diff --git a/roles/auditd/defaults/main.yml b/roles/auditd/defaults/main.yml index 7a4c631..c37ffa7 100644 --- a/roles/auditd/defaults/main.yml +++ b/roles/auditd/defaults/main.yml @@ -1,2 +1,25 @@ --- auditd_enabled: true + +# LES FICHIERS QU'UNE NOMENCLATURE ABANDONNEE A LAISSES DERRIERE ELLE. +# +# Ce role a porte le nom `chezlepro` avant de porter celui du moteur. Le renommage a +# change le fichier DEPOSE sans retirer le precedent — meme mue que `ssh_baseline`, meme +# registre, et une consequence PIRE ici. +# +# `augenrules` CONCATENE tout `rules.d/`. Deux fichiers portant la meme regle la +# presentent donc deux fois au noyau, qui refuse la seconde : +# +# Error sending add rule data request (Rule exists) +# There was an error in line 16 of /etc/audit/audit.rules +# +# `audit-rules.service` echoue, et `auditd.service` ne demarre pas — c'est sa dependance. +# Resultat mesure le 2026-08-30 sur les quinze machines de Chezlepro : ONZE REGLES +# ARMEES DANS LE NOYAU, ET PERSONNE POUR COLLECTER. `auditctl -l` les affiche, ce qui +# donne toutes les apparences d'un audit qui fonctionne. +# +# On n'ajoute a cette liste que des noms qu'on a REELLEMENT deposes un jour : retirer un +# fichier qu'on n'a jamais ecrit serait s'arroger le droit de supprimer la configuration +# de quelqu'un d'autre. +auditd_fichiers_perimes: + - 99-chezlepro.rules diff --git a/roles/auditd/tasks/main.yml b/roles/auditd/tasks/main.yml index 1ca1604..e63865b 100644 --- a/roles/auditd/tasks/main.yml +++ b/roles/auditd/tasks/main.yml @@ -6,6 +6,18 @@ - audispd-plugins state: present +# AVANT DE POSER LES NOTRES, RETIRER CELLES D'UNE NOMENCLATURE ABANDONNEE. +# +# `augenrules` concatene tout `rules.d/` : un doublon fait echouer le chargement ENTIER, +# et `auditd` ne demarre pas faute de sa dependance. Voir defaults/main.yml. +- name: Retirer les regles auditd d'une nomenclature abandonnee + ansible.builtin.file: + path: "/etc/audit/rules.d/{{ item }}" + state: absent + loop: "{{ auditd_fichiers_perimes }}" + notify: Restart auditd + when: auditd_enabled | bool + - name: Déployer les règles auditd Set-OPS ansible.builtin.template: src: 99-setops.rules.j2 @@ -16,10 +28,30 @@ notify: Restart auditd when: auditd_enabled | bool +# `failed_when: false` A CACHE LA PANNE PENDANT TOUT UN DEPLOIEMENT (2026-08-30). +# +# Quinze machines rapportaient `failed=0` alors qu'`auditd` etait mort sur les quinze. Un +# service de securite qui ne demarre pas doit se VOIR : c'est precisement le genre de +# panne que personne ne va chercher, puisque rien ne la signale et que `auditctl -l` +# affiche des regles. +# +# On tolere encore l'echec sur les machines ou l'audit n'est pas demande — mais quand il +# l'est, on le dit. - name: Activer auditd ansible.builtin.systemd: name: auditd enabled: true state: started when: auditd_enabled | bool - failed_when: false + register: auditd_demarrage + +- name: Refuser si l'audit est demande mais ne tourne pas + ansible.builtin.assert: + that: + - auditd_demarrage is not failed + fail_msg: >- + `auditd_enabled` est vrai mais le service n'a pas demarre. Cause la plus frequente : + un doublon dans `/etc/audit/rules.d/` fait echouer `audit-rules.service`, dont + `auditd` depend — verifier `augenrules --check` et `auditd_fichiers_perimes`. + Les regles peuvent etre ARMEES dans le noyau sans que personne ne collecte. + when: auditd_enabled | bool diff --git a/roles/common_packages/tasks/main.yml b/roles/common_packages/tasks/main.yml index 281c24a..17495cc 100644 --- a/roles/common_packages/tasks/main.yml +++ b/roles/common_packages/tasks/main.yml @@ -21,6 +21,32 @@ when: common_packages_desarmer_maj_auto | bool failed_when: false # une unite absente n'est pas une faute : elle est desarmee +# EFFACER LA TRACE QUE LE DESARMEMENT LAISSE (mesure du 2026-08-30). +# +# Masquer le service pendant que son timer TOURNE lui fait perdre sa cible. systemd le +# note, et le garde : +# +# apt-daily.timer: Unit to trigger vanished. +# apt-daily.timer: Failed with result 'resources'. +# +# `state: stopped` n'efface pas un etat `failed` — seul `reset-failed` le fait. Sans lui, +# CHAQUE machine porte deux unites en echec permanent. Constate sur les quinze de +# Chezlepro : `systemctl list-units --state=failed` en rend deux partout. +# +# CE N'EST PAS COSMETIQUE. Une supervision qui compte les unites en echec compte ces +# deux-la pour toujours — et le jour ou une VRAIE panne s'ajoute, elle se noie dans un +# bruit qu'on a appris a ignorer. C'est le meme defaut que le journal de la frontiere +# noye sous 982 000 entrees : ce qui ment le plus n'est pas ce qui se tait, c'est ce qui +# crie sans raison. +- name: Effacer l'etat d'echec laisse par le desarmement + ansible.builtin.command: + argv: ["systemctl", "reset-failed", "{{ item }}"] + loop: "{{ common_packages_unites_maj_auto }}" + register: common_packages_reset + changed_when: false + failed_when: false # rien a effacer n'est pas une faute + when: common_packages_desarmer_maj_auto | bool + - name: Mettre à jour le cache APT ansible.builtin.apt: update_cache: true