pare-feu Proxmox : le filtrage est-ouest intra-tenant, dérivé (P25)
`make devis-proxmox-fw` : 34 groupes de sécurité, 40 règles, 2 tenants — depuis les 57 flux intra-tenant que le registre connaissait déjà. Défense en profondeur, pas remplacement : l'hyperviseur filtre puis l'hôte destinataire filtre à nouveau. Coût de maintenance nul, les deux barrières lisent le registre par les MÊMES fonctions — la duplication est dans l'application, jamais dans la décision. Un IPSet par rôle porte les membres, les groupes y renvoient : ajouter un hôte à un rôle met à jour toutes les règles qui l'autorisent, en un endroit. Garde ajoutée après coup : Proxmox limite un nom de groupe à 18 caractères. Ma première version tronquait sans vérifier — deux rôles tronqués au même nom auraient fusionné leurs règles, donnant à une VM les autorisations d'un rôle qu'elle ne porte pas, silencieusement. Le préfixe porte maintenant l'index plutôt que l'étiquette, et une garde échoue sur toute collision. Exercée. Conséquence consignée : tout ce qui entre dans un tenant passant par la frontière, le contrôleur Ansible aussi — l'OPNsense devient un prérequis de déploiement, pas une étape parmi d'autres. Preuves : 25 OK, 0 échec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
89dc5ed3d4
commit
fd62b59989
8 changed files with 298 additions and 2 deletions
29
CHANGELOG.md
29
CHANGELOG.md
|
|
@ -1,5 +1,34 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 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
|
||||
dérivé pour l'hyperviseur : **34 groupes de sécurité, 40 règles, 2 tenants** — depuis les
|
||||
57 flux intra-tenant que le registre connaissait déjà.
|
||||
|
||||
Décidé : la défense est **en profondeur**, pas en remplacement. L'hyperviseur filtre, puis
|
||||
l'hôte destinataire filtre à nouveau. Une VM compromise doit franchir les deux. Le coût de
|
||||
maintenance est nul : les deux barrières lisent le registre **par les mêmes fonctions**
|
||||
(`_resoudre_sources`, `_pairs`, `_hotes_du_groupe`) — la duplication est dans l'application,
|
||||
jamais dans la décision.
|
||||
|
||||
Un IPSet par rôle porte les membres, les groupes de sécurité y renvoient : ajouter un hôte à
|
||||
un rôle met à jour toutes les règles qui l'autorisent, en un seul endroit.
|
||||
|
||||
### La garde qui manquait à ma première version
|
||||
Proxmox limite un nom de groupe à **18 caractères**. Ma première version tronquait sans
|
||||
vérifier : deux rôles tronqués au même nom auraient **fusionné leurs règles**, donnant à une VM
|
||||
les autorisations d'un rôle qu'elle ne porte pas — silencieusement.
|
||||
|
||||
Le préfixe porte maintenant l'**index** (`t17-`) plutôt que l'étiquette (`chez17-`), ce qui
|
||||
rend la troncature bien plus rare, et une garde **échoue** sur toute collision plutôt que
|
||||
d'émettre un devis pareil. Exercée.
|
||||
|
||||
### Conséquence à retenir
|
||||
Puisque **tout** ce qui entre dans un tenant passe par la frontière, le contrôleur Ansible
|
||||
aussi. **L'OPNsense devient un prérequis de déploiement**, pas une étape parmi d'autres :
|
||||
sans lui, plus rien ne se déploie.
|
||||
|
||||
## 2026-08-03 (suite 7) — le modèle déclarait un MTU que le matériel n'a pas
|
||||
|
||||
En préparant le déplacement des adresses de VTEP, la lecture des interfaces a montré deux
|
||||
|
|
|
|||
7
Makefile
7
Makefile
|
|
@ -348,6 +348,13 @@ devis-reseau: ansible-runtime ## Devis switch (VLANs/SVIs/ACLs) du reseau conver
|
|||
devis-opnsense: ansible-runtime ## Devis OPNsense (frontiere nord/sud), derive du registre des flux (pair: externe)
|
||||
python3 scripts/devis_opnsense.py $(if $(JSON),--json,)
|
||||
|
||||
.PHONY: devis-proxmox-fw devis-proxmox-fw-verifier
|
||||
devis-proxmox-fw: ansible-runtime ## Devis pare-feu Proxmox (est-ouest intra-tenant), derive du registre des flux
|
||||
python3 scripts/devis_proxmox_fw.py $(if $(JSON),--json,)
|
||||
|
||||
devis-proxmox-fw-verifier:
|
||||
python3 scripts/devis_proxmox_fw.py --verifier
|
||||
|
||||
devis-opnsense-verifier:
|
||||
python3 scripts/devis_opnsense.py --verifier
|
||||
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ Légende du statut : ✅ prouvée · 🟡 partielle · ❌ fausse · ⚪ invéri
|
|||
| AFF-103 | « L'underlay ne chevauche aucun supernet tenant et reste sous la plage VLAN tenant » (`underlay.yml.example` ; wiki) | structurelle | **P23** (`scripts/underlay.py --verifier`) | ✅ | Sauté si aucun `underlay.yml` monté (dépôt public nu). |
|
||||
| AFF-104 | « La frontière déclare toujours une source d'administration ; un devis dont `nftables_admin_ssh` est vide est refusé » (`docs/frontiere-opnsense.md` §5) | fonctionnelle | **P24** (`scripts/devis_opnsense.py --verifier`) | ✅ | Garde anti-lockout : sans cet intrant, la règle SSH n'aurait aucune source et le `block` final fermerait l'accès. |
|
||||
| AFF-105 | « Les modèles publics valident, underlay compris » (`exemples/modeles/socle/README.md`) | structurelle | **P17** (`scripts/modeles.py verifier`) | ✅ | Underlay facultatif ; validé pour sa cohérence **interne** seulement — un modèle est un gabarit, pas un site déployé. |
|
||||
| AFF-107 | « Le filtrage est-ouest intra-tenant est dérivé du registre des flux, et redit la même politique que les nftables d'hôte » (`docs/sdn-evpn.md` §3) | structurelle | **P25** (`scripts/devis_proxmox_fw.py --verifier`) | ✅ | 34 groupes, 40 règles sur 2 tenants. Les deux barrières lisent le registre par les **mêmes fonctions** : elles ne peuvent pas diverger. |
|
||||
| AFF-106 | « Le panneau couvre le schéma du plan : aucun champ n'oblige à éditer le YAML » (`docs/intrants-base-gui-conception.md`) | fonctionnelle | **P19** (`scripts/couverture_gui.py`) | 🟡 | Vrai des registres du plan. **Exceptions assumées** : `reseaux` et `hotes` de l'underlay (listes de tables) restent hors du panneau ; les ports physiques et le nœud de sortie EVPN se nomment à la main. |
|
||||
|
||||
**Non enregistré volontairement.** Les devis eux-mêmes (`make devis-reseau`,
|
||||
|
|
@ -391,6 +392,7 @@ Chaque affirmation ✅ est soit **rejouée** par une preuve de `make prouver`
|
|||
| AFF-104 | **P24** (frontière : accès d'administration déclaré) |
|
||||
| AFF-105 | **P17** (tous les modèles valident, underlay compris) |
|
||||
| AFF-106 | **P19** (le GUI couvre le schéma du plan) |
|
||||
| AFF-107 | **P25** (pare-feu Proxmox est-ouest) |
|
||||
|
||||
**Structurelles/doctrinales, hors harnais récurrent** (vérifiées à l'audit) : AFF-005
|
||||
(`make`=aide), AFF-014 (ciblage `groupe:&hotes_actifs`), AFF-024 (`instancier-appliquer`,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (24 OK · 0 echec · 0 saute)
|
||||
- **Verdict** : ✅ CONFORME (25 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -37,6 +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), 40 regle(s). |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ sont les seules vérifiables.
|
|||
| **D-07** | **Pas d'ACL** sur cette fabric (`acl_inter_tenant: false`) | le matériel ne sait pas lier une ACL à un SVI ; des règles jamais liées auraient l'air d'isoler | `frontiere-opnsense.md` §1 | — |
|
||||
| **D-08** | Le **routage tenant passe au SDN EVPN** — une zone par tenant | une zone EVPN est un VRF : l'isolation devient structurelle, pas réglementaire | `sdn-evpn.md` | P23 (MTU) |
|
||||
| **D-09** | Le **filtrage inter-zone d'un tenant** se fait au même niveau (EVPN) | là où le routage a lieu | `sdn-evpn.md` §3 | — |
|
||||
| **D-31** | Le filtrage est-ouest est appliqué **deux fois** : hyperviseur **puis** nftables d'hôte | défense en profondeur ; les deux dérivent du même registre par les mêmes fonctions, donc ne peuvent pas diverger | `sdn-evpn.md` §3 | P25 |
|
||||
| **D-32** | **Tout** ce qui entre ou sort d'un tenant passe par la frontière | un VRF n'a qu'une sortie ; conséquence : la frontière devient un **prérequis de déploiement** | `sdn-evpn.md` §3 | — |
|
||||
| **D-10** | L'**inter-tenant passe obligatoirement par la frontière** | il sort du VRF, donc traverse une bordure en `block` par défaut : il ne peut plus être oublié | `sdn-evpn.md` §3 | — |
|
||||
| **D-11** | La **sortie générale est déclarée** dans le registre des flux | `block out` est un vrai default-deny ; un besoin oublié ne se manifeste pas par un refus clair | `frontiere-opnsense.md` §8 | P09 |
|
||||
| **D-29** | L'**overlay EVPN plafonne à 1450** ; le transport doit donc dépasser 1500 | choix d'exploitation ; l'encapsulation VXLAN coûte 50 octets | `sdn-evpn.md` §5 | P23 (MTU du transport) |
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ plafond de 245 tenants reste, sa cause change.
|
|||
| **entre zones d'un même tenant** | zone EVPN du tenant (VRF, sur les hyperviseurs) | **au même endroit** |
|
||||
| **entre tenants** | sort du VRF → nœud de sortie → **OPNsense** | la frontière, et elle seule |
|
||||
| **vers l'extérieur** | idem | la frontière |
|
||||
| **de service à service, sur un hôte** | — | nftables d'hôte, `policy drop` |
|
||||
| **de service à service, sur un hôte** | — | **deux barrières** : pare-feu Proxmox (`make devis-proxmox-fw`) **puis** nftables d'hôte (`make flux`) |
|
||||
|
||||
Deux conséquences qui méritent d'être dites.
|
||||
|
||||
|
|
@ -59,6 +59,11 @@ sortir du VRF, donc traverser la bordure, qui est en `block` par défaut. Un flu
|
|||
légitime devra être **déclaré** pour exister — le registre des flux n'a pas encore de mot-clé
|
||||
pour ça, c'est un point ouvert.
|
||||
|
||||
**La défense est en profondeur, sans coût de maintenance.** Le filtrage est-ouest est appliqué
|
||||
deux fois : par l'hyperviseur, puis par l'hôte destinataire. Une VM compromise doit franchir
|
||||
les deux. Et comme les deux **dérivent du même registre par les mêmes fonctions**, elles ne
|
||||
peuvent pas se contredire — la duplication est dans l'application, jamais dans la décision.
|
||||
|
||||
**Le commutateur ne voit plus rien du trafic tenant.** Il transporte du VXLAN qu'il ne lit
|
||||
pas. Y chercher une trace d'un problème applicatif serait perdre son temps : le miroir utile
|
||||
est sur l'hyperviseur.
|
||||
|
|
|
|||
248
scripts/devis_proxmox_fw.py
Normal file
248
scripts/devis_proxmox_fw.py
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Devis de pare-feu Proxmox — le filtrage EST-OUEST intra-tenant.
|
||||
|
||||
DERIVE du meme registre des flux que les nftables d'hote et que la frontiere. Les trois
|
||||
lisent `roles/*/meta/flux.yml` par les MEMES fonctions : ils ne peuvent pas diverger.
|
||||
|
||||
PARTAGE DES ROLES (cf. docs/sdn-evpn.md) :
|
||||
- ce devis : est-ouest INTRA-tenant, applique par l'hyperviseur ;
|
||||
- `make flux` : la meme politique, rejouee sur l'hote lui-meme (defense en
|
||||
profondeur — une VM compromise franchit deux barrieres) ;
|
||||
- `make devis-opnsense` : tout ce qui ENTRE ou SORT d'un tenant.
|
||||
|
||||
Les flux `pair: externe` sont donc SAUTES ici : ils relevent de la frontiere.
|
||||
|
||||
NON destructif : ce script n'ecrit rien sur le cluster. Il produit un devis a relire,
|
||||
puis (a venir) un JSON pour l'API Proxmox.
|
||||
|
||||
Usage :
|
||||
python3 scripts/devis_proxmox_fw.py # devis lisible
|
||||
python3 scripts/devis_proxmox_fw.py --json # meme contenu, pour l'API
|
||||
python3 scripts/devis_proxmox_fw.py --verifier # garde : aucun role sans source
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
from resoudre_flux import ( # noqa: E402
|
||||
charger_flux,
|
||||
est_groupe_operationnel,
|
||||
_enfants,
|
||||
_hotes_du_groupe,
|
||||
_ip_par_hote,
|
||||
_pairs,
|
||||
_resoudre_sources,
|
||||
)
|
||||
from devis_reseau import admin_de, decouvrir, inventaire_de, prefixe # noqa: E402
|
||||
|
||||
|
||||
def nom_ipset(role: str, etiquette: str) -> str:
|
||||
return f"{etiquette}_{role}".lower()
|
||||
|
||||
|
||||
LONGUEUR_MAX_GROUPE = 18 # limite de Proxmox pour un nom de groupe de securite
|
||||
|
||||
|
||||
def nom_groupe(role: str, index: int) -> str:
|
||||
"""Nom de groupe de securite, sous la limite de 18 caracteres de Proxmox.
|
||||
|
||||
Le prefixe porte l'INDEX du tenant plutot que son etiquette : `t17-` au lieu de
|
||||
`chez17-`, ce qui laisse trois caracteres de plus au role et rend la troncature
|
||||
beaucoup plus rare. Les groupes sont a l'echelle du datacenter, d'ou le prefixe.
|
||||
"""
|
||||
court = role.replace("serveur_", "srv-").replace("client_", "cli-").replace("_", "-")
|
||||
return f"t{index}-{court}".lower()[:LONGUEUR_MAX_GROUPE]
|
||||
|
||||
|
||||
def _verifier_unicite(noms: list[str], tenant: str) -> None:
|
||||
"""Une troncature qui fait collisionner deux roles FUSIONNERAIT leurs regles.
|
||||
|
||||
Silencieusement, et dans le mauvais sens : une VM recevrait les autorisations d'un
|
||||
role qu'elle ne porte pas. On echoue plutot que d'emettre un devis pareil.
|
||||
"""
|
||||
vus: dict[str, str] = {}
|
||||
for role, nom in noms:
|
||||
if nom in vus:
|
||||
raise SystemExit(
|
||||
f"erreur: {tenant} — les roles '{vus[nom]}' et '{role}' donnent le meme "
|
||||
f"groupe '{nom}' apres troncature a {LONGUEUR_MAX_GROUPE} caracteres. "
|
||||
f"Leurs regles fusionneraient. Renommer un role ou raccourcir le prefixe.")
|
||||
vus[nom] = role
|
||||
|
||||
|
||||
def _ports(fl: dict) -> list[str]:
|
||||
p = fl["port"]
|
||||
return [str(x) for x in (p if isinstance(p, list) else [p])]
|
||||
|
||||
|
||||
def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||
"""Le devis, en structure. Un bloc par tenant federe."""
|
||||
flux_par_role = charger_flux()
|
||||
blocs: list[dict] = []
|
||||
|
||||
for nom, pfx, n in tenants:
|
||||
inv = inventaire_de(nom)
|
||||
if inv is None:
|
||||
continue
|
||||
etiquette = f"{pfx}{n['index']}"
|
||||
data = yaml.safe_load(inv.read_text(encoding="utf-8")) or {}
|
||||
# Les PLANIFIES comptent : le pare-feu se prepare avant que la VM existe,
|
||||
# sinon le devis changerait a chaque materialisation d'hote.
|
||||
tous = set(_ip_par_hote(data))
|
||||
ips = _ip_par_hote(data)
|
||||
|
||||
# Un IPSet par role porte dans ce tenant : les membres sont ses hotes.
|
||||
ipsets: dict[str, dict] = {}
|
||||
for groupe in sorted(_enfants(data)):
|
||||
if not est_groupe_operationnel(groupe):
|
||||
continue
|
||||
membres = sorted({ips[h] for h in _hotes_du_groupe(data, groupe) if h in ips})
|
||||
if membres:
|
||||
ipsets[nom_ipset(groupe, etiquette)] = {
|
||||
"role": groupe, "membres": membres,
|
||||
}
|
||||
|
||||
groupes: list[dict] = []
|
||||
sans_source: list[str] = []
|
||||
for groupe in sorted(_enfants(data)):
|
||||
if not est_groupe_operationnel(groupe):
|
||||
continue
|
||||
if not _hotes_du_groupe(data, groupe):
|
||||
continue
|
||||
regles: list[dict] = []
|
||||
for fl in flux_par_role.get(groupe, []):
|
||||
if fl.get("sens") != "ingress":
|
||||
continue
|
||||
pairs = _pairs(fl)
|
||||
if "externe" in pairs:
|
||||
continue # la frontiere s'en charge
|
||||
if pairs == ["localhost"]:
|
||||
continue # boucle locale : aucune regle inter-noeud
|
||||
srcs = _resoudre_sources(data, fl.get("pair"), tous, ips)
|
||||
if not srcs:
|
||||
sans_source.append(f"{groupe}:{','.join(_ports(fl))}")
|
||||
continue
|
||||
# Source par IPSet quand le pair est UN role : une seule liste a tenir.
|
||||
source = (f"+{nom_ipset(pairs[0], etiquette)}"
|
||||
if len(pairs) == 1 and nom_ipset(pairs[0], etiquette) in ipsets
|
||||
else ",".join(srcs))
|
||||
for port in _ports(fl):
|
||||
regles.append({
|
||||
"action": "ACCEPT", "sens": "IN",
|
||||
"source": source,
|
||||
"proto": fl.get("protocole", "tcp"),
|
||||
"dport": port,
|
||||
"raison": fl.get("raison", ""),
|
||||
})
|
||||
if regles:
|
||||
groupes.append({
|
||||
"nom": nom_groupe(groupe, n["index"]), "role": groupe, "regles": regles,
|
||||
})
|
||||
_verifier_unicite([(g["role"], g["nom"]) for g in groupes], nom)
|
||||
|
||||
# Affectation : quelle VM recoit quels groupes.
|
||||
affect: list[dict] = []
|
||||
par_nom_groupe = {g["role"]: g["nom"] for g in groupes}
|
||||
for hote in sorted(_hotes_du_groupe(data, "hotes_actifs")
|
||||
or [h for h in ips]):
|
||||
vmid = None
|
||||
for g, membres in _enfants(data).items():
|
||||
h = (membres.get("hosts") or {}).get(hote) or {}
|
||||
if h.get("proxmox_vmid"):
|
||||
vmid = h["proxmox_vmid"]
|
||||
break
|
||||
porte = [par_nom_groupe[g] for g in sorted(_enfants(data))
|
||||
if hote in (_enfants(data)[g].get("hosts") or {}) and g in par_nom_groupe]
|
||||
if porte:
|
||||
affect.append({"hote": hote, "vmid": vmid, "groupes": porte})
|
||||
|
||||
blocs.append({
|
||||
"tenant": nom, "etiquette": etiquette,
|
||||
"ipsets": ipsets, "groupes": groupes,
|
||||
"affectations": affect, "sans_source": sorted(set(sans_source)),
|
||||
"admin": admin_de(nom),
|
||||
})
|
||||
|
||||
return {"blocs": blocs}
|
||||
|
||||
|
||||
def rendre(devis: dict) -> str:
|
||||
out = [
|
||||
"# ============================================================",
|
||||
"# DEVIS PARE-FEU PROXMOX — est-ouest INTRA-tenant",
|
||||
"# Genere par scripts/devis_proxmox_fw.py. NE PAS EDITER A LA MAIN.",
|
||||
"# Derive du MEME registre des flux que les nftables d'hote : les deux",
|
||||
"# barrieres disent la meme chose, et ne peuvent pas diverger.",
|
||||
"#",
|
||||
"# Ce qui ENTRE ou SORT d'un tenant n'est PAS ici : c'est la frontiere",
|
||||
"# (`make devis-opnsense`). Les flux `pair: externe` sont sautes.",
|
||||
"# ============================================================",
|
||||
"",
|
||||
"## 0. Prerequis, au niveau du datacenter",
|
||||
"# Pare-feu active, politique d'entree DROP. Sans cela, ces regles",
|
||||
"# s'ajoutent a un tout-permis et n'interdisent rien.",
|
||||
"",
|
||||
]
|
||||
for b in devis["blocs"]:
|
||||
out += [f"## Tenant {b['tenant']} ({b['etiquette']})", ""]
|
||||
out.append(f"### IPSets — {len(b['ipsets'])} (les membres d'un role, en un seul endroit)")
|
||||
for nom, s in sorted(b["ipsets"].items()):
|
||||
out.append(f"ipset {nom} # {s['role']}")
|
||||
for ip in s["membres"]:
|
||||
out.append(f" {ip}")
|
||||
out += ["", f"### Groupes de securite — {len(b['groupes'])} (les flux entrants d'un role)"]
|
||||
for g in b["groupes"]:
|
||||
out.append(f"securitygroup {g['nom']} # {g['role']}")
|
||||
for r in g["regles"]:
|
||||
out.append(f" {r['sens']} {r['action']} -source {r['source']} "
|
||||
f"-p {r['proto']} -dport {r['dport']}")
|
||||
if r["raison"]:
|
||||
out.append(f" # {r['raison']}")
|
||||
out += ["", f"### Affectation aux VM — {len(b['affectations'])}"]
|
||||
for a in b["affectations"]:
|
||||
out.append(f" vmid {a['vmid'] or '(non materialise)'} {a['hote']:<18} "
|
||||
f"-> {', '.join(a['groupes'])}")
|
||||
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"]]
|
||||
out.append("")
|
||||
out += [
|
||||
"## Defaut",
|
||||
"# Politique d'entree DROP au datacenter : ce qui n'est pas ci-dessus",
|
||||
"# n'entre pas. Un flux oublie se declare dans `roles/<role>/meta/flux.yml`,",
|
||||
"# jamais a la main dans Proxmox — la regle serait perdue a la generation.",
|
||||
]
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--json", action="store_true")
|
||||
ap.add_argument("--verifier", action="store_true")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
devis = construire(decouvrir())
|
||||
if args.verifier:
|
||||
total_g = sum(len(b["groupes"]) for b in devis["blocs"])
|
||||
total_r = sum(len(g["regles"]) for b in devis["blocs"] for g in b["groupes"])
|
||||
if not total_g:
|
||||
print("erreur: aucun groupe de securite derive — le registre ou l'inventaire "
|
||||
"est vide.", file=sys.stderr)
|
||||
return 2
|
||||
print(f"CONFORME : pare-feu Proxmox, {len(devis['blocs'])} tenant(s), "
|
||||
f"{total_g} groupe(s), {total_r} regle(s).")
|
||||
return 0
|
||||
print(json.dumps(devis, indent=2, ensure_ascii=False) if args.json else rendre(devis))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
@ -292,6 +292,8 @@ PREUVES: list[dict] = [
|
|||
"cmds": [[sys.executable, "scripts/plan_recette.py", "--verifier"]]},
|
||||
{"id": "P23", "titre": "Underlay sans collision avec la plage tenant", "refs": ["AFF-103"],
|
||||
"cmds": [[sys.executable, "scripts/underlay.py", "--verifier"]]},
|
||||
{"id": "P25", "titre": "Pare-feu Proxmox : est-ouest intra-tenant derive", "refs": ["AFF-107"],
|
||||
"cmds": [[sys.executable, "scripts/devis_proxmox_fw.py", "--verifier"]]},
|
||||
{"id": "P24", "titre": "Frontiere nord/sud : acces d'administration declare", "refs": ["AFF-104"],
|
||||
"cmds": [[sys.executable, "scripts/devis_opnsense.py", "--verifier"]]},
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue