feat(windows): tunnel installé une fois, puis démarré — et code validé par le serveur
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>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Service Windows privilégié pour WGSecure.
|
||||
|
||||
Installe/désinstalle le tunnel WireGuard, pose/retire les règles NRPT
|
||||
Installe, démarre, arrête et désinstalle le tunnel WireGuard, pose/retire les règles NRPT
|
||||
(split-DNS) et lit l'état du tunnel (`wg show`) sur demande de l'app — non
|
||||
privilégiée — pour éviter une invite d'authentification à chaque connexion
|
||||
et déconnexion. Tourne en LocalSystem.
|
||||
@@ -11,7 +11,7 @@ le pipe nommé (`app/utils/helper_ipc.py`, ctypes pur, sans dépendance à
|
||||
pywin32).
|
||||
|
||||
Invariant de sécurité central : le pipe reste accessible à tout process
|
||||
d'un utilisateur local, pas seulement WGSecure — donc CINQ commandes fixes
|
||||
d'un utilisateur local, pas seulement WGSecure — donc SEPT commandes fixes
|
||||
seulement, jamais de script/commande arbitraire. Chaque commande valide
|
||||
strictement ses paramètres avant d'exécuter quoi que ce soit.
|
||||
"""
|
||||
@@ -183,6 +183,25 @@ def _handle_uninstall_tunnel(req: dict, handle) -> dict:
|
||||
return {"ok": code == 0, "message": msg}
|
||||
|
||||
|
||||
def _handle_start_tunnel_service(req: dict, handle) -> dict:
|
||||
"""Démarre le service d'un tunnel déjà installé.
|
||||
|
||||
Complète install/uninstall : dans le fonctionnement courant le tunnel
|
||||
est installé une fois, puis seulement démarré et arrêté. Le nom est
|
||||
validé, et le préfixe « WireGuardTunnel$ » est ajouté ici — l'appelant
|
||||
ne choisit donc jamais quel service piloter.
|
||||
"""
|
||||
name = _validate_interface_name(req.get("name"))
|
||||
code, msg = _run(["sc.exe", "start", f"WireGuardTunnel${name}"], timeout=45)
|
||||
return {"ok": code == 0, "message": msg}
|
||||
|
||||
|
||||
def _handle_stop_tunnel_service(req: dict, handle) -> dict:
|
||||
name = _validate_interface_name(req.get("name"))
|
||||
code, msg = _run(["sc.exe", "stop", f"WireGuardTunnel${name}"], timeout=45)
|
||||
return {"ok": code == 0, "message": msg}
|
||||
|
||||
|
||||
def _handle_set_split_dns(req: dict, handle) -> dict:
|
||||
server = _validate_server(req.get("server"))
|
||||
domains = _validate_domains(req.get("domains"))
|
||||
@@ -217,6 +236,8 @@ def _handle_get_status(req: dict, handle) -> dict:
|
||||
_HANDLERS = {
|
||||
"install_tunnel": _handle_install_tunnel,
|
||||
"uninstall_tunnel": _handle_uninstall_tunnel,
|
||||
"start_tunnel_service": _handle_start_tunnel_service,
|
||||
"stop_tunnel_service": _handle_stop_tunnel_service,
|
||||
"set_split_dns": _handle_set_split_dns,
|
||||
"clear_split_dns": _handle_clear_split_dns,
|
||||
"get_status": _handle_get_status,
|
||||
|
||||
Reference in New Issue
Block a user