Безопасная настройка systemd-сервисов: systemd-analyze security и sandboxing | AdminWiki

Безопасная настройка systemd-сервисов: systemd-analyze security и sandboxing

28 августа 2026 10 мин. чтения
Содержание статьи

Перед ужесточением systemd-сервиса снимите базовую оценку через systemd-analyze security, затем добавляйте ограничения по одному и после каждого изменения проверяйте, не сломалась ли загрузка или работа сервиса. Для безопасной настройки важны резервная копия unit-файла, постепенное включение NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp, RestrictAddressFamilies, SystemCallFilter, CapabilityBoundingSet, DynamicUser и ReadWritePaths, а также понятный план отката.

Это руководство показывает практический цикл усиления systemd-сервиса без потери работоспособности. Вы узнаете, как оценить текущую конфигурацию, какие ограничения вводить первыми, как проверять результат и быстро откатывать несовместимые параметры. Материал ориентирован на Debian/Ubuntu и RHEL-подобные системы с systemd версии 240 и новее.

Как безопасно усилить systemd-сервис

Sandboxing в systemd снижает последствия компрометации сервиса. Ограничения изолируют файловую систему, домашние каталоги, временные файлы, capabilities, системные вызовы, сетевые семейства и права процесса. Это не заменяет контроль доступа, обновления и мониторинг, но уменьшает поверхность атаки.

Одновременное изменение нескольких параметров усложняет поиск причины отказа. Поэтому применяйте принцип минимального изменения: один параметр, один перезапуск, одна функциональная проверка, одна запись результата. Ведите журнал изменений с исходным score, новым score и замеченными побочными эффектами.

Что именно защищает systemd-sandboxing

Systemd-песочница ограничивает доступ процесса к ресурсам операционной системы. ProtectSystem делает системные каталоги доступными только для чтения, ProtectHome скрывает или делает недоступными домашние каталоги пользователей, PrivateTmp предоставляет отдельное пространство для временных файлов. NoNewPrivileges запрещает процессу получать новые привилегии через setuid или file capabilities. CapabilityBoundingSet ограничивает набор Linux capabilities, SystemCallFilter сужает доступные системные вызовы, RestrictAddressFamilies контролирует создание сокетов. DynamicUser запускает сервис с временным UID/GID, а ReadWritePaths точечно разрешает запись при общей защите.

Эти механизмы не исправляют уязвимости в самом приложении. Если сервис имеет эксплуатируемую ошибку, злоумышленник получит доступ в рамках разрешённых ограничений. Поэтому sandboxing дополняет, а не заменяет регулярные обновления и другие меры безопасности.

Почему ограничения включают по одному

Пошаговое включение параметров позволяет быстро локализовать несовместимость. Если после добавления трёх ограничений сервис перестал работать, вы не знаете, какое из них вызвало проблему. При последовательном подходе вы всегда можете откатить последнее изменение и проверить гипотезу.

Пример рабочего цикла:

  1. Зафиксируйте исходное состояние: systemd-analyze security имя-сервиса, systemctl status имя-сервиса, journalctl -u имя-сервиса -b.
  2. Добавьте одно ограничение через drop-in.
  3. Выполните systemctl daemon-reload.
  4. Перезапустите сервис: systemctl restart имя-сервиса.
  5. Проверьте статус и логи: systemctl status имя-сервиса, journalctl -u имя-сервиса -b.
  6. Выполните функциональный тест (HTTP-запрос, тестовое задание, запись в каталог).
  7. Сравните новый профиль безопасности с исходным.
  8. При несовместимости откатите последний шаг.

Итоговый score systemd-analyze security является ориентиром для поиска избыточной поверхности атаки, а не самостоятельной гарантией безопасности. Не пытайтесь механически добиться минимального числового значения, если это ломает функции сервиса.

Перед началом: проверить unit-файл и подготовить откат

До изменения конфигурации определите фактически загруженные параметры. Команда systemctl cat имя-сервиса покажет основной unit-файл и все drop-in. Команда systemctl show имя-сервиса выведет итоговые значения параметров после применения всех переопределений.

Изменения предпочтительно вносить через systemctl edit имя-сервиса, а не править пакетный unit напрямую. Drop-in-файлы сохраняются в /etc/systemd/system/имя-сервиса.service.d/ и переживают обновления пакетов. Для полного сброса настроек можно использовать systemctl revert имя-сервиса.

Сделать резервную копию исходной конфигурации

Сохраните содержимое unit-файла и drop-in:

mkdir -p ~/systemd-backup/имя-сервиса
systemctl cat имя-сервиса > ~/systemd-backup/имя-сервиса/unit-with-dropins.txt
systemctl show имя-сервиса > ~/systemd-backup/имя-сервиса/show.txt
systemctl status имя-сервиса > ~/systemd-backup/имя-сервиса/status.txt
journalctl -u имя-сервиса -b --no-pager > ~/systemd-backup/имя-сервиса/journal.txt

Резервная копия должна включать не только основной unit, но и локальные переопределения из /etc/systemd/system/имя-сервиса.service.d/. При необходимости скопируйте весь каталог.

Проверить конфигурацию до запуска

Используйте systemd-analyze verify для проверки синтаксиса unit-файлов и зависимостей:

systemd-analyze verify имя-сервиса.service

Успешная синтаксическая проверка не подтверждает работоспособность сервиса. Она лишь гарантирует, что systemd сможет загрузить конфигурацию. Функциональные проблемы выявляются только запуском и тестированием.

Определить безопасную точку возврата

План отката зависит от способа внесения изменений. Если вы использовали systemctl edit, удалите или переименуйте созданный drop-in:

sudo mv /etc/systemd/system/имя-сервиса.service.d/override.conf /etc/systemd/system/имя-сервиса.service.d/override.conf.bak
sudo systemctl daemon-reload
sudo systemctl restart имя-сервиса

Для полного возврата к исходной конфигурации восстановите резервную копию unit-файла и drop-in. Проверьте итоговую конфигурацию через systemctl cat имя-сервиса.

Для критичных сервисов заранее предусмотрите консольный доступ, второй сеанс SSH и окно обслуживания. Тестируйте изменения сначала на копии или стенде, если это возможно.

Оценить сервис через systemd-analyze security

Команда systemd-analyze security имя-сервиса выводит оценку изоляции сервиса. Чем ниже score, тем меньше поверхность атаки. Но значение зависит от версии systemd и типа сервиса, поэтому сравнивайте только относительные изменения.

Базовый снимок состояния

Сохраните вывод анализа и текущий статус:

systemd-analyze security имя-сервиса > ~/systemd-backup/имя-сервиса/security-before.txt
systemctl status имя-сервиса > ~/systemd-backup/имя-сервиса/status-before.txt
journalctl -u имя-сервиса -b --no-pager > ~/systemd-backup/имя-сервиса/journal-before.txt

Проверьте effective-конфигурацию через systemctl show имя-сервиса, поскольку итоговые значения формируются из основного unit, drop-in и зависимостей.

Как читать предупреждения и score

Сначала разбирайте ограничения с большим практическим эффектом: доступ к корневой файловой системе, домашним каталогам, привилегии и capabilities. Параметр должен соответствовать модели работы приложения: сетевому сервису нужна сеть, а stateful-процессу нужен явно разрешённый каталог записи.

Пример вывода systemd-analyze security для типового сервиса:

  NAME                                                        DESCRIPTION                                                       EXPOSURE
✗ PrivateNetwork=                                             Service has access to the host's network                                    0.5
✗ User=/DynamicUser=                                         Service runs as root user                                                  0.4
✗ CapabilityBoundingSet=~CAP_SYS_ADMIN                        Service may issue system administration commands                           0.3
✗ ProtectSystem=                                             Service has full access to the OS file system                              0.3
✗ ProtectHome=                                               Service has full access to home directories                                0.2
✓ NoNewPrivileges=                                           Service process cannot gain new privileges                                  0.1
✓ PrivateTmp=                                                Service has no access to other software's temporary files                   0.1
✓ RestrictAddressFamilies=~AF_UNIX                           Service cannot create exotic sockets                                        0.1

Overall exposure level for имя-сервиса.service: 8.2 SAFE 😀

Каждая строка показывает параметр, его описание и вклад в общую оценку. Символ ✗ означает небезопасное значение, ✓ - безопасное. Общий уровень exposure level классифицируется от SAFE до UNSAFE.

Включить базовые ограничения файловой системы и привилегий

Начните с наиболее предсказуемых мер: NoNewPrivileges, PrivateTmp, ProtectHome, ProtectSystem и точечные разрешения через ReadWritePaths. Эти параметры редко ломают сервисы, но значительно снижают поверхность атаки.

NoNewPrivileges: запретить получение новых привилегий

Параметр NoNewPrivileges=true запрещает процессу получать новые привилегии через setuid, file capabilities или другие механизмы. Это предотвращает повышение прав даже при наличии уязвимости в приложении.

Добавьте в drop-in:

[Service]
NoNewPrivileges=true

Проверьте запуск, дочерние процессы, работу helper-программ и операции, требующие повышения прав. Некоторые приложения, например, использующие setuid-бинарники или exec-переход к более привилегированному процессу, могут быть несовместимы.

PrivateTmp и ProtectHome

PrivateTmp=true предоставляет сервису отдельное пространство /tmp и /var/tmp. Другие процессы не видят эти временные файлы, что снижает риск атак через предсказуемые имена.

ProtectHome ограничивает доступ к домашним каталогам. Варианты:

  • yes - домашние каталоги недоступны.
  • read-only - доступ только для чтения.
  • tmpfs - домашние каталоги заменяются пустой tmpfs.

Выберите значение по фактической необходимости. Например, для веб-сервера обычно достаточно ProtectHome=true, если он не обращается к файлам в домашних каталогах.

ProtectSystem и ReadWritePaths

ProtectSystem=full делает всю файловую систему доступной только для чтения, за исключением /dev, /proc и /sys. ProtectSystem=strict дополнительно запрещает запись в эти виртуальные файловые системы.

Если сервису нужна запись в определённые каталоги, разрешите их через ReadWritePaths:

[Service]
ProtectSystem=strict
ReadWritePaths=/var/lib/имя-сервиса /var/log/имя-сервиса

Убедитесь, что каталоги существуют и имеют корректные права. Иначе сервис не сможет писать и завершится с ошибкой.

Сузить сетевые возможности и системные вызовы

Более строгий этап hardening снижает поверхность атаки, но требует наблюдения за фактическим поведением приложения. RestrictAddressFamilies ограничивает создание сокетов, а SystemCallFilter ограничивает набор доступных системных вызовов.

RestrictAddressFamilies systemd: оставить только нужные семейства

Типовые профили:

  • Локальный сервис, работающий только через Unix-сокеты: RestrictAddressFamilies=AF_UNIX
  • Сервис с IPv4 и IPv6: RestrictAddressFamilies=AF_INET AF_INET6
  • Сервис, которому нужны и сеть, и локальные сокеты: RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

Проверьте входящие соединения, исходящие подключения, DNS и взаимодействие с локальными сокетами. Если сервис использует DNS, ему может потребоваться AF_INET или AF_INET6 для резолвера.

SystemCallFilter: переходить от широкого к узкому профилю

Подход allowlist безопаснее, но сложнее в настройке. Начните с denylist для явно опасных групп, например, SystemCallFilter=~@mount @debug @reboot @swap. Постепенно сужайте набор, проверяя логи на ошибки EPERM или Operation not permitted.

Учитывайте системные вызовы для fork, сети, файловых операций, работы с потоками и загрузки библиотек. Ошибки выявляйте по журналу и трассировке на тестовой среде.

Убрать лишние capabilities и включить DynamicUser

Linux capabilities предоставляют процессу привилегии без полного root. Минимизация набора capabilities снижает последствия компрометации. DynamicUser изолирует учётную запись процесса.

CapabilityBoundingSet: оставить только необходимое

Начните с пустого набора и добавляйте capabilities только при подтверждённой необходимости:

[Service]
CapabilityBoundingSet=
# Добавьте, если сервису нужно:
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_DAC_OVERRIDE

Проверьте привязку к привилегированному порту (меньше 1024), raw-сокетам, смене владельца, монтированию и управлению сетевыми интерфейсами. Если сервис не требует специальных прав, пустой набор безопаснее.

DynamicUser: когда динамический пользователь подходит

DynamicUser=true запускает сервис с временным UID/GID, который генерируется при каждом запуске. Это подходит для stateless-сервисов без требования сохранять владельца файлов между перезапусками.

Влияние на каталоги:

  • StateDirectory, CacheDirectory, RuntimeDirectory автоматически создаются и принадлежат динамическому пользователю.
  • Доступ к заранее созданным каталогам с постоянным владельцем может быть ограничен.
  • Взаимодействие с другими сервисами через файлы требует настройки ACL или общих групп.

Для сервисов с постоянным состоянием используйте обычного системного пользователя с User= и Group=.

Проверять сервис после каждого изменения

После каждого изменения выполняйте полный цикл проверки: systemctl daemon-reload, systemd-analyze verify, systemctl restart, systemctl status, journalctl, функциональный тест, systemd-analyze security. Сравнивайте результаты с базовым снимком.

Проверка запуска и состояния

Проверьте код завершения, состояние active, время запуска, зависимости, сокеты и дочерние процессы. При отказе изучите journalctl -u имя-сервиса -b и ищите ошибки:

  • Permission denied - нет доступа к файлу или каталогу.
  • Operation not permitted - запрещённая операция (например, системный вызов или capability).
  • No such file or directory - отсутствует файл или каталог, возможно, из-за ProtectSystem или ProtectHome.
  • Ошибки доступа к сокетам - неверный RestrictAddressFamilies.

Функциональный тест вместо одной проверки active

Состояние active не гарантирует работоспособность. Для веб-сервиса проверьте HTTP-запрос, для worker отправьте тестовую задачу, для агента проверьте отправку метрик, для демона с хранилищем выполните чтение и запись в разрешённый каталог. Зафиксируйте ожидаемые результаты до ужесточения.

Сравнить новый профиль безопасности с исходным

Сопоставьте новый вывод systemd-analyze security с базовым: какие проверки стали пройдены, какие ограничения активны, какие исключения остались. Не удаляйте рабочие исключения только ради улучшения числовой оценки.

Типовые несовместимости и план отката

Наиболее вероятные сбои:

  • Невозможность записи при ProtectSystem - сервис пытается писать в защищённый каталог.
  • Отсутствие файлов пользователя при ProtectHome - сервис обращается к домашним каталогам.
  • Отказ сокета при RestrictAddressFamilies - сервис пытается создать сокет запрещённого семейства.
  • EPERM после SystemCallFilter или CapabilityBoundingSet - запрещён системный вызов или capability.
  • Проблемы с постоянным состоянием при DynamicUser - файлы принадлежат другому UID.

Как найти параметр, который сломал сервис

Откатите только последнее изменение, повторите daemon-reload и restart, затем снова выполните функциональный тест. Если проблема исчезла, зафиксируйте несовместимость и замените глобальное ограничение точечным исключением, например, ReadWritePaths.

Откат drop-in без удаления исходного unit

Удалите или временно переименуйте созданный drop-in, выполните systemctl daemon-reload и перезапустите сервис. Для полного возврата используйте сохранённую копию и проверьте итоговую конфигурацию через systemctl cat.

Что делать при недоступности сервиса после перезагрузки

Заранее предусмотрите консольный доступ и тестируйте изменения сначала на копии или стенде. При аварии загрузите систему с административным доступом, отключите проблемный drop-in, восстановите сервис, затем повторите эксперимент с одним ограничением.

Готовый базовый профиль sandboxing для адаптации

Пример drop-in для типового сетевого сервиса, которому нужны запись в каталог состояния и сетевые подключения:

[Service]
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/имя-сервиса /var/log/имя-сервиса
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
CapabilityBoundingSet=
# Добавьте необходимые capabilities, например:
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SystemCallFilter=@system-service
# Для более строгой фильтрации используйте allowlist:
# SystemCallFilter=~@mount @debug @reboot @swap

Каждую строку адаптируйте под конкретное приложение. Проверьте зависимости от версии systemd и особенностей сервиса.

Что нужно заменить в шаблоне

Обязательно замените имя сервиса, пользователя, каталоги состояния, каталоги логов, сетевые семейства, capabilities и требования к системным вызовам. Убедитесь, что каталоги существуют и имеют правильные права.

Финальный чек-лист перед применением в production

  • Резервная копия unit-файла и drop-in создана.
  • systemd-analyze verify не выявил ошибок.
  • Сервис успешно запускается после перезагрузки.
  • Функциональные тесты пройдены.
  • В журнале нет ошибок доступа или запрещённых операций.
  • Доступ к разрешённым каталогам работает.
  • Сетевые подключения установлены.
  • Ненужные capabilities отсутствуют.
  • План отката протестирован.

Дополнительные материалы по безопасности systemd и Linux: управление секретами в systemd, анализ логов через journalctl, практический hardening Linux-сервера.

Поделиться:
Сохранить гайд? В закладки браузера