v0.7.0 : instance unique, handshake ré-affiché, sudoers fiabilisé, À propos refaite

- 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é.
This commit is contained in:
2026-09-01 19:18:24 +02:00
parent 6251a3ea8e
commit 8c09c02362
13 changed files with 590 additions and 104 deletions
+51 -8
View File
@@ -280,6 +280,23 @@ def run_privileged(cmd: list[str], timeout: int = 60) -> tuple[int, str, str]:
)
def run_privileged_readonly(cmd: list[str], timeout: int = 10) -> tuple[int, str, str]:
"""Comme `run_privileged`, mais pour un sondage périodique en lecture seule.
N'ouvre jamais de dialogue (askpass/pkexec/UAC) : un sondage lancé toutes
les quelques secondes (état du tunnel, handshake) rendrait l'application
inutilisable si chaque appel pouvait faire apparaître une invite. Ne
fonctionne donc que si le process est déjà root/administrateur, ou si la
commande est couverte par une règle sudo NOPASSWD (`make setup-sudoers`).
Échoue silencieusement sinon, comme un manque de privilèges ordinaire.
"""
if has_root_privileges():
return run_command(cmd, timeout)
if is_windows():
return -1, "", "Élévation requise"
return run_command(["sudo", "-n"] + cmd, timeout, env=_c_locale_env())
def _find_askpass() -> str:
"""Cherche un programme askpass graphique disponible."""
candidates = [
@@ -302,13 +319,39 @@ def _find_askpass() -> str:
# Commandes que WGSecure lance avec des privilèges, et ce qu'elles servent.
_PRIVILEGED_COMMANDS = (
(["wg-quick", "--help"], "monter / démonter le tunnel"),
(["install", "--version"], "écrire la config dans /etc/wireguard"),
(["ip", "-V"], "supprimer une interface orpheline"),
(["resolvconf", "--version"], "retirer l'entrée DNS du tunnel"),
(["resolvectl", "--version"], "purger le DNS systemd-resolved"),
)
#
# `make setup-sudoers` installe des règles NOPASSWD restreintes à des motifs
# d'arguments précis (ex. `ip link delete dev *`), jamais à la commande nue.
# Les sondes doivent donc reproduire un appel réel qui matche ce motif :
# tester avec `--version`/`-V` interroge un appel que sudoers n'autorise
# jamais, et affiche « dialogue à chaque appel » même quand tout est
# correctement configuré.
_PRIVILEGE_PROBE_IFACE = "wgsecure-privcheck0"
def _install_probe_cmd() -> list[str]:
"""Reproduit le motif `install -D -m 600 -o root -g root * /etc/wireguard/*`.
Écrit un fichier vide et sans conséquence (`.wgsecure-privcheck`, jamais
lu par wg-quick) : c'est le seul moyen fiable de tester ce motif, `sudo
-n -l` ne le pouvant pas (cf. `_sudo_refused_auth`).
"""
dest = os.path.join(get_wg_config_dir(), ".wgsecure-privcheck")
return ["install", "-D", "-m", "600", "-o", "root", "-g", "root",
"/dev/null", dest]
def _privileged_commands() -> tuple[tuple[list[str], str], ...]:
return (
(["wg-quick", "--help"], "monter / démonter le tunnel"),
(_install_probe_cmd(), "écrire la config dans /etc/wireguard"),
(["ip", "link", "delete", "dev", _PRIVILEGE_PROBE_IFACE],
"supprimer une interface orpheline"),
(["resolvconf", "-d", _PRIVILEGE_PROBE_IFACE],
"retirer l'entrée DNS du tunnel"),
(["resolvectl", "revert", _PRIVILEGE_PROBE_IFACE],
"purger le DNS systemd-resolved"),
)
def privilege_report() -> list[tuple[str, bool, str]]:
@@ -322,7 +365,7 @@ def privilege_report() -> list[tuple[str, bool, str]]:
report: list[tuple[str, bool, str]] = []
if is_windows() or has_root_privileges():
return report
for probe, role in _PRIVILEGED_COMMANDS:
for probe, role in _privileged_commands():
if not _sh.which(probe[0]):
continue
code, _, err = run_command(["sudo", "-n"] + probe, 8, env=_c_locale_env())