Set-OPS-Public/docs/flux-conception.md
Daniel Allaire 91355bcdef frontiere : etanche — CONFORME sur 56 lignes, dans les deux sens
L'exploitant a retire la derniere regle heritee. `make frontiere-mesurer`
rend CONFORME (code 0) : tout ce qui est declare est livre, tout le reste est
refuse — y compris collab-01:9980, le seul qui livrait vraiment un HTTP 200
depuis le poste.

Et mon instrument avait tort, pas la frontiere. Il comptait 38 ecarts en
concluant depuis le client : « connexion etablie => la bordure a relaye ».
Faux, verifie A LA DESTINATION : pendant que le poste tenait une connexion
« etablie » vers idm-01:389, idm-01 n'en voyait aucune ; collab-01 n'en
voyait aucune sur 9980. La frontiere repond a la poignee TCP sans relayer.

Le devis raisonne desormais sur la LIVRAISON seule, et le controle ne rend le
releve NUL que s'il LIVRE des donnees — qu'il ressorte AMBIGU est attendu ici
et le rapport le dit a chaque execution. Cette relaxation rend aussi le sens
sortant mesurable : il etait declare NUL en permanence.

Nouveau mot-cle `poste: false` dans meta/flux.yml : un service publie n'est
pas forcement fait pour un poste de travail. Le 25 entrant de Postfix est un
flux serveur a serveur ; la frontiere l'etendait au VLAN d'administration ou
le nftables de l'hote le refusait. Deux regles retirees. Le mot-cle vit avec
le role qui sait ce que son port veut dire ; le generateur ne connait
toujours aucun numero de port.

Verifie : frontiere-plan sans ecart (41 regles, 12 routes), flotte 14/14,
frontiere-mesurer CONFORME, prouver.py 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:33:04 -04:00

5.4 KiB

Registre des flux réseau — conception

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)

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

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). nftables reste préparé mais non activé tant que le registre n'est pas complet et validé.

Séquence

  1. figer le schéma (ce doc) + piloter sur postgresql / client_metrique / nginx ;
  2. le résolveur (agrégation → règles + registre) ;
  3. remplir tous les rôles (large transcription du travail zéro-confiance déjà fait) ;
  4. générer + registre d'audit ; puis activer nftables nœud par nœud.

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 backup-01 — 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.