Hook funguje, když ho spustíte ručně jako root, a vrací Operation not permitted ve chvíli, kdy ho služba spustí. Soubor se mezi těmito běhy nezměnil; změnilo se prostředí. systemd dává procesu menší svět: méně schopností, jiný /dev, cesty souborového systému jen pro čtení a strop úloh. Čtěte selhání jako hranici, která odmítla jednu konkrétní operaci, ne jako skript, který se rozbil.

Začněte operací, která byla odmítnuta

Řádek logu, že unit selhal, vám neřekne téměř nic. Užitečný řádek pojmenuje volání, které bylo odmítnuto: uzel zařízení, který nešlo otevřít, adresář, do kterého nešlo zapisovat, jmenný prostor, který nešlo vytvořit. Najděte to volání, než se dotknete jediné direktivy, protože každá odpovídá jinému omezení. Pak si přečtěte unit, který skutečně běžel, včetně drop-in souborů, místo souboru, který dodal balíček.

Dotaz níže vypíše cestu fragmentu, adresáře pro drop-in a vlastnosti, které jsou důležité při odmítnutí zařízení nebo souborového systému. Přečtěte si je všechny, než rozhodnete, která hranice je relevantní:

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'

Nic v tom bloku nezapisuje. Pokud odmítnuté volání v logu chybí, zvyšte podrobnost klienta na jeden běh, místo abyste hádali direktivy. Běžící proces dokazuje, že unit nastartoval, ne že hook běžel s potřebnými oprávněními.

Jmenný prostor potřebuje práva k montování, která zařízení TUN nikdy nepoužívá

Dvě operace, které uvnitř jednoho hooku vypadají stejně, mají různé profily oprávnění. Otevření /dev/net/tun a vytvoření rozhraní potřebuje správu sítě plus přístup k tomu uzlu. Vytvoření pojmenovaného síťového jmenného prostoru také potřebuje práci s montováním, protože nástroj pro jmenné prostory montuje čerstvý /sys pohled a připojuje adresář stavu na jeho místo. klientský unit z upstreamu záměrně nechává správu montování mimo svou množinu omezení, takže krok s jmenným prostorem je odmítnut, zatímco tunel samotný by fungoval.

Proto udělení uzlu zařízení skript nezachrání. Povolení zařízení odpovídá na jednu otázku, zda tento proces smí otevřít tento uzel, a otázka jmenného prostoru je jiná. Přiřaďte selhávající volání k jeho schopnosti, než cokoli rozšíříte.

Test ve vaší shellu dědí všechny schopnosti, které má neomezený root, a unit dědí to, co jeho množina omezení zachovala. Ty dva běhy jsou různé experimenty i když je binárka stejná.

Zařízení, chráněné cesty a stropy úloh

Když je odmítnutá operace známá, tři skupiny direktiv vysvětlí většinu selhání. Přístup k zařízením řídí PrivateDevices, který nahrazuje /dev minimální sadou pseudozařízení, a DevicePolicy s DeviceAllow, které rozhodují, co cgroup dovolí. /dev/net/tun není součástí této minimální sady, takže unit s PrivateDevices=yes ho nemůže otevřít, ať politika zařízení říká cokoli.

Ochrana souborového systému odpovídá na druhou otázku. ProtectHome=yes dělá /home, /root a /run/user nedostupnými, a ProtectSystem=full dělá /usr, /boot a /etc jen pro čtení. Proto hook, který zapisuje vedle svého konfiguračního souboru, funguje ve vaší shellu a pod unitem selhává, se stejným tvarem oprávnění jako chybějící schopnost. Přesuňte zapisovaný vstup do cesty, kterou politika dovoluje, nebo přidejte úzký ReadWritePaths= záznam.

Limity prostředků jsou třetí skupina a nejméně zřejmá. TasksMax= omezuje, kolik úloh smí unit vytvořit, a strop deskriptorů souborů se chová stejně pro sockety a handle. Nasazení, které přerostlo strop, selhává s chybou prostředků, ne s chybou oprávnění, a proto na přesné zprávě pořád záleží.

Přesuňte krok s jmenným prostorem z hooku

Příprava, která potřebuje oprávnění, jež klient nesmí mít, patří do vlastního unitu. Oneshot, který běží před klientem, drží si svůj výsledek a je seřazen před spojením, dá kroku s jmenným prostorem, co potřebuje, aniž by ta práva předal procesu tunelu. Klient pak na ten unit odkazuje, místo aby skript volal ze svého up hooku.

Šablona níže je počáteční tvar, ne něco, co se aplikuje nepřečtené. Drží přípravu tam, kde žije oprávnění, a unit tunelu nechává nedotčený:

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

Přidejte řazení přes drop-in, místo abyste upravovali zabalený unit, aby upgrade nemohl tiše zahodit vaši změnu. Adresář je vázaný na název instance, takže druhý profil potřebuje vlastní soubor:

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

Zopakujte stejné volání po změně

Oprava je potvrzena jen tehdy, když stejné volání dá jiný výsledek. Restartujte oba unity, spusťte operaci, která selhala, a porovnejte její výsledek s řádkem logu, který jste si uložili před změnou. Unit, který nastartuje, ale hook už nikdy neběží, není oprava.

Sekvence níže drží srovnání poctivé, protože kontroluje odmítnuté volání, ne barvu unitu. Uložte její výstup vedle řádku logu, který jste zachytili před změnou:

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

Počítejte odmítnutí, místo abyste četli poslední řádek, protože hook může selhat, zkusit to znovu a uspět, a jen počet ukáže, zda se hranice posunula. Nechte v drop-inu komentář s názvem direktivy, kterou jste povolili, a důvodem, protože příští upgrade porovnává vaši konfiguraci se zabaleným výchozím nastavením. Unit, který nastartuje jen tehdy, když je vypnutá každá ochrana, není opravený.

Související články