pare-feu Proxmox : le SSH inter-nœud était perdu
Je sautais le flux entier dès qu'un de ses pairs valait `externe`. Or le SSH du socle est déclaré `[flotte, externe]` : la moitié `externe` relève de la frontière, mais la moitié `flotte` — le SSH entre hôtes, celui d'Ansible — était perdue. Sous une politique DROP, plus aucun hôte n'aurait été joignable en SSH depuis l'intérieur. Même piège pour le SMTP interne de Postfix, déclaré `[externe, client_smtp]`. `externe` est sauté pair par pair, jamais le flux entier. 36 groupes, 56 règles. Ajouté : la liste des rôles sans règle entrante, avec leur motif. Onze rôles sont injoignables sous DROP, et c'est voulu dans les onze cas — boucle locale pour Prometheus, Redis, rspamd, Icinga et Unbound ; frontière seule pour nginx ; aucun service pour `serveur_durci` et les clients. Un douzième motif existe, marqué d'un avertissement : « flux entrants déclarés mais aucune source résolue ici » — celui-là serait un vrai trou. Preuves : 25 OK, 0 échec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7b2c9272d7
commit
e9786700aa
3 changed files with 58 additions and 4 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -1,5 +1,26 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-03 (suite 9) — le SSH inter-nœud était perdu
|
||||
|
||||
En éclatant les règles par rôle source, un défaut de ma première version est apparu : je
|
||||
**sautais le flux entier** dès qu'un de ses pairs valait `externe`.
|
||||
|
||||
Or le SSH du socle est déclaré `pair: [flotte, externe]`. La moitié `externe` relève bien de
|
||||
la frontière — mais la moitié **`flotte`**, le SSH entre hôtes, celui d'Ansible, était perdue.
|
||||
Sous une politique `DROP`, **plus aucun hôte n'aurait été joignable en SSH depuis l'intérieur**.
|
||||
Même piège pour le SMTP interne de Postfix, déclaré `[externe, client_smtp]`.
|
||||
|
||||
`externe` est désormais sauté **pair par pair**, jamais le flux entier. 36 groupes, 56 règles.
|
||||
|
||||
### Ajouté — ce qui n'a aucune règle entrante, et pourquoi
|
||||
Onze rôles portés n'ont aucune règle entrante : sous `DROP`, ils sont injoignables. C'est
|
||||
voulu dans les onze cas — la boucle locale pour Prometheus, Redis, rspamd, Icinga et Unbound,
|
||||
la frontière seule pour nginx, aucun service pour `serveur_durci` et les clients.
|
||||
|
||||
Le devis les **nomme avec leur motif** au lieu de laisser un lecteur le vérifier lui-même. Et
|
||||
un douzième motif existe, marqué `/!\` : « flux entrants déclarés mais aucune source résolue
|
||||
ici » — celui-là serait un vrai trou.
|
||||
|
||||
## 2026-08-03 (suite 8) — le pare-feu Proxmox, troisième lecture du même registre
|
||||
|
||||
`make devis-proxmox-fw` (**preuve P25**). Le filtrage est-ouest intra-tenant est désormais
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
| # | Preuve | Affirmations | Statut | Detail |
|
||||
|---|---|---|---|---|
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | |
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | <unknown>:1: SyntaxWarning: invalid decimal literal |
|
||||
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | 4 tests passes. |
|
||||
| P03 | Diff-vide du plan (inventaire genere) | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | DIFF VIDE : le plan reproduit exactement l'inventaire actuel. Bascule possible. |
|
||||
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
| 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-103 | ✅ OK | Underlay conforme : 5 reseau(x), aucune collision avec la plage tenant. |
|
||||
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 26 regles, 2 routes, admin=192.168.254.2/32,192.168.255.0/24,192.168.255.2/32. |
|
||||
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 34 groupe(s), 52 regle(s). |
|
||||
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 36 groupe(s), 56 regle(s). |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -141,8 +141,14 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
if fl.get("sens") != "ingress":
|
||||
continue
|
||||
pairs = _pairs(fl)
|
||||
if "externe" in pairs:
|
||||
continue # la frontiere s'en charge
|
||||
# `externe` est saute PAIR PAR PAIR, jamais le flux entier : le SSH du
|
||||
# socle est declare `[flotte, externe]`, et sauter tout le flux perdait
|
||||
# la moitie `flotte` — plus aucun hote joignable en SSH depuis
|
||||
# l'interieur, Ansible compris. Meme piege pour le SMTP interne de
|
||||
# Postfix, declare `[externe, client_smtp]`.
|
||||
pairs = [p for p in pairs if p != "externe"]
|
||||
if not pairs:
|
||||
continue # purement externe : la frontiere s'en charge
|
||||
if pairs == ["localhost"]:
|
||||
continue # boucle locale : aucune regle inter-noeud
|
||||
if not _resoudre_sources(data, fl.get("pair"), tous, ips):
|
||||
|
|
@ -198,7 +204,29 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
if porte:
|
||||
affect.append({"hote": hote, "vmid": vmid, "groupes": porte})
|
||||
|
||||
# Roles portes mais SANS regle entrante : sous une politique DROP ils sont
|
||||
# injoignables. C'est souvent voulu (ils n'ecoutent que sur la boucle locale, ou
|
||||
# seule la frontiere les atteint) — mais l'absence doit se lire, pas se deviner.
|
||||
avec = {g["role"] for g in groupes}
|
||||
muets: list[dict] = []
|
||||
for groupe in sorted(_enfants(data)):
|
||||
if not est_groupe_operationnel(groupe) or groupe in avec:
|
||||
continue
|
||||
if not _hotes_du_groupe(data, groupe):
|
||||
continue
|
||||
ing = [f for f in flux_par_role.get(groupe, []) if f.get("sens") == "ingress"]
|
||||
if not ing:
|
||||
motif = "aucun service ecoute"
|
||||
elif all(set(_pairs(f)) <= {"localhost"} for f in ing):
|
||||
motif = "n'ecoute que sur la boucle locale"
|
||||
elif all(set(_pairs(f)) <= {"externe"} for f in ing):
|
||||
motif = "joignable seulement par la frontiere"
|
||||
else:
|
||||
motif = "A VERIFIER — flux entrants declares mais aucune source resolue ici"
|
||||
muets.append({"role": groupe, "motif": motif})
|
||||
|
||||
blocs.append({
|
||||
"muets": muets,
|
||||
"tenant": nom, "etiquette": etiquette,
|
||||
"ipsets": {k: v for k, v in ipsets.items() if k in utilises},
|
||||
"groupes": groupes,
|
||||
|
|
@ -251,6 +279,11 @@ def rendre(devis: dict) -> str:
|
|||
for a in b["affectations"]:
|
||||
out.append(f" vmid {a['vmid'] or '(non materialise)'} {a['hote']:<18} "
|
||||
f"-> {', '.join(a['groupes'])}")
|
||||
if b["muets"]:
|
||||
out += ["", "### Sans regle entrante — injoignables sous la politique DROP"]
|
||||
for m in b["muets"]:
|
||||
marque = " /!\\" if m["motif"].startswith("A VERIFIER") else ""
|
||||
out.append(f" {m['role']:<22} {m['motif']}{marque}")
|
||||
if b["sans_source"]:
|
||||
out += ["", "# /!\\ Flux ingress sans source resolue dans ce tenant (aucune regle emise) :"]
|
||||
out += [f"# {x}" for x in b["sans_source"]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue