Hook działa, gdy uruchomisz go ręcznie jako root, i zwraca Operation not permitted w chwili, gdy usługa go uruchamia. Plik nie zmienił się między tymi uruchomieniami; zmieniło się środowisko. systemd daje procesowi mniejszy świat: mniej zdolności, inny /dev, ścieżki systemu plików tylko do odczytu i górny limit zadań. Czytaj błąd jako granicę, która odrzuciła jedną konkretną operację, a nie jako zepsuty skrypt.

Zacznij od operacji, która została odrzucona

Linia logu mówiąca, że unit zawiódł, nie mówi ci prawie nic. Przydatna linia nazywa wywołanie, które zostało odrzucone: węzeł urządzenia, którego nie udało się otworzyć, katalog, do którego nie udało się zapisać, przestrzeń nazw, której nie udało się utworzyć. Znajdź to wywołanie, zanim dotkniesz choćby jednej dyrektywy, bo każda odpowiada innemu ograniczeniu. Potem przeczytaj unit, który naprawdę działał, wraz z drop-inami, a nie plik dostarczony przez pakiet.

Poniższe zapytanie wypisuje ścieżkę fragmentu, katalogi drop-in i właściwości, które mają znaczenie przy odmowie urządzenia lub systemu plików. Przeczytaj je wszystkie, zanim zdecydujesz, która granica jest istotna:

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 w tym bloku nie zapisuje. Jeśli odrzuconego wywołania nie ma w dzienniku, podnieś szczegółowość klienta na jedno uruchomienie zamiast zgadywać dyrektywy. Działający proces dowodzi, że unit wystartował, a nie że hook działał z potrzebnymi uprawnieniami.

Przestrzeń nazw wymaga uprawnień mount, których urządzenie TUN nigdy nie używa

Dwie operacje, które wewnątrz jednego hooka wyglądają podobnie, mają różne profile uprawnień. Otwieranie /dev/net/tun i tworzenie interfejsu wymaga administracji siecią oraz dostępu do tego węzła. Utworzenie nazwanej przestrzeni nazw sieci wymaga też pracy z mount, bo narzędzie przestrzeni nazw montuje świeży /sys widok i przypina katalog stanu na jego miejscu. nadrzędny unit klienta celowo zostawia administrację mount poza swoim zbiorem granic, więc krok z przestrzenią nazw jest odrzucany, choć sam tunel by działał.

Dlatego przyznanie węzła urządzenia nie ratuje skryptu. Zezwolenie na urządzenie odpowiada na jedno pytanie, czy ten proces może otworzyć ten węzeł, a pytanie o przestrzeń nazw jest inne. Dopasuj zawodzące wywołanie do jego zdolności, zanim cokolwiek rozszerzysz.

Test w twojej powłoce dziedziczy wszystkie zdolności, jakie ma nieograniczony root, a unit dziedziczy to, co zachował jego zbiór granic. Te dwa uruchomienia to różne eksperymenty nawet gdy plik binarny jest identyczny.

Urządzenia, chronione ścieżki i limity zadań

Gdy odrzucona operacja jest znana, trzy grupy dyrektyw wyjaśniają większość błędów. Dostęp do urządzeń reguluje PrivateDevices, który zastępuje /dev minimalnym zestawem pseudo-urządzeń, oraz DevicePolicy z DeviceAllow, które decydują, co dopuszcza cgroup. /dev/net/tun nie jest częścią tego minimalnego zestawu, więc unit z PrivateDevices=yes nie może go otworzyć, cokolwiek mówi polityka urządzeń.

Ochrona systemu plików odpowiada na drugie pytanie. ProtectHome=yes czyni /home, /root i /run/user niedostępnymi, a ProtectSystem=full czyni /usr, /boot i /etc tylko do odczytu. Dlatego hook, który pisze obok własnego pliku konfiguracji, działa w twojej powłoce i zawodzi pod unitem, z takim samym kształtem uprawnień jak brakująca zdolność. Przenieś zapisywane wejście na ścieżkę, którą polityka dopuszcza, albo dodaj wąski ReadWritePaths= wpis.

Limity zasobów to trzecia grupa i najmniej oczywista. TasksMax= ogranicza, ile zadań unit może utworzyć, a limit deskryptorów plików zachowuje się tak samo dla socketów i uchwytów. Wdrożenie, które wyrosło ponad limit, zawodzi z błędem zasobu, a nie z błędem uprawnień, i dlatego dokładny komunikat wciąż ma znaczenie.

Wyjmij krok z przestrzenią nazw z hooka

Przygotowanie wymagające uprawnienia, którego klient nie powinien mieć, należy do własnego unitu. Oneshot, który działa przed klientem, zachowuje swój wynik i jest uporządkowany przed połączeniem, daje krokowi z przestrzenią nazw to, czego potrzebuje, nie przekazując tych praw procesowi tunelu. Klient odwołuje się wtedy do tego unitu zamiast wywoływać skrypt ze swojego hooka up.

Poniższy szablon to forma wyjściowa, a nie coś, co stosuje się bez czytania. Trzyma przygotowanie tam, gdzie mieszka uprawnienie, i zostawia unit tunelu nietknięty:

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

Dodaj kolejność przez drop-in zamiast edytować zapakowany unit, żeby aktualizacja nie mogła po cichu odrzucić twojej zmiany. Katalog jest powiązany z nazwą instancji, więc drugi profil potrzebuje własnego pliku:

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

Powtórz to samo wywołanie po zmianie

Naprawa jest potwierdzona tylko wtedy, gdy to samo wywołanie daje inny wynik. Zrestartuj oba unity, wykonaj operację, która zawiodła, i porównaj jej wynik z linią dziennika zapisaną przed zmianą. Unit, który startuje, gdy hook już nigdy nie działa, nie jest naprawą.

Poniższa sekwencja utrzymuje porównanie uczciwym, bo sprawdza odrzucone wywołanie, a nie kolor unitu. Zapisz jej wyjście obok linii dziennika przechwyconej przed zmianą:

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

Licz odmowy zamiast czytać ostatnią linię, bo hook może zawieść, spróbować ponownie i odnieść sukces, i tylko liczba pokazuje, czy granica się przesunęła. Zostaw w drop-inie komentarz nazywający poluzowaną dyrektywę i powód, ponieważ następna aktualizacja porównuje twoją konfigurację z zapakowanym domyślnym. Unit, który startuje dopiero po wyłączeniu każdej ochrony, nie został naprawiony.

Powiązane artykuły