proxmox : le pare-feu ne s'arme que dans le SDN — et 45e preuve
Some checks are pending
verifier / verifier (push) Waiting to run

Le decoupage du site en quatre zones a revele un defaut qui dormait dans le
moteur. `firewall=1` sur l'interface d'une VM fait passer tout le trafic ponte
par conntrack. Sur un VNet SDN c'est sans consequence : en EVPN le routage
inter-VNet se fait dans le VRF, SUR LE NOEUD, et le flux ne quitte jamais
l'hyperviseur. Sur un pont classique route par une frontiere externe, deux VM du
MEME noeud dans deux VLAN differents ne se parlent qu'en EPINGLE : la trame sort
par le lien physique, la frontiere la route, elle revient sur le meme pont. La
meme table conntrack voit alors les deux moities de la connexion, classe le
retour INVALID, et PVEFW-FORWARD le jette.

La mesure a tranche :

  ops(asgard)  -> pki(asgard)     0/8    dns(gandalf) -> cache(gandalf)  0/8
  ops(asgard)  -> forge(vishnu)   6/8    dns(gandalf) -> pki(asgard)     6/8
  ops(asgard)  -> cache(gandalf)  8/8    dns(gandalf) -> forge(vishnu)   8/8

Toutes les paires intra-noeud echouent, toutes les paires inter-noeuds passent.
Douze tentatives faisaient monter le compteur `ctstate INVALID` de +112 sur
asgard et +116 sur gandalf ; `firewall=0` pose, il ne bouge plus — 0 sur les
deux, pour le meme trafic.

Le defaut se deguise en panne reseau : la poignee TCP ABOUTIT, et ce sont les
paquets de DONNEES qui disparaissent. L'AC le disait dans son propre journal
(`TLS handshake error ... i/o timeout` : elle accepte et attend un ClientHello
qui n'arrive jamais). Ecartes un par un, par la mesure : regles identiques champ
par champ, alias corrects, assignation des interfaces confirmee, ARP et routes
saines, IPS desactive, shaper vide, NAT source limite a `wan`, MTU a 1500 de
bout en bout — et la taille sans effet, 100 octets se perdant comme 1460.

L'INTENTION DECLAREE NE SUFFIT PAS : un tenant declare
`proxmox_clone_parefeu_interface: true` avec `proxmox_clone_pont: vmbr1` comme
valeur PAR DEFAUT, chaque hote la remplacant par son VNet derive. L'hote qui
retombe sur `vmbr1` naitrait arme sur un pont classique. `cloner_vm_debian.yml`
croise donc l'intention avec le pont REELLEMENT utilise, et le dit quand il
desarme — un desarmement muet serait le meme piege, en silence.

P45 EVALUE l'expression du playbook sur quatre cas plutot que d'en lire le
texte, dont un qui DOIT rendre vrai : sans lui, une expression constamment
fausse passerait la preuve sans rien garantir. Controle negatif verifie — le
drapeau remis a plat fait echouer la preuve.

Ce commit emporte aussi le desarmement d'`unattended-upgrades` dans
`common_packages`, jusqu'ici applique sur les machines mais pas versionne : le
verrou dpkg tenu 48 minutes par une vague de maj automatiques a coute deux
deploiements.

Le site a revele ce defaut parce qu'il a ete le premier a porter plusieurs
zones. Ce n'est pas une particularite du site : un tenant derive les siennes du
meme principe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-25 19:50:53 -04:00
parent 1a7c042e5b
commit ac48b7650b
6 changed files with 216 additions and 5 deletions

View file

@ -1,5 +1,54 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-08-25 — Le pare-feu Proxmox ne s'arme que dans le SDN
**45 preuves.** Le découpage du site en quatre zones a révélé un défaut qui dormait dans
le moteur : `firewall=1` sur l'interface d'une VM, posé sur un **pont classique**, jette
le retour des flux en épingle.
Le mécanisme. `firewall=1` fait passer tout le trafic ponté par conntrack. Sur un VNet
SDN c'est sans conséquence : en EVPN le routage inter-VNet se fait dans le VRF, **sur le
nœud**, et le flux ne quitte jamais l'hyperviseur. Sur un pont classique routé par une
frontière externe, deux VM du **même nœud** dans deux VLAN différents ne se parlent qu'en
épingle : la trame sort par le lien physique, la frontière la route, elle revient sur le
même pont. La même table conntrack voit alors les deux moitiés de la connexion, classe le
retour `INVALID`, et `PVEFW-FORWARD` le jette.
La mesure, et c'est elle qui a tranché :
```
ops(asgard) -> pki(asgard) 0/8 dns(gandalf) -> cache(gandalf) 0/8
ops(asgard) -> forge(vishnu) 6/8 dns(gandalf) -> pki(asgard) 6/8
ops(asgard) -> cache(gandalf) 8/8 dns(gandalf) -> forge(vishnu) 8/8
```
**Toutes** les paires intra-nœud échouent, **toutes** les paires inter-nœuds passent.
Douze tentatives faisaient monter le compteur `ctstate INVALID` de **+112** sur asgard et
**+116** sur gandalf ; `firewall=0` posé, il ne bouge plus — 0 sur les deux.
Ce qui rend le défaut coûteux, c'est son déguisement : la poignée TCP **aboutit** (SYN et
SYN-ACK créent l'état), et ce sont les paquets de **données** qui disparaissent. L'AC le
disait dans son propre journal — `TLS handshake error … i/o timeout`, elle accepte la
connexion et attend un `ClientHello` qui n'arrive jamais. On accuse le MTU, la frontière,
la zone, le port. Écartés un par un, par la mesure : règles identiques champ par champ,
alias corrects, assignation des interfaces confirmée, ARP et routes saines, IPS désactivé,
shaper vide, NAT source limité à `wan`, MTU à 1500 de bout en bout et la taille sans effet
(100 octets se perdent comme 1460).
**La règle, désormais dans le moteur** : le pare-feu Proxmox ne s'arme que sur un VNet
SDN. L'intention déclarée ne suffit pas — un tenant déclare
`proxmox_clone_parefeu_interface: true` avec `proxmox_clone_pont: vmbr1` comme valeur *par
défaut*, chaque hôte la remplaçant par son VNet dérivé ; l'hôte qui retombe sur `vmbr1`
naîtrait armé sur un pont classique. `cloner_vm_debian.yml` croise donc l'intention avec
le pont **réellement utilisé**, et le dit quand il désarme.
**P45** évalue l'expression du playbook sur quatre cas plutôt que d'en lire le texte, dont
un qui **doit rendre vrai** — sans lui, une expression constamment fausse passerait la
preuve sans rien garantir.
*Le site a révélé ce défaut parce qu'il a été le premier à porter plusieurs zones. Ce
n'est pas une particularité du site : un tenant dérive ses zones du même principe.*
## 2026-08-25 — Le SITE en quatre zones, et l'ordre inscrit dans les intégrations ## 2026-08-25 — Le SITE en quatre zones, et l'ordre inscrit dans les intégrations
**44 preuves.** Le site n'est plus un `/24` plat : une zone par **nature d'autorité**, **44 preuves.** Le site n'est plus un `/24` plat : une zone par **nature d'autorité**,

View file

@ -6,8 +6,8 @@
> [`docs/audit/README.md`](README.md), et le registre trace : > [`docs/audit/README.md`](README.md), et le registre trace :
> [`docs/audit/affirmations.md`](affirmations.md). > [`docs/audit/affirmations.md`](affirmations.md).
- **Instance** : `instance` — inventaire `instance/inventories/production/hosts.yml` - **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/production/hosts.yml`
- **Verdict** : ✅ CONFORME (44 OK · 0 echec · 0 saute) - **Verdict** : ✅ CONFORME (44 OK · 0 echec · 1 saute)
## Preuves ## Preuves
@ -28,9 +28,9 @@
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. | | P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. | | P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 5 hotes, 14 groupes (inventaire dechiffre et parse). | | P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | | P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 18 secret(s) exige(s), tous presents. Voute reelle : 21 cle(s), aucun manque. | | P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 18 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 14 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | | P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 14 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. | | P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. | | P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
@ -57,6 +57,7 @@
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product | | P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 56 regle(s) du site. | | P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 5 machine(s) du plan retrouvees, 56 regle(s) du site. |
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. | | P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
## Couverture des affirmations ✅ du registre ## Couverture des affirmations ✅ du registre

View file

@ -438,6 +438,21 @@
- proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0 - proxmox_clone_coeurs is defined and proxmox_clone_coeurs | string | length > 0
- proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0 - proxmox_clone_memoire is defined and proxmox_clone_memoire | string | length > 0
# Un desarmement doit se VOIR. Sans ce message, l'ecart entre ce que l'ecosysteme
# declare et ce que la VM recoit resterait muet — et c'est precisement le genre de
# silence qui a coute une soiree de diagnostic le 2026-08-25.
- name: Dire que le pare-feu Proxmox reste desarme (pont classique, pas un VNet SDN)
ansible.builtin.debug:
msg: >-
`proxmox_clone_parefeu_interface` vaut vrai, mais {{ proxmox_clone_nom }} nait
sur `{{ proxmox_clone_pont }}` — un pont Linux, pas un VNet SDN. Le pare-feu
Proxmox y jetterait le retour des flux en epingle (`ctstate INVALID`) : il reste
desarme. La frontiere continue de policer ce segment.
when:
- proxmox_clone_parefeu_interface | default(false) | bool
- proxmox_clone_pont is defined
- proxmox_clone_pont is match('^vmbr[0-9]+$')
- name: Ajuster le reseau de la VM clonee - name: Ajuster le reseau de la VM clonee
community.general.proxmox_nic: community.general.proxmox_nic:
api_host: "{{ proxmox_api_host_effectif }}" api_host: "{{ proxmox_api_host_effectif }}"
@ -451,7 +466,36 @@
model: virtio model: virtio
bridge: "{{ proxmox_clone_pont }}" bridge: "{{ proxmox_clone_pont }}"
tag: "{{ proxmox_clone_vlan | int if proxmox_clone_vlan is defined and proxmox_clone_vlan | string | length > 0 else omit }}" tag: "{{ proxmox_clone_vlan | int if proxmox_clone_vlan is defined and proxmox_clone_vlan | string | length > 0 else omit }}"
firewall: "{{ proxmox_clone_parefeu_interface | default(false) | bool }}" # LE PARE-FEU PROXMOX NE S'ARME QUE SUR UN VNET SDN (2026-08-25).
#
# `firewall=1` fait passer tout le trafic ponte par conntrack. Sur un VNET SDN
# c'est sans consequence : en EVPN le routage inter-VNet se fait dans le VRF, SUR
# LE NOEUD. Le flux ne quitte jamais l'hyperviseur, conntrack en voit un chemin
# coherent, et le devis est-ouest (`devis_proxmox_fw.py`) peut le policer.
#
# Sur un PONT CLASSIQUE route par une frontiere externe, le meme reglage devient
# un piege. Deux VM du meme noeud dans deux VLAN differents ne se parlent qu'en
# EPINGLE : la trame sort par le lien physique, la frontiere la route, et elle
# revient sur LE MEME PONT. La meme table conntrack voit alors les deux moities de
# la connexion, classe le retour `INVALID`, et `PVEFW-FORWARD` le jette.
#
# MESURE DU 2026-08-25, sur le site decoupe en quatre zones : `site-ops-01` et
# `site-pki-01` (asgard), `site-dns-01` et `site-cache-01` (gandalf) ne se
# joignaient plus — 0/8 — alors que TOUTES les paires entre noeuds differents
# etaient a 8/8. Douze tentatives faisaient monter le compteur `ctstate INVALID`
# de +112 sur asgard et +116 sur gandalf ; `firewall=0` pose, il ne bouge plus.
#
# Le defaut se deguise en panne reseau : la poignee TCP aboutit (SYN/SYN-ACK
# creent l'etat), et ce sont les paquets de DONNEES qui disparaissent. On accuse
# le MTU, la frontiere, la zone. Rien de tout cela n'y est pour quelque chose.
#
# L'INTENTION DECLAREE NE SUFFIT DONC PAS : un tenant declare
# `proxmox_clone_parefeu_interface: true` avec `proxmox_clone_pont: vmbr1` comme
# valeur PAR DEFAUT, chaque hote la remplacant par son VNet derive. L'hote qui
# retombe sur `vmbr1` naitrait arme sur un pont classique. On croise donc
# l'intention avec le pont REELLEMENT utilise. Un pont Linux se nomme `vmbrN` ;
# un VNet SDN porte le nom derive de sa zone (`t29serv`).
firewall: "{{ (proxmox_clone_parefeu_interface | default(false) | bool) and (proxmox_clone_pont is not match('^vmbr[0-9]+$')) }}"
# `mtu: 1` est la valeur Proxmox pour « HERITE DU PONT » (virtio uniquement). # `mtu: 1` est la valeur Proxmox pour « HERITE DU PONT » (virtio uniquement).
# #
# Sans elle, l'invite nait a 1500 quel que soit le pont. En SDN EVPN la zone est a # Sans elle, l'invite nait a 1500 quel que soit le pont. En SDN EVPN la zone est a

View file

@ -50,3 +50,28 @@ common_packages_list:
# Attente du verrou dpkg (secondes). Genereux : au premier demarrage, les mises a # Attente du verrou dpkg (secondes). Genereux : au premier demarrage, les mises a
# jour automatiques de Debian le tiennent par vagues pendant plusieurs minutes. # jour automatiques de Debian le tiennent par vagues pendant plusieurs minutes.
common_packages_lock_timeout: 300 common_packages_lock_timeout: 300
# --- QUI DÉCIDE DE CE QUI S'INSTALLE (2026-08-25) -----------------------------
#
# Debian arme `unattended-upgrades` et les minuteurs `apt-daily` par défaut. Set-OPS
# applique DÉJÀ les mises à jour, explicitement, dans ce rôle. **Deux mécanismes font le
# même travail sur la même flotte, et se disputent le même verrou dpkg.**
#
# Sur une machine tranquille ça ne se voit jamais. Après six redémarrages et des heures
# sans réseau — ce qu'a vécu le site le 2026-08-25 — `unattended-upgrades` rattrape tout
# son retard et tient le verrou 48 minutes. `lock_timeout` vaut 300 s : les déploiements
# échouent, trois fois de suite, sur une course que personne n'a décidée.
#
# Allonger l'attente serait un pansement : on patienterait plus longtemps devant un
# concurrent qu'on n'a pas voulu. On désarme donc, et Set-OPS reste seul maître de ce qui
# s'installe — un écosystème souverain ne laisse pas un démon décider seul quand redémarrer
# ses services.
#
# CONTREPARTIE ASSUMÉE, ET ELLE EST RÉELLE : les correctifs de sécurité ne s'appliquent
# plus qu'au passage de Set-OPS. C'est tenable si les déploiements sont réguliers, et ça
# devient une dette sinon. Mettre à `false` pour rendre la main à Debian.
common_packages_desarmer_maj_auto: true
common_packages_unites_maj_auto:
- apt-daily.timer
- apt-daily-upgrade.timer
- unattended-upgrades.service

View file

@ -6,6 +6,21 @@
# #
# Attendre ici plutot que seulement avant le playbook : le verrou peut etre repris # Attendre ici plutot que seulement avant le playbook : le verrou peut etre repris
# ENTRE deux taches, ce qu'une verification ponctuelle en amont ne peut pas empecher. # ENTRE deux taches, ce qu'une verification ponctuelle en amont ne peut pas empecher.
# DÉSARMER AVANT TOUTE OPÉRATION APT, ET NON APRÈS.
#
# Une tâche apt placée avant celle-ci attendrait le verrou que celle-ci doit justement
# libérer — l'ordre n'est pas cosmétique. `state: stopped` en plus de `masked` : masquer
# empêche un démarrage futur, pas celui qui est déjà en cours.
- name: Désarmer les mises à jour automatiques de Debian (Set-OPS les applique lui-même)
ansible.builtin.systemd_service:
name: "{{ item }}"
state: stopped
enabled: false
masked: true
loop: "{{ common_packages_unites_maj_auto }}"
when: common_packages_desarmer_maj_auto | bool
failed_when: false # une unite absente n'est pas une faute : elle est desarmee
- name: Mettre à jour le cache APT - name: Mettre à jour le cache APT
ansible.builtin.apt: ansible.builtin.apt:
update_cache: true update_cache: true

View file

@ -1068,6 +1068,81 @@ def preuve_ordre_des_integrations() -> tuple[bool, str]:
return True, (f"{vus} integration(s) appliquent leur serveur avant leurs clients.") return True, (f"{vus} integration(s) appliquent leur serveur avant leurs clients.")
def preuve_parefeu_proxmox_sdn_seulement() -> tuple[bool, str]:
"""Le pare-feu Proxmox ne s'arme que sur un VNet SDN, jamais sur un pont classique.
POURQUOI (2026-08-25). `firewall=1` fait passer tout le trafic ponte par conntrack.
Sur un VNet SDN c'est sans consequence : en EVPN le routage inter-VNet se fait dans le
VRF, SUR LE NOEUD, et le flux ne quitte jamais l'hyperviseur.
Sur un pont classique route par une frontiere externe, le meme reglage devient un
piege. Deux VM du meme noeud dans deux VLAN differents ne se parlent qu'en EPINGLE :
la trame sort par le lien physique, la frontiere la route, elle revient sur LE MEME
PONT. La meme table conntrack voit les deux moities de la connexion, classe le retour
`INVALID`, et `PVEFW-FORWARD` le jette.
Mesure : sur le site decoupe en quatre zones, les paires intra-noeud etaient a 0/8 et
toutes les paires inter-noeuds a 8/8 ; douze tentatives faisaient monter le compteur
`ctstate INVALID` de +112 sur asgard et +116 sur gandalf. `firewall=0` pose, il ne
bouge plus. Le defaut se deguise en panne reseau : la poignee TCP aboutit, et ce sont
les paquets de DONNEES qui disparaissent — on accuse le MTU, la frontiere, la zone.
L'INTENTION DECLAREE NE SUFFIT PAS : un tenant declare
`proxmox_clone_parefeu_interface: true` avec `proxmox_clone_pont: vmbr1` comme valeur
PAR DEFAUT, chaque hote la remplacant par son VNet derive. L'hote qui retombe sur
`vmbr1` naitrait arme sur un pont classique.
Cette preuve n'inspecte pas le texte du playbook : elle EVALUE l'expression qu'il
porte, sur quatre cas. Le cas « intention vraie sur un VNet » doit rendre VRAI —
sinon une expression constamment fausse passerait la preuve sans rien garantir.
"""
import jinja2
pb = RACINE / "playbooks" / "proxmox" / "cloner_vm_debian.yml"
if not pb.is_file():
return False, f"{pb.relative_to(RACINE)} est introuvable."
lignes = [l for l in pb.read_text(encoding="utf-8").splitlines()
if re.match(r"\s*firewall:\s*", l)]
if len(lignes) != 1:
return False, (f"{pb.relative_to(RACINE)} porte {len(lignes)} ligne(s) "
f"`firewall:` — la preuve en attend exactement une, sinon elle ne "
f"sait pas laquelle gouverne la carte.")
expr = lignes[0].split("firewall:", 1)[1].strip().strip('"').strip("'")
# Jinja2 seul ne connait ni le test `match` ni le filtre `bool` — ils viennent
# d'Ansible. On les redonne a l'identique, sinon la preuve echouerait sur l'outil
# plutot que sur ce qu'elle mesure.
env = jinja2.Environment(undefined=jinja2.StrictUndefined)
env.tests["match"] = lambda v, motif: re.match(motif, str(v)) is not None
env.filters["bool"] = lambda v: (v if isinstance(v, bool)
else str(v).strip().lower() in ("true", "yes", "on", "1"))
cas = [ # (intention, pont, attendu, pourquoi)
(True, "t29serv", True, "un VNet SDN : le routage inter-VNet reste sur le noeud"),
(True, "vmbr3", False, "un pont classique : le retour en epingle serait jete"),
(True, "vmbr1", False, "le pont par defaut d'un tenant, si l'hote y retombe"),
(False, "t29serv", False, "l'ecosysteme ne demande pas de filtrage est-ouest"),
]
manques: list[str] = []
for intention, pont, attendu, pourquoi in cas:
try:
rendu = env.from_string(expr).render(
proxmox_clone_parefeu_interface=intention, proxmox_clone_pont=pont)
except jinja2.UndefinedError as e:
manques.append(f"intention={intention} pont={pont} : l'expression reclame "
f"une variable que la preuve ne fournit pas ({e})")
continue
obtenu = str(rendu).strip().lower() in ("true", "yes", "1")
if obtenu is not attendu:
manques.append(f"intention={intention} pont={pont} : rend {obtenu}, "
f"attendu {attendu} — {pourquoi}")
if manques:
return False, ("Le pare-feu Proxmox n'est pas correctement croise avec le pont :"
"\n - " + "\n - ".join(manques))
return True, ("Le pare-feu Proxmox ne s'arme que sur un VNet SDN "
f"({len(cas)} cas evalues, dont un qui doit rendre VRAI).")
def preuve_glossaire_enseigne() -> tuple[bool, str]: def preuve_glossaire_enseigne() -> tuple[bool, str]:
"""Tout mot que le depot emploie devant l'exploitant est explique au glossaire. """Tout mot que le depot emploie devant l'exploitant est explique au glossaire.
@ -1409,6 +1484,8 @@ PREUVES: list[dict] = [
"func": preuve_edge_porte_ses_noms}, "func": preuve_edge_porte_ses_noms},
{"id": "P44", "titre": "Integrations : le serveur avant ses clients", "refs": [], {"id": "P44", "titre": "Integrations : le serveur avant ses clients", "refs": [],
"func": preuve_ordre_des_integrations}, "func": preuve_ordre_des_integrations},
{"id": "P45", "titre": "Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs",
"refs": [], "func": preuve_parefeu_proxmox_sdn_seulement},
{"id": "P43", "titre": "Frontiere : le devis voit les machines du site", "refs": [], {"id": "P43", "titre": "Frontiere : le devis voit les machines du site", "refs": [],
"func": preuve_devis_frontiere_du_site}, "func": preuve_devis_frontiere_du_site},
{"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [], {"id": "P33", "titre": "Aucune collision de port entre roles co-localises", "refs": [],