Hook-ul funcționează când îl rulați manual ca root și returnează Operation not permitted în momentul în care serviciul îl pornește. Fișierul nu s-a schimbat între aceste rulări; mediul s-a schimbat. systemd îi dă procesului o lume mai mică: mai puține capabilități, un /dev diferit, căi ale sistemului de fișiere doar pentru citire și un plafon de sarcini. Citiți eșecul ca pe o graniță care a refuzat o operație anume, nu ca pe un script stricat.

Începeți cu operația care a fost refuzată

O linie de jurnal care spune că unit-ul a eșuat nu vă spune aproape nimic. Linia utilă numește apelul care a fost refuzat: un nod de dispozitiv care nu a putut fi deschis, un director în care nu s-a putut scrie, un spațiu de nume care nu a putut fi creat. Găsiți acel apel înainte de a atinge o singură directivă, pentru că fiecare corespunde unei restricții diferite. Apoi citiți unit-ul care a rulat efectiv, inclusiv drop-in-urile, în locul fișierului livrat de pachet.

Interogarea de mai jos afișează calea fragmentului, directoarele drop-in și proprietățile care contează pentru un refuz de dispozitiv sau de sistem de fișiere. Citiți-le pe toate înainte de a decide care graniță este relevantă:

systemctl show "[email protected]" -p FragmentPath -p DropInPaths -p PrivateDevices -p DevicePolicy -p DeviceAllow -p ProtectHome -p ProtectSystem -p TasksMax
journalctl -u "[email protected]" -b --no-pager | grep -iE 'operation not permitted|permission denied|cannot open|failed'

Nimic din acel bloc nu scrie. Dacă apelul refuzat lipsește din jurnal, creșteți verbositatea clientului pentru o singură rulare în loc să ghiciți directive. Un proces care rulează dovedește că unit-ul a pornit, nu că hook-ul a rulat cu privilegiile de care avea nevoie.

Un spațiu de nume are nevoie de drepturi de montare pe care un dispozitiv TUN nu le folosește niciodată

Două operații care arată la fel în interiorul unui hook au profiluri de permisiuni diferite. Deschiderea /dev/net/tun și crearea interfeței necesită administrare de rețea plus acces la acel nod. Crearea unui spațiu de nume de rețea cu nume necesită și muncă de montare, pentru că instrumentul de spații de nume montează o /sys vedere nouă și leagă un director de stare la locul lui. unit-ul clientului original lasă în mod deliberat administrarea montării în afara mulțimii sale de limite, așa că pasul spațiului de nume este refuzat, deși tunelul în sine ar fi funcționat.

De aceea acordarea nodului de dispozitiv nu salvează scriptul. O permisiune de dispozitiv răspunde la o singură întrebare, dacă acest proces poate deschide acest nod, iar întrebarea spațiului de nume este alta. Potriviți apelul care eșuează cu capabilitatea lui înainte de a lărgi ceva.

Un test în shell-ul dumneavoastră moștenește fiecare capabilitate pe care o are un root nelimitat, iar unit-ul moștenește ce a păstrat mulțimea lui de limite. Cele două rulări sunt experimente diferite chiar când binarul este identic.

Dispozitive, căi protejate și plafoane de sarcini

Odată ce operația refuzată este cunoscută, trei grupuri de directive explică majoritatea eșecurilor. Accesul la dispozitive este reglementat de PrivateDevices, care înlocuiește /dev cu o mulțime minimă de pseudo-dispozitive, și de DevicePolicy cu DeviceAllow, care decid ce permite cgroup-ul. /dev/net/tun nu face parte din acea mulțime minimă, așa că un unit cu PrivateDevices=yes nu îl poate deschide, orice ar spune politica de dispozitive.

Protecția sistemului de fișiere răspunde la o a doua întrebare. ProtectHome=yes face /home, /root și /run/user inaccesibile, iar ProtectSystem=full face /usr, /boot și /etc doar pentru citire. De aceea un hook care scrie lângă propriul fișier de configurare funcționează în shell-ul dumneavoastră și eșuează sub unit, cu aceeași formă de permisiune ca o capabilitate lipsă. Mutați intrarea care se poate scrie pe o cale permisă de politică sau adăugați o intrare ReadWritePaths= îngustă.

Limitele de resurse sunt al treilea grup și cel mai puțin evident. TasksMax= limitează câte sarcini poate crea unit-ul, iar un plafon de descriptori de fișier se comportă la fel pentru socketuri și mânere. O implementare care a crescut peste plafon eșuează cu o eroare de resurse, nu cu una de permisiuni, și de aceea mesajul exact contează în continuare.

Scoateți pasul spațiului de nume din hook

Pregătirea care necesită un privilegiu pe care clientul nu trebuie să îl aibă aparține propriului unit. Un oneshot care rulează înainte de client, își păstrează rezultatul și este ordonat înaintea conexiunii îi dă pasului spațiului de nume ce are nevoie fără a preda acele drepturi procesului tunelului. Clientul face apoi referire la acel unit în loc să apeleze scriptul din hook-ul său up.

Șablonul de mai jos este o formă de început, nu ceva ce se aplică necitit. Păstrează pregătirea acolo unde trăiește privilegiul și lasă unit-ul tunelului neatins:

# /etc/systemd/system/[email protected]
[Unit]
Description=Namespace preparation for %i
Before=openvpn-client@%i.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/netns-up.sh %i
CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_ADMIN
[Install]
WantedBy=multi-user.target

Adăugați ordinea cu un drop-in în loc să editați unit-ul împachetat, ca o actualizare să nu poată arunca schimbarea dumneavoastră în tăcere. Directorul este legat de numele instanței, așa că un al doilea profil are nevoie de propriul fișier:

sudo mkdir -p /etc/systemd/system/[email protected]
sudo tee /etc/systemd/system/[email protected]/10-namespace.conf >/dev/null <<'CONF'
[Unit]
[email protected]
[email protected]
CONF
sudo systemctl daemon-reload

Repetați aceeași invocare după schimbare

O reparație este confirmată doar când aceeași invocare produce un rezultat diferit. Reporniți ambele unit-uri, executați operația care a eșuat și comparați rezultatul ei cu linia de jurnal pe care ați salvat-o înainte de schimbare. Un unit care pornește în timp ce hook-ul nu mai rulează niciodată nu este o reparație.

Secvența de mai jos păstrează comparația onestă, pentru că verifică apelul refuzat și nu culoarea unit-ului. Salvați ieșirea ei lângă linia de jurnal pe care ați capturat-o înainte de schimbare:

sudo systemctl restart "[email protected]" "[email protected]"
systemctl status "[email protected]" --no-pager
journalctl -u "[email protected]" -b --no-pager | grep -c 'Operation not permitted'
ip -brief addr show tun0

Numărați refuzurile în loc să citiți ultima linie, pentru că un hook poate eșua, reîncerca și reuși, și doar numărul arată dacă granița s-a mișcat. Lăsați un comentariu în drop-in care numește directiva pe care ați relaxat-o și motivul, fiindcă următoarea actualizare compară configurația dumneavoastră cu implicitul pachetului. Un unit care pornește doar după ce fiecare protecție este oprită nu este reparat.

Articole similare