wireguard : pairs de Technolibre appliques ; un renommage se fait sur place

Le meme appareil renomme garde sa cle : creer puis retirer echouait (cle en
double) et laissait le tunnel sans pair dans la configuration. rapprocher le
reconnait a la cle et modifie le pair existant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-28 17:33:04 -04:00
parent 3063bd4bf3
commit cf08e51c38
2 changed files with 48 additions and 6 deletions

View file

@ -1,5 +1,23 @@
# CHANGELOG — Set-OPS
## 2026-09-28 (22) — Pairs WireGuard de Technolibre appliqués ; un renommage n'est plus une création
**Appliqué** (`vpn_admin.py appliquer --portee OPS-Technolibre`, à la demande de l'exploitant) :
`technolibre-mathieu-portable` devient `technolibre-responsable-portable`. `fabric` au vert :
les quatre plans de la fabric sont conformes.
**Ce que l'application a révélé.** Le « nouveau » pair portait la MÊME clé publique que
l'ancien : le même appareil, renommé. Le script créait avant de retirer ; OPNsense a refusé
la création (« Public keys should be unique ») — mais le retrait, lui, était passé. La
configuration n'avait plus AUCUN pair pour ce tunnel ; seul le fait que l'échec empêche le
rechargement a gardé l'ancien en service. Rejoué aussitôt : le nouveau pair est créé, relu
en service.
**Corrigé** : `rapprocher` reconnaît un renommage à la clé (à créer ET à retirer, même clé)
et le fait en MODIFICATION sur place (`set_client`) — l'accès n'est jamais coupé, la
frontière ne voit jamais deux fois la même clé. Un vrai remplacement (autre clé) reste une
création suivie d'un retrait. Éprouvé sur un état simulé, dans les deux cas.
## 2026-09-28 (21) — La sonde « genome-a-jour » : le filet sous `make publier`
`make publier` tient la forge du site à jour dans le geste courant. Reste le jour où l'on

View file

@ -183,18 +183,32 @@ def rapprocher(api: appl.Frontiere, vpn: dict) -> dict:
return (str(p.get("pubkey")) != str(d.get("cle_publique"))
or _choisi(p.get("tunneladdress")) != [str(d.get("adresse"))])
creer = {n: d for n, d in declares.items() if n not in poses}
# UN PAIR ATTACHE A NOTRE INSTANCE QUE LE PLAN NE DECLARE PLUS : c'est un acces qui
# survit a la decision de le retirer. On le nomme, et `appliquer` le retire.
retirer = {n: u for n, (u, _p) in poses.items() if n not in declares or n in retires}
# UN RENOMMAGE N'EST NI UNE CREATION NI UN RETRAIT (2026-09-28). Le meme appareil
# renomme (`technolibre-mathieu-portable` -> `technolibre-responsable-portable`) garde
# sa cle. Le traiter en creation PUIS retrait echouait : OPNsense refuse deux pairs de
# meme cle (« Public keys should be unique »), et l'echec laissait la configuration
# sans aucun pair pour ce tunnel. On le reconnait a la cle, et on MODIFIE sur place.
cle_retiree = {str(poses[n][1].get("pubkey")): n for n in retirer}
renommer = {}
for n, d in list(creer.items()):
ancien = cle_retiree.get(str(d.get("cle_publique")))
if ancien:
renommer[n] = (retirer.pop(ancien), ancien, d)
del creer[n]
return {
"nom": nom,
"uuid_serveur": uuid_srv,
"serveur_a_creer": uuid_srv is None,
"serveur": srv.get(uuid_srv or "", {}),
"pairs_creer": {n: d for n, d in declares.items() if n not in poses},
"pairs_creer": creer,
"pairs_majer": {n: d for n, d in declares.items() if n in poses and different(n)},
"pairs_renommer": renommer,
"pairs_garder": {n for n in declares if n in poses and not different(n)},
# UN PAIR ATTACHE A NOTRE INSTANCE QUE LE PLAN NE DECLARE PLUS : c'est un acces qui
# survit a la decision de le retirer. On le nomme, et `appliquer` le retire.
"pairs_retirer": {n: u for n, (u, _p) in poses.items()
if n not in declares or n in retires},
"pairs_retirer": retirer,
}
@ -210,12 +224,15 @@ def afficher(p: dict, vpn: dict) -> bool:
print(f" + pair {n:<24} {d.get('adresse')}")
for n, d in sorted(p["pairs_majer"].items()):
print(f" ~ pair MODIFIE {n:<24} {d.get('adresse')}")
for n, (_u, ancien, d) in sorted(p["pairs_renommer"].items()):
print(f" ~ pair RENOMME {ancien} -> {n} {d.get('adresse')}")
for n in sorted(p["pairs_retirer"]):
print(f" - pair RETIRE {n}")
print(f"\n a creer : {len(p['pairs_creer']) + (1 if p['serveur_a_creer'] else 0)}"
f" | a retirer : {len(p['pairs_retirer'])}"
f" | inchange : {len(p['pairs_garder'])}")
return bool(p["serveur_a_creer"] or p["pairs_creer"] or p["pairs_majer"] or p["pairs_retirer"])
return bool(p["serveur_a_creer"] or p["pairs_creer"] or p["pairs_majer"]
or p["pairs_renommer"] or p["pairs_retirer"])
def _cle_privee() -> tuple[str, str]:
@ -271,6 +288,13 @@ def appliquer(api: appl.Frontiere, p: dict, vpn: dict) -> int:
else:
fait(api("/api/wireguard/client/add_client", corps), f"pair {nom_p}")
for nom_p, (u, _ancien, d) in sorted(p["pairs_renommer"].items()):
fait(api(f"/api/wireguard/client/set_client/{u}", {"client": {
"enabled": "1", "name": nom_p, "pubkey": str(d["cle_publique"]), "psk": "",
"tunneladdress": str(d["adresse"]), "keepalive": "25",
"servers": uuid_srv or "",
}}), f"renommage du pair {nom_p}")
for nom_p, u in sorted(p["pairs_retirer"].items()):
fait(api(f"/api/wireguard/client/del_client/{u}", {}), f"retrait du pair {nom_p}")