Хукът работи, когато го стартирате на ръка като root, и връща Operation not permitted в момента, в който услугата го стартира. Файлът не се е променил между тези пускания; средата се промени. systemd дава на процеса по-малък свят: по-малко способности, различен /dev, пътища на файловата система само за четене и таван на задачите. Четете отказа като граница, която е отказала една конкретна операция, а не като скрипт, който се е счупил.

Започнете от операцията, която беше отказана

Ред в журнала, който казва, че unit е неуспешен, не ви казва почти нищо. Полезният ред назовава извикването, което е отказано: устройствен възел, който не е могъл да се отвори, директория, в която не е могло да се пише, namespace, който не е могъл да се създаде. Намерете това извикване, преди да пипнете дори една директива, защото всяка отговаря на различно ограничение. След това прочетете unit, който действително е работил, включително drop-in файловете, вместо файла, който пакетът е доставил.

Заявката по-долу отпечатва пътя на фрагмента, директориите за drop-in и свойствата, които имат значение при отказ за устройство или файлова система. Прочетете ги всички, преди да решите коя граница е от значение:

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'

Нищо в този блок не пише. Ако отказаното извикване липсва в журнала, вдигнете подробността на клиента за едно пускане, вместо да гадаете директиви. Работещ процес доказва, че unit е стартирал, а не че хукът е работил с нужните привилегии.

Namespace изисква права за монтиране, които TUN устройството никога не използва

Две операции, които изглеждат еднакво в един хук, имат различни профили на разрешения. Отварянето на /dev/net/tun и създаването на интерфейса изисква мрежова администрация плюс достъп до този възел. Създаването на именуван мрежов namespace също изисква работа по монтиране, защото инструментът за namespace монтира нов /sys изглед и закача директория за състояние на мястото ѝ. клиентският unit от upstream умишлено оставя администрирането на монтирането извън своя bounding set, затова стъпката с namespace се отказва, докато самият тунел би работил.

Ето защо даването на устройствения възел не спасява скрипта. Разрешението за устройство отговаря на един въпрос, дали този процес може да отвори този възел, а въпросът за namespace е друг. Съпоставете отказаното извикване с неговата способност, преди да разширявате каквото и да е.

Тест в вашата обвивка наследява всяка способност, която има неограничен root, а unit наследява това, което е запазил неговият bounding set. Двете пускания са различни експерименти дори когато двоичният файл е идентичен.

Устройства, защитени пътища и тавани на задачите

След като отказаната операция е известна, три групи директиви обясняват повечето откази. Достъпът до устройства се управлява от PrivateDevices, който заменя /dev с минимален набор псевдоустройства, и от DevicePolicy с DeviceAllow, които решават какво позволява cgroup. /dev/net/tun не е част от този минимален набор, затова unit с PrivateDevices=yes не може да го отвори, каквото и да казва политиката за устройства.

Защитата на файловата система отговаря на втори въпрос. ProtectHome=yes прави /home, /root и /run/user недостъпни, а ProtectSystem=full прави /usr, /boot и /etc само за четене. Затова хук, който пише до собствения си конфигурационен файл, работи в обвивката ви и се проваля под unit, със същата форма на разрешение като липсваща способност. Преместете записвания вход на път, който политиката позволява, или добавете тесен ReadWritePaths= запис.

Ограниченията на ресурсите са третата група и най-малко очевидната. TasksMax= ограничава колко задачи може да създаде unit, а таванът на файловите дескриптори работи по същия начин за сокети и манипулатори. Внедряване, което е надхвърлило тавана, се проваля с ресурсна грешка, а не с грешка за разрешение, и точно затова точното съобщение все още има значение.

Извадете стъпката с namespace извън хука

Подготовка, която изисква привилегия, която клиентът не бива да притежава, принадлежи в собствен unit. Oneshot, който работи преди клиента, запазва резултата си и е подреден преди връзката, дава на стъпката с namespace каквото ѝ трябва, без да предава тези права на процеса на тунела. След това клиентът реферира този unit, вместо да вика скрипта от своя up hook.

Шаблонът по-долу е начална форма, а не нещо, което да приложите непрочетено. Той пази подготовката там, където живее привилегията, и оставя тунелния unit недокоснат:

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

Добавете подредбата с drop-in, вместо да редактирате пакетирания unit, така че надграждане да не може тихо да изпусне промяната ви. Директорията е обвързана с името на инстанцията, затова вторият профил се нуждае от свой файл:

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

Повторете същото извикване след промяната

Ремонтът се потвърждава само когато същото извикване даде различен резултат. Рестартирайте двата unit, изпълнете операцията, която се провали, и сравнете резултата ѝ с реда в журнала, който сте запазили преди промяната. Unit, който стартира, докато хукът повече не се изпълнява, не е ремонт.

Последователността по-долу пази сравнението честно, защото проверява отказаното извикване, а не цвета на unit. Запазете изхода ѝ до реда в журнала, който сте уловили преди промяната:

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

Бройте отказите, вместо да четете последния ред, защото хукът може да се провали, да опита отново и да успее, и само броят показва дали границата е мръднала. Оставете коментар в drop-in, който назовава директивата, която сте разхлабили, и причината, тъй като следващото надграждане сравнява конфигурацията ви с пакетираната по подразбиране. Unit, който стартира само след изключване на всяка защита, не е поправен.

Подобни статии