O hook funciona cando o executas a man como root e devolve Operation not permitted no momento en que o servizo o arrinca. O ficheiro non cambiou entre esas execucións; a contorna si. systemd entrega ao proceso un mundo máis pequeno: menos capacidades, un /dev distinto, rutas do sistema de ficheiros de só lectura e un teito de tarefas. Le o fallo como unha fronteira que rexeitou unha operación concreta, non como un script que se rompeu.

Comeza pola operación que se rexeitou

Unha liña de rexistro que di que a unidade fallou non che di case nada. A liña útil nomea a chamada que se rexeitou: un nodo de dispositivo que non se puido abrir, un directorio no que non se puido escribir, un espazo de nomes que non se puido crear. Atopa esa chamada antes de tocar unha soa directiva, porque cada unha corresponde a unha restrición distinta. Despois le a unidade que realmente se executou, cos drop-in incluídos, no canto do ficheiro que enviou o paquete.

A consulta de abaixo imprime a ruta do fragmento, os directorios de drop-in e as propiedades que importan para un rexeitamento de dispositivo ou de sistema de ficheiros. Léas todas antes de decidir que fronteira é relevante:

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'

Nada dese bloque escribe. Se a chamada rexeitada falta no rexistro, sube a verbosidade do cliente para unha execución no canto de adiviñar directivas. Un proceso en marcha demostra que a unidade arrincou, non que o hook se executou cos privilexios que necesitaba.

Un espazo de nomes necesita dereitos de montaxe que un dispositivo TUN nunca usa

Dúas operacións que parecen iguais dentro dun mesmo hook teñen perfís de permisos distintos. Abrir /dev/net/tun e crear a interface necesita administración de rede máis acceso a ese nodo. Crear un espazo de nomes de rede con nome tamén necesita traballo de montaxe, porque a ferramenta de espazos de nomes monta unha /sys vista nova e liga un directorio de estado no seu sitio. unidade de cliente orixinal deixa deliberadamente a administración de montaxe fóra do seu conxunto de límites, así que o paso do espazo de nomes rexéitase mentres o túnel mesmo tería funcionado.

Por iso conceder o nodo de dispositivo non rescata o script. Un permiso de dispositivo responde unha pregunta, se este proceso pode abrir este nodo, e a pregunta do espazo de nomes é outra. Emparexa a chamada que falla coa súa capacidade antes de ampliar nada.

Unha proba na túa shell herda todas as capacidades que ten un root sen restricións, e a unidade herda o que conservou o seu conxunto de límites. As dúas execucións son experimentos distintos aínda que o binario sexa idéntico.

Dispositivos, rutas protexidas e teitos de tarefas

Unha vez coñecida a operación rexeitada, tres grupos de directivas explican a maioría dos fallos. O acceso a dispositivos govérao PrivateDevices, que substitúe /dev por un conxunto mínimo de pseudo-dispositivos, e DevicePolicy con DeviceAllow, que deciden que permite o cgroup. /dev/net/tun non forma parte dese conxunto mínimo, así que unha unidade con PrivateDevices=yes non o pode abrir diga o que diga a política de dispositivos.

A protección do sistema de ficheiros responde unha segunda pregunta. ProtectHome=yes fai /home, /root e /run/user inaccesibles, e ProtectSystem=full fai /usr, /boot e /etc de só lectura. Por iso un hook que escribe xunto ao seu propio ficheiro de configuración funciona na túa shell e falla baixo a unidade, coa mesma forma de permiso que unha capacidade ausente. Move a entrada escribible a unha ruta que a política permita, ou engade unha entrada ReadWritePaths= estreita.

Os límites de recursos son o terceiro grupo, e o menos evidente. TasksMax= limita cantas tarefas pode crear a unidade, e un teito de descritores de ficheiro compórtase igual para sockets e xestores. Un despregamento que medrou máis alá do teito falla cun erro de recursos no canto dun erro de permisos, e por iso a mensaxe exacta segue importando.

Saca o paso do espazo de nomes do hook

A preparación que necesita un privilexio que o cliente non debe ter pertence á súa propia unidade. Un oneshot que se executa antes do cliente, conserva o seu resultado e está ordenado antes da conexión dálle ao paso do espazo de nomes o que necesita sen entregar eses dereitos ao proceso do túnel. Despois o cliente fai referencia a esa unidade no canto de chamar o script desde o seu hook up.

A plantilla de abaixo é unha forma inicial, non algo que se aplique sen ler. Mantén a preparación onde vive o privilexio e deixa intacta a unidade do túnel:

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

Engade a orde cun drop-in no canto de editar a unidade empaquetada, para que unha actualización non poida descartar o teu cambio en silencio. O directorio vai ligado ao nome da instancia, así que un segundo perfil necesita o seu propio ficheiro:

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

Repite a mesma invocación despois do cambio

Unha reparación só se confirma cando a mesma invocación produce un resultado distinto. Reinicia ambas as unidades, executa a operación que fallou e compara o seu resultado coa liña de rexistro que gardaches antes do cambio. Unha unidade que arrinca mentres o hook non volve executarse non é unha reparación.

A secuencia de abaixo mantén a comparación honesta, porque comproba a chamada rexeitada e non a cor da unidade. Garda a súa saída xunto á liña de rexistro que capturaches antes do cambio:

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

Conta os rexeitamentos no canto de ler a última liña, porque un hook pode fallar, reintentar e ter éxito, e só a conta mostra se a fronteira se moveu. Deixa un comentario no drop-in que nomee a directiva que afrouxaches e o motivo, xa que a seguinte actualización compara a túa configuración coa predeterminada do paquete. Unha unidade que só arrinca cando todas as proteccións están desactivadas non está arranxada.

Artigos relacionados