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>
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 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>
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>
- 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>
- wg-quick down échoue silencieusement si l'interface a déjà disparu
(crash, kill, veille) et ne retire donc jamais l'entrée DNS posée par
wg-quick up : la résolution reste pointée sur un résolveur mort.
Ajout d'un nettoyage DNS inconditionnel (app/core/dns.py) déclenché à
la connexion, la déconnexion, la fermeture et au démarrage suivant.
- is_connected() se basait sur `wg show`, qui rend un code 0 même en cas
d'échec de permission pour un utilisateur non-root : l'app se croyait
déconnectée en permanence et ne démontait donc jamais le tunnel à la
fermeture. Détection réécrite via sysfs (sans privilèges).
- Connexion/déconnexion et ping tournaient sur le thread Qt : gel de
l'UI pendant les élévations de privilèges. Déportés dans des
QThread (app/ui/worker.py).
- Restauration réseau garantie sur toutes les sorties (croix, tray,
SIGINT/SIGTERM, atexit, exception) via app/core/shutdown.py.
- run_privileged() : la détection "sudo veut un mot de passe" ratait en
session non-anglophone et sur les comptes ALL=(ALL) ALL ; réécrite
pour se fier au message d'erreur réel plutôt qu'à `sudo -l`.
- Écriture de la config WireGuard dans /etc/wireguard (root:root 0700)
passe maintenant par install -D via la chaîne de privilèges complète
au lieu d'un tee non autorisé par les sudoers.
- Ajout d'un diagnostic des droits (make check-privileges, bouton
Admin) et mise à jour de setup-sudoers pour couvrir toutes les
commandes désormais utilisées.