Une règle qu'aucune garde ne vérifie finit par ne plus être vraie — c'est ce qui était arrivé aux 28 lignes d'intégration recopiées. Chaque rôle serveur_* porte un meta/authentification.yml, confronté à son code par P29. web-sso 5, socle-identite 2 (keycloak/openldap : ils SONT la chaîne d'identité), ldap-direct 2, interne-sans-auth 2, sans-auth-humaine 12. La preuve refuse l'oubli ET le mensonge. Éprouvée par sabotage sur sept cas : déclaration supprimée, portée inventée, secours retiré, posture de formulaire retirée, raison retirée, ldap-direct mensonger, réglage retiré des defaults. Les deux derniers passaient dans la première version : - le mensonge passait à cause d'un commentaire. Je cherchais le mot « ldap » dans le rôle, et serveur_grafana/defaults/main.yml contient « désactiver quelqu'un dans LDAP » : de la prose validait une déclaration fausse. La preuve exige maintenant un indice nommé — variable <rôle>_oidc / <rôle>_ldap, ou URI ldap:// - le réglage retiré passait parce que le gabarit citait encore la variable alors que plus rien ne lui donnait de valeur. La preuve lit defaults/main.yml en YAML et exige que la clé y soit définie, pas mentionnée. Elle a aussi forcé une valeur : oauth2-proxy était déclaré « formulaire local fermé » alors qu'il n'a aucun compte local. D'où formulaire_local: aucun, qui distingue « il n'y en a jamais eu » de « il y en a un, il est fermé ». Correction d'une note de la veille : Prometheus et Loki ne sont PAS exposés publiquement (aucun expose au plan). Seuls six groupes le sont. Le risque est intra-tenant, pas frontalier. Les deux lacunes sont comptées à chaque exécution, pas masquées. AFF-111, D-42. 29 preuves OK, ansible-lint (production) sur 375 fichiers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
Registre des décisions d'architecture
À quoi sert ce document. Les décisions sont écrites là où elles s'appliquent —
frontiere-opnsense.md,sdn-evpn.md,migration-tenant.md,underlay.yml.example— et leur histoire vit dans leCHANGELOG. Ce registre ne les répète pas : il dit quelles décisions existent, pourquoi, et où lire le détail. Sans lui, « pourquoi le/29et pas le/30? » demande de relire vingt entrées de journal.
Ce n'est pas la doctrine de conduite (AGENTS.md §Principes), ni le registre des
affirmations prouvables (docs/audit/affirmations.md). Une décision peut n'être gardée par
aucune preuve : elle reste une décision.
Qui décide. Toutes ces décisions sont celles de l'opérateur du dépôt. Plusieurs ont été prises sur recommandation — l'assistance propose et argumente, elle ne tranche pas. La distinction compte pour la suite : une décision se renverse par celui qui l'a prise, et savoir qu'elle a été choisie plutôt que héritée change ce qu'on s'autorise à en faire.
Les dates de la §5 sont celles de l'historique git, pas du moment de la conversation : ce sont les seules vérifiables.
1. Le réseau
| # | Décision | Pourquoi | Détail | Garde |
|---|---|---|---|---|
| D-01 | OPNsense est une frontière nord/sud, pas la passerelle des zones | le routage inter-zone reste au débit ligne ; la bordure ne voit pas l'est-ouest | frontiere-opnsense.md §1 |
— |
| D-02 | Le lien de transit vit dans l'underlay, pas dans un tenant | la frontière route vers tous les tenants par le même saut : il ne peut dériver d'aucun index |
frontiere-opnsense.md §6 |
P23 |
| D-03 | Le /29 de transit : frontières en bas (.1,.2), SVI en haut (.6), .3 réservée |
deux pare-feux cohabitent pendant une transition ; .3 attend une IP virtuelle CARP |
frontiere-opnsense.md §6 |
P23 |
| D-04 | Un point de routage porte le même dernier octet sur tous ses sous-réseaux | on retient une adresse, pas treize | frontiere-opnsense.md §4 |
P23 |
| D-05 | Un seul commutateur route ; les autres restent en L2 pur | sans MLAG, dupliquer les SVI créerait autant de conflits d'adresses que de zones | underlay.yml.example |
P23 |
| D-06 | Les réseaux déclarent leur fabric ; un devis ne parle que de la sienne | le stockage jumbo vit sur ses propres commutateurs — un devis est une configuration, pas un inventaire | sdn-evpn.md §4 |
P23 |
| D-07 | Pas d'ACL sur cette fabric (acl_inter_tenant: false) |
le matériel ne sait pas lier une ACL à un SVI ; des règles jamais liées auraient l'air d'isoler | frontiere-opnsense.md §1 |
— |
| D-08 | Le routage tenant passe au SDN EVPN — une zone par tenant | une zone EVPN est un VRF : l'isolation devient structurelle, pas réglementaire | sdn-evpn.md |
P23 (MTU) |
| D-09 | Le filtrage inter-zone d'un tenant se fait au même niveau (EVPN) | là où le routage a lieu | sdn-evpn.md §3 |
— |
| D-31 | Le filtrage est-ouest est appliqué deux fois : hyperviseur puis nftables d'hôte | défense en profondeur ; les deux dérivent du même registre par les mêmes fonctions, donc ne peuvent pas diverger | sdn-evpn.md §3 |
P25 |
| D-32 | Tout ce qui entre ou sort d'un tenant passe par la frontière | un VRF n'a qu'une sortie ; conséquence : la frontière devient un prérequis de déploiement | sdn-evpn.md §3 |
— |
| D-10 | L'inter-tenant passe obligatoirement par la frontière | il sort du VRF, donc traverse une bordure en block par défaut : il ne peut plus être oublié |
sdn-evpn.md §3 |
— |
| D-11 | La sortie générale est déclarée dans le registre des flux | block out est un vrai default-deny ; un besoin oublié ne se manifeste pas par un refus clair |
frontiere-opnsense.md §8 |
P09 |
| D-29 | L'overlay EVPN plafonne à 1450 ; le transport doit donc dépasser 1500 | choix d'exploitation ; l'encapsulation VXLAN coûte 50 octets | sdn-evpn.md §5 |
P23 (MTU du transport) |
| D-30 | L'ICMP « fragmentation nécessaire » est déclaré, dans les deux sens | à 1450, tout ce qui traverse la frontière dépend de la découverte de MTU de chemin ; une bordure en default-deny la casse en silence | roles/serveur_debian/meta/flux.yml |
P09 |
| D-12 | Nommage : bifrost aux frontières, sleipnir à la fabric |
Bifröst est le pont vers l'extérieur ; Sleipnir traverse les mondes sans en sortir | frontiere-opnsense.md §6 |
— |
2. Qui possède quoi
| # | Décision | Pourquoi | Détail | Garde |
|---|---|---|---|---|
| D-13 | Un hébergeur sert plusieurs tenants et a son tenant par défaut | Chezlepro est les deux à la fois, ce qui masquait la distinction | frontiere-opnsense.md §2 |
— |
| D-14 | underlay.yml appartient à l'hébergeur, monté par symlink |
ce sont ses commutateurs, ses câbles ; le moteur est générique, un tenant n'en possède pas | sdn-evpn.md, underlay.yml.example |
— |
| D-15 | Ce symlink ne suit pas make instance-utiliser |
basculer le tenant actif ne change pas la fabric | frontiere-opnsense.md §2 |
— |
| D-16 | Les intrants de la frontière se lisent chez l'hébergeur | un hébergeur n'a qu'une frontière pour tous ses tenants | frontiere-opnsense.md §2 |
— |
| D-17 | L'hébergeur n'est pas déclaré : le symlink le désigne | une seconde déclaration ouvrirait deux valeurs contradictoires | frontiere-opnsense.md §2 |
— |
| D-35 | Le cluster Proxmox appartient à l'hébergeur ; seuls le golden template et les défauts de placement restent au tenant | recopié chez chaque tenant, l'inventaire du cluster avait déjà divergé — deux listes de stockages contradictoires pour le même matériel | config-proxmox.md |
P27 |
| D-36 | Le panneau nomme le propriétaire de chaque section d'intrants | éditer une section « hébergeur » vaut pour tous ses tenants ; l'écran ne le disait pas | scripts/inventory_gui.py (INTRANTS_SCHEMA) |
— |
| D-37 | Chaque tenant a son pool Proxmox ; les noms courts de VM restent identiques d'un tenant à l'autre | 11 serveurs sur 14 sont homonymes — c'est la preuve que la nomenclature est un gabarit ; le coût est humain (la console affiche le nom), et le pool le corrige sans rien renommer | devis_proxmox_pools.py |
P28 |
| D-18 | Chaque tenant a un responsable désigné | sans lui, « qui peut décider de déménager cette organisation ? » se pose au pire moment | migration-tenant.md §3 |
— |
| D-38 | Toute authentification web passe par Keycloak ; LDAP est la source unique des comptes | une identité, un mot de passe ; aucun service ne tient son propre répertoire d'humains | authentification.md §1-2 | — |
| D-39 | Les protocoles qui ne parlent pas OIDC (IMAP, SMTP) se lient directement à LDAP | le chemin varie, la source ne varie pas | authentification.md §2 | — |
| D-40 | L'accès de secours passe par sudo sur l'hôte, pas par un compte web permanent | service → Keycloak → LDAP est en série : sans secours, une panne exclut tout le monde, y compris pour réparer | authentification.md §4 | — |
| D-41 | Le formulaire de connexion locale n'est pas proposé au repos (<rôle>_connexion_locale: false) | il contourne la politique de mot de passe, le MFA et surtout la révocation centrale ; sudo est le mécanisme de réouverture | authentification.md §3 | — |
| D-42 | Chaque rôle déclare sa position d'authentification (meta/authentification.yml), gardée par une preuve | une directive qu'aucune garde ne vérifie finit par ne plus être vraie — c'était le cas des 28 lignes d'intégration recopiées | authentification.md §5 | P29 |
3. Les secrets
| # | Décision | Pourquoi | Détail | Garde |
|---|---|---|---|---|
| D-19 | Une seule voûte par instance (group_vars/all/vault.yml) |
un mot de passe, un endroit | config-proxmox.md |
P18 |
| D-20 | La liste des secrets se recense, elle ne s'écrit pas | trois copies manuelles ont existé, toutes ont divergé | intrants-communs.md §H |
P18 |
| D-21 | L'empreinte du root CA n'est pas un secret : elle se dérive à chaud | un from-zero régénère l'AC ; figée en voûte, elle serait périmée |
roles/client_pki/README.md |
— |
| D-22 | Migrer un tenant, c'est révoquer, pas transmettre | sinon l'ancien hébergeur garde à vie l'accès aux secrets d'un client parti | migration-tenant.md §6 |
— |
4. La méthode
| # | Décision | Pourquoi | Détail | Garde |
|---|---|---|---|---|
| D-23 | Les cibles hors flotte reçoivent un devis, pas un rôle | commutateurs, frontière et SDN sont des objets de cluster ; un rôle boucle sur des hôtes | sdn-evpn.md §7 |
— |
| D-24 | Un devis n'écrit rien : il se relit, puis s'applique | proportionné au risque — une config ratée partitionne tout un cluster | frontiere-opnsense.md §7 |
— |
| D-25 | Le dépôt n'affirme pas que ses devis s'appliquent, il affirme qu'ils dérivent | leur syntaxe dépend d'un matériel que le dépôt ne possède pas | audit/affirmations.md §10 |
— |
| D-26 | Un modèle peut porter un underlay ; tous les hébergeurs n'ont pas le même matériel | générique public, étoffés en privé | exemples/modeles/socle/README.md |
P17 |
| D-27 | Migration : le receveur est prouvé prêt avant tout gel | l'interruption se réduit au delta et à la propagation DNS | migration-tenant.md §4 |
— |
| D-28 | Le mandat de migration est signé par le tenant, pas convenu entre hébergeurs | il n'y a pas de registre central pour arbitrer ; une organisation n'est pas la propriété de son hébergeur | migration-tenant.md §2 |
— |
| D-33 | Une intégration universelle est déclarée par le rôle, jamais recopiée par serveur | 28 des 57 lignes du plan disaient oui à ce qui vaut pour tous : elles n'existaient que pour être oubliées — et quatre l'avaient été | integrations-vm.md §Politique |
P26 |
| D-34 | Une exemption se dérive du service rendu (sauf_role), jamais d'un nom d'hôte |
l'AC ne s'enrôle pas auprès d'elle-même ; l'exemption doit suivre step-ca si on le déplace | roles/client_pki/meta/integration.yml |
P26 |
5. Décisions renversées
Les garder évite de refaire le chemin, et explique pourquoi le code porte encore des branches qui semblent inutiles.
| Décision | Tenue du … au … | Renversée par | Pourquoi | Ce qu'il en reste |
|---|---|---|---|---|
| L'isolation inter-tenant est portée par des ACL de commutateur | 2026-07-07 → 08-03 | 059d76a |
le matériel ne sait pas lier une ACL à une interface de routage | acl_inter_tenant: true reste le défaut : une autre fabric peut en être capable |
| Le routage inter-zone est porté par les commutateurs L3 | 2026-07-07 → 08-03 | e5ce2b9 |
sans ACL, l'isolation devenait déclarative ; EVPN la rend structurelle | routage_tenants: switch reste le défaut et reste généré |
L'underlay.yml vit à la racine du moteur, gitignoré |
2026-07-24 → 08-02 | 0eae97c |
consommé par deux générateurs, validé par une preuve, versionné nulle part | SETOPS_UNDERLAY permet toujours de le pointer ailleurs |
Ce que ces renversements ont en commun. Aucun ne vient d'un changement d'avis : les trois viennent d'un fait découvert après la décision — une commande absente de l'aide du matériel, une capacité manquante, une dizaine de modifications irrécupérables. C'est l'argument le plus fort pour éprouver avant de figer.