Hook-urile de conectare de pe un daemon VPN rulează ca daemon, iar asta înseamnă de obicei că rulează ca root. În momentul în care unul dintre aceste hook-uri scrie în directorul home al cuiva sau repornește un helper de desktop, ați dat unui eveniment de rețea capacitatea de a acționa ca orice utilizator local. Scurtătura obișnuită, să citiți un nume de utilizator dintr-o variabilă de mediu și să-l transmiteți mai departe, nu rezolvă asta. Ea doar reetichetează acțiunea fără a schimba contul care o execută. Ce funcționează este o predare în trei părți, în care pasul privilegiat spune ce s-a întâmplat, iar o unitate separată execută acțiunea sub identitatea corectă.

Un nume de utilizator din mediu nu este un comutator de identitate

Unix decide la ce poate atinge un proces după ID-ul său efectiv de utilizator, nu după șirurile din mediul său. Setarea USER=alice sau exportarea LOGNAME schimbă ce tipărește un software neatent în ieșirea sa; nu schimbă nimic în verificările de proprietate ale fișierelor. Un hook care rulează ca root cu acea variabilă setată deschide în continuare fișierele lui Alice ca root.

Nici numele nu sunt același nume. Autentificarea pentru acces la distanță, contul Unix local și numele comun al certificatului sunt trei identificatori care coincid adesea și uneori nu. Un utilizator care se autentifică drept a.smith poate să nu aibă deloc cont local sau poate fi mapat la asmith printr-un serviciu de director. Orice design care presupune că un singur șir servește toate cele trei roluri se va rupe la primul contractant.

Deci prima întrebare nu este „care utilizator este conectat”, ci „cărui cont local îi este permis să execute această acțiune”. Răspundeți la asta din propria bază de date de conturi, nu din ce a trimis clientul:

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

A treia comandă este cea pe care oamenii o sar. Dacă fișierul țintă aparține lui root pentru că hook-ul anterior l-a creat, o acțiune de utilizator configurată corect va eșua cu o eroare de permisiune care arată ca o problemă systemd și nu este.

Daemonul rămâne unde este

Schimbarea propriului User= al serviciului VPN în Alice este pârghia greșită. Modifică felul în care daemonul leagă interfețele, citește cheile, scrie fișierul de stare și administrează rutele, și mută tot acest risc într-o unitate care funcționa. Daemonul are nevoie de privilegiile sale; acțiunea ulterioară nu.

Uitați-vă ce face de fapt unitatea înainte de a o atinge. Două comenzi vă spun identitatea de execuție și liniile de comandă exacte în vigoare, inclusiv tot ce a adăugat o suprascriere:

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

La o instalare implicită din pachet daemonul este root, iar scriptul --up moștenește asta. Confirmați în loc să presupuneți, fiindcă o distribuție întărită poate l-a mutat pe un cont dedicat, iar un drop-in scris de mână poate rula hook-ul printr-un înveliș cu alte reguli.

Trei părți numite, un declanșator îngust

Aranjamentul care supraviețuiește mentenanței are un eveniment, un declanșator și o acțiune. Daemonul raportează că a urcat o sesiune numită. Un declanșator acceptă acel mic fapt și cere o singură acțiune fixă. Acțiunea rulează sub contul local care deține fișierele pe care le atinge. Nimic din lanț nu acceptă o cale de comandă arbitrară.

Acțiunea în sine este o unitate de serviciu obișnuită, iar identitatea este o singură directivă din interiorul ei:

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

Observați că User=alice se aplică întregii unități, iar systemd documentează o excepție care merită știută: o comandă cu prefixul + rulează cu privilegii depline indiferent de asta. Acel prefix este un instrument legitim pentru un singur pas privilegiat și o modalitate comodă de a anula separarea pe care tocmai ați construit-o, așa că tratați orice + din acea unitate ca ceva ce trebuie justificat la revizuire.

Păstrați intrarea declanșatorului minimă și ne-secretă. Identificatorul sesiunii, contul mapat și o marcă de timp sunt de obicei tot ce este necesar. Nu transmiteți niciodată peste graniță un fragment de shell, o cale de executabil sau un bloc de mediu furnizat de client, fiindcă partea care primește nu are cum să distingă o valoare legitimă de una injectată.

Verificați identitatea care a rulat de fapt

Revizuirea configurației nu este dovadă. După o conexiune de test aprobată, confirmați UID-ul care a atins fișierul și UID-ul pe care daemonul îl mai folosește:

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

Prima linie ar trebui să arate Alice și o marcă de timp în fereastra dvs. de test. A doua ar trebui să arate în continuare root sau contul de serviciu al pachetului, ceea ce dovedește că daemonul nu s-a mișcat. A treia este cea mai ieftină modalitate de a testa permisiunile acțiunii fără a aștepta o reconectare reală.

Dacă proprietarul fișierului este Alice, dar grupul sau modul lui s-a schimbat între rulări, căutați un UMask= sau o diferență de permisiuni ale directorului în loc de o eroare de logică. Dacă marca de timp nu s-a mișcat deloc, declanșatorul nu s-a declanșat niciodată, iar pasul următor util este journalctl -u pentru unitatea de acțiune în loc de încă o reconectare a clientului.

Ce încă nu dovedește un eveniment de conectare

Un tunel stabilit este un singur fapt. Nu înseamnă că sesiunea de desktop este deblocată, că magistrala de sesiune este accesibilă sau că resursa privată pe care o vrea acțiunea este deja disponibilă. O unitate oneshot sub UID-ul lui Alice are identitatea ei și nimic din sesiunea ei, așa că tot ce necesită o magistrală grafică trebuie să treacă prin mecanismul de sesiune de utilizator suportat și să definească ce se întâmplă când ea este deconectată.

Reconectările se repetă și ele. Un laptop care doarme într-un tren poate produce o duzină de evenimente de conectare într-o oră, iar un hook care adaugă o linie de fiecare dată transformă un fișier de stare într-un jurnal pe care nimeni nu-l poate citi. Faceți acțiunea idempotentă sau lăsați-o să înlocuiască o singură înregistrare cu cheie pe sesiune.

În final, limitați raza de daună în unitatea însăși: o vedere a sistemului de fișiere doar pentru citire în afara directorului de stare, fără privilegii noi și cu un timeout explicit. Separarea dintre daemon și acțiunea utilizatorului merită prin ea însăși, dar merită mult mai mult când acțiunea nu poate face nimic altceva decât singurul lucru pe care l-ați aprobat.

Articole similare