fix: DNS coupée après arrêt du tunnel, gel UI, dialogues sudo à répétition

- 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.
This commit is contained in:
tuxgyver
2026-08-31 11:16:04 +02:00
parent 2fa50a5873
commit 1b74719204
11 changed files with 1295 additions and 233 deletions
+29
View File
@@ -10,6 +10,9 @@ from PyQt6.QtWidgets import QApplication, QMessageBox, QInputDialog, QLineEdit,
from PyQt6.QtCore import Qt
from app.core.config import Config
from app.core import dns as dns_util
from app.core import log as conn_log
from app.core import shutdown as shutdown_guard
from app.ui.main_window import MainWindow
from app.ui.systray import SystemTray
from app.ui import icons
@@ -27,6 +30,23 @@ def _install_taskbar_icon(app_icon) -> None:
pass
def recover_dns_state(config: Config) -> None:
"""Répare la résolution DNS laissée détournée par une session précédente.
Si l'application (ou la machine) s'est arrêtée sans exécuter
`wg-quick down`, la configuration DNS du tunnel reste active alors que le
résolveur correspondant est injoignable : plus aucun nom ne se résout.
Le nettoyage n'a lieu que si un résidu est effectivement constaté.
"""
iface = config.wg.get("interface_name", "wgs0")
try:
repaired, msg = dns_util.repair_if_broken(iface, config.wg.get("dns", ""))
except Exception as e: # noqa: BLE001
repaired, msg = False, f"Vérification DNS impossible : {e}"
if msg:
conn_log.log_event(msg, "warning" if repaired else "error")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="wgsecure",
@@ -85,6 +105,15 @@ def main():
config = Config()
# Reste d'une session précédente mal terminée : DNS détourné vers un
# résolveur mort. À faire avant d'ouvrir la fenêtre pour rendre l'accès
# au réseau tout de suite.
recover_dns_state(config)
# Restauration de l'état réseau garantie sur toutes les sorties :
# Quitter, croix, SIGINT/SIGTERM, atexit, exception non rattrapée.
shutdown_guard.install(config, report=conn_log.log_event, app=app)
# Mode Admin direct (--admin)
if args.admin:
if not require_admin_auth(config, app):