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:
@@ -9,6 +9,7 @@ from PyQt6.QtGui import QFont
|
||||
|
||||
from app.core.config import Config
|
||||
from app.core import wireguard as wg_core
|
||||
from app.core import dns as dns_util
|
||||
from app.core import mfa as mfa_core
|
||||
|
||||
# CSS sombre commun à tous les onglets
|
||||
@@ -465,6 +466,13 @@ class AdminWindow(QDialog):
|
||||
btn_dns = QPushButton("🔍 Analyser le DNS")
|
||||
btn_dns.clicked.connect(self._run_dns_test)
|
||||
g2.addWidget(btn_dns)
|
||||
btn_dns_fix = QPushButton("🛠️ Réparer le DNS (tunnel arrêté)")
|
||||
btn_dns_fix.setToolTip(
|
||||
"Retire la configuration DNS laissée par un tunnel arrêté "
|
||||
"brutalement, qui empêche toute résolution de noms."
|
||||
)
|
||||
btn_dns_fix.clicked.connect(self._repair_dns)
|
||||
g2.addWidget(btn_dns_fix)
|
||||
self._dns_result = QLabel("")
|
||||
self._dns_result.setWordWrap(True)
|
||||
self._dns_result.setStyleSheet(
|
||||
@@ -472,6 +480,24 @@ class AdminWindow(QDialog):
|
||||
)
|
||||
g2.addWidget(self._dns_result)
|
||||
lay.addWidget(grp2)
|
||||
|
||||
# Droits d'exécution
|
||||
grp3 = QGroupBox("🔐 Droits d'exécution")
|
||||
g3 = QVBoxLayout(grp3)
|
||||
g3.addWidget(QLabel(
|
||||
"Sans règle sudo, chaque connexion ouvre un dialogue d'authentification."
|
||||
))
|
||||
btn_priv = QPushButton("🔐 Vérifier les droits")
|
||||
btn_priv.clicked.connect(self._check_privileges)
|
||||
g3.addWidget(btn_priv)
|
||||
self._priv_result = QLabel("")
|
||||
self._priv_result.setWordWrap(True)
|
||||
self._priv_result.setStyleSheet(
|
||||
"color: white; padding: 8px; border-radius: 6px; font-size: 11px;"
|
||||
)
|
||||
g3.addWidget(self._priv_result)
|
||||
lay.addWidget(grp3)
|
||||
|
||||
lay.addStretch()
|
||||
return w
|
||||
|
||||
@@ -948,6 +974,12 @@ class AdminWindow(QDialog):
|
||||
elif status == "leak":
|
||||
lines.append("⚠️ Fuite DNS potentielle !")
|
||||
css = "background: rgba(231,76,60,0.2); color: #f1948a;"
|
||||
elif status == "down":
|
||||
lines.append("🚫 Résolution DNS hors service — utilisez « Réparer le DNS »")
|
||||
css = "background: rgba(231,76,60,0.2); color: #f1948a;"
|
||||
elif not r.get("tunnel_up", False):
|
||||
lines.append("ℹ️ Tunnel inactif : la comparaison n'a pas de sens")
|
||||
css = "color: #aed6f1;"
|
||||
else:
|
||||
lines.append("❓ Statut inconnu")
|
||||
css = "color: #aed6f1;"
|
||||
@@ -962,6 +994,71 @@ class AdminWindow(QDialog):
|
||||
f"padding: 8px; border-radius: 6px; font-size: 11px; {css}"
|
||||
)
|
||||
|
||||
def _check_privileges(self):
|
||||
"""Liste les commandes privilégiées et celles qui exigeront un dialogue."""
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from app.utils.platform_utils import privilege_report
|
||||
self._priv_result.setText("Vérification en cours…")
|
||||
QApplication.processEvents()
|
||||
|
||||
rows = privilege_report()
|
||||
if not rows:
|
||||
self._priv_result.setText(
|
||||
"✅ Exécution avec les privilèges root : rien à configurer."
|
||||
)
|
||||
css = "background: rgba(39,174,96,0.2); color: #a9dfbf;"
|
||||
else:
|
||||
lines = [
|
||||
("✅ " if ok else "⚠️ ") + f"{name} — {role}"
|
||||
+ ("" if ok else " (dialogue à chaque appel)")
|
||||
for name, ok, role in rows
|
||||
]
|
||||
missing = [n for n, ok, _ in rows if not ok]
|
||||
if missing:
|
||||
lines.append("")
|
||||
lines.append("Pour supprimer ces dialogues : make setup-sudoers")
|
||||
css = "background: rgba(241,196,15,0.2); color: #f9e79f;"
|
||||
else:
|
||||
css = "background: rgba(39,174,96,0.2); color: #a9dfbf;"
|
||||
self._priv_result.setText("\n".join(lines))
|
||||
self._priv_result.setStyleSheet(
|
||||
f"padding: 8px; border-radius: 6px; font-size: 11px; {css}"
|
||||
)
|
||||
|
||||
def _repair_dns(self):
|
||||
"""Retire la configuration DNS résiduelle d'un tunnel mal arrêté.
|
||||
|
||||
`wg-quick down` refuse de s'exécuter dès que l'interface a disparu et
|
||||
ne retire donc jamais l'entrée DNS qu'il avait posée : la résolution
|
||||
reste dirigée vers un serveur injoignable.
|
||||
"""
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
self._dns_result.setText("Réparation en cours…")
|
||||
QApplication.processEvents()
|
||||
|
||||
actions = wg_core.force_cleanup(self._cfg)
|
||||
works = dns_util.resolution_works()
|
||||
|
||||
if works:
|
||||
msg = "✅ Résolution DNS fonctionnelle"
|
||||
if actions:
|
||||
msg += "\nActions : " + ", ".join(actions)
|
||||
css = "background: rgba(39,174,96,0.2); color: #a9dfbf;"
|
||||
elif actions:
|
||||
msg = ("⚠️ Résidus retirés (" + ", ".join(actions) +
|
||||
") mais la résolution échoue toujours.\n"
|
||||
"Vérifiez la connexion réseau physique.")
|
||||
css = "background: rgba(241,196,15,0.2); color: #f9e79f;"
|
||||
else:
|
||||
msg = ("🚫 Aucun résidu du tunnel détecté et la résolution échoue :"
|
||||
" le problème vient du réseau, pas de WGSecure.")
|
||||
css = "background: rgba(231,76,60,0.2); color: #f1948a;"
|
||||
|
||||
self._dns_result.setText(msg)
|
||||
self._dns_result.setStyleSheet(
|
||||
f"padding: 8px; border-radius: 6px; font-size: 11px; {css}"
|
||||
)
|
||||
|
||||
def _set_admin_password(self):
|
||||
p1 = self._admin_pw1.text()
|
||||
p2 = self._admin_pw2.text()
|
||||
|
||||
Reference in New Issue
Block a user