Directive : toute authentification web passe par Keycloak, LDAP est la source unique des comptes, chaque service garde un accès de secours par sudo sur l'hôte. Les trois sont indissociables — la chaîne service → Keycloak → LDAP est en série, donc sans secours une panne exclut tout le monde, y compris pour réparer. Portée : le web seulement ; IMAP/SMTP se lient à LDAP directement et SSH est en clé seule. Posture <rôle>_connexion_locale, false par défaut. Le compte local existe — il ne peut pas dépendre de Keycloak — mais son formulaire n'est plus proposé au repos : ouvert en permanence, il contourne la politique de mot de passe, le MFA et surtout la révocation centrale. Vérifié auprès de l'amont, puis par rendu réel des gabarits dans les deux postures : - Grafana GF_AUTH_DISABLE_LOGIN_FORM → ferme ; - Forgejo ENABLE_INTERNAL_SIGNIN + ENABLE_BASIC_AUTHENTICATION → ferme, API Basic comprise. N'existe que depuis la v10 (ticket amont 7476) ; le rôle épingle 10.0.0 et un assert refuse la fermeture en deçà, car le réglage serait ignoré sans erreur ; - Nextcloud hide_login_form → MASQUE seulement : ?direct=1 reste le chemin de secours documenté par l'amont. Écrit comme tel, sans prétendre à l'équivalence. Défaut attrapé par le rendu : la condition Forgejo sans `| bool` n'émettait rien dans aucune posture — une valeur en chaîne est vraie au sens Jinja, la connexion locale serait restée ouverte en silence. docs/authentification.md, décisions D-38 à D-41. ansible-lint (production) sans échec, 28 preuves OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 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 | — |
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.