Exécuter une action après connexion VPN en tant qu’utilisateur, pas en tant que démon
Une variable d’environnement qui dit USER=alice ne change pas le compte sous lequel un hook s’exécute. Séparez l’événement, le déclencheur et l’action, puis prouvez l’UID.
Les hooks de connexion d’un démon VPN s’exécutent en tant que démon, et cela veut généralement dire en tant que root. À l’instant où l’un de ces hooks écrit dans le répertoire personnel de quelqu’un ou redémarre un assistant de bureau, vous avez donné à un événement réseau la capacité d’agir en tant qu’utilisateur local arbitraire. Le raccourci habituel, lire un nom d’utilisateur dans une variable d’environnement et le transmettre, ne corrige pas cela. Il réétiquette l’action sans changer le compte qui l’exécute. Ce qui fonctionne est une remise en trois parties où l’étape privilégiée dit ce qui s’est passé et une unité distincte accomplit l’action sous la bonne identité.
Un nom d’utilisateur dans l’environnement n’est pas un commutateur d’identité
Unix décide de ce qu’un processus peut toucher d’après son identifiant utilisateur effectif, pas d’après les chaînes de son environnement. Définir USER=alice ou exporter LOGNAME change ce qu’un logiciel négligent imprime dans sa sortie ; cela ne change rien aux contrôles de propriété des fichiers. Un hook exécuté en root avec cette variable définie ouvre toujours les fichiers d’Alice en root.
Les noms ne sont pas non plus le même nom. L’identifiant d’accès distant, le compte Unix local et le nom commun du certificat sont trois identifiants qui coïncident souvent et parfois pas. Un utilisateur qui s’authentifie comme a.smith peut n’avoir aucun compte local, ou être mappé vers asmith via un service d’annuaire. Toute conception qui suppose qu’une seule chaîne sert les trois rôles cassera au premier sous-traitant.
La première question n’est donc pas « quel utilisateur est connecté » mais « quel compte local est autorisé à effectuer cette action ». Répondez-y depuis votre propre base de comptes, pas depuis ce que le client a envoyé :
id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json
La troisième commande est celle que les gens sautent. Si le fichier cible appartient à root parce que le hook précédent l’a créé, une action utilisateur correctement configurée échouera avec une erreur de permission qui ressemble à un problème systemd et n’en est pas un.
Le démon reste là où il est
Changer le User= du service VPN lui-même pour Alice est le mauvais levier. Cela modifie la façon dont le démon lie les interfaces, lit les clés, écrit son fichier d’état et gère les routes, et cela déplace tout ce risque dans une unité qui fonctionnait. Le démon a besoin de ses privilèges ; l’action qui suit non.
Regardez ce que fait réellement l’unité avant d’y toucher. Deux commandes vous donnent l’identité d’exécution et les lignes de commande exactes en vigueur, y compris tout ce qu’une surcharge a ajouté :
systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server
Sur une installation de paquet par défaut, le démon est root et le script --up hérite de cela. Confirmez-le plutôt que de le supposer, car une distribution durcie a pu le déplacer vers un compte dédié, et un drop-in écrit à la main peut lancer le hook via un wrapper aux règles différentes.
Trois parties nommées, un déclencheur étroit
L’agencement qui survit à la maintenance comporte un événement, un déclencheur et une action. Le démon signale qu’une session nommée est montée. Un déclencheur accepte ce petit fait et demande une action fixe. L’action s’exécute sous le compte local qui possède les fichiers qu’elle touche. Rien dans la chaîne n’accepte un chemin de commande arbitraire.
L’action elle-même est une unité de service ordinaire, et l’identité est une seule directive à l’intérieur :
[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i
Notez que User=alice s’applique à toute l’unité, et systemd documente une exception qu’il vaut la peine de connaître : une commande préfixée par + s’exécute avec tous les privilèges quoi qu’il arrive. Ce préfixe est un outil légitime pour une seule étape privilégiée et un moyen commode d’annuler la séparation que vous venez de construire, donc traitez tout + dans cette unité comme quelque chose à justifier en revue.
Gardez l’entrée du déclencheur minimale et non secrète. L’identifiant de session, le compte mappé et un horodatage suffisent généralement. Ne faites jamais passer un fragment de shell, un chemin d’exécutable ou un bloc d’environnement fourni par le client à travers la frontière, car le côté récepteur n’a aucun moyen de distinguer une valeur légitime d’une valeur injectée.
Vérifiez l’identité qui s’est réellement exécutée
La revue de configuration n’est pas une preuve. Après une connexion de test approuvée, confirmez l’UID qui a touché le fichier et l’UID que le démon utilise encore :
stat -c '%U %G %y %n' /home/alice/.local/state/vpn-status.json
ps -o user=,pid=,cmd= -p "$(pgrep -o -f 'openvpn --daemon')"
systemd-run --uid=alice --pipe /usr/local/bin/refresh-vpn-status --dry-run
La première ligne devrait montrer Alice et un horodatage dans votre fenêtre de test. La seconde devrait toujours montrer root ou le compte de service du paquet, ce qui prouve que le démon n’a pas bougé. La troisième est le moyen le moins coûteux de tester les permissions de l’action sans attendre une vraie reconnexion.
Si le propriétaire du fichier est Alice mais que son groupe ou son mode a changé entre deux exécutions, cherchez un UMask= ou une différence de permissions de répertoire plutôt qu’une erreur de logique. Si l’horodatage n’a pas bougé du tout, le déclencheur ne s’est jamais déclenché, et la prochaine étape utile est journalctl -u pour l’unité d’action plutôt qu’une nouvelle reconnexion du client.
Ce qu’un événement de connexion ne prouve toujours pas
Un tunnel établi est un seul fait. Cela ne signifie pas que la session de bureau est déverrouillée, que le bus de session est joignable ou que la ressource privée que veut l’action est déjà disponible. Une unité oneshot sous l’UID d’Alice a son identité et rien de sa session, donc tout ce qui exige un bus graphique doit passer par le mécanisme de session utilisateur pris en charge et définir ce qui se passe lorsqu’elle est déconnectée.
Les reconnexions se répètent aussi. Un portable qui dort dans un train peut produire une douzaine d’événements de connexion en une heure, et un hook qui ajoute une ligne à chaque fois transforme un fichier d’état en journal que personne ne peut lire. Rendez l’action idempotente, ou laissez-la remplacer un seul enregistrement clé par session.
Enfin, bornez le rayon de dégâts dans l’unité elle-même : une vue du système de fichiers en lecture seule en dehors du répertoire d’état, aucun nouveau privilège et un délai d’expiration explicite. La séparation entre le démon et l’action utilisateur vaut la peine en soi, mais elle vaut bien plus quand l’action ne peut rien faire d’autre que la seule chose que vous avez approuvée.