Kør en VPN-handling efter forbindelse som brugeren, ikke som daemonen
En miljøvariabel, der siger USER=alice, ændrer ikke, hvilken konto et hook kører som. Del hændelsen, udløseren og handlingen op, og bevis derefter UID’en.
Forbindelses-hooks på en VPN-daemon kører som daemonen, og det betyder normalt, at de kører som root. I det øjeblik et af disse hooks skriver i nogens hjemmekatalog eller genstarter en skrivebordshjælper, har du givet en netværkshændelse evnen til at handle som en vilkårlig lokal bruger. Den sædvanlige genvej, at læse et brugernavn fra en miljøvariabel og sende det videre, løser ikke det. Den omdøber handlingen uden at ændre, hvilken konto der udfører den. Det, der virker, er en overdragelse i tre dele, hvor det privilegerede trin siger, hvad der skete, og en separat enhed udfører handlingen under den rigtige identitet.
Et brugernavn i miljøet er ikke en identitetskontakt
Unix afgør, hvad en proces må røre, ud fra dens effektive bruger-ID, ikke ud fra strengene i dens miljø. At sætte USER=alice eller eksportere LOGNAME ændrer, hvad skødesløs software udskriver i sin output; det ændrer intet ved filens ejerskabskontroller. Et hook, der kører som root med den variabel sat, åbner stadig Alices filer som root.
Navnene er heller ikke det samme navn. Fjernadgangslogin, den lokale Unix-konto og certificatets fællesnavn er tre identifikatorer, som ofte falder sammen og indimellem ikke. En bruger, der godkender som a.smith, har måske slet ingen lokal konto eller mapper måske til asmith via en katalogtjeneste. Ethvert design, der antager, at én streng tjener alle tre roller, brækker sammen ved den første konsulent.
Så det første spørgsmål er ikke »hvilken bruger er forbundet«, men »hvilken lokal konto har tilladelse til at udføre denne handling«. Besvar det ud fra din egen kontodatabase, ikke ud fra hvad klienten har sendt:
id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json
Den tredje kommando er den, folk springer over. Hvis målfilen ejes af root, fordi det forrige hook oprettede den, vil en korrekt konfigureret brugerhandling fejle med en tilladelsesfejl, der ligner et systemd-problem og ikke er det.
Daemonen bliver, hvor den er
At ændre VPN-tjenestens egen User= til Alice er det forkerte håndtag. Det ændrer, hvordan daemonen binder grænseflader, læser nøgler, skriver sin statusfil og styrer ruter, og det flytter hele den risiko ind i en enhed, der fungerede. Daemonen har brug for sine privilegier; den efterfølgende handling har ikke.
Se, hvad enheden faktisk laver, før du rører ved den. To kommandoer fortæller dig udførelsesidentiteten og de præcise kommandolinjer, der gælder, inklusive alt hvad et override har tilføjet:
systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server
Ved en standard pakkeinstallation er daemonen root, og --up-scriptet arver det. Bekræft det i stedet for at antage det, fordi en hærdet distribution kan have flyttet det til en dedikeret konto, og et håndskrevet drop-in kan køre hooket gennem en wrapper med andre regler.
Tre navngivne dele, én snæver udløser
Den ordning, der overlever vedligeholdelse, har en hændelse, en udløser og en handling. Daemonen rapporterer, at en navngivet session er kommet op. En udløser accepterer den lille kendsgerning og beder om én fast handling. Handlingen kører under den lokale konto, der ejer de filer, den rører. Intet i kæden accepterer en vilkårlig kommandosti.
Handlingen selv er en almindelig serviceenhed, og identiteten er ét direktiv inde i den:
[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i
Bemærk, at User=alice gælder for hele enheden, og systemd dokumenterer én undtagelse, der er værd at kende: en kommando med præfikset + kører med fulde privilegier uanset hvad. Det præfiks er et legitimt værktøj til et enkelt privilegeret trin og en bekvem måde at fortryde den adskillelse, du lige har bygget, så behandl enhver + i den enhed som noget, der skal begrundes i review.
Hold udløserens input minimalt og ikke-hemmeligt. Sessionsidentifikatoren, den mappede konto og et tidsstempel er normalt alt, hvad der er brug for. Send aldrig et shell-fragment, en eksekverbar sti eller en klientleveret miljøblok over grænsen, fordi den modtagende side ikke har nogen måde at skelne en legitim værdi fra en injiceret.
Bekræft den identitet, der faktisk kørte
Konfigurationsgennemgang er ikke bevis. Efter en godkendt testforbindelse, bekræft den UID, der rørte filen, og den UID, daemonen stadig bruger:
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
Den første linje bør vise Alice og et tidsstempel inden for dit testvindue. Den anden bør stadig vise root eller pakkens servicekonto, hvilket beviser, at daemonen ikke flyttede sig. Den tredje er den billigste måde at teste handlingens tilladelser på uden at vente på en rigtig genopkobling.
Hvis filens ejer er Alice, men dens gruppe eller tilstand ændrede sig mellem kørsler, så se efter en UMask= eller en forskel i mappetilladelser i stedet for en logikfejl. Hvis tidsstemplet slet ikke flyttede sig, blev udløseren aldrig udløst, og det nyttige næste skridt er journalctl -u for handlingsenheden i stedet for endnu en klientgenopkobling.
Hvad en forbindelseshændelse stadig ikke beviser
En etableret tunnel er én kendsgerning. Det betyder ikke, at skrivebordssessionen er låst op, at sessionsbussen kan nås, eller at den private ressource, handlingen vil have, er tilgængelig endnu. En oneshot-enhed under Alices UID har hendes identitet og intet af hendes session, så alt, der kræver en grafisk bus, skal gå gennem den understøttede brugersessionsmekanisme og definere, hvad der sker, når hun er logget ud.
Genopkoblinger gentager sig også. En bærbar computer, der sover i et tog, kan producere et dusin forbindelseshændelser på en time, og et hook, der tilføjer en linje hver gang, gør en statusfil til en log, ingen kan læse. Gør handlingen idempotent, eller lad den erstatte en enkelt post med nøgle på session.
Endelig skal du begrænse skadesradius i selve enheden: en skrivebeskyttet visning af filsystemet ud over statusmappen, ingen nye privilegier og en eksplicit timeout. Adskillelsen mellem daemon og brugerhandling er værd at have i sig selv, men den er meget mere værd, når handlingen ikke kan gøre andet end den ene ting, du godkendte.