Kjør en VPN-handling etter tilkobling som brukeren, ikke som daemonen
En miljøvariabel som sier USER=alice endrer ikke hvilken konto et hook kjører som. Del opp hendelsen, utløseren og handlingen, og bevis deretter UID-en.
Tilkoblings-hooks på en VPN-daemon kjører som daemonen, og det betyr vanligvis at de kjører som root. I det øyeblikket en av disse hookene skriver i noens hjemmekatalog eller starter en skrivebordshjelper på nytt, har du gitt en nettverkshendelse evnen til å handle som en vilkårlig lokal bruker. Den vanlige snarveien, å lese et brukernavn fra en miljøvariabel og sende det videre, fikser ikke dette. Den merker bare handlingen på nytt uten å endre hvilken konto som utfører den. Det som virker, er en overdragelse i tre deler, der det privilegerte trinnet sier hva som skjedde, og en egen enhet utfører handlingen under riktig identitet.
Et brukernavn i miljøet er ikke en identitetsbryter
Unix avgjør hva en prosess får røre ut fra sin effektive bruker-ID, ikke ut fra strengene i miljøet sitt. Å sette USER=alice eller eksportere LOGNAME endrer hva uforsiktig programvare skriver ut i utdataene; det endrer ingenting i eierskapskontrollene av filer. En hook som kjører som root med den variabelen satt, åpner fortsatt Alices filer som root.
Navnene er heller ikke det samme navnet. Fjerninnloggingen, den lokale Unix-kontoen og sertifikatets vanlige navn er tre identifikatorer som ofte sammenfaller og noen ganger ikke. En bruker som autentiserer seg som a.smith, har kanskje ingen lokal konto i det hele tatt, eller mappes til asmith via en katalogtjeneste. Ethvert design som antar at én streng tjener alle tre rollene, vil bryte sammen hos den første innleide konsulenten.
Så det første spørsmålet er ikke «hvilken bruker er tilkoblet», men «hvilken lokal konto har lov til å utføre denne handlingen». Svar på det fra din egen kontodatabase, ikke fra det klienten sendte:
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 kommandoen er den folk hopper over. Hvis målfilen eies av root fordi den forrige hooken opprettet den, vil en riktig konfigurert brukerhandling feile med en tilgangsfeil som ser ut som et systemd-problem og ikke er det.
Daemonen blir der den er
Å endre VPN-tjenestens egen User= til Alice er feil spak. Det endrer hvordan daemonen binder grensesnitt, leser nøkler, skriver statusfilen sin og administrerer ruter, og det flytter all denne risikoen inn i en enhet som fungerte. Daemonen trenger privilegiene sine; oppfølgingshandlingen gjør det ikke.
Se hva enheten faktisk gjør før du rører den. To kommandoer forteller deg kjøreidentiteten og de nøyaktige kommandolinjene som gjelder, inkludert alt en overstyring har lagt til:
systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server
Ved en standard pakkeinstallasjon er daemonen root, og --up-skriptet arver det. Bekreft det i stedet for å anta det, fordi en herdet distribusjon kan ha flyttet det til en dedikert konto, og en håndskrevet drop-in kan kjøre hooken gjennom en innpakning med andre regler.
Tre navngitte deler, én smal utløser
Ordningen som overlever vedlikehold, har en hendelse, en utløser og en handling. Daemonen rapporterer at en navngitt økt er oppe. En utløser godtar det lille faktum og ber om én fast handling. Handlingen kjører under den lokale kontoen som eier filene den berører. Ingenting i kjeden godtar en vilkårlig kommandobane.
Selve handlingen er en vanlig tjenesteenhet, og identiteten er ett direktiv inne i den:
[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i
Legg merke til at User=alice gjelder for hele enheten, og systemd dokumenterer ett unntak som er verdt å kjenne: en kommando med prefikset + kjører med fulle privilegier uansett. Det prefikset er et legitimt verktøy for ett enkelt privilegert trinn og en praktisk måte å oppheve separasjonen du nettopp bygde, så behandle hvilken som helst + i den enheten som noe som må begrunnes i gjennomgangen.
Hold inndataene til utløseren minimale og ikke-hemmelige. Øktidentifikatoren, den mappede kontoen og et tidsstempel er vanligvis alt som trengs. Send aldri et shell-fragment, en kjørbar filbane eller en klientlevert miljøblokk over grensen, fordi mottakersiden ikke har noen måte å skille en legitim verdi fra en injisert.
Verifiser identiteten som faktisk kjørte
Konfigurasjonsgjennomgang er ikke bevis. Etter en godkjent testtilkobling, bekreft UID-en som rørte filen, og UID-en som daemonen fortsatt bruker:
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 linjen bør vise Alice og et tidsstempel innenfor testvinduet ditt. Den andre bør fortsatt vise root eller pakkens tjenestekonto, noe som beviser at daemonen ikke flyttet seg. Den tredje er den billigste måten å teste handlingens tillatelser på uten å vente på en ekte gjenoppkobling.
Hvis filens eier er Alice, men gruppen eller modusen endret seg mellom kjøringer, se etter en UMask= eller en forskjell i katalogtillatelser i stedet for en logikkfeil. Hvis tidsstemplet ikke flyttet seg i det hele tatt, ble utløseren aldri utløst, og det nyttige neste trinnet er journalctl -u for handlingsenheten i stedet for nok en klientgjenoppkobling.
Hva en tilkoblingshendelse fortsatt ikke beviser
En etablert tunnel er ett faktum. Det betyr ikke at skrivebordsøkten er låst opp, at øktbussen er tilgjengelig, eller at den private ressursen handlingen vil ha, allerede er tilgjengelig. En oneshot-enhet under Alices UID har identiteten hennes og ingenting av økten hennes, så alt som krever en grafisk buss, må gå gjennom den støttede brukerøktmekanismen og definere hva som skjer når hun er logget ut.
Gjenoppkoblinger gjentar seg også. En bærbar pc som sover på et tog, kan produsere et dusin tilkoblingshendelser på en time, og en hook som legger til en linje hver gang, gjør en statusfil til en logg ingen kan lese. Gjør handlingen idempotent, eller la den erstatte én post med nøkkel på økt.
Til slutt, begrens skaderadiusen i selve enheten: en skrivebeskyttet visning av filsystemet utenfor statuskatalogen, ingen nye privilegier og en eksplisitt tidsavbrudd. Separasjonen mellom daemon og brukerhandling er verdt noe i seg selv, men den er verdt mye mer når handlingen ikke kan gjøre noe annet enn det ene du godkjente.