systemd умеет не только запускать службы, но и ограничивать их права: закрывать доступ к файловой системе, устройствам, сети и системным вызовам. Проблема в том, что стандартные юниты редко используют эти возможности, а включать ограничения вслепую опасно — сервис перестанет работать. Ниже — практический порядок аудита: инвентаризация, замер, точечные drop-in, проверка и откат.

Что показывает systemd-analyze security

Команда оценивает юнит по набору эвристик и выдаёт балл экспозиции от 0 до 10: чем ниже, тем лучше. Это не приговор и не точная оценка защищённости, а карта незакрытых возможностей. В отчёте видно, какие директивы уже заданы, какие отсутствуют и на что они влияют. Для незагруженного файла используйте --offline=true и путь к юниту.

systemd-analyze security --no-pager ssh.service
systemd-analyze security --no-pager --offline=true /etc/systemd/system/myapp.service
systemd-analyze security --no-pager ssh.service | tail -30
systemctl show ssh.service -p NoNewPrivileges -p ProtectSystem -p CapabilityBoundingSet

Не гонитесь за идеальным баллом. Сервис, которому нужен доступ к сети, не станет безопаснее от PrivateNetwork=yes, а попытка поставить MemoryDenyWriteExecute=yes сломает Java, Node.js или другой runtime с JIT. Цель — убрать то, что действительно не нужно конкретной службе.

Инвентаризация юнитов

Сначала составьте список того, что реально работает и что включается при загрузке. Отдельно найдите самодельные юниты в /etc/systemd/system и drop-in в каталогах .d. Обратите внимание на права: файл, доступный на запись обычному пользователю, позволяет подменить команду запуска.

systemctl list-units --type=service --state=running --no-pager
systemctl list-unit-files --type=service --state=enabled --no-pager
find /etc/systemd/system -maxdepth 2 -name '*.service' -printf '%m %u:%g %p\n'
systemd-analyze verify /etc/systemd/system/*.service

В списке стоит отдельно отметить службы, работающие от root, слушающие внешний интерфейс или имеющие доступ к устройствам. Именно они дают наибольший выигрыш от песочницы. Юниты, которые запускаются с DynamicUser или от непривилегированного пользователя, уже имеют меньшую поверхность атаки.

Чтение отчёта и базовый замер

Сохраните отчёт до изменений, чтобы потом сравнить. Удобно складывать результаты в отдельный каталог и делать diff. Если служба обновляется пакетным менеджером, её штатный юнит может измениться, а ваш drop-in останется — это нормально, но проверять совместимость нужно после каждого крупного обновления.

mkdir -p ~/audit/systemd/before
systemd-analyze security --no-pager ssh.service > ~/audit/systemd/before/ssh.txt
systemctl cat ssh.service | head -40
systemctl show ssh.service -p FragmentPath -p DropInPaths
systemd-analyze security --no-pager ssh.service | head -20

Drop-in вместо правки чужих файлов

Не редактируйте юниты из /usr/lib/systemd/system: их перезапишет обновление пакета. systemctl edit создаёт файл в /etc/systemd/system/<unit>.service.d/override.conf, который дополняет или переопределяет настройки. После сохранения выполните daemon-reload и перезапустите службу.

sudo systemctl edit ssh.service
sudo systemctl daemon-reload
systemctl cat ssh.service
systemctl restart ssh.service

Если systemctl edit открывает пустой файл, добавьте секцию [Service] вручную. Для сброса значения используйте пустое присваивание, например ProtectHome=, но делайте это осознанно: пустое значение возвращает поведение по умолчанию, а не обязательно «выключено».

Базовый набор ограничений

Начните с директив, которые почти не ломают обычные службы. NoNewPrivileges запрещает повышение прав через setuid, PrivateTmp даёт отдельный /tmp, ProtectSystem=strict делает систему только для чтения, ProtectHome скрывает домашние каталоги. CapabilityBoundingSet= с пустым значением убирает все capabilities — это подходит демонам, которым не нужны привилегированные операции.

[Service]
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
CapabilityBoundingSet=

Если служба пишет данные, добавьте ReadWritePaths=/var/lib/myapp или используйте StateDirectory=myapp. Последний вариант создаёт каталог с правильными правами и переживает перезапуск. Для логов есть LogsDirectory=, для runtime-файлов — RuntimeDirectory=.

Сетевые службы и capabilities

Веб-серверу нужен порт 80 или 443, поэтому пустой CapabilityBoundingSet его сломает. Разрешите только CAP_NET_BIND_SERVICE и добавьте ambient-возможность, иначе после смены пользователя capability потеряется. Одновременно закройте запись в системные каталоги и оставьте только необходимые пути.

[Service]
User=www-data
Group=www-data
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/cache/nginx /var/lib/nginx /var/log/nginx /run/nginx
PrivateTmp=yes
PrivateDevices=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
SystemCallFilter=@system-service

Для службы без сети используйте PrivateNetwork=yes: она получит изолированный network namespace с локальным интерфейсом. Файловые Unix-сокеты при этом продолжают работать. Для контейнерных runtime не ставьте RestrictNamespaces=yes — им нужны namespace-операции; вместо этого разрешите конкретные типы через синтаксис с ~ и проверьте запуск контейнера.

Проверка и типичные поломки

Меняйте по одной группе настроек и после каждого шага проверяйте функцию, а не только active (running). Правя SSH или другую сетевую службу, держите открытую резервную сессию и доступ через консоль. Смотрите журнал с уровнем ошибок, тестовый запрос или сценарий. Если служба упала в цикле перезапуска, systemd покажет код выхода, а systemd-analyze security — что именно вы включили.

sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl status myapp.service --no-pager
journalctl -u myapp.service -n 80 --no-pager
systemd-analyze security --no-pager myapp.service
  • ProtectHome=yes ломает приложение, которое читает файлы из /home.
  • PrivateDevices=yes скрывает GPU и устройства видеозахвата.
  • MemoryDenyWriteExecute=yes несовместим с JIT и некоторыми плагинами.
  • SystemCallFilter=@system-service может не хватить старому или экзотическому ПО; тогда добавляйте конкретные вызовы, а не отключайте фильтр целиком.
  • RestrictSUIDSGID=yes ломает службы, которые намеренно запускают setuid-помощников.
  • ProtectSystem=strict требует ReadWritePaths для каталогов, куда служба пишет данные, кроме приватного /tmp.

Чек-лист аудита

  • Соберите список запущенных и включённых служб, отметьте root и сетевые демоны.
  • Проверьте права на юниты и отсутствие world-writable файлов.
  • Снимите базовый отчёт systemd-analyze security и сохраните его.
  • Добавляйте ограничения через drop-in, по одной группе за раз.
  • После каждого изменения перезапускайте службу и проверяйте реальную функцию.
  • Фиксируйте исключения и причины, чтобы коллега не сломал сервис повторно.
  • Повторяйте аудит после крупных обновлений и смены версии дистрибутива.

Итог

Аудит systemd-юнитов — это не разовая команда, а цикл: измерить, ограничить, проверить, задокументировать. Начните с одной некритичной службы, доведите её до стабильного состояния и перенесите подход на остальные. Песочница systemd хорошо дополняет SELinux или AppArmor, но не заменяет их, и не стоит включать ограничения, совместимость которых вы не проверили.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте