diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c6d9a5..31a9f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,129 @@ # CHANGELOG — Set-OPS +## 2026-08-27 — P52 : matérialiser n'exige pas d'entrer chez le tenant + +**52 preuves.** `creer-vm` confirmait son succès en attendant une réponse SSH. Le runner du +SITE matérialise le terrain de **tous** les tenants, mais la frontière lui refuse d'entrer +chez eux — c'est le sens même de leur isolation. La première VM qu'il a créée a donc été +déclarée en échec après 600 secondes alors qu'elle tournait, avec l'adresse exacte que le +plan lui destinait : + +``` +Attente de SSH sur ops-01 ............ ECHEC: injoignable apres 600s. +``` + +**La tentation était d'ouvrir le SSH du runner vers tous les tenants.** Ça aurait réparé la +mesure en détruisant ce qu'elle protège : une machine capable d'entrer chez chaque locataire +est précisément ce que cette architecture refuse d'avoir. + +L'agent invité répond sans rien ouvrir — l'API des hyperviseurs est déjà le flux par lequel +la VM vient d'être créée, donc qui peut la créer peut la voir naître — et il **prouve +davantage**. « Quelque chose écoute sur le port 22 » ne dit ni quel système a démarré, ni si +cloud-init a posé la bonne adresse. Sur `ops-01` : + +``` +10.17.19.41 portee sur asgard — Debian GNU/Linux 13 (trixie) 6.12.101 +``` + +Ses échecs distinguent deux causes très différentes : « la machine démarre mais rapporte une +**autre** adresse — cloud-init, ou le pont sur lequel elle est posée », et « introuvable sur +la fabric ». + +**Attendre la disponibilité a changé de main.** Guetter cloud-init et la libération de dpkg +appartient à qui va **configurer** : `deployer` le fait désormais, là où il se contentait +d'un `ping` unique. Il y gagne ce qu'il n'avait pas — attendre le verrou APT, faute de quoi +la première couche échouait dessus. Contrepartie assumée : un nom d'hôte erroné patiente au +lieu d'échouer vite ; échouer vite interdirait de chaîner création et déploiement, le geste +central d'une reconstruction. + +*Une preuve qui exige un pouvoir que l'architecture refuse ne mesure pas le système : elle +mesure ce qu'il faudrait casser pour la satisfaire.* + +## 2026-08-27 — P51 : quatre défauts de portabilité, révélés par le runner + +**51 preuves.** Première matérialisation d'une VM de tenant depuis le runner du SITE. Elle a +échoué quatre fois d'affilée, sur quatre dépendances que le moteur ne déclarait nulle part. +Chacune fonctionnait chez le mainteneur pour une raison **différente**, et aucune de ces +raisons n'existe sur une autre machine. + +**1. Versions des collections.** `requirements.yml` les nommait sans les épingler. Le runner +a reçu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la 11 a retiré les +modules Proxmox de cette collection (ils vivent désormais dans `community.proxmox`). + +``` +ERROR! couldn't resolve module/action 'community.general.proxmox_pool' +``` + +*Une dépendance non épinglée n'est pas une dépendance, c'est un pari sur l'état d'Internet à +la date du déploiement.* + +**2. Installation en avant.** `ansible-galaxy` ne rétrograde pas : déclarer la 10.3.0 ne +suffisait pas à défaire une 13.3.0 déjà posée. `--force`. Un poste peut être en **avance**, +pas seulement en retard, et le dépôt doit faire autorité dans les deux sens. + +**3. Emplacement.** Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous `make`, +Ansible ne lit **que** `/.ansible/collections`. Le rôle déposait dans +`/.ansible/collections` — à côté, jamais lu. Invisible chez le mainteneur, où les +collections viennent du paquet système, toujours dans le chemin quel que soit `ANSIBLE_HOME`. +Et le garde-fou d'installation suivait le **dépôt des archives** : changer la destination ne +le déclenchait pas. Il mesure désormais la destination — même piège qu'au 2026-08-24, déplacé +d'un cran. + +**4. Bibliothèques Python.** Le venv recevait `ansible-core` et `pyyaml`, écrits en dur. Les +modules Proxmox tournent `delegate_to: localhost` et exigent `proxmoxer` **sur le +contrôleur**. + +``` +La bibliotheque Python proxmoxer est absente +``` + +Né d'ici : `requirements-python.txt`, avec sa frontière écrite — il ne déclare **que** ce qui +tourne sur le contrôleur. `python-ldap` et `psycopg2` s'exécutent sur leurs cibles ; le poste +du mainteneur ne les a pas et la flotte se déploie, ce qui prouve que la ligne est au bon +endroit. + +**P51** garde les trois faiblesses de cette famille : une dépendance utilisée sans être +déclarée (le défaut du 2026-08-24, `ansible.posix`), une dépendance déclarée sans version, et +`proxmoxer` absent alors que des playbooks Proxmox existent. La preuve ne lit que les fichiers +de **tâches** : un `defaults/main.yml` porte `net.ipv4.ip_forward`, qu'un motif trop large +prendrait pour un module. + +**Résultat :** `ops-01` matérialisée par le runner du site. L'agent invité rapporte +`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage dérivé du plan, appliqué. + +**Migration connue, pas faite :** passer les quatre modules Proxmox à `community.proxmox` +permettra de suivre `community.general` au-delà de la 11. + +*Un seul exécutant masque ces écarts indéfiniment ; un second les révèle tous en une soirée. +Même mécanique que le second tenant en août.* + +## 2026-08-27 — P50 : le devis de la frontière sait refuser SANS consigner + +**50 preuves.** L'outil ne savait qu'**autoriser** — `"action": "pass"` était en dur dans +l'émetteur. Une règle de silence ne pouvait donc pas naître du dépôt, et j'en avais posé deux +à la main sur le boîtier : exactement ce que ce projet refuse. + +**Ce qui l'a motivé.** Le journal de la frontière écrivait 982 000 entrées par jour, dont +82 % un balayage Internet contre le port VNC et le reste du bavardage de découverte du réseau +local. Sa fenêtre utile était tombée à **quarante-quatre secondes**. J'y ai cherché la trace +d'un flux du site vers les hyperviseurs, je n'ai rien trouvé, et j'en ai conclu à tort +qu'aucune règle ne bloquait. **Un journal noyé ment aussi sûrement qu'un journal mort.** +Mesuré après déclaration : ~20 700/jour. + +Une règle de silence ne change **aucun** comportement : ce qu'elle vise était déjà refusé par +le défaut. Elle ne supprime qu'une trace que personne ne lira. + +**Trois pièces.** `cle_regle` accepte une action sans changer d'un octet la clé des règles +`pass` déjà posées — l'ajout naïf d'un champ les aurait toutes détruites pour les recréer à +l'identique, sur la frontière, en production ; le plan l'a confirmé : 7 à créer, 0 à retirer, +121 inchangées. `_corps_regle` lit l'action, la consignation et la **séquence** depuis le +devis : la séquence est ce qui rend un `block` sûr, puisque OPNsense évalue en `quick` et +qu'un blocage large émis avant les `pass` fermerait courrier, web et accès distant. +`devis_opnsense` lit `opnsense_silences` et en fabrique règles et alias, **motif compris** — +une règle `block` muette sans raison écrite est indiscernable d'un oubli. + +P50 garde ces deux dangers : un silence en séquence 1 échoue, un silence sans motif échoue. + ## 2026-08-26 — P49 : le registre des flux avait dérivé sans bruit **48 preuves.** `docs/registre-flux.md` est **généré** depuis les `roles/*/meta/flux.yml`, diff --git a/docs/audit/preuve-2026-08-28.md b/docs/audit/preuve-2026-08-28.md new file mode 100644 index 0000000..0ff7531 --- /dev/null +++ b/docs/audit/preuve-2026-08-28.md @@ -0,0 +1,88 @@ +# Preuve de conformite — Set-OPS — 2026-08-28 + +> 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 (52 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 : 37 groupes classes, aucun cycle, aucune arete en arriere. | +| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 35 rôles, 92 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 | playbook: playbooks/proxmox/cloner_vm_debian.yml | +| 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 : 25 cle(s), aucun manque. | +| 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), 50 groupe(s), 79 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 | 29 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 18, 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 | 51 scripts expliques et atteignables, 104 cibles make documentees, 61 roles avec README. | +| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 40 exigence(s) de role, toutes satisfaites (126 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 (26 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 : 36 role(s) serveur/client tous nommes, 37 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 : 47 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, 56 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 (110 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 121 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. | + +## 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-28._ diff --git a/docs/carte-set-ops.md b/docs/carte-set-ops.md index 1ed733b..2e24518 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 | 61 | `roles/*/` | | README de rôles | 61 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette | | documents | 38 | `docs/*.md` | -| pièces d'audit | 28 | `docs/audit/*` | +| pièces d'audit | 29 | `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 |