El hook funciona quan l'executeu a mà com a root i retorna Operation not permitted en el moment que el servei el posa en marxa. El fitxer no va canviar entre aquestes execucions; l'entorn sí. systemd lliura al procés un món més petit: menys capacitats, un /devdiferent, camins de sistema de fitxers de només lectura i un sostre de tasques. Llegeixi la fallada com una frontera que va refusar una operació concreta, no com un script que s'ha trencat.

Comenci per l'operació que es va refusar

Una línia de registre que diu que la unitat ha fallat no li diu gairebé res. La línia útil anomena la crida que es va refusar: un node de dispositiu que no s'ha pogut obrir, un directori on no s'ha pogut escriure, un espai de noms que no s'ha pogut crear. Trobi aquesta crida abans de tocar una sola directiva, perquè cadascuna correspon a una restricció diferent. Després llegeixi la unitat que realment es va executar, drop-ins inclosos, en lloc del fitxer que va enviar el paquet.

La consulta de sota imprimeix el camí del fragment, els directoris de drop-in i les propietats que importen per a un refús de dispositiu o de sistema de fitxers. Llegeixi-les totes abans de decidir quina frontera és rellevant:

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'

Res d'aquest bloc no escriu. Si la crida refusada no apareix al registre, apugi la verbositat del client per a una execució en lloc d'endevinar directives. Un procés en marxa demostra que la unitat s'ha iniciat, no que el hook s'hagi executat amb els privilegis que necessitava.

Un espai de noms necessita drets de muntatge que un dispositiu TUN no fa servir mai

Dues operacions que semblen iguals dins d'un mateix hook tenen perfils de permisos diferents. Obrir /dev/net/tun i crear la interfície necessita administració de xarxa més accés a aquest node. Crear un espai de noms de xarxa amb nom també necessita feina de muntatge, perquè l'eina d'espais de noms munta una /sys vista nova i lliga un directori d'estat al seu lloc. La unitat de client d'origen deixa deliberadament l'administració de muntatge fora del seu conjunt de límits, així que el pas de l'espai de noms es refusa mentre el túnel mateix hauria funcionat.

Per això concedir el node de dispositiu no rescata el script. Un permís de dispositiu respon una pregunta, si aquest procés pot obrir aquest node, i la pregunta de l'espai de noms n'és una altra. Faci correspondre la crida que falla amb la seva capacitat abans d'ampliar res.

Una prova a la seva shell hereta totes les capacitats que té un root sense restriccions, i la unitat hereta el que va conservar el seu conjunt de límits. Les dues execucions són experiments diferents encara que el binari sigui idèntic.

Dispositius, camins protegits i sostres de tasques

Un cop coneguda l'operació refusada, tres grups de directives expliquen la majoria de fallades. L'accés als dispositius el governa PrivateDevices, que substitueix /dev per un conjunt mínim de pseudo-dispositius, i DevicePolicy amb DeviceAllow, que decideixen què permet el cgroup. /dev/net/tun no forma part d'aquest conjunt mínim, així que una unitat amb PrivateDevices=yes no el pot obrir digui el que digui la política de dispositius.

La protecció del sistema de fitxers respon una segona pregunta. ProtectHome=yes fa /home, /root i /run/user inaccessibles, i ProtectSystem=full fa /usr, /boot i /etc de només lectura. Per tant, un hook que escriu al costat del seu propi fitxer de configuració funciona a la seva shell i falla sota la unitat, amb la mateixa forma de permís que una capacitat absent. Mogui l'entrada escriptible a un camí que la política permeti, o afegeixi una entrada ReadWritePaths= estreta.

Els límits de recursos són el tercer grup, i el menys evident. TasksMax= limita quantes tasques pot crear la unitat, i un sostre de descriptors de fitxer es comporta igual per a sockets i nanses. Un desplegament que ha crescut més enllà del sostre falla amb un error de recursos en lloc d'un error de permisos, i per això el missatge exacte encara importa.

Tregui el pas de l'espai de noms del hook

La preparació que necessita un privilegi que el client no ha de tenir pertany a la seva pròpia unitat. Un oneshot que s'executa abans del client, conserva el seu resultat i s'ordena abans de la connexió dóna al pas de l'espai de noms el que necessita sense lliurar aquests drets al procés del túnel. Aleshores el client fa referència a aquesta unitat en lloc de cridar el script des del seu hook up.

La plantilla de sota és una forma inicial, no una cosa per aplicar sense llegir. Manté la preparació allà on viu el privilegi i deixa la unitat del túnel intacta:

# /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

Afegeixi l'ordenació amb un drop-in en lloc d'editar la unitat empaquetada, perquè una actualització no pugui descartar el seu canvi en silenci. El directori va lligat al nom de la instància, així que un segon perfil necessita el seu propi fitxer:

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

Repeteixi la mateixa invocació després del canvi

Una reparació només es confirma quan la mateixa invocació produeix un resultat diferent. Reiniciï les dues unitats, executi l'operació que va fallar i compari el seu resultat amb la línia de registre que va desar abans del canvi. Una unitat que arrenca mentre el hook no torna a executar-se no és una reparació.

El següent ordre manté la comparació honesta, perquè comprova la crida refusada i no el color de la unitat. Desa'n la sortida al costat de la línia de registre que va capturar abans del canvi:

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

Compti els refusos en lloc de llegir l'última línia, perquè un hook pot fallar, reintentar i reeixir, i només el recompte mostra si la frontera s'ha mogut. Deixi un comentari al drop-in que anomeni la directiva que ha afluixat i el motiu, ja que la propera actualització compara la seva configuració amb la predeterminada empaquetada. Una unitat que només arrenca quan totes les proteccions estan desactivades no s'ha arreglat.

Articles relacionats