Set-OPS-Public/docs/flux-conception.md
Daniel Allaire 5bc3bceac1
Some checks failed
verifier / verifier (push) Has been cancelled
documentation : la tournee des 74 documents, parce qu un balayage ne lit pas
La revision a commence par un balayage par motifs — chemins morts, cibles make
absentes, comptes derives. Il a trouve une trentaine d ecarts et rate presque
tout le reste : un motif ne voit que ce qui s exprime en motif.

make hote-planifier en est l exemple. La cible EXISTE, donc le controle passait
au vert. C est une cible depreciee qui refuse et sort en 2, recommandee par
AGENTS.md, et qui contredit la REGLE D OR du meme fichier trois ecrans plus
haut. Il fallait lire pour la voir.

74 documents lus un par un. 66 corriges, 8 exacts.

CE QUI ETAIT FRANCHEMENT FAUX

AGENTS.md, la source d autorite, annoncait la flotte pas encore executee contre
des VM reelles. Elle a ete rasee et remontee depuis zero trois fois.
ecosysteme-chezlepro.md, le document montre a un client, portait la meme
phrase : il se sous-vendait gravement.

courriel-conception.md s ouvrait sur aucun role n est encore ecrit, au-dessus de
son propre paragraphe 1 qui les nomme. autorisation.md se terminait sur rien n
est construit alors qu il rapporte des mesures datees du role en fonctionnement.
hebergeur-exploitation.md disait rien n est fait d un depot qui existe.
filiation-emancipation.md se contredisait a deux ecrans de distance.

DES MODELES DECRITS D APRES UN MONDE ANTERIEUR

Le resolveur : cinq documents decrivaient un Unbound par VM en opt-in, trois le
donnaient en exemple d integration FACULTATIVE — il est universel depuis le
2026-08-24. L adressage de nomenclature-vm.md : reseau unique, VLAN 11-15, VMID
a cinq chiffres. Le nommage SDN de sdn-evpn.md contre le code : c est le wiki
qui avait raison.

CE QUI CASSE AU PREMIER ESSAI

Le nom du gabarit dore etait faux a quatre endroits, dont la procedure qui le
FABRIQUE et le critere R2 de l epreuve d operateur independant.
preparer-un-site-hebergeur.md avertissait qu une VM faite a la main serait
detruite : raser derive du plan, il ne la detruira jamais — le risque est l
inverse. Un mot de passe d essai en clair dans un depot public.

DEUX PREUVES ETENDUES, ET UNE QUI SE TROMPAIT ELLE-MEME

P57 couvre les groupes : elle a signale aussitot 29 groupes annonces au-dessus d
un tableau qui en cite 40. P29 confronte le tableau de authentification.md aux
declarations reelles : 12 annonces, 21 reels.

Et P57 imposait un chiffre faux — 56 preuves alors que le depot en porte 57, la
conditionnelle vivant hors de tout comptage. Un garde-fou qui fait respecter une
erreur ajoute l assurance a l erreur.

CE QUI RESTE, ET QU AUCUNE PREUVE NE TIENT

Deux comptes trouves a la main. Et une lacune reelle : rien ne garde les
meta/acces.yml — ni qu un service web-sso en porte un, ni que le groupe qu il
nomme existe. P29 tient les positions d authentification, personne ne tient les
habilitations.

make prouver : CONFORME, 56 OK, 0 echec, 1 saute. 0 lien mort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-06 16:18:23 -04:00

7.8 KiB

Registre des flux réseau — conception

Pour qui : le mainteneur qui déclare un flux réseau dans roles/*/meta/flux.yml.

But

Set-OPS tient un registre des flux (qui parle à qui, sur quel port, dans quel sens, pourquoi) pour deux usages :

  1. Générer les règles nftables de chaque serveur — autoriser les flux déclarés, tout refuser d'autre (moindre privilège). Complète la couche chiffrement (TLS) par la couche accès.
  2. Preuve d'audit — la matrice d'accès réseau documentée (source → dest : port : chiffrement : raison), exigée par un audit de sécurité et le label de certification.

Principe : le rôle possède ses flux

Comme meta/empreinte.yml (dimensionnement) est la propriété du rôle, roles/<rôle>/meta/flux.yml déclare les flux du logiciel. Le rôle sait sur quoi il écoute et à quoi il se connecte ; personne d'autre. Le registre global se dérive de l'ensemble des flux.yml des rôles présents sur un nœud.

Schéma de meta/flux.yml

flux:
  - sens: ingress          # ingress = ce rôle ÉCOUTE ; egress = ce rôle SE CONNECTE
    port: 5432             # entier ou liste [80, 443]
    protocole: tcp         # tcp | udp
    pair: [serveur_keycloak, serveur_forgejo]   # QUI — voir « Résolution du pair »
    chiffrement: tls-requis # tls-requis | tls | starttls | ssh | tls-cible | clair | n-a   (bonus audit)
    #   tls-requis : chiffré + pair vérifié (verify-full)  | tls : chiffré  | starttls : mise à niveau opportuniste
    #   ssh : transport SSH (chiffré, hôte vérifié)  | tls-cible : TLS visé mais pas encore appliqué (feuille de route)
    #   clair : non chiffré (local ou terminé à l'edge)  | n-a : sans objet
    raison: "Connexions applicatives (verify-full)."   # lisible, pour l'audit

Résolution du pair

Valeur Résout vers
un rôle/groupe (serveur_prometheus) les IP des hôtes de ce groupe (via le plan)
edge le(s) hôte(s) de l'edge (serveur_nginx)
flotte tous les nœuds de l'instance
externe hors flotte (frontière publique — géré à l'OPNsense, pas dans le nœud)
localhost boucle locale — aucune règle inter-nœud (nftables autorise lo)
expositions dérivé des expose: des applications (cas de l'edge → backends)
admin les réseaux d'administration (intrant nftables_admin_ssh) — l'exploitant n'est ni la flotte, ni l'Internet
voisins_site les autres tenants fédérés de la même fabric (leurs supernets) — le voisinage, quatrième chemin d'arrivée
fabric le matériel de l'hébergeur : hyperviseurs, frontière, commutateurs
runner_site le runner du site, seul autorisé à matérialiser des VM
derive résolu ailleurs que par le nœud (hors périmètre de sa règle)

La résolution pair → IP réutilise le plan (registre IP/FQDN/zones) déjà en place.

Quatre de ces mots sont nés d'un piège, et pas d'un besoin de vocabulaire. voisins_site (2026-08-24) : sans lui, le chaînage des caches d'artefacts n'aurait pu se déclarer qu'en ingress + externe — ce qui aurait publié le cache à l'Internet entier. fabric (2026-08-25) : le runner de site déclarait son API Proxmox en externe, or « externe » se rend par « tout sauf les espaces privés » et les hyperviseurs sont en RFC 1918 — la règle avait l'air d'ouvrir le flux et l'excluait. Un flux qui a l'air ouvert et qui ne l'est pas est pire qu'un flux fermé : il ne se cherche pas.

Les cinq derniers ne rendent pas des hôtes : admin, voisins_site, fabric et runner_site rendent des CIDR ou des sources extérieures à l'écosystème, derive ne rend rien. Ils sont donc traités à part dans scripts/resoudre_flux.py.

Génération

Un résolveur (miroir de instancier) agrège, par serveur, les flux.yml de tous ses rôles (services + intégrations), résout les pair, et produit :

  • nftables : ruleset par serveur (allow des flux résolus, policy drop par défaut), consommé par le rôle nftables_baseline ;
  • le registre d'audit : docs/registre-flux.md (généré) + une cible make flux.

Activation prudente

Activer nftables = action destructive (peut couper l'accès) → confirmation explicite + déploiement graduel (garder l'accès SSH/Ansible, tester par nœud).

Le pare-feu est armé sur la flotte depuis. Ce paragraphe disait « nftables reste préparé mais non activé tant que le registre n'est pas complet et validé ». Le registre est complet — P49 vérifie qu'il reproduit exactement ce que les meta/flux.yml déclarent — et nftables_baseline_enabled vaut true pour hotes_actifs dans le plan. Le rôle, lui, garde false par défaut : le pare-feu n'est pas une propriété du rôle, c'est une décision de l'instance. Le gabarit doré reste à false, et c'est voulu.

Séquence — franchie

  1. ✅ figer le schéma (ce doc) + piloter sur postgresql / client_metrique / nginx ;
  2. ✅ le résolveur (scripts/resoudre_flux.py) — agrégation → règles + registre ;
  3. ✅ remplir tous les rôles (transcription du travail zéro-confiance) ;
  4. ✅ générer + registre d'audit (docs/registre-flux.md, gardé par P49) ; puis activer nftables nœud par nœud — fait.

Ce qui a suivi la séquence n'y était pas prévu : le même registre alimente désormais aussi le pare-feu est-ouest de l'hyperviseur (make proxmox-fw-plan, garde P45) et la frontière nord/sud OPNsense (make frontiere-plan). Trois couches, une seule décision — elles ne peuvent pas se contredire parce qu'elles dérivent de la même source.

poste: false — un service publié qui ne s'adresse pas à un humain

Ajouté le 2026-08-09. Ne concerne que les flux ingress dont le pair inclut externe, c'est-à-dire les services publiés.

La frontière étend ces services au VLAN d'administration : le poste de l'exploitant y est, et c'est de là qu'il ouvre ses consoles web ou son client de courriel. Mais tous les services publiés ne s'adressent pas à un humain — le 25 entrant de Postfix est un flux serveur à serveur, les MX distants.

Situation Exemple poste
un poste de travail s'y connecte serveur_nginx 443, serveur_dovecot 993 absent (défaut true)
flux serveur à serveur uniquement serveur_postfix 25 entrant false

Sans ce mot-clé, la frontière autorisait admin → 25 que le nftables de l'hôte refusait : deux couches déclarant deux politiques différentes. Mesuré par make frontiere-mesurer, qui distingue précisément ce cas d'une vraie fuite.

Le mot-clé vit dans meta/flux.yml, avec le rôle qui sait ce que son port veut dire. Le générateur de la frontière, lui, ne connaît aucun numéro de port.

partage: — décrire une écoute plutôt que l'ouvrir

Ajouté le 2026-08-09 avec P33, qui refuse que deux rôles co-localisés revendiquent le même port.

Le registre confondait deux situations :

Situation Exemple partage
le rôle ouvre l'écoute serveur_dovecot lie 12345 (SASL réseau) absent
le rôle décrit celle d'un autre serveur_backup emprunte le sshd de serveur_debian true

Sans cette distinction, la seule co-location légitime de la flotte — tcp/22 sur le dépôt de sauvegarde, site-backup-01 depuis que les écosystèmes déposent chez leur hébergeur — serait signalée à tort. Une preuve qui crie sur un cas sain finit par être ignorée, ce qui est pire que de ne pas l'avoir.

Corollaire à retenir : un port qu'on subit (le défaut amont d'un logiciel) doit être imposé et déclaré comme les autres. Celui d'Alloy ne l'était pas, et c'est la seule raison pour laquelle la collision a pu durer des semaines.