Les binaires ne peuvent pas venir du dépôt Gitea : il est privé, et un jeton
embarqué dans une application distribuée est un jeton donné à tous ses
utilisateurs. Ils viennent donc du serveur WGSecure, où le contrôle d'accès
existe déjà — l'API n'est joignable que par le tunnel, et on y parvient parce
que sa clé WireGuard est déclarée. Aucun secret à embarquer.
Le serveur expose GET /api/version, qui rend la version publiée et l'empreinte
de chaque artefact, et /api/version/download/{clé} qui les sert. Le manifeste
est déposé par `make publish-updates`, avec les condensats calculés une fois à
la publication : les recalculer à chaque appel bloquerait l'API sur 125 Mo.
`ANNOUNCE=` permet d'annoncer les artefacts sous un autre numéro, pour exercer
la chaîne sans compiler une seconde version.
Rien ne s'installe sans un clic. La vérification est automatique — au montage
du tunnel puis toutes les demi-heures, car ne la faire qu'au montage laissait
un serveur momentanément injoignable annuler toute proposition pour la session
— mais le téléchargement attend le bouton. Cette application monte un VPN, et
une version défaillante qui se propagerait seule couperait l'accès d'un parc
entier sans que personne ne l'ait demandé.
L'artefact est vérifié contre son empreinte SHA-256 avant d'être mis en place,
et le fichier temporaire est détruit à la moindre anomalie. Le canal est déjà
authentifié par WireGuard ; le condensat couvre ce qu'il ne couvre pas — un
téléchargement tronqué, un disque plein, un artefact mal publié.
Sous Linux, le binaire est remplacé par renommage, sur le même système de
fichiers que sa destination. Un déplacement depuis /tmp se rabattait sur une
copie, donc sur une écriture dans l'exécutable en cours, que le noyau refuse
(ETXTBSY). Un renommage ne touche qu'une entrée de répertoire : l'ancien inode
reste vivant pour le processus, qui continue jusqu'à sa fermeture — et
l'application propose désormais de redémarrer plutôt que de le laisser deviner.
Sous Windows, l'installation passe par le service, qui tourne en LocalSystem et
écrit dans Program Files sans invite d'élévation. La commande `apply_update` ne
prend aucun paramètre : le service lit le manifeste, télécharge et vérifie
lui-même. Lui passer un fichier déjà téléchargé aurait donné à tout compte du
poste — le pipe est ouvert aux utilisateurs interactifs — le moyen de faire
exécuter ce qu'il veut avec les privilèges du système.
L'installeur orchestre le remplacement, ce qu'il ne faisait pas : les deux
exécutables à remplacer tournent au moment de la mise à jour, et Windows
verrouille l'image d'un processus vivant. Il ferme donc l'application par le
gestionnaire de redémarrage, arrête les tunnels et le service avant la copie —
en attendant la libération réelle du fichier, `sc stop` rendant la main avant
la fin de l'arrêt — et garde `restartreplace` en secours. Le service, lui,
lance l'installeur détaché et ne l'attend pas : il lui demande de se remplacer
lui-même.
Durcissement du service au passage. Le premier appelant devient propriétaire et
son SID est retenu ; ensuite seuls ce compte et les administrateurs sont
servis. Le pipe étant ouvert à tout utilisateur interactif, n'importe quel
compte du poste pouvait jusqu'ici couper le tunnel d'un autre ou poser une
règle NRPT valable pour toute la machine. Le contenu d'un `.conf` est également
vérifié — seul son chemin l'était — et les directives exécutables y sont
refusées.
Corrige enfin un défaut de compilation : Linux et Windows partageaient le
répertoire de travail de PyInstaller, que `--clean` vide au démarrage. Lancées
à la suite, les deux cibles effaçaient mutuellement leurs fichiers
intermédiaires, produisant un .exe gonflé de 44 Mo et un binaire Linux tronqué
dont l'archive ne se décompressait plus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'assistant « configuration serveur + client » disparaît. Il datait d'avant la
v0.8.0 et produisait un serveur WireGuard nu — sans API, sans quarantaine, sans
TOTP — incompatible avec le produit actuel, où le serveur enrôle le compte et
fournit un .conf. Son bouton « Appliquer le volet client » écrasait clés client
et clé publique du serveur : sur un poste déjà enrôlé, il détruisait le lien
avec le serveur, qui déclare l'ancienne clé publique.
L'identifiant VPN disparaît aussi, devenu sans emploi depuis que le serveur
reconnaît le poste à l'adresse de son tunnel. Un champ qui ne fait rien est
pire qu'un champ absent : il laisse croire qu'il compte. Les configurations
existantes sont purgées au chargement, profils compris. La pastille « À propos »
qui en déduisait « Code d'accès serveur » ou « Aucun code d'accès » n'affirme
plus rien qu'elle ne sache : l'accès passe par le serveur dans tous les cas.
Le sous-onglet Tunnel passe d'une liste de quinze champs à trois groupes :
l'essentiel, le split-DNS nommé comme tel — trois champs y portaient « DNS »
sans que rien ne dise lesquels allaient ensemble — et un repli « Avancé ».
Celui-ci réunit ce qui vient du serveur (adresse, port, clé publique) et ce qui
est correct par défaut (nom d'interface, URL de l'API, keepalive, MTU).
Le verrouillage après import d'un .conf devient persistant. Il existait déjà,
mais `_load_values` le levait sans condition : rouvrir le panneau ou changer de
profil rendait éditables, en silence, les champs décrivant un pair déjà déclaré
côté serveur. L'état est désormais enregistré et suit la configuration chargée.
Le diagnostic gagne une étape « Serveur DNS du réseau distant », qui couvre un
angle mort : le tunnel monte, le trafic passe, la résolution est au vert, et
pas un nom interne ne se résout. Deux causes, l'une et l'autre muettes — le
serveur DNS distant n'est couvert par aucune IP autorisée, ou il l'est mais son
adresse tombe aussi dans une plage que porte une carte du poste, auquel cas la
route locale l'emporte. Le second cas ne se déduit pas de la configuration :
l'étape interroge la route réellement retenue par le noyau, sans émettre de
paquet, et propose la route d'hôte qui reprend la main.
Le sous-onglet « Fuite DNS » devient « Résolution des noms » : il en faisait
trois choses sous un titre qui n'en annonçait qu'une. Son verdict « Statut
inconnu » recouvrait trois causes appelant trois gestes différents ; chacune
est maintenant nommée, avec l'action qui suit.
Enfin, la fenêtre. Sa hauteur augmente de 15 %, et son plancher passe de 520 à
703 : la page de test réclame 569 px et se chevauchait en dessous. Le rapport
de diagnostic passe en tableau à deux colonnes — il occupait 224 px quelle que
soit la largeur de la fenêtre, soit jusqu'à 79 % de blanc à droite, et tient
maintenant sur 38 % de hauteur en moins.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La fenêtre de saisie réclamait l'Identifiant VPN à chaque ouverture sur un
poste neuf, et l'authentification échouait. Les deux symptômes n'en faisaient
qu'un : l'identifiant n'était enregistré qu'en cas d'authentification réussie,
or aucune n'aboutissait, donc il était redemandé sans fin.
Il n'est plus transmis du tout. `POST /api/session` désigne désormais le compte
par l'adresse source de la requête, comme `GET /api/session` le fait depuis la
v0.8.6. Le serveur l'a toujours su ; le poste, lui, ne faisait que répéter un
renseignement qu'il pouvait taper de travers. C'est aussi plus sûr : sur
WireGuard l'adresse source est garantie par le routage par clé, alors qu'un
identifiant en clair permettait d'ouvrir l'accès d'un compte depuis n'importe
quelle adresse. Exige la v0.3.2 du serveur, qui accepte les deux formes.
Le champ disparaît de la fenêtre de saisie. Celui du panneau Administrateur
reste, purement indicatif désormais, avec une infobulle qui le dit.
Trois décisions d'interface se fondaient sur « un identifiant est-il
configuré ? », une question que le poste ne pouvait pas trancher : proposer ou
non la saisie automatique, le libellé de la ligne Accès distant, et l'aspect du
bouton. Elles suivent maintenant la réponse du serveur, qui distingue « aucun
pair à cette adresse » (401, nouveau) de « accès fermé, code attendu » (403).
Un poste non enrôlé n'est donc plus harcelé pour un code qui n'ouvrirait rien,
et la ligne affiche « Non géré par le serveur » au lieu de le supposer.
Corrige aussi une régression de la v0.9.0 : `tunnel_gateway` retenait la route
par défaut des IPs autorisées comme réseau de tunnel, et rendait donc 0.0.0.1
comme passerelle en tunnel intégral — sondage et validation partaient vers une
adresse inexistante. Elle ne retient plus que les réseaux contenant l'adresse
du client, préfère le plus spécifique, et se replie sur /24 (ou /64) faute de
candidat. Le diagnostic « Pair joignable dans le tunnel » en bénéficie aussi.
Enfin, `authenticate` distingue le silence du réseau d'un refus du serveur,
comme `session_status` le faisait déjà.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Audit du parcours complet — montage du tunnel, sondage de l'état, saisie
du code, ouverture de l'accès. Neuf défauts, dont plusieurs laissaient le
poste en quarantaine sans moyen visible d'en sortir : exactement ce que le
bouton « Saisir le code d'accès » existe pour éviter depuis la v0.8.2.
Adresse de l'API. api_base_url retranchait le dernier octet de l'adresse
du client, ce qui suppose un /24 et ignore les IPs autorisées. Or une
adresse client en /32 est la forme la plus courante et ne décrit aucun
réseau : un client en 10.6.1.7/32 sur un 10.6.0.0/16 était expédié vers
10.6.1.1, qui n'existe pas. La déduction passe désormais par
wireguard.tunnel_gateway, qui servait déjà au diagnostic et gère /32,
IPv6 et le repli sur les IPs autorisées. Une adresse IPv6 est encadrée de
crochets, sans quoi son « : » se confondait avec celui du port.
Révocation invisible. Le client ne sondait qu'au retour du tunnel : une
révocation avant terme — redémarrage du serveur, purge de sa table de
sessions, décision d'un administrateur — n'était annoncée par rien et
l'application affichait « Ouvert ✓ » devant un réseau muet, bouton
masqué, jusqu'à la chute du tunnel. Sans échéance annoncée, indéfiniment.
L'accès est reconfirmé chaque minute tant qu'il est cru ouvert.
Authentification perdue. « Plus tard » restait actif pendant la
vérification, Échap et la croix aussi. Fermer là rendait la main sur un
refus alors que le serveur pouvait encore accepter : sa réponse arrivait
après la sortie d'exec() et n'était lue par personne. L'accès était
ouvert côté serveur, l'application affichait la quarantaine, et le code
saisi était consommé pour rien. La fenêtre ne se ferme plus tant qu'une
requête est en vol.
Fenêtre survivante. Ni accept() ni reject() ne passent par closeEvent :
le minuteur de 500 ms continuait à battre après la fermeture, et la
fenêtre — parentée à la principale — survivait à sa disparition.
dispose() l'arrête et programme la destruction ; _access_dialog est remis
à None, son thread n'étant plus attendu à l'arrêt de l'application.
Repli 404 trompeur. La sonde d'accessibilité tenait pour preuve toute
réponse du serveur DNS du split-DNS, sans vérifier qu'il était joint par
le tunnel. Une adresse RFC1918 routée par le réseau local du poste
répondait donc hors tunnel, et le repli censé éviter le blocage le
provoquait : accès déclaré ouvert sans échéance, bouton masqué. La route
est vérifiée d'abord, sans émettre de paquet — connect() sur une socket
UDP suffit à connaître l'adresse source retenue par le noyau.
Proxy. urlopen emploie l'ouvreur par défaut, dont le ProxyHandler lit
http_proxy dans l'environnement : sur un poste d'entreprise la requête
vers l'adresse privée du tunnel partait au proxy, dont la réponse — 404
souvent — était prise pour celle du serveur, déclenchant le repli
ci-dessus. Panne intermittente parfaite : selon l'environnement de
lancement, ça passait ou non.
Silence pris pour un refus. Un ok faux couvrait « le serveur refuse » et
« le serveur n'a rien dit », traités pareil : un hoquet au montage du
tunnel faisait surgir la fenêtre et brûler un code alors que l'accès
était peut-être déjà ouvert. AuthResult porte désormais authoritative.
Un refus du serveur referme du premier coup, il en est seul juge ; un
silence déclenche un second sondage avant de déranger l'utilisateur, et
n'entame l'accès qu'après trois minutes sans réponse.
Annulation destructrice. _open_access_dialog écrivait l'état d'accès
quoi qu'il arrive. Un sondage abouti pendant que la fenêtre était ouverte
— les minuteries tournent dans la boucle imbriquée d'exec() — pouvait
établir que l'accès l'était, que « Plus tard » remettait aussitôt à
fermé. Renoncer à saisir un code ne referme rien côté serveur ; plus rien
ici non plus.
api_url. Le schéma est vérifié plutôt que repris tel quel : identifiant
et code partent dans cette URL. Reste ouvert, et hors de ce dépôt : l'API
par défaut est en clair et le serveur n'est pas authentifié.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mode d'emploi, Prérequis et Sécurité rejoignent Fonctions, Garanties,
Technique, Journal et Auteur.
Le contenu vit dans app_info.py, séparé de sa présentation : le relire
ou le corriger ne demande pas de traverser le code de l'interface. Deux
aides — _about_group_label et _about_grouped — découpent en groupes
titrés les listes d'une vingtaine de cartes, sans quoi Prérequis et
Sécurité se lisaient comme un seul bloc indifférencié.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
prepare_quit() attend les threads en cours avant toute destruction, mais sa
liste en oubliait deux : le sondage d'état ajouté en 0.8.6 — ma régression —
et le thread de validation de la fenêtre de saisie, parenté à cette fenêtre
depuis la v0.8.2 et jamais attendu. Fermer la fenêtre de code pendant qu'une
requête est en vol suffisait à avorter le process, ce qui arrive dès que le
serveur tarde. Reproduit avec un serveur qui accepte sans jamais répondre,
puis vérifié corrigé sur les deux chemins.
Le sondage est ramené à 4 s : l'arrêt l'attend désormais, et dix secondes y
seraient une interface figée. Son échec ne coûte rien, contrairement à une
validation ratée qui gâche un code déjà saisi.
Ajoute le temps restant sur la ligne Accès distant, et un repli pour les
serveurs sans GET /api/session : sur un 404, le client observe si le trafic
atteint le réseau distant plutôt que de réclamer un code déjà validé. Un 403
reste une réponse qui fait autorité.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le client tenait pour acquis que l'autorisation ne survivait pas à la chute
du tunnel — un commentaire l'affirmait noir sur blanc. C'est l'inverse : le
serveur autorise l'adresse de tunnel du pair jusqu'à l'échéance enregistrée,
et cette adresse ne change pas d'une reconnexion à l'autre. Se reconnecter
ne révoque rien ; seul le client oubliait, et redemandait un code que le
serveur avait déjà accepté.
Il pose maintenant la question au lieu de la supposer, via GET /api/session
(serveur >= 0.3.0), dès que le tunnel monte : démarrage sur un tunnel déjà
actif, reconnexion automatique, ou avant toute invite de saisie. Le serveur
identifie l'appelant à l'IP source, aucun identifiant ne circule.
Serveur injoignable, réponse illisible ou serveur antérieur à la 0.3.0 ne
valent pas « accès ouvert » : sans information, le client ne conclut rien et
laisse le bouton disponible — le sens sûr, et le comportement de la 0.8.5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'autorisation obtenue auprès du serveur a un terme, mais le client n'en
tenait aucun compte : `access_until`, pourtant renvoyé à chaque validation,
ne servait qu'à composer une ligne de journal. L'accès était marqué ouvert
jusqu'à la chute du tunnel, si bien qu'une fois le délai écoulé l'interface
affichait « Ouvert ✓ » et masquait le bouton comme l'entrée de systray —
laissant l'utilisateur devant un réseau muet, sans issue visible, dans la
situation même que ce bouton existe pour couvrir depuis la v0.8.2.
L'échéance remonte désormais jusqu'à la fenêtre principale, qui la relit
sans jamais la calculer : sa durée est un réglage serveur, que le poste n'a
pas à deviner. Un serveur qui n'en annonce pas laisse l'accès ouvert jusqu'à
la déconnexion, plutôt que de se voir imposer un terme inventé ici.
Corrige au passage l'heure de fin, annoncée en UTC mais présentée comme
locale — deux heures d'avance en été.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le bouton et l'entrée de systray étaient conditionnés à un champ vide par
défaut depuis son introduction en v0.8.0 : un poste tenu en quarantaine par
le serveur n'affichait donc aucun moyen d'en sortir. La saisie ne dépend
plus que de l'état du tunnel ; la fenêtre demande l'identifiant quand il
manque et ne l'enregistre qu'une fois le serveur l'ayant accepté.
L'état « Non géré par le serveur » disparaît au passage : le client
l'affirmait à partir de sa seule configuration locale, sans rien vérifier.
« Non vérifié » est la vérité tant qu'aucun code n'a été validé.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le bouton « Saisir le code d'accès » était créé et placé, mais sous le bord
inférieur : rendre un widget visible ne met pas les layouts à jour dans la
foulée — Qt poste un LayoutRequest traité au tour de boucle suivant. La
fenêtre, de hauteur fixe recalculée à chaque changement de contenu, se
mesurait avant ce traitement et obtenait une hauteur en retard d'un cycle.
Le symptôme était déjà visible dans les tests de la v0.8.2 : la hauteur
montait bien de 258 à 298 px à l'apparition du bouton, mais ne redescendait
jamais à sa disparition. J'avais mis cette asymétrie sur le compte du
backend graphique de test au lieu d'y voir le retard lui-même.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La fenêtre de saisie ne s'ouvrait qu'une fois, juste après une connexion
manuelle réussie. Un refus, une fenêtre fermée, une reconnexion automatique
— qui n'ouvre volontairement aucune modale — ou l'expiration de
l'autorisation au bout de 12 h laissaient un tunnel monté, un réseau muet
et aucun élément d'interface pour ressaisir un code.
Un bouton sous l'action de connexion et une entrée de systray apparaissent
désormais dès qu'une saisie a un sens : identifiant VPN configuré, tunnel
monté, accès non ouvert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La v0.8.0 laissait cohabiter deux vérifications du code à 6 chiffres : la
fenêtre MFA locale, qui contrôlait un code contre un secret détenu par le
poste lui-même — et ne verrouillait donc que sa propre interface — puis la
validation par le serveur, seule à décider de l'accès réseau. L'ancienne
disparaît, avec le secret TOTP qu'elle stockait en clair dans config.json
(effacé au premier lancement) et l'onglet MFA du panneau Administrateur.
La saisie passe par une fenêtre dédiée qui relaie le refus exact du serveur
et laisse réessayer, la requête partant dans un thread pour qu'un serveur
muet ne fige pas l'application. Une ligne « Accès distant » distingue
désormais l'état de l'autorisation de celui du tunnel : monté sans code
validé, celui-ci ne mène nulle part.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux changements liés, qui achèvent le déplacement de la sécurité vers
le serveur.
1. Le tunnel n'est plus recréé à chaque connexion. Créer ou supprimer un
service Windows n'est jamais délégable à un utilisateur standard, le
démarrer peut l'être : c'est cette réinstallation systématique qui
imposait une authentification administrateur à chaque connexion ET à
chaque déconnexion. Désormais connect() démarre le service s'il
existe déjà, disconnect() se contente de l'arrêter, et le tunnel
reste installé entre les deux.
Trois voies en cascade pour ce démarrage/arrêt : sc.exe sans
élévation, puis le service wgsecure-helper, puis l'élévation en
dernier recours. Le helper gagne donc deux commandes
(start/stop_tunnel_service). Cette cascade tient quelle que soit la
configuration du poste — le test en session standard a montré que
l'ACL posée sur le service WireGuard ne donne aucun droit sur le
service du tunnel, qui est un objet distinct.
2. Après le montage, l'application demande le code à 6 chiffres et le
fait valider PAR LE SERVEUR (app/core/vpn_session.py). Jusqu'ici le
code était vérifié localement, contre un secret que l'application
détenait : elle validait donc un code qu'elle pouvait produire, et ne
verrouillait que sa propre interface. L'ordre est imposé par le
réseau — l'API n'est joignable que depuis le tunnel, qui sert de
réseau de quarantaine tant que le code n'est pas passé.
L'URL de l'API est déduite de l'adresse du client (10.6.0.6 →
10.6.0.1) faute de valeur explicite : c'est l'adresse du serveur
lui-même, la seule jamais filtrée depuis la quarantaine.
Sans identifiant VPN configuré, l'étape est ignorée : le serveur peut
tourner en mode permissif, où un compte non enrôlé garde son accès, et
imposer la saisie bloquerait des installations qui fonctionnent.
Chemins Linux inchangés (branches is_windows()).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sécurité : la clé privée ne subsiste plus en clair dans %APPDATA%.
Le .conf généré est écrasé puis supprimé dès que wireguard.exe l'a
consommé (il en garde sa propre copie chiffrée DPAPI), et l'import
propose d'effacer le fichier source. Windows uniquement : sous Linux
/etc/wireguard/<iface>.conf reste requis par wg-quick down.
Corrige aussi ImpersonateNamedPipeClient, qui appartient à
win32security et non à win32pipe (vérifié par introspection).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige le refus "chemin de configuration hors du dossier attendu" :
le service exigeait %ProgramData%\WireGuard\ alors que l'app écrit sa
config dans %APPDATA%\WGSecure\ (seul dossier inscriptible par un
utilisateur non-admin). Ce chemin n'étant pas recalculable depuis
LocalSystem, la validation repose désormais sur l'usurpation
d'identité du client du pipe : le fichier doit être ouvrable par
l'appelant lui-même, plus des contrôles structurels sur le nom et le
dossier.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige la cause racine des invites d'authentification persistantes :
le service wgsecure-helper ne démarrait jamais réellement. Lancé par
le SCM (sans commande sur la ligne d'appel), HandleCommandLine() se
contentait d'afficher son aide et de sortir, sans entrer dans le
dispatcher — SvcDoRun n'était donc jamais exécuté, aucun journal
n'était créé, le pipe n'existait jamais, et l'app retombait toujours
sur l'élévation UAC. Bascule sur PrepareToHostSingle() +
StartServiceCtrlDispatcher() quand argv est vide.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Durcit la boucle principale de wgsecure-helper : ne capturait que
pywintypes.error (pas Exception en général) et ne protégeait pas
CloseHandle individuellement dans le nettoyage, si bien qu'une
exception inattendue sur une requête pouvait arrêter tout le
service — expliquant un premier échange réussi (juste après
démarrage) puis un service injoignable ensuite. Gère aussi
ERROR_PIPE_CONNECTED (course normale, pas rare, entre CreateNamedPipe
et ConnectNamedPipe) au lieu de la traiter comme une erreur.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige helper_ipc.py : seul CreateFileW avait des argtypes/restype
ctypes explicites. Sans eux pour ReadFile/WriteFile/CloseHandle, le
HANDLE (pointeur 64 bits) était marshalé comme un entier 32 bits par
défaut et tronqué silencieusement, faisant échouer systématiquement
l'échange avec wgsecure-helper — l'app retombait donc toujours sur
son chemin élevé (2 invites UAC) malgré un service qui tournait et
répondait correctement.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Remplace l'enregistrement du service wgsecure-helper (sc.exe create/
start via [Run]) par un appel Exec()+AddQuotes en Pascal script :
l'échappement de guillemets imbriqués dans la chaîne Parameters
produisait une commande sc.exe invalide, silencieusement, alors que
la même commande fonctionnait lancée à la main. Le service n'était
donc jamais enregistré malgré une installation "réussie".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige Add-DnsClientNrptRule qui échouait systématiquement : ce
cmdlet n'a pas de paramètre -Name (contrairement à Get-/Set-/
Remove-), résolu comme abréviation ambiguë entre -Namespace/
-NameServers/-NameEncoding. Identification de la règle via -Comment
à la place, comme le nettoyage générique existant. Bénéficie au
chemin élevé classique et au service wgsecure-helper.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute wgsecure-helper, un service Windows privilégié (LocalSystem)
qui installe/désinstalle le tunnel et pose/retire les règles NRPT
sans invite d'authentification répétée. L'ACL du service WireGuard
(v0.7.6) ne délègue que démarrer/arrêter un tunnel déjà installé,
jamais le créer/supprimer, ce que WGSecure fait pourtant à chaque
connexion/déconnexion — d'où les prompts qui persistaient malgré
v0.7.6-v0.7.11. Corrige aussi le handshake, qui échouait pour la même
raison (wg show sans élévation complète).
Purement additif : l'app retombe sur son chemin élevé existant si le
service est absent (composant optionnel dans l'installeur).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rend le redémarrage du service WireGuard best-effort dans "Configurer
les permissions" : Restart-Service échoue si un tunnel est actif, ce
qui annulait toute l'opération alors que l'ACL/registre venaient
d'être appliqués avec succès.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Détecte "déjà membre du groupe" via FullyQualifiedErrorId au lieu du
texte anglais du message d'erreur, qui est localisé (le français ne
contenait jamais "already a member", faisant échouer le bouton à
chaque nouvelle tentative une fois le groupe déjà accordé).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige la SDDL invalide (erreur 1804) produite par le fix précédent
quand le service porte une clause S: (SACL/étiquette d'intégrité), et
les accents corrompus dans les messages d'erreur Windows (mauvais
codepage/encodage lors de la capture de sortie).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige l'ACL du service WireGuard (bouton "Configurer les permissions")
qui n'était en réalité jamais posée : la SDDL était lue via
Get-WmiObject.DACL.ToString(), qui ne produit pas de SDDL valide.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- _run_elevated_windows() capture désormais la sortie réelle d'une commande
élevée (fichier temp écrit par le process élevé lui-même) : ShellExecuteEx
runas ne transmet jamais les tubes hérités, donc tout message d'échec
élevé (split-DNS NRPT, install/uninstall du tunnel) restait "erreur
inconnue" jusqu'ici.
- run_privileged_readonly() tente réellement `wg show` sous Windows non élevé
au lieu d'abandonner sans essayer — le handshake ne se peuplait jamais.
- Le bouton "Configurer les permissions" active aussi le mécanisme officiel
WireGuard (LimitedOperatorUI + groupe Network Configuration Operators via
son SID, le nom étant localisé) en plus de l'ACL du service déjà en place.
- Le diagnostic Windows précise désormais que le split-DNS (NRPT) exige
toujours une authentification admin séparée à la connexion/déconnexion,
aucune ACL ne pouvant le déléguer (aucun mécanisme documenté trouvé).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ajoute un fichier log texte rotatif (wgsecure.log) dans le dossier
applicatif, en complément du journal JSON interne, pour diagnostiquer
un échec (ex. split-DNS) sans dépendre de l'UI. Corrige le panneau
Journal qui coupait les messages longs sans moyen de les lire en
entier (défilement horizontal réactivé + infobulle).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrige l'échec silencieux « Accès refusé » à la connexion Windows :
wireguard.exe /install|uninstalltunnelservice était lancé sans élévation
en misant sur son manifeste UAC natif, qui ne s'auto-déclenche jamais via
un appel process-à-process. L'appel tente maintenant sans élévation
(compatible avec les ACLs accordées via le bouton dédié), puis retente
avec une vraie élévation UAC si le premier essai échoue. Le diagnostic
d'élévation est mis à jour pour refléter les deux cas.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
La version s'affiche déjà dynamiquement depuis app.__version__ ; seul le
contenu curaté (FEATURES) devait suivre les apports réels de la 0.7.4 :
diagnostic du split-DNS et verrouillage des champs après import .conf.
Split-DNS repose désormais sur resolvectl par interface (Linux) et une
règle NRPT complète (Windows), avec diagnostic de résolution distante,
verrouillage des champs après import d'un .conf, et consolidation des
couleurs de l'UI dans theme.py.
- Mise à jour du README avec la version 0.7.3 et le badge
- Mise à jour du CHANGELOG avec les notes de version 0.7.3
- Ajout du Split-DNS dans les fonctionnalités de la page About
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Ajout du support complet du split-DNS pour Linux (systemd-resolved) et Windows (NRPT)
- Bouton de configuration des permissions ACL pour WireGuard sous Windows
- Suppression de tous les appels à sc.exe (remplacés par PowerShell + wireguard.exe)
- UAC gérée par wireguard.exe lui-même (pas par WGSecure)
- Infobulles ajoutées sur tous les champs de configuration principaux
- Correction du blocage du handshake lié au redémarrage de systemd-resolved
- Incrémentation de la version à 0.7.3
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Supprime tous les appels à sc.exe (remplacés par PowerShell Get-Service + wireguard.exe)
- wireguard.exe appelé via run_command() sous Windows (UAC gérée par son manifeste)
- Supprime le double appel à uninstalltunnelservice (évite l'erreur 'service inexistant')
- Ajoute un bouton dans l'onglet Élévation pour configurer les ACLs du service WireGuard
(permet à un utilisateur standard de gérer ses tunnels sans UAC après configuration admin)
- Incrémente la version à 0.7.2
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Nouveau diagnostic : signale un DNS non couvert par les IPs autorisées
sur un tunnel scindé (résolution forcée via une interface qui n'a pas
de route vers ce serveur).
- Corrige le test de connexion, l'analyse DNS et la réparation DNS qui
gelaient l'application (sondes lancées sur le thread graphique).
- Corrige le CHANGELOG absent de l'exécutable compilé (page « À propos »
vide) et le badge de latence bloqué sur « hors ligne » quand l'ICMP est
filtré alors que le tunnel fonctionne.
- wgsecure.iss installe désormais WireGuard for Windows automatiquement.
- CHANGELOG.md n'était pas empaqueté par PyInstaller (datas=[]) et son
chemin, calculé depuis __file__, ne survivait pas à l'extraction
onefile : la page « À propos » restait vide une fois compilée. Le
fichier est désormais embarqué (--add-data) et son chemin bascule sur
sys._MEIPASS en mode frozen.
- Le badge de latence se basait uniquement sur un ping ICMP vers
l'endpoint : de nombreux serveurs/pare-feux le bloquent alors que le
tunnel WireGuard fonctionne, d'où « hors ligne » permanent. Repli sur
l'âge du dernier handshake réel quand l'ICMP échoue.
- wgsecure.iss installe désormais WireGuard for Windows en silencieux
(prérequis manquant jusqu'ici), au même titre que le VC++
Redistributable ; make installer-deps télécharge les deux binaires.
- Instance unique : un second lancement réveille l'instance active.
- Autostart et reconnexion auto activés par défaut (nouvelle installation).
- Page « À propos » refaite en 5 onglets (contenu séparé dans app_info.py).
- Corrige le handshake/RX-TX qui ne s'affichaient plus (wg show sans élévation).
- Corrige le diagnostic sudo trompeur (sondes qui ne matchaient jamais les règles).
- Corrige make setup-sudoers bloqué hors session graphique (pkexec -> sudo), idempotent.
- Corrige le README : Kill Switch inexistant retiré, ordre d'élévation réel documenté.
Le diagnostic s'arrêtait sur « le port UDP ne rejette rien ». C'est le maximum
qu'une sonde aveugle puisse dire : un serveur WireGuard ignore silencieusement
tout paquet non authentifié, si bien qu'un port non redirigé, une clé inconnue
du serveur et un serveur en parfaite santé produisent exactement la même
absence de réponse. Le tunnel n'étant pas monté, les étapes suivantes étaient
sautées et le rapport concluait « chaîne validée » sur une configuration qui
n'avait aucune chance de négocier.
WGSecure construit désormais un vrai message d'initiation (Noise_IKpsk2_25519_
ChaChaPoly_BLAKE2s) et interprète la réponse. Le serveur ne répond que si notre
clé publique statique lui est connue et si sa propre clé est celle que nous
croyons : une réponse prouve la configuration, et son déchiffrement dit quelle
clé pré-partagée est en jeu. Aucun tunnel n'est monté, aucun privilège demandé,
aucun état conservé.
Quand le serveur reste muet, une seconde tentative part depuis le port du
serveur. S'il répond alors, c'est qu'un équipement en chemin n'accepte le
trafic que si le port source égale le port de destination — une règle courante
sur FortiGate, dont l'objet Service porte un udp-portrange 51820:51820. Un
client WireGuard émettant depuis un port aléatoire ne peut jamais la
satisfaire, et rien nulle part ne le disait.
Corrige aussi le verdict rendu quand le tunnel est inactif : « chaîne validée »
laissait croire que tout avait été vérifié, alors que les étapes sautées sont
précisément celles qui échouent. Le rapport les nomme désormais.
La fenêtre d'administration revient à 720x600 et les encarts posés au-dessus
des sous-onglets disparaissent, où ils répétaient ce que les sous-onglets
annoncent déjà.
La présence d'une clé pré-partagée se devinait au fait que le champ soit
rempli. Un serveur qui n'en utilise pas — le cas par défaut de WireGuard —
déclenchait donc un avertissement à chaque diagnostic, et une clé oubliée dans
le champ finissait écrite dans la configuration alors que le serveur n'en
attendait aucune, ce qui casse le handshake aussi sûrement qu'une clé
manquante. Une case à cocher tranche désormais, et le diagnostic distingue les
quatre états au lieu d'un avertissement unique.
Le drapeau vaut None par défaut et non False : _merge() recopie le défaut dans
toute configuration antérieure, et False aurait désactivé la PSK d'un tunnel
qui fonctionnait.
Refonte des onglets, qui passaient de sept à cinq :
- WireGuard et Clés se chevauchaient — la clé publique du serveur se saisissait
dans l'un pendant que l'assistant qui la produit vivait dans l'autre, et les
quatre façons de sortir une configuration étaient réparties entre les deux.
Fusionnés en sous-onglets Tunnel, Clés, Profils & fichiers.
- Les trois tests s'empilaient sur une page unique où le rapport de diagnostic
se retrouvait comprimé. Chacun a son sous-onglet.
- MFA et Sécurité répondent à la même question et sont regroupés.
- La section des droits parlait de sudo et de setup-sudoers sous Windows, où
rien de tout cela n'existe, et concluait « privilèges root » sur un rapport
simplement vide. Elle vérifie maintenant l'élévation UAC et WireGuard.
Chaque onglet s'ouvre sur un encart expliquant ce qu'on y décide, et les
couleurs — vingt-quatre valeurs en dur, cinq bandeaux sans rapport de teinte,
des blocs de résultat au fond incohérent — passent par app/ui/theme.py.
Quand la passerelle qui mène au serveur appartient à une plage des « IPs
autorisées », wg-quick pose sa route vers le tunnel avec une métrique de 0.
Elle bat la route de lien de la carte réseau : la passerelle n'est plus
joignable que par le tunnel, alors que le tunnel a besoin d'elle pour sortir.
Le tunnel montait, l'interface existait, et plus aucun paquet de handshake ne
quittait la machine.
Le diagnostic détecte désormais ce cas via _next_hop() (ip route get sous
Linux, Find-NetRoute sous Windows) et le classe en échec, au lieu du simple
avertissement sur l'imprimante et le NAS qui n'en disait pas la gravité.
Corrige aussi le rapport de test de connexion : un QLabel que la mise en page
comprimait à quelques lignes tronquées, remplacé par une zone de texte
défilante ; la fenêtre d'administration, figée à 700x600, devient
redimensionnable.
fix: la clé pré-partagée n'était écrite dans aucune config client
L'assistant de génération produisait une PresharedKey affichée à l'écran mais
stockée nulle part, et l'import d'un .conf ignorait la ligne. Face à un serveur
qui en attend une, le tunnel montait et l'interface existait — sans qu'aucun
handshake n'aboutisse jamais. La PSK devient un champ à part entière.
fix: options de ligne de commande wireguard.exe incorrectes sous Windows
/installtunnel et /uninstalltunnel n'existent pas ; les vrais verbes sont
/installtunnelservice et /uninstalltunnelservice. S'y ajoutaient l'absence de
résolution du chemin d'installation (WireGuard n'est pas dans le PATH),
l'absence totale d'élévation UAC alors que ces commandes l'exigent, et une
détection d'installation qui sondait une application graphique avec --help.
feat: diagnostic de bout en bout de la chaîne de connexion
Le bouton de test s'arrêtait à la joignabilité UDP et concluait « serveur
joignable » sur un tunnel qui n'échangeait rien. Il déroule désormais quatorze
étapes, de la configuration locale jusqu'au DNS dans le tunnel, et désigne
l'étape qui bloque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>