Verbindingshooks op een VPN-daemon draaien als de daemon, en dat betekent meestal dat ze als root draaien. Op het moment dat een van die hooks in iemands thuismap schrijft of een desktophulp opnieuw start, heb je een netwerkgebeurtenis de mogelijkheid gegeven om als een willekeurige lokale gebruiker te handelen. De gebruikelijke sluiproute, een gebruikersnaam uit een omgevingsvariabele lezen en doorgeven, lost dat niet op. Ze herlabelt de actie zonder te veranderen welk account haar uitvoert. Wat werkt, is een overdracht in drie delen, waarbij de bevoorrechte stap zegt wat er is gebeurd en een aparte eenheid de handeling onder de juiste identiteit verricht.

Een gebruikersnaam in de omgeving is geen identiteitsschakelaar

Unix beslist waar een proces aan mag komen op basis van zijn effectieve gebruikers-ID, niet op basis van de strings in zijn omgeving. USER=alice instellen of LOGNAME exporteren verandert wat slordige software in zijn uitvoer afdrukt; het verandert niets aan de controles op bestandseigendom. Een hook die als root draait met die variabele gezet, opent Alices bestanden nog steeds als root.

De namen zijn ook niet dezelfde naam. De aanmelding voor toegang op afstand, het lokale Unix-account en de algemene naam van het certificaat zijn drie identificatoren die vaak samenvallen en soms niet. Een gebruiker die zich authenticeert als a.smith heeft misschien helemaal geen lokaal account, of wordt via een directorydienst aan asmith gekoppeld. Elk ontwerp dat aanneemt dat één string alle drie de rollen dient, breekt bij de eerste contractant.

De eerste vraag is dus niet «welke gebruiker is verbonden» maar «welk lokaal account mag deze actie uitvoeren». Beantwoord dat vanuit je eigen accountdatabase, niet vanuit wat de client heeft gestuurd:

id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json

Het derde commando is degene die mensen overslaan. Als het doelbestand eigendom is van root omdat de vorige hook het heeft aangemaakt, mislukt een correct geconfigureerde gebruikersactie met een toestemmingsfout die op een systemd-probleem lijkt en het niet is.

De daemon blijft waar hij is

De eigen User= van de VPN-dienst naar Alice veranderen is de verkeerde hefboom. Het verandert hoe de daemon interfaces bindt, sleutels leest, zijn statusbestand schrijft en routes beheert, en het verplaatst al dat risico naar een eenheid die werkte. De daemon heeft zijn privileges nodig; de vervolgactie niet.

Kijk wat de eenheid werkelijk doet voordat je haar aanraakt. Twee commando's vertellen je de uitvoeringsidentiteit en de exacte commandoregels die gelden, inclusief alles wat een override heeft toegevoegd:

systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server

Bij een standaard pakketinstallatie is de daemon root en het --up-script erft dat. Bevestig het in plaats van het aan te nemen, want een geharde distributie heeft het misschien naar een toegewijd account verplaatst, en een met de hand geschreven drop-in kan de hook via een wrapper met andere regels laten lopen.

Drie benoemde delen, één smalle trigger

De opstelling die onderhoud overleeft, heeft een gebeurtenis, een trigger en een actie. De daemon meldt dat een benoemde sessie omhoog is gekomen. Een trigger accepteert dat kleine feit en vraagt om één vaste actie. De actie draait onder het lokale account dat eigenaar is van de bestanden die ze aanraakt. Niets in de keten accepteert een willekeurig commandopad.

De actie zelf is een gewone service-eenheid, en de identiteit is één richtlijn erin:

[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i

Merk op dat User=alice voor de hele eenheid geldt, en systemd documenteert één uitzondering die het kennen waard is: een commando met het voorvoegsel + draait ongeacht daarvan met volledige privileges. Dat voorvoegsel is een legitiem hulpmiddel voor één bevoorrechte stap en een handige manier om de scheiding die je net hebt gebouwd ongedaan te maken, dus behandel elke + in die eenheid als iets wat in de beoordeling moet worden gerechtvaardigd.

Houd de invoer van de trigger minimaal en niet-geheim. De sessie-identificatie, het gekoppelde account en een tijdstempel zijn meestal alles wat nodig is. Geef nooit een shellfragment, een pad naar een uitvoerbaar bestand of een door de client geleverd omgevingsblok over de grens, want de ontvangende kant kan een legitieme waarde niet van een geïnjecteerde onderscheiden.

Controleer de identiteit die werkelijk heeft gedraaid

Configuratiebeoordeling is geen bewijs. Bevestig na een goedgekeurde testverbinding de UID die het bestand heeft aangeraakt en de UID die de daemon nog gebruikt:

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

De eerste regel zou Alice en een tijdstempel binnen je testvenster moeten tonen. De tweede zou nog steeds root of het serviceaccount van het pakket moeten tonen, wat bewijst dat de daemon niet is verschoven. De derde is de goedkoopste manier om de rechten van de actie te testen zonder op een echte herverbinding te wachten.

Als de eigenaar van het bestand Alice is maar de groep of modus is tussen runs veranderd, zoek dan naar een UMask= of een verschil in maprechten in plaats van een logische fout. Als de tijdstempel helemaal niet is verschoven, is de trigger nooit afgegaan, en de nuttige volgende stap is journalctl -u voor de actie-eenheid in plaats van nog een herverbinding van de client.

Wat een verbindingsgebeurtenis nog steeds niet bewijst

Een opgezette tunnel is één feit. Het betekent niet dat de desktopsessie ontgrendeld is, de sessiebus bereikbaar is of dat de privébron die de actie wil al beschikbaar is. Een oneshot-eenheid onder Alices UID heeft haar identiteit en niets van haar sessie, dus alles wat een grafische bus nodig heeft, moet via het ondersteunde gebruikerssessiemechanisme gaan en definiëren wat er gebeurt als zij is uitgelogd.

Herverbindingen herhalen zich ook. Een laptop die in de trein slaapt, kan een dozijn verbindingsgebeurtenissen per uur produceren, en een hook die elke keer een regel toevoegt, maakt van een statusbestand een log dat niemand kan lezen. Maak de actie idempotent, of laat haar één record met sleutel op sessie vervangen.

Beperk ten slotte de schadecirkel in de eenheid zelf: een alleen-lezen bestandssysteemweergave buiten de statusmap, geen nieuwe privileges en een expliciete time-out. De scheiding tussen daemon en gebruikersactie is op zichzelf de moeite waard, maar ze is veel meer waard wanneer de actie niets anders kan doen dan het ene dat je hebt goedgekeurd.

Gerelateerde artikelen