frontiere : poster en formulaire encode — independant de la version du boitier
Mesure du 2026-08-13 sur un OPNsense 24.7 (version ANCIENNE, en cours de mise a
jour depuis) : toute ecriture du moteur y echouait. Alias, regles, NAT, routes —
`make frontiere-appliquer` n'aurait rien pose sur ce boitier.
Ce n'est donc PAS un defaut universel du moteur : il ecrit correctement sur la
frontiere de Chezlepro, plus recente. C'est un probleme de COMPATIBILITE, et le
correctif vaut surtout comme garantie de portabilite — le jour ou l'on arrive sur
un site dont on ne choisit pas le firmware, ce qui est exactement le cas ici.
LE SYMPTOME MERITE D'ETRE RETENU, lui, quelle que soit la version. Le controleur
d'OPNsense lit ses champs avec `hasPost(<racine>)`. Si le corps arrive en
`application/json` et que le boitier ne le decompose pas en variables de POST, ce
test est FAUX : reponse `{"result":"failed"}` NUE — HTTP 200, aucune redirection,
et surtout AUCUNE validation. Le controleur ne dit pas quel champ manque, parce que
de son point de vue il n'y avait aucun champ.
Trois fausses pistes avant la bonne : valeur invalide (un corps VIDE echouait
pareil), racine de payload erronee (elle etait juste), privileges de la cle (la
lecture passait). Ce qui a tranche : un corps vide aurait DU produire des
validations. Leur absence disait que le controleur n'avait rien recu.
CORRECTIF : `Frontiere` poste desormais `racine[champ]=valeur`. C'est la forme que
poste l'interface web elle-meme — aucune version d'OPNsense ne la refuse, alors que
le JSON depend du boitier. Tous les corps du moteur sont des dicts plats de chaines
(alias, rule, route) : un seul niveau d'imbrication suffit.
EPROUVE SUR LE BOITIER, dans les deux sens : addItem d'un alias sonde -> `saved`,
delItem -> `deleted`, aucune trace laissee, lecture intacte (11 alias). A re-eprouver
apres la mise a jour, pour verifier que le formulaire reste bon sur la version
recente — c'est le seul point qui reste ouvert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1d875f50ec
commit
073c5b6b69
2 changed files with 30 additions and 5 deletions
|
|
@ -30,7 +30,7 @@
|
|||
| 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 | 14 hotes, 31 groupes (inventaire dechiffre et parse). |
|
||||
| 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 : 24 secret(s) exige(s), tous presents. Voute reelle : 27 cle(s), aucun manque. |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 26 secret(s) exige(s), tous presents. Voute reelle : 29 cle(s), aucun manque. |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 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. |
|
||||
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. |
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 45 scripts expliques et atteignables, 93 cibles make documentees, 54 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 35 exigence(s) de role, toutes satisfaites (118 cle(s) declaree(s) par l'instance). |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 35 exigence(s) de role, toutes satisfaites (126 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 32 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 40 document(s) declarent leur lecteur (16 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import ssl
|
|||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -63,11 +64,35 @@ class Frontiere:
|
|||
self.auth = "Basic " + base64.b64encode(f"{cle}:{secret}".encode()).decode()
|
||||
self.ctx = None if verifier else ssl._create_unverified_context()
|
||||
|
||||
@staticmethod
|
||||
def _formulaire(corps: dict) -> bytes:
|
||||
"""Encode `{racine: {champ: valeur}}` en `racine[champ]=valeur`.
|
||||
|
||||
POURQUOI PAS DU JSON (mesure du 2026-08-13, sur un OPNsense 24.7 neuf).
|
||||
Le controleur d'OPNsense lit ses champs avec `hasPost(<racine>)`. Quand le
|
||||
corps arrive en `application/json` et que le boitier ne le decompose pas en
|
||||
variables de POST, ce test est FAUX : le controleur renvoie `{"result":
|
||||
"failed"}` NU — sans la moindre validation, donc sans dire ce qui manque.
|
||||
Toute ecriture echouait ainsi : alias, regles, NAT, routes. Le meme corps en
|
||||
formulaire encode passe du premier coup.
|
||||
|
||||
C'est la forme que poste l'interface web elle-meme : aucune version
|
||||
d'OPNsense ne peut la refuser, alors que le JSON depend du boitier.
|
||||
"""
|
||||
champs: dict[str, str] = {}
|
||||
for racine, valeurs in corps.items():
|
||||
if isinstance(valeurs, dict):
|
||||
for k, v in valeurs.items():
|
||||
champs[f"{racine}[{k}]"] = "" if v is None else str(v)
|
||||
else:
|
||||
champs[racine] = "" if valeurs is None else str(valeurs)
|
||||
return urllib.parse.urlencode(champs).encode()
|
||||
|
||||
def __call__(self, chemin: str, corps: dict | None = None) -> dict:
|
||||
data = json.dumps(corps).encode() if corps is not None else None
|
||||
data = self._formulaire(corps) if corps is not None else None
|
||||
h = {"Authorization": self.auth}
|
||||
if data:
|
||||
h["Content-Type"] = "application/json"
|
||||
if data is not None:
|
||||
h["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(self.base + chemin, data=data, headers=h,
|
||||
method="POST" if data is not None else "GET")
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in a new issue