Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

Hardening сервисов на VDS: sandbox опции systemd — ProtectSystem, PrivateTmp, CapabilityBoundingSet

Как ограничить права Linux‑сервисов на VDS с помощью sandbox‑опций systemd. Разбираем ProtectSystem, PrivateTmp, CapabilityBoundingSet и NoNewPrivileges, показываем безопасные шаблоны для Nginx, PHP‑FPM, PostgreSQL и Redis, а также приёмы отладки без простоя.
Hardening сервисов на VDS: sandbox опции systemd — ProtectSystem, PrivateTmp, CapabilityBoundingSet

Зачем вообще hardening сервисов на VDS

Даже идеально настроенный файрвол и обновлённые пакеты не спасут, если скомпрометирован один конкретный сервис. В таком случае нападающему важно, какие действия сможет выполнять процесс: писать в конфиги, подменять бинарники, сканировать файловую систему, выходить в сеть на привилегированные порты, подгружать модули ядра и т.д. Чем меньше прав у сервиса, тем меньше последствий от уязвимости. Этот подход и называется hardening на уровне сервиса.

В системах с systemd мы можем изолировать процессы и урезать их права буквально несколькими строками в юните. На VDS это особенно критично: на одном узле часто живёт несколько приложений, и изоляция снижает риск каскадных инцидентов.

Как работает sandbox в systemd

Опции sandbox в systemd создают для процесса ограниченное окружение: монтируют каталоги в read-only, прячут устройства, отделяют /tmp, отрезают опасные системные вызовы и Linux capabilities. Это работает на уровне пространств имён ядра (namespaces), cgroups и политики монтирования. В отличие от контейнеров, мы не меняем модель деплоя: сервис запускается как обычно, но в более строгих рамках.

Идея проста: пусть сервис видит и трогает только то, что ему действительно нужно. Всё остальное — read-only или недоступно.

Схема: изоляция сервиса через опции sandbox в systemd

Ключевые опции, которые стоит включать первыми

ProtectSystem

ProtectSystem переводит системные каталоги в режим только для чтения. Это рвёт целую категорию атак: подмена бинарников, модификация конфигов базовой системы, неожиданные записи в системные директории. На практике используют значения full или strict. При full системные области вроде /usr и других критичных путей становятся read-only. Режим strict делает ещё жёстче: почти вся файловая система становится только для чтения, а исключения на запись задаются явными списками через ReadWritePaths, StateDirectory, CacheDirectory, LogsDirectory и т.п. Для большинства сервисов full — безопасная отправная точка, а strict — цель для зрелой конфигурации.

PrivateTmp

PrivateTmp=yes выдаёт сервису собственный изолированный /tmp и /var/tmp. Один сервис больше не видит временные файлы другого. Это снижает вероятность как преднамеренных атак через подмену файлов в /tmp, так и случайных коллизий. Из важных нюансов: если два сервиса ожидали «встречаться» через общий /tmp, они перестанут видеть файлы друг друга. Лучше заменить такие практики на выделенные каталоги через RuntimeDirectory или обмен через сокеты/IPC.

CapabilityBoundingSet

Linux capabilities разрезают «root» на набор мелких привилегий. CapabilityBoundingSet позволяет жёстко задать список capabilities, которые разрешены процессу. Например, веб‑серверу часто нужен только CAP_NET_BIND_SERVICE для портов ниже 1024. Остальные (особенно опасные вроде CAP_SYS_ADMIN, CAP_SYS_MODULE, CAP_SYS_PTRACE, CAP_NET_ADMIN) лучше вырезать. Чем меньше список, тем безопаснее. Для сервисов без нужды слушать привилегированные порты смело делайте пустой набор.

NoNewPrivileges

NoNewPrivileges=yes запрещает процессу повышать свои права после запуска. Любые setuid-бинарники и дополнительные capabilities из файловых атрибутов не дадут прироста. Это дешёвая и очень эффективная защита, которую стоит включать почти везде.

ProtectSystem: от «full» к «strict» без боли

Чаще всего переходят так: сначала ProtectSystem=full, сервис работает — затем двигаются к strict. Главная сложность — правильно перечислить каталоги, куда процесс всё-таки должен писать. Вместо открытия целых деревьев рекомендуются декларативные директории StateDirectory, CacheDirectory, LogsDirectory, RuntimeDirectory. Они автоматически создаются с подходящими правами, попадают в нужные tmpfs или постоянные места, и вам не нужно держать список путей вручную.

Если же нужно явно разрешить запись в конкретный путь, используйте ReadWritePaths. Для доступа только на чтение — ReadOnlyPaths, а для запрета даже чтения — InaccessiblePaths. Так вы соберёте минимум поверхностей записи, который необходим приложению, а остальное останется read-only или недоступным.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

PrivateTmp: типичные сценарии и отладка

Изоляция временных файлов чаще помогает, чем мешает. Однако проблемы всплывают, когда логика приложения опиралась на общий /tmp. Примеры:

  • Веб‑сервер писал временные загруженные файлы в /tmp, а backend брал их оттуда по пути. После включения PrivateTmp backend больше их не видит.
  • Два сервиса обменивались сокетом в /tmp.

Правильные решения: настройте оба сервиса на использование общего RuntimeDirectory одного из них (или вынесите сокет в выделенный каталог под /run), либо явно укажите путь вне /tmp, например в /var/lib/yourapp/tmp, открыв к нему доступ через StateDirectory или ReadWritePaths. Проверка проста: смотрите реальный путь к /tmp внутри неймспейса процесса, а также логи отказов на запись.

CapabilityBoundingSet: как подбирать минимальный набор

Подход снизу вверх: сначала запрещаем всё (пустой список), затем добавляем ровно те capabilities, без которых сервис не стартует или теряет требуемую функциональность. Несколько ориентиров:

  • HTTP‑сервер на 80/443 портах обычно требует только CAP_NET_BIND_SERVICE. Для HTTPS не забудьте про актуальные SSL-сертификаты.
  • Сервис без низкопортового bind и без управления сетевыми интерфейсами не нуждается в сетевых привилегиях вовсе.
  • Базы данных не нуждаются в CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_SYS_MODULE.
  • Процессы, запускаемые от непривилегированных пользователей, часто обходятся без любых capabilities.

Помните про AmbientCapabilities: если процесс реально должен наследовать capability в дочерних процессах, добавьте её туда. Но в большинстве случаев обходитесь только CapabilityBoundingSet.

Пример: анализ безопасности unit через systemd-analyze security

NoNewPrivileges: включайте по умолчанию

Опция редко ломает легитимные сценарии и значительно сокращает класс атак через исполняемые файлы с setuid и файловые capabilities. Если сервис не зависит от запуска setuid‑бинарников (а это редкость для сетевых демонов), просто включайте NoNewPrivileges=yes.

Практический шаблон: безопасный override для типового сервиса

Используйте systemctl edit your.service и добавляйте минимальный профиль, постепенно ужесточая настройки. Пример стартового варианта для сетевого демона, слушающего 80/443:

[Service]
User=www-data
Group=www-data
NoNewPrivileges=yes
ProtectSystem=full
ProtectHome=true
PrivateTmp=yes
PrivateDevices=yes
ProtectControlGroups=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectHostname=yes
LockPersonality=yes
RestrictSUIDSGID=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# Явные каталоги для записи
StateDirectory=yourapp
CacheDirectory=yourapp
LogsDirectory=yourapp
RuntimeDirectory=yourapp

После успешного прогона тестов можно перейти к ProtectSystem=strict и, при необходимости, добавить ReadWritePaths для оставшихся путей.

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

Nginx

Часто достаточно: NoNewPrivileges=yes, ProtectSystem=full, PrivateTmp=yes, CapabilityBoundingSet=CAP_NET_BIND_SERVICE, каталоги на запись — /var/log/nginx, /var/cache/nginx. Если пойдёте в strict, добавьте нужные ReadWritePaths или используйте LogsDirectory/CacheDirectory. Для HTTPS пригодятся актуальные SSL-сертификаты.

PHP-FPM

Включайте PrivateTmp=yes без страха: обмен с Nginx идёт через сокеты/порт, а не через общий /tmp. Ограничьте записи только пулами логов и сессий. Сами сессии удобно хранить в каталоге, который создаёт StateDirectory. NoNewPrivileges и строгий набор capabilities (вплоть до пустого) обычно работают без проблем.

PostgreSQL/MySQL

У базы данных нет причин трогать системные каталоги или ядро. Рекомендуются: NoNewPrivileges=yes, ProtectSystem=full (в перспективе — strict), PrivateTmp=yes. Обязательно откройте каталог данных и логов через StateDirectory/LogsDirectory или ReadWritePaths. Набор capabilities — пустой.

Redis

Redis хорошо изолируется: пустой CapabilityBoundingSet, NoNewPrivileges=yes, ProtectSystem=strict с разрешением записи в каталог данных и логов, PrivateTmp=yes. Если используется RDB/AOF, не забудьте разрешить запись туда, где лежат файлы персистентности.

Отладка и верификация

Несколько приёмов, чтобы проверять и улучшать профиль безопасности:

  • systemd-analyze security your.service — оценка безопасности юнита и подсказки по улучшениям.
  • journalctl -u your.service -b — ищите Permission denied и отказ в монтировании или открытии файлов.
  • systemctl show -p FragmentPath -p DropInPaths your.service — убедитесь, что override подхватился.
  • systemctl status your.service — быстрый взгляд на ошибки запуска.
  • Тестовый запуск с параметрами: systemd-run --unit=sandbox-test -p PrivateTmp=yes -p NoNewPrivileges=yes /usr/bin/env.

Про оркестрацию задач см. также материал про таймеры systemd: планирование без crontab, а для фона и очередей — systemd‑воркеры.

Совместимость с SELinux/AppArmor и ядром

Опции systemd и MAC‑политики (SELinux/AppArmor) комплементарны. Если у вас активен SELinux в enforcing‑режиме, некоторые операции могут блокироваться до попадания в логику sandbox systemd. Это неплохо: многослойная защита — цель. Просто учитывайте оба источника ограничений при отладке. Сторонние модули ядра или нестандартные драйверы потребуют аккуратнее подходить к ProtectKernelModules и CapabilityBoundingSet.

План безопасного внедрения на проде

  1. Снимите «снимок» состояния: systemd-analyze security для ключевых юнитов, зафиксируйте метрики.
  2. Начните с безобидных опций: NoNewPrivileges=yes, PrivateTmp=yes, пустой или минимальный CapabilityBoundingSet.
  3. Перейдите к ProtectSystem=full; протестируйте сценарии записи (логи, кэш, загрузки).
  4. Выделите каталоги через StateDirectory/CacheDirectory/LogsDirectory/RuntimeDirectory.
  5. Доводите до ProtectSystem=strict, добавляя точечные ReadWritePaths при необходимости.
  6. Включайте дополнительные ограничения: RestrictSUIDSGID, RestrictNamespaces, MemoryDenyWriteExecute, ProtectKernel*.
  7. Катите поэтапно, начиная со стейджа и непиковых часов. Держите под рукой быстрый откат: systemctl revert your.service.

Частые ошибки и как их диагностировать

  • Сервис не может открыть порт 80/443 после ужесточения. Проверьте, что в CapabilityBoundingSet и AmbientCapabilities есть CAP_NET_BIND_SERVICE.
  • Permission denied при записи в лог. Либо используйте LogsDirectory, либо дайте доступ через ReadWritePaths к каталогу логов.
  • Пропали временные файлы между сервисами. Это PrivateTmp. Перенесите обмен в общий каталог под /run или используйте сокеты/IPC.
  • Конфиг изменяется приложением и перестал сохраняться. ProtectSystem сделал /etc read‑only. Перенесите динамичную конфигурацию в /var/lib или отдельный путь с ReadWritePaths.
  • Непредсказуемые сбои при загрузке модулей или управлении сетью. Вероятно, вырезаны критичные capabilities. Временно расширьте набор, найдите минимально достаточный список и зафиксируйте его.

Итог

Sandbox‑опции systemd позволяют быстро и без миграции в контейнеры резко сократить последствия возможной компрометации сервиса. Начните с NoNewPrivileges и PrivateTmp, закрепите результат ProtectSystem=full, затем переходите к strict и минимальному CapabilityBoundingSet. Дальше наращивайте дополнительные ограничения. Такой поэтапный подход безопасен для продакшена и ощутимо повышает уровень защиты на VDS без заметных затрат.

Поделиться статьей

Вам будет интересно

Память на малом VDS без сюрпризов: swap, zram, vm.overcommit и OOM‑killer на практике OpenAI Статья написана AI Fastfox

Память на малом VDS без сюрпризов: swap, zram, vm.overcommit и OOM‑killer на практике

Малый VDS часто упирается в память: PHP‑FPM, базы, кэш и воркеры делят считанные гигабайты. Ошибка Killed в логах и зависания — пр ...
Wildcard DNS и превью‑стенды: поддомены на каждую ветку Git через Nginx map на VDS OpenAI Статья написана AI Fastfox

Wildcard DNS и превью‑стенды: поддомены на каждую ветку Git через Nginx map на VDS

Показываю рабочую схему превью‑стендов: одна VDS, wildcard DNS на dev‑домен, Nginx с map и скрипты, поднимающие приложения на уник ...
Умный кэш Nginx для API и внешних бэкендов: proxy_cache, stale‑while‑revalidate и cache lock OpenAI Статья написана AI Fastfox

Умный кэш Nginx для API и внешних бэкендов: proxy_cache, stale‑while‑revalidate и cache lock

Как снизить латентность и нагрузку на внешние бэкенды, не ломая логику API? Разберём боевые паттерны Nginx: proxy_cache, ключи и T ...