diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dc3fb2..9e5fb97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,78 @@ # CHANGELOG — Set-OPS +## 2026-09-11 (2) — La piste d'audit sort de la machine auditee + +`auditd` tournait, onze regles etaient armees, et rien de tout cela ne servait a grand-chose. +Quatre manques, mesures avant d'etre combles. + +### 1. Rien ne sortait de la machine + +C'etait le trou decisif. La configuration d'Alloy contenait **zero** reference a l'audit, et +les cinq greffons d'`auditd` etaient tous `active = no`. La piste vivait uniquement sur la +machine auditee : qui la compromet possede la preuve de l'avoir fait, et le fichier est +precisement ce qu'on efface en premier. + +Alloy lit maintenant `/var/log/audit/audit.log` et le pousse vers Loki. **Aucune permission +a poser** — mesure, pas suppose : + + uid=999(alloy) groupes=989(alloy),4(adm),999(systemd-journal) + drwxr-x--- 2 root adm /var/log/audit + +ON A ECARTE L'AUTRE VOIE, et pour une raison precise : activer le greffon `syslog.conf` +ferait passer les evenements par journald, deja collecte — mais journald applique une LIMITE +DE DEBIT. Une rafale d'evenements d'audit, c'est-a-dire exactement le moment qui compte, +serait tronquee en silence. + +L'activation DERIVE de l'appartenance au groupe (`serveur_durci` dans `group_names`), pas +d'une liste par instance : une liste de plus qui suit une autre prendrait du retard. + +### 2. Aucune sonde, alors que le depot racontait lui-meme la panne + +`roles/auditd/` n'avait pas de `meta/` du tout. Et son propre `defaults/main.yml` documentait +l'incident du 2026-08-30 : onze regles armees dans le noyau sur quinze machines ou `auditd` +etait mort, `auditctl -l` les affichant toutes. + +La sonde `audit` mesure donc la COLLECTE — service actif **et** journal qui a bouge. Le +nombre de regles part en **perfdata, jamais en verdict** : c'est exactement le chiffre qui +restait bon pendant la panne. + +Declaree dans `roles/serveur_durci/meta/` (le GROUPE) et deposee par `roles/auditd/` : la +derivation de supervision ne traverse pas les roles-taches. Meme lecon que la sonde +`correctifs`, deplacee la veille. + +### 3. La retention n'etait declaree nulle part + +Mesure sur `infra-edge-01` au repos, deploiement termine : + + 3 380 octets / 60 s -> ~4,6 Mo/jour + Debian : 8 Mo x 5 = 40 Mo -> ~9 jours + +Sauf qu'une reconstruction a elle seule en brule 4 Mo. Portee a 16 Mo x 8 = 128 Mo (~28 +jours), et le raisonnement a change en route : depuis (1), l'archive n'est plus ici, elle est +chez le collecteur. Le local n'est plus qu'un TAMPON pour traverser une panne de Loki. + +### 4. Les regles ignoraient le materiel qui signe tout le reste + +Elles surveillaient `passwd`, `sudoers`, `sshd_config`, `/etc/apt/` — les fichiers par +lesquels on prend un COMPTE. Aucune ne regardait `/etc/step/certs/`, la cle privee de +l'hote : qui la copie parle ensuite au nom de la machine devant toute la flotte, sans +toucher a un mot de passe ni declencher une seule des regles precedentes. + + -w /etc/step/ -p wa -k pki + -w /etc/step-ca/ -p wa -k pki-autorite (autorite seulement) + +**LA SECONDE EST CONDITIONNELLE, ET CE N'EST PAS UN DETAIL.** `auditctl` REFUSE un `-w` vers +un chemin absent, et `augenrules` fait alors echouer le chargement ENTIER — `auditd` ne +demarre plus, faute de sa dependance. Poser cette ligne sur les treize machines qui ne sont +pas l'autorite rejouerait exactement la panne du 2026-08-30. Verifie sur l'autorite d'abord, +seule : 13 regles armees, `audit-rules` non en echec, sonde a 0. + +### Un effet de bord assume + +Creer `roles/serveur_durci/` pour porter la declaration en fait un role a part entiere : il +lui faut son README et sa `meta/authentification.yml`, comme `serveur_debian`. Trois preuves +l'ont exige (P29, P31, P48) — elles ont fait exactement leur travail. + ## 2026-09-11 (1) — Reconstruction a froid : les noms derives naissent justes Quatrieme reconstruction depuis zero de Chezlepro, demandee pour une raison precise : trois diff --git a/docs/audit/preuve-2026-09-11.md b/docs/audit/preuve-2026-09-11.md index a7f2c3f..5b52a74 100644 --- a/docs/audit/preuve-2026-09-11.md +++ b/docs/audit/preuve-2026-09-11.md @@ -41,16 +41,16 @@ | P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 6 integration(s) universelle(s) : aucune lacune, aucune recopie (0 exemption(s) derivee(s) du service rendu). | | P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. | | P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 34 VM placee(s), aucun nom ni VMID en collision. | -| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | +| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, 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 | 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, 68 roles avec README. | | P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (137 cle(s) declaree(s) par l'instance). | | 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 (37 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) de l'ecosysteme et 3 du site 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 : 40 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. | +| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 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 : 55 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | @@ -69,14 +69,14 @@ | 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 | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. | | 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 (68 preuves, 67 roles, 41 groupes). | +| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (68 preuves, 68 roles, 41 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 `53e2f78` (publie le 2026-09-11). | | 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). | | 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 | 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_ | +| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 25 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. | | 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 (6 exposition(s)). | | P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 6 service(s) expose(s) portent le nom du plan (6 groupe(s) derive(s)). | diff --git a/docs/authentification.md b/docs/authentification.md index bce0161..851f4af 100644 --- a/docs/authentification.md +++ b/docs/authentification.md @@ -98,7 +98,7 @@ Une directive qu'aucune garde ne vérifie finit par ne plus être vraie. Chaque | `socle-identite` | **est** la chaîne d'identité, ne peut pas se déléguer à elle-même | keycloak, openldap | | `ldap-direct` | protocole non-OIDC lié à LDAP | dovecot, postfix | | `interne-sans-auth` | interface joignable **dans** le tenant sans authentification, non exposée | prometheus, loki | -| `sans-auth-humaine` | aucun point d'authentification humaine | 21 | +| `sans-auth-humaine` | aucun point d'authentification humaine | 22 | La preuve refuse **l'oubli et le mensonge** : un rôle sans déclaration, une portée inventée, un `web-sso` sans accès de secours ou sans posture de formulaire, un `web-sso diff --git a/docs/carte-set-ops.md b/docs/carte-set-ops.md index 491e729..c8f66c5 100644 --- a/docs/carte-set-ops.md +++ b/docs/carte-set-ops.md @@ -23,8 +23,8 @@ README de rôles). Cette page comble ces deux trous. | Ce qu'on compte | Combien | Comment on le mesure | |---|---|---| -| rôles | 67 | `roles/*/` | -| README de rôles | 67 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette | +| rôles | 68 | `roles/*/` | +| README de rôles | 68 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette | | documents | 40 | `docs/*.md` | | pièces d'audit | 42 | `docs/audit/*` | | unités de wiki | 27 | `wiki/*.md` | diff --git a/roles/auditd/defaults/main.yml b/roles/auditd/defaults/main.yml index c37ffa7..1672443 100644 --- a/roles/auditd/defaults/main.yml +++ b/roles/auditd/defaults/main.yml @@ -23,3 +23,34 @@ auditd_enabled: true # de quelqu'un d'autre. auditd_fichiers_perimes: - 99-chezlepro.rules + +# RETENTION LOCALE : UN TAMPON, PLUS UNE ARCHIVE (2026-09-11). +# +# Debian livre 8 Mo x 5 fichiers = 40 Mo. Mesure sur `infra-edge-01` au repos, +# deploiement termine : 3 380 octets en 60 s, soit ~4,6 Mo/jour. Les 40 Mo tiennent +# donc environ NEUF JOURS — sauf qu'une reconstruction a elle seule en brule 4 Mo : les +# trois du 2026-09-10 auraient mange trois jours de piste. +# +# CE QUI CHANGE LE RAISONNEMENT : depuis aujourd'hui, Alloy expedie `audit.log` vers Loki +# (`client_journal`). L'archive n'est plus ici — elle est chez le collecteur, hors de la +# machine auditee, ce qui est le seul endroit ou elle ait une valeur de preuve. Ce qui +# reste en local n'est plus qu'un TAMPON : de quoi traverser une panne de Loki sans +# perdre la piste. +# +# 16 Mo x 8 = 128 Mo, soit ~28 jours au repos. Sur un disque de 40 Go c'est negligeable, +# et ca couvre largement toute indisponibilite plausible du collecteur. +# +# CE CHOIX EST UNE DECISION D'EXPLOITATION, pas une derivation : deux lignes a changer si +# la mesure du debit evolue. +auditd_max_log_file: 16 # Mo par fichier +auditd_num_logs: 8 # fichiers conserves + +# SONDE « audit » — AGE MAXIMAL TOLERE SANS UNE SEULE ECRITURE. +# +# Repose sur une observation, et il faut le savoir : le journal bouge meme SANS activite +# humaine. La regle `time-change` capte les ajustements d'horloge (218 evenements sur la +# premiere heure d'une machine neuve), et les sessions `sudo` du deploiement le reste du +# temps. Une heure de silence COMPLET n'est donc pas un creux normal : c'est soit +# `disk_full_action = SUSPEND` qui a mordu, soit la collecte qui s'est arretee sans que le +# service meure. +auditd_sonde_age_max: 3600 diff --git a/roles/auditd/tasks/main.yml b/roles/auditd/tasks/main.yml index e63865b..8756c63 100644 --- a/roles/auditd/tasks/main.yml +++ b/roles/auditd/tasks/main.yml @@ -18,6 +18,29 @@ notify: Restart auditd when: auditd_enabled | bool +# LA RETENTION EST UN CHOIX, DONC ELLE S'ECRIT (2026-09-11). Voir defaults/main.yml pour +# l'arithmetique : depuis qu'Alloy expedie `audit.log` vers Loki, le local n'est plus +# l'archive mais un TAMPON pour traverser une panne du collecteur. +# +# `lineinfile` plutot qu'un gabarit complet : `auditd.conf` porte une quinzaine de +# reglages que Debian ajuste, et les reecrire tous nous rendrait responsables de choix +# que nous n'avons pas faits. +- name: Fixer la retention locale du journal d'audit + ansible.builtin.lineinfile: + path: /etc/audit/auditd.conf + regexp: "^{{ item.cle }}\\s*=" + line: "{{ item.cle }} = {{ item.valeur }}" + owner: root + group: root + mode: "0640" + loop: + - { cle: "max_log_file", valeur: "{{ auditd_max_log_file }}" } + - { cle: "num_logs", valeur: "{{ auditd_num_logs }}" } + loop_control: + label: "{{ item.cle }}" + notify: Restart auditd + when: auditd_enabled | bool + - name: Déployer les règles auditd Set-OPS ansible.builtin.template: src: 99-setops.rules.j2 @@ -55,3 +78,19 @@ `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 + +# LA SONDE MESURE LA COLLECTE, PAS L'ARMEMENT. Voir le gabarit : le 2026-08-30, onze +# regles etaient armees dans le noyau sur quinze machines ou personne ne collectait. +# +# ELLE EST DECLAREE PAR LE GROUPE `serveur_durci` ET DEPOSEE ICI. La derivation de +# supervision ne traverse pas les roles-taches : declaree dans `roles/auditd/meta/`, elle +# serait restee invisible d'Icinga — et le menage de `client_sante` l'aurait effacee au +# passage suivant. Meme lecon que la sonde `correctifs`, le 2026-09-10. +- name: Deposer la sonde de collecte d'audit + ansible.builtin.template: + src: sonde-audit.sh.j2 + dest: /usr/local/lib/setops/sondes/audit.sh + owner: root + group: root + mode: "0750" + when: auditd_enabled | bool diff --git a/roles/auditd/templates/99-setops.rules.j2 b/roles/auditd/templates/99-setops.rules.j2 index 5cf5a2d..8143c86 100644 --- a/roles/auditd/templates/99-setops.rules.j2 +++ b/roles/auditd/templates/99-setops.rules.j2 @@ -13,6 +13,29 @@ -w /etc/ssh/sshd_config.d/ -p wa -k sshd -w /etc/apt/ -p wa -k apt-config +# LE MATERIEL QUI SIGNE TOUT LE RESTE (2026-09-11). +# +# Les regles ci-dessus surveillent les fichiers par lesquels on prend un compte. Celles-ci +# surveillent ce par quoi on prend une IDENTITE DE MACHINE : `/etc/step/certs/` porte la +# cle privee de l'hote. Qui la copie parle ensuite au nom de la machine devant toute la +# flotte — PostgreSQL en `verify-full`, Loki, l'annuaire — sans jamais toucher a un +# mot de passe ni declencher une seule des regles precedentes. +-w /etc/step/ -p wa -k pki +{% if 'serveur_step_ca' in group_names %} + +# LA RACINE DE CONFIANCE, ET ELLE N'EXISTE QUE SUR L'AUTORITE. +# +# `/etc/step-ca/` contient `password.txt` et `secrets/` : de quoi emettre un certificat +# valide pour N'IMPORTE QUEL nom de l'ecosysteme. C'est le seul secret dont la copie ne +# se repare pas par une rotation — il faut refaire la PKI entiere. +# +# LA GARDE EST CONDITIONNELLE, ET CE N'EST PAS UN DETAIL : `auditctl` REFUSE un `-w` vers +# un chemin absent, et `augenrules` fait alors echouer le chargement ENTIER. Poser cette +# ligne sur les treize machines qui ne sont pas l'autorite rejouerait exactement la panne +# du 2026-08-30 — regles armees, personne pour collecter. +-w /etc/step-ca/ -p wa -k pki-autorite +{% endif %} + -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 diff --git a/roles/auditd/templates/sonde-audit.sh.j2 b/roles/auditd/templates/sonde-audit.sh.j2 new file mode 100644 index 0000000..f074736 --- /dev/null +++ b/roles/auditd/templates/sonde-audit.sh.j2 @@ -0,0 +1,48 @@ +#!/bin/bash +# GENERE par Set-OPS (role auditd). Ne pas editer a la main. +# +# SONDE « audit » — QUELQU'UN COLLECTE-T-IL, vraiment ? +# +# ELLE NE COMPTE PAS LES REGLES, ET C'EST TOUT SON OBJET. Le 2026-08-30, sur les quinze +# machines de Chezlepro, `auditctl -l` affichait onze regles armees dans le noyau pendant +# qu'`auditd` etait mort : un doublon dans `rules.d/` faisait echouer `audit-rules.service`, +# dont le demon depend. Une sonde qui aurait compte les regles aurait dit « vert » pendant +# toute la panne. On mesure donc la COLLECTE — le service, puis l'ecriture. +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `auditd_sonde_age_max` (le baisser a 1). +set -uo pipefail + +JOURNAL=/var/log/audit/audit.log +AGE_MAX={{ auditd_sonde_age_max }} + +# Les regles ne sont PAS le verdict : elles partent en perfdata, pour que le diagnostic +# distingue « rien d'arme » de « arme mais personne ne collecte ». +regles=$(auditctl -l 2>/dev/null | grep -cvE '^No rules$' || true) +regles=${regles:-0} + +if ! systemctl is-active --quiet auditd; then + echo "auditd ne tourne pas : ${regles} regle(s) peuvent etre armees, personne ne collecte." \ + "| regles=${regles} age=U taille=0B" + exit 2 +fi + +if [[ ! -f "${JOURNAL}" ]]; then + echo "auditd tourne mais ${JOURNAL} n'existe pas : rien n'est ecrit." \ + "| regles=${regles} age=U taille=0B" + exit 2 +fi + +taille=$(stat -c%s "${JOURNAL}" 2>/dev/null || echo 0) +age=$(( $(date +%s) - $(stat -c%Y "${JOURNAL}" 2>/dev/null || echo 0) )) + +if (( age > AGE_MAX )); then + echo "auditd tourne mais n'a rien ecrit depuis ${age} s (seuil ${AGE_MAX} s) :" \ + "collecte suspendue, disque plein ou regles vides." \ + "| regles=${regles} age=${age}s;${AGE_MAX} taille=${taille}B" + exit 1 +fi + +echo "Audit collecte : ecriture il y a ${age} s, ${regles} regle(s) armee(s)." \ + "| regles=${regles} age=${age}s;${AGE_MAX} taille=${taille}B" +exit 0 diff --git a/roles/client_journal/defaults/main.yml b/roles/client_journal/defaults/main.yml index 92fd5a8..4ba86ee 100644 --- a/roles/client_journal/defaults/main.yml +++ b/roles/client_journal/defaults/main.yml @@ -80,3 +80,11 @@ 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" + +# LE JOURNAL D'AUDIT, EXPEDIE COMME LE RESTE (2026-09-11). +# +# DERIVE DE L'APPARTENANCE AU GROUPE, pas declare par instance : `auditd` vient avec +# `serveur_durci`, donc la machine qui a l'un a l'autre. Ecrire la liste a la main serait +# 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" diff --git a/roles/client_journal/templates/config.alloy.j2 b/roles/client_journal/templates/config.alloy.j2 index 85a5cb0..33caadb 100644 --- a/roles/client_journal/templates/config.alloy.j2 +++ b/roles/client_journal/templates/config.alloy.j2 @@ -20,6 +20,31 @@ loki.source.journal "journal" { } } +{% if client_journal_audit_actif %} +// --- LA PISTE D'AUDIT SORT DE LA MACHINE AUDITEE (2026-09-11) --- +// +// `auditd` ecrit dans `/var/log/audit/audit.log` et nulle part ailleurs. Tant que la +// piste reste sur la machine qu'elle surveille, qui compromet la machine possede la +// preuve de l'avoir fait — et le fichier est precisement ce qu'on efface en premier. +// +// POURQUOI PAS LE GREFFON `syslog.conf` D'AUDITD, qui ferait passer les evenements par +// journald (deja collecte) : journald applique une LIMITE DE DEBIT. Une rafale +// d'evenements d'audit — c'est-a-dire exactement le moment qui compte — serait tronquee +// en silence. On lit donc le fichier directement. +// +// AUCUNE PERMISSION A POSER : `/var/log/audit` est `root:adm` en 0750, et l'utilisateur +// `alloy` appartient DEJA au groupe `adm` (mesure le 2026-09-11). Si cela changeait, +// Alloy se tairait sans erreur visible — la sonde `audit` reste le filet cote machine. +loki.source.file "audit" { + targets = [{ + __path__ = "{{ client_journal_audit_chemin }}", + job = "audit", + host = "{{ inventory_hostname }}", + }] + forward_to = [loki.write.loki.receiver] +} +{% endif %} + {% if client_journal_syslog_ecoute %} // --- RECEPTEUR SYSLOG : CE QUI NE PEUT PAS PORTER D'AGENT (2026-09-10) --- // diff --git a/roles/serveur_durci/README.md b/roles/serveur_durci/README.md new file mode 100644 index 0000000..917dd1a --- /dev/null +++ b/roles/serveur_durci/README.md @@ -0,0 +1,41 @@ +# serveur_durci + +**Rôle-catégorie** du durcissement : il ne porte **aucune tâche**. Son unique contenu est +`meta/supervision.yml` — la déclaration de la sonde `audit`. + +## Pourquoi un rôle sans tâches + +La dérivation de supervision agrège les `roles//meta/supervision.yml` **par nom de +groupe**. Déclarée dans `roles/auditd/`, qui est un rôle-tâche et non un groupe, la sonde +serait restée invisible d'Icinga — et le ménage de `client_sante` l'aurait effacée au +passage suivant. Même leçon que la sonde `correctifs`, déplacée de `common_packages` vers +`serveur_debian` le 2026-09-10. + +Le fichier est déposé par `roles/auditd/` ; il est **déclaré** ici. La preuve **P64** garde +les deux moitiés ensemble. + +## Le travail réel + +Il est fait par le playbook du groupe, `playbooks/groupes/serveur_durci.yml`, qui applique +dans l'ordre : + +| Rôle | Apport | +| --- | --- | +| `hardening_packages` | Paquets de durcissement | +| `sysctl_hardening` | Paramètres noyau | +| `core_dumps` | Vidages mémoire désarmés | +| `unattended_upgrades` | Mises à jour automatiques **masquées** (voir la sonde `correctifs`) | +| `apparmor` | Confinement des services | +| `auditd` | Piste d'audit : règles, rétention, sonde | +| `fail2ban_ssh` | Bannissement des tentatives SSH | + +## La sonde `audit` + +Elle mesure la **collecte**, pas l'armement — et c'est tout son objet. Le 2026-08-30, sur +les quinze machines de Chezlepro, `auditctl -l` affichait onze règles armées dans le noyau +pendant qu'`auditd` était mort : un doublon dans `rules.d/` faisait échouer +`audit-rules.service`, dont le démon dépend. Une sonde qui aurait compté les règles aurait +dit « vert » pendant toute la panne. + +Le nombre de règles part donc en **perfdata**, jamais en verdict — pour que le diagnostic +distingue « rien d'armé » de « armé mais rien de collecté ». diff --git a/roles/serveur_durci/meta/authentification.yml b/roles/serveur_durci/meta/authentification.yml new file mode 100644 index 0000000..9af0c16 --- /dev/null +++ b/roles/serveur_durci/meta/authentification.yml @@ -0,0 +1,10 @@ +--- +# Position de ce role dans la directive d'authentification (D-38..D-41). +# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette +# declaration, ou dont la declaration contredit son code, fait echouer le harnais. +authentification: + portee: sans-auth-humaine + raison: >- + Role-categorie du durcissement : il ne porte aucune tache et n'expose aucune + interface. Le travail est fait par les roles que son playbook applique, chacun + avec sa propre declaration. diff --git a/roles/serveur_durci/meta/supervision.yml b/roles/serveur_durci/meta/supervision.yml new file mode 100644 index 0000000..bb4de28 --- /dev/null +++ b/roles/serveur_durci/meta/supervision.yml @@ -0,0 +1,25 @@ +--- +# Supervision derivee du groupe. Voir docs/supervision-conception.md. +# +# POURQUOI CETTE SONDE EXISTE, ET CE QU'ELLE NE MESURE PAS. +# +# `auditd` est le seul service de securite du socle dont la panne soit SILENCIEUSE et +# TROMPEUSE a la fois. Le 2026-08-30, sur les quinze machines de Chezlepro, un doublon +# dans `rules.d/` faisait echouer `audit-rules.service` — dont le demon depend. Resultat : +# onze regles armees dans le noyau, `auditctl -l` les affichant toutes, et personne pour +# collecter. Pendant tout ce temps le deploiement rapportait `failed=0`. +# +# CE QU'ON NE MESURE PAS : le nombre de regles. C'est exactement le chiffre qui restait +# bon pendant la panne ; en faire un verdict aurait donne du vert. Il part en perfdata, +# pour que le diagnostic distingue « rien d'arme » de « arme mais rien de collecte ». +# +# CE QU'ON MESURE : le service tourne, ET le journal a bouge recemment. Les deux, parce +# qu'un `auditd` vivant qui n'ecrit plus — disque plein, `disk_full_action = SUSPEND` — +# laisse la meme trace qu'un audit en bonne sante pour qui ne regarde que `systemctl`. +sondes: + - nom: audit + ttl: 5400 + raison: >- + La piste d'audit s'ecrit-elle vraiment ? Des regles armees dans le noyau ne + prouvent pas qu'un demon collecte : le 2026-08-30, quinze machines ont eu les + deux apparences de la sante sans qu'une seule ligne soit ecrite.