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

133 lines
7.8 KiB
Markdown

# 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`
```yaml
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.