2026-07-05 09:41:42 -04:00
|
|
|
# Registre des flux réseau — conception
|
|
|
|
|
|
preuve : P34 — chaque document declare son lecteur (D-74)
La refonte de ce matin posait une convention. Une convention qu'on n'outille
pas tient tant que quelqu'un y pense : c'est le raisonnement de D-70, applique
au corpus documentaire.
Etat de depart mesure : 2 documents sur 34 declaraient leur lecteur. Les 32
autres disaient leur SUJET — ce qui avait enfoui le runbook de reprise le plus
utile du depot au §6 de autorisation.md.
Les 38 le declarent desormais, lecteur determine document par document et non
colle au gabarit : l'exploitant (devis, migration de tenant, cycle de vie,
gabarit d'or), le mainteneur (conceptions, registres, carte), le lecteur
externe (ecosysteme-chezlepro), l'agent IA (MISE-A-JOUR-CODEX-CLAUDE).
Deux exemptions DERIVEES, pas listees — un chemin en dur aurait vieilli a la
premiere page ajoutee : un document qui s'annonce genere, et un fragment sans
titre. Les 13 exemptes verifies un par un ; aucun document ecrit a la main
n'est exempte par accident. La preuve ne lit que l'EN-TETE, ce qui empeche
frontiere-opnsense.md et plan-et-generation.md — qui parlent de generation
dans leur corps — d'etre exemptes a tort.
Eprouvee dans les deux sens. Elle a echoue seule des sa premiere execution en
nommant deux documents que mon inventaire avait manques (docs/audit/). Puis
test negatif delibere : declaration retiree de meta-classe.md -> ECHEC la
nommant ; restauree -> OK.
Ce qu'elle ne teste pas : que le lecteur declare soit le BON. Ca se juge en
revue ; elle garantit qu'on a du y penser.
P01–P34. Comptes perimes corriges au passage (AGENTS.md et devis-services.md
annoncaient encore 30 preuves).
Verifie : prouver.py 0 (34 OK), plan-recette inchange.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 07:53:04 -04:00
|
|
|
> **Pour qui :** le **mainteneur** qui déclare un flux réseau dans `roles/*/meta/flux.yml`.
|
|
|
|
|
|
2026-07-05 09:41:42 -04:00
|
|
|
## 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 »
|
2026-07-07 03:08:09 -04:00
|
|
|
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
|
2026-07-05 09:41:42 -04:00
|
|
|
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.
|
2026-08-09 09:46:17 -04:00
|
|
|
|
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
|
|
|
## `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.
|
|
|
|
|
|
2026-08-09 09:46:17 -04:00
|
|
|
## `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.
|
|
|
|
|
|