diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab0c02..32025a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # CHANGELOG — Set-OPS +## 2026-08-02 (suite) — la sortie générale est déclarée, pas subie + +Le devis frontière se terminait par `block out log all` avec **une seule** règle sortante +(le relais SMTP). Appliqué tel quel, il coupait la flotte d'Internet : plus de `apt`, plus +de NTP, plus de récursion DNS. Rien ne le signalait — c'était la ligne la plus lourde de +conséquences du devis, posée à la suite des autres. + +La sortie est désormais **déclarée dans le registre**, donc dérivée comme tout le reste : + +- `serveur_debian` (le socle, porté par les 14 hôtes) — `443/tcp` et `80/tcp` pour les + dépôts apt, `123/udp` pour l'horloge. Une dérive d'horloge fait échouer la validation des + certificats step-ca et le SSO, des semaines après la cause. +- `client_unbound` — `53/udp` et `53/tcp` : `client_unbound_transitaires` est vide, donc + Unbound interroge lui-même la racine. C'est le choix souverain ; il a un coût réseau qu'il + faut déclarer. Le TCP n'est pas optionnel — c'est le repli obligatoire dès qu'une réponse + DNSSEC dépasse la taille UDP. + +Le devis passe de 6 à 11 règles, et sa section 5 **énonce** le default-deny sortant, le +nombre de règles qui l'accompagnent, et où déclarer un besoin oublié — jamais à la main dans +le pare-feu, la règle serait perdue à la génération suivante. + +Vérifié : les nftables d'hôte sont **inchangés**, octet pour octet. Le pair `externe` reste +sauté par `resoudre_flux.py` — ces flux relèvent de la bordure, et d'elle seule. + +Non déclaré volontairement : le rôle `chrony` existe mais n'est référencé par aucun groupe, +aucun playbook ni le graphe de dépendances. Lui écrire un flux aurait créé une règle morte. + ## 2026-08-02 — les interfaces se nomment par leur identifiant `opt1`, `igb1` et `TENANTS` désignent le même port dans OPNsense : l'identifiant interne, le diff --git a/docs/audit/preuve-2026-08-02.md b/docs/audit/preuve-2026-08-02.md index 2b09180..d5e3573 100644 --- a/docs/audit/preuve-2026-08-02.md +++ b/docs/audit/preuve-2026-08-02.md @@ -21,7 +21,7 @@ | P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. | | P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). | | P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. | -| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 63 flux, schéma + matrice OK. | +| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 68 flux, schéma + matrice OK. | | P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). | | P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml | | P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. | @@ -36,7 +36,7 @@ | P21 | Federation : aucun index en collision | AFF-001 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. | | P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (19 sections). | | P23 | Underlay sans collision avec la plage tenant | AFF-001 | ✅ OK | Underlay conforme : 5 reseau(x), aucune collision avec la plage tenant. | -| P24 | Frontiere nord/sud : acces d'administration declare | AFF-001 | ✅ OK | CONFORME : frontiere nord/sud, 6 regles, 2 routes, admin=192.168.255.0/24. | +| P24 | Frontiere nord/sud : acces d'administration declare | AFF-001 | ✅ OK | CONFORME : frontiere nord/sud, 11 regles, 2 routes, admin=192.168.255.0/24. | ## Couverture des affirmations ✅ du registre diff --git a/docs/frontiere-opnsense.md b/docs/frontiere-opnsense.md index 4fcd59d..95dacb8 100644 --- a/docs/frontiere-opnsense.md +++ b/docs/frontiere-opnsense.md @@ -231,7 +231,9 @@ ansible-vault edit instance/inventories/principal/group_vars/all/vault.yml `binardat` n'a pas été confrontée au matériel. À vérifier sur un `show running-config`. - **Les ports physiques restent à nommer** — ``, `` et `` ; rien dans le modèle ne peut les deviner. -- **La sortie générale** (mises à jour apt, ACME) n'est pas déclarée dans le registre : les - hôtes sortent aujourd'hui parce que la politique `output` des nftables est `accept` et que - l'ACL de switch se termine par `permit ip any`. À trancher : la déclarer - explicitement, ou l'assumer comme politique par défaut de la bordure. +- ~~**La sortie générale** n'est pas déclarée~~ — **réglé le 2026-08-02.** Elle est + déclarée dans le registre, donc dérivée comme le reste : `serveur_debian` (le socle, porté + par tous les hôtes) déclare 443, 80 et 123/udp — dépôts apt et horloge ; `client_unbound` + déclare 53 en UDP et TCP, la récursion depuis la racine que le choix souverain implique. + Le `block out` de la section 5 est donc un vrai default-deny **assumé**, et le devis + l'énonce désormais au lieu de le poser en silence. diff --git a/docs/registre-flux.md b/docs/registre-flux.md index 9f53117..65cf5ce 100644 --- a/docs/registre-flux.md +++ b/docs/registre-flux.md @@ -13,10 +13,15 @@ | `client_smtp` | egress | 25 | tcp | serveur_postfix | starttls | Relais des notifications locales vers le MTA central (Postfix), STARTTLS. | | `client_unbound` | ingress | 53 | udp | localhost | clair | Résolveur local sur boucle locale (les processus du nœud interrogent 127.0.0.1). | | `client_unbound` | egress | 53 | tcp | serveur_powerdns | clair | Transfert des requêtes de la zone souveraine vers le DNS autoritatif interne (PowerDNS). | +| `client_unbound` | egress | 53 | udp | externe | clair | Récursion DNS depuis la racine (UDP d'abord), validée par DNSSEC — la confidentialité du transport n'est pas l'enjeu, l'authenticité l'est. | +| `client_unbound` | egress | 53 | tcp | externe | clair | Récursion DNS en TCP : repli obligatoire quand la réponse dépasse la taille UDP (fréquent avec DNSSEC). | | `serveur_backup` | ingress | 22 | tcp | client_backup | ssh | Dépôt restic servi par SSH (utilisateur restreint restic + clé) ; chaque client_backup pousse ses instantanés. | | `serveur_collabora` | ingress | 9980 | tcp | edge | clair | Éditeur servi au navigateur via l'edge (WebSocket WOPI ; TLS terminé à l'edge). | | `serveur_collabora` | ingress | 9980 | tcp | localhost | clair | Vérifications WOPI serveur→Collabora depuis Nextcloud co-localisé. | | `serveur_debian` | ingress | 22 | tcp | flotte, externe | ssh | Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). | +| `serveur_debian` | egress | 80 | tcp | externe | clair | Dépôts apt en clair et redirections HTTP des miroirs (l'intégrité vient de la signature des paquets, pas du transport). | +| `serveur_debian` | egress | 123 | udp | externe | n-a | Synchronisation d'horloge (NTP). Une dérive fait échouer la validation des certificats step-ca et l'authentification SSO. | +| `serveur_debian` | egress | 443 | tcp | externe | tls-requis | Dépôts apt en HTTPS (Debian, Smallstep, Grafana, Icinga, Forgejo, Nextcloud) — sans quoi aucun correctif de sécurité n'entre. | | `serveur_dovecot` | ingress | 24 | tcp | serveur_postfix | tls-requis | Remise LMTP depuis Postfix (edge-mta -> mailstore), en TLS vérifié (lmtp_tls_security_level=verify). | | `serveur_dovecot` | ingress | 993 | tcp | externe | tls-requis | Accès courriel des utilisateurs (IMAPS). Frontière publique gérée à l'OPNsense. | | `serveur_dovecot` | ingress | 12345 | tcp | serveur_postfix | tls | Authentification SASL déléguée : Postfix valide les identifiants de soumission contre Dovecot. | @@ -72,9 +77,10 @@ ## Synthèse chiffrement -- **clair** : 23 flux +- **clair** : 26 flux +- **n-a** : 1 flux - **ssh** : 3 flux - **starttls** : 6 flux - **tls** : 8 flux - **tls-cible** : 2 flux -- **tls-requis** : 21 flux +- **tls-requis** : 22 flux diff --git a/roles/client_unbound/meta/flux.yml b/roles/client_unbound/meta/flux.yml index 62c2cce..3983d0c 100644 --- a/roles/client_unbound/meta/flux.yml +++ b/roles/client_unbound/meta/flux.yml @@ -13,3 +13,20 @@ flux: pair: serveur_powerdns chiffrement: clair raison: "Transfert des requêtes de la zone souveraine vers le DNS autoritatif interne (PowerDNS)." + + # Récursion sortante : `client_unbound_transitaires` est vide par défaut, donc Unbound + # interroge lui-même la racine puis les autoritatifs — c'est le choix souverain, sans + # dépendre du résolveur d'un fournisseur. Renseigner des transitaires rendrait ces deux + # flux inutiles, mais les laisser déclarés ne coûte rien et évite une panne muette. + - sens: egress + port: 53 + protocole: udp + pair: externe + chiffrement: clair + raison: "Récursion DNS depuis la racine (UDP d'abord), validée par DNSSEC — la confidentialité du transport n'est pas l'enjeu, l'authenticité l'est." + - sens: egress + port: 53 + protocole: tcp + pair: externe + chiffrement: clair + raison: "Récursion DNS en TCP : repli obligatoire quand la réponse dépasse la taille UDP (fréquent avec DNSSEC)." diff --git a/roles/serveur_debian/meta/flux.yml b/roles/serveur_debian/meta/flux.yml index b4504f8..0e28490 100644 --- a/roles/serveur_debian/meta/flux.yml +++ b/roles/serveur_debian/meta/flux.yml @@ -8,3 +8,26 @@ flux: pair: [flotte, externe] chiffrement: ssh raison: "Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense)." + + # --- Sortie générale de la flotte --- + # Portée par le socle parce qu'elle vaut pour TOUS les hôtes, sans exception. + # Non déclarée, la frontière la bloquerait : son `block out` final est un vrai + # default-deny, et la flotte perdrait ses mises à jour et son horloge en silence. + - sens: egress + port: 443 + protocole: tcp + pair: externe + chiffrement: tls-requis + raison: "Dépôts apt en HTTPS (Debian, Smallstep, Grafana, Icinga, Forgejo, Nextcloud) — sans quoi aucun correctif de sécurité n'entre." + - sens: egress + port: 80 + protocole: tcp + pair: externe + chiffrement: clair + raison: "Dépôts apt en clair et redirections HTTP des miroirs (l'intégrité vient de la signature des paquets, pas du transport)." + - sens: egress + port: 123 + protocole: udp + pair: externe + chiffrement: n-a + raison: "Synchronisation d'horloge (NTP). Une dérive fait échouer la validation des certificats step-ca et l'authentification SSO." diff --git a/scripts/devis_opnsense.py b/scripts/devis_opnsense.py index cd0b5f3..7d61574 100644 --- a/scripts/devis_opnsense.py +++ b/scripts/devis_opnsense.py @@ -299,9 +299,18 @@ def rendre(devis: dict) -> str: f"# {rg['role']} ({rg['chiffrement']})" ) out.append(f"# {rg['raison']}") + sortants = sum(1 for r in devis["regles"] if r["sens"] == "out") out += [ "", - "## 5. Defaut", + "## 5. Defaut — DENY DANS LES DEUX SENS", + "# `block out` est un vrai default-deny : ce qui n'est pas declare en section 4", + f"# ne sort PAS. {sortants} regle(s) sortante(s) y figurent, toutes derivees du", + "# registre des flux (`sens: egress`, `pair: externe`).", + "#", + "# Un besoin sortant oublie ne se manifeste pas par un refus clair : apt expire,", + "# l'horloge derive et les certificats deviennent invalides des semaines plus tard.", + "# Le declarer dans `roles//meta/flux.yml` — ne jamais l'ouvrir ici a la main,", + "# la regle serait perdue a la prochaine generation.", "block in log all # tout ce qui n'est pas ci-dessus", "block out log all", ]