Як знайти, який systemd sandbox відхилив ваш OpenVPN hook
Hook працює як root і зазнає невдачі під systemd, бо unit зберігає менше можливостей, менший /dev і шляхи лише для читання.
Hook працює, коли ви запускаєте його вручну як root, і повертає Operation not permitted у момент, коли служба запускає його. Файл не змінився між цими запусками; змінилося середовище. systemd дає процесу менший світ: менше можливостей, інший /dev, шляхи файлової системи лише для читання і стеля завдань. Читайте невдачу як межу, яка відхилила одну конкретну операцію, а не як скрипт, що зламався.
Почніть з операції, яку було відхилено
Рядок журналу, що каже, ніби unit зазнав невдачі, майже нічого вам не каже. Корисний рядок називає виклик, який було відхилено: вузол пристрою, який не вдалося відкрити, каталог, до якого не вдалося записати, простір імен, який не вдалося створити. Знайдіть цей виклик, перш ніж торкатися бодай однієї директиви, бо кожна відповідає іншому обмеженню. Потім прочитайте 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 запустився, а не що hook працював із потрібними привілеями.
Простору імен потрібні права на mount, яких пристрій TUN ніколи не використовує
Дві операції, що виглядають однаково всередині одного hook, мають різні профілі дозволів. Відкриття /dev/net/tun і створення інтерфейсу потребує мережевого адміністрування плюс доступу до того вузла. Створення іменованого мережевого простору імен також потребує роботи з mount, бо інструмент простору імен монтує свіжий /sys вигляд і прив'язує каталог стану на його місце. висхідний unit клієнта навмисно залишає адміністрування mount поза своєю множиною обмежень, тож крок простору імен відхиляється, хоча сам тунель працював би.
Ось чому надання вузла пристрою не рятує скрипт. Дозвіл на пристрій відповідає на одне питання, чи може цей процес відкрити цей вузол, а питання простору імен інше. Зіставте виклик, що зазнає невдачі, з його можливістю, перш ніж щось розширювати.
Тест у вашій оболонці успадковує кожну можливість, яку має необмежений root, а unit успадковує те, що зберегла його множина обмежень. Ці два запуски — різні експерименти навіть коли бінарний файл ідентичний.
Пристрої, захищені шляхи та стелі завдань
Коли відхилену операцію відомо, три групи директив пояснюють більшість невдач. Доступ до пристроїв регулює PrivateDevices, який замінює /dev мінімальним набором псевдопристроїв, і DevicePolicy з DeviceAllow, які вирішують, що дозволяє cgroup. /dev/net/tun не є частиною цього мінімального набору, тож unit із PrivateDevices=yes не може відкрити його, хоч би що казала політика пристроїв.
Захист файлової системи відповідає на друге питання. ProtectHome=yes робить /home, /root і /run/user недоступними, а ProtectSystem=full робить /usr, /boot і /etc лише для читання. Тому hook, який пише поруч зі своїм конфігураційним файлом, працює у вашій оболонці й зазнає невдачі під unit, з тією самою формою дозволу, що й відсутня можливість. Перемістіть вхід, до якого треба писати, на шлях, дозволений політикою, або додайте вузький ReadWritePaths= запис.
Обмеження ресурсів — третя група, і найменш очевидна. TasksMax= обмежує, скільки завдань unit може створити, а стеля файлових дескрипторів поводиться так само для сокетів і ручок. Розгортання, що переросло стелю, зазнає невдачі з помилкою ресурсу, а не з помилкою дозволу, і саме тому точне повідомлення досі має значення.
Винесіть крок простору імен з hook
Підготовка, яка потребує привілею, якого клієнт не повинен мати, належить до власного unit. Oneshot, що працює перед клієнтом, зберігає свій результат і впорядкований перед з'єднанням, дає кроку простору імен потрібне, не передаючи ці права процесу тунелю. Клієнт далі посилається на той 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, що запускається, коли hook більше ніколи не працює, не є виправленням.
Послідовність нижче тримає порівняння чесним, бо перевіряє відхилений виклик, а не колір 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Рахуйте відмови замість читання останнього рядка, бо hook може зазнати невдачі, повторити спробу й досягти успіху, і лише кількість показує, чи зрушила межа. Залиште коментар у drop-in, називаючи директиву, яку ви послабили, і причину, оскільки наступне оновлення порівнює вашу конфігурацію з упакованим типовим. Unit, що запускається лише після вимкнення кожного захисту, не виправлений.