Directum RX в кластере с политиками: Pod Security, NetworkPolicy, RBAC и что из этого ломает систему
Как закрыть кластер Kubernetes под Directum RX по требованиям ИБ и не остановить систему. Уровни Pod Security, запрет root и записи в файловую систему, сетевые политики по умолчанию «всё запрещено», RBAC и токены сервисных учёток, лимиты. Порядок ввода через режим предупреждений и типовые поломки.
Коротко. Pod Security вводится через
--dry-run=serverи режимwarn, файловая система закрывается с отдельными томами под/tmp, журналы и ключи. Сеть закрывается политикой «всё запрещено» с явными правилами для DNS, ingress и внешних систем по адресам. Сервисным учёткам RX токен не монтируется, людямexecи чтение секретов выдаются поштучно. Всё сначала на тестовом стенде.
RX развёрнут в Kubernetes, работает, пользователи довольны. Потом приходит служба ИБ с перечнем: контейнеры не от root, сеть между подами закрыта, у сервисных учёток нет лишних прав, у каждого пода лимиты. Если включить всё сразу, половина подов не стартует, а вторая половина перестанет видеть базу. Разбираем, что каждая политика делает, что из неё задевает RX и в каком порядке её вводить.
Симптом
После включения политик обычно видно одно из следующего:
- Поды не создаются, в событиях
violates PodSecurity "restricted"или отказ вебхука политик. - Под стартует и падает с
Permission deniedилиRead-only file systemпри записи во временный каталог. - Сервисы RX не подключаются к PostgreSQL, RabbitMQ или Elasticsearch: таймаут соединения, хотя сеть «не трогали».
- Перестали приходить уведомления в веб-клиент: закрыт трафик между сервисами внутри пространства имён.
- Перестал работать DNS внутри подов: политика запретила исходящий трафик целиком, включая запросы к CoreDNS.
Почему так
В Kubernetes четыре независимых механизма, и все четыре по умолчанию открыты.
| Механизм | Что ограничивает | По умолчанию |
|---|---|---|
| Pod Security Admission | с какими правами может работать контейнер: root, привилегии, доступ к хосту | разрешено всё |
| NetworkPolicy | кто с кем может соединяться по сети | разрешено всё |
| RBAC | что можно делать через API кластера людям и сервисным учёткам | зависит от того, кто что выдал |
| ResourceQuota и LimitRange | сколько процессора и памяти может занять под и пространство имён | без ограничений |
Образы прикладных систем часто собираются в расчёте на открытый кластер: процесс работает от root, пишет во временные каталоги внутри контейнера, ходит в сеть куда угодно. Политики ломают именно эти предположения.
Pod Security
В Kubernetes встроены три уровня (Pod Security Standards): privileged без ограничений, baseline запрещает
явно опасное (доступ к сети и процессам хоста, привилегированный режим), restricted дополнительно требует
работу не от root, запрет повышения привилегий, сброс всех capabilities и профиль seccomp.
Уровень задаётся меткой на пространстве имён, и у каждой метки три режима: enforce отклоняет под, warn
выводит предупреждение при создании, audit пишет нарушение в журнал аудита API.
Узнать, что сломается, до включения можно пробным применением метки:
kubectl label --dry-run=server --overwrite ns rx pod-security.kubernetes.io/enforce=restricted
Команда ничего не меняет, но выводит предупреждение по каждому работающему поду, который нарушит уровень, и называет, какое именно поле мешает.
Что обычно мешает подам прикладной системы на уровне restricted и как с этим быть:
- Процесс работает от root. Проверьте, от какого пользователя собран образ:
docker inspect --format '{{.Config.User}}' <образ>. Пустое значение означает root. Если образ поддерживает работу от другого пользователя, достаточно задатьrunAsNonRoot: trueи числовойrunAsUser. Если не поддерживает, это вопрос к поставщику образа, а пространство имён временно остаётся на уровнеbaselineс записанной причиной. - Нет
allowPrivilegeEscalation: falseиcapabilities: drop: ["ALL"]. Обычно добавляется в значения чарта без последствий. Исключение составляют процессы, которые слушают порт ниже 1024 не от root: им нуженNET_BIND_SERVICEили перенос на порт выше 1024. - Нет
seccompProfile: RuntimeDefault. Прикладные сервисы почти никогда от этого не страдают.
Файловая система только для чтения
readOnlyRootFilesystem: true в уровень restricted не входит, но службы ИБ его спрашивают часто. Сервисы
на .NET при таком запрете падают при первой попытке записи. Обычно им нужно:
/tmpдля временных файлов и канала диагностики среды выполнения;- каталог журналов, если сервис пишет журналы в файл;
- домашний каталог пользователя процесса: туда .NET по умолчанию складывает ключи защиты данных (Data Protection), если они не настроены явно.
Каждый такой путь монтируется отдельным томом emptyDir, остальная файловая система остаётся закрытой. Список
путей проще всего получить опытом: включить запрет на тестовом стенде и читать ошибки при старте и при работе.
Отдельно смотрите на ключи защиты данных: если они лежат в emptyDir, после перезапуска пода ключи новые,
и старые cookie и токены перестают расшифровываться. Как это выглядит, описано в
справочнике ошибок.
NetworkPolicy
Сетевые политики работают, только если их поддерживает сетевой плагин кластера. Calico и Cilium поддерживают, flannel без дополнений нет: политики создаются, но ничего не фильтруют. Проверьте это до того, как отчитываться о закрытой сети.
Рабочая схема для пространства имён RX:
- Запрет по умолчанию на входящий и исходящий трафик для всех подов пространства.
- DNS. Исходящий UDP и TCP на порт 53 к подам CoreDNS. Без этого правила ломается всё сразу, и диагностика уходит не туда.
- Вход снаружи только от контроллера ingress к веб-серверу и сервису клиентских подключений.
- Внутри пространства между сервисами RX. На первом шаге проще разрешить весь трафик внутри пространства имён, а потом сужать по портам.
- Выход к внешним системам по адресам и портам: PostgreSQL, RabbitMQ, Elasticsearch, S3, почта, Active Directory, Keycloak, если они вне кластера.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: rx
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: rx
spec:
podSelector: {}
policyTypes: ["Egress"]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }
Внешние системы в NetworkPolicy задаются блоками адресов (ipBlock), а не именами. Если адрес базы или брокера
поменялся при переезде, политика молча продолжает пускать на старый адрес и не пускает на новый. Поэтому при
любом переезде сервера список адресов в политиках входит в чек-лист.
Пробы живости от kubelet политики не блокируют: трафик с узла к его подам разрешён всегда.
Проверить, что правило работает, удобнее всего временным подом в том же пространстве с теми же метками:
kubectl -n rx run nettest --rm -it --image=busybox:1.36 --labels=app=nettest -- \
sh -c 'nc -zv -w3 10.0.0.15 5432; nslookup kubernetes.default'
В Cilium отброшенные пакеты видно сразу: hubble observe --verdict DROPPED -n rx. В Calico включается журналирование
для правила с действием Log.
RBAC и токены
Сервисам RX доступ к API кластера обычно не нужен. Тогда:
- для их сервисной учётки ставится
automountServiceAccountToken: false, и в под не монтируется токен, который можно украсть; - у учётки нет привязок к ролям, проверяется это командой:
kubectl auth can-i --list --as=system:serviceaccount:rx:default -n rx
Для людей отдельно смотрите на два права. pods/exec позволяет зайти в контейнер и прочитать всё, что
ему передано, включая пароли в переменных окружения. secrets get и list в пространстве RX позволяют
прочитать строки подключения напрямую. Эти права выдаются короткому списку людей, остальным хватает
просмотра подов и журналов.
Лимиты
LimitRange задаёт запросы и лимиты по умолчанию для контейнеров, у которых их нет, ResourceQuota
ограничивает всё пространство имён. Для RX это защита от соседей: другой проект в кластере не съест память,
на которую рассчитаны сервисы. Лимиты памяти ставятся по измеренному потреблению с запасом, и сервисы .NET
должны знать о лимите контейнера. Почему, разобрано в статье про память сервисов .NET.
Политики сверх встроенных
Встроенные уровни не умеют требовать, например, образы только из своего реестра, запрет тега latest или
обязательные лимиты. Для этого ставят движок политик: Kyverno или OPA Gatekeeper. Две оговорки.
- Сначала аудит. Новая политика включается в режиме аудита, нарушения смотрятся в отчётах, и только потом она переводится в запрет.
- Вебхук не должен стать точкой отказа. Если движок политик недоступен, а вебхук настроен отклонять запросы
при ошибке, в кластере перестают создаваться поды, включая системные. Пространства имён самого движка
и
kube-systemисключаются из проверки, а за доступностью движка следит мониторинг. Как читать отказ вебхука, описано в справочнике.
Порядок ввода
- На тестовом стенде включить все уровни в режиме
warnиaudit, собрать нарушения. - Поправить значения чартов: контекст безопасности, тома для записи, отключение токенов, лимиты.
- Включить NetworkPolicy на тестовом стенде, прогнать основные сценарии: вход, создание документа, запуск задачи, уведомления, поиск, предпросмотр, интеграции.
- Перевести тестовый стенд в
enforce, прожить неделю. - Повторить на продуктивном, начиная с режима
warn.
Что отделяет контейнер от хоста на уровне ядра (seccomp, AppArmor, SELinux, песочницы), разобрано в статье про изоляцию контейнеров.
Чего не делать
- Не включать
enforceна продуктивном без пробного прогона. Под, который не создался ночью при перезапуске узла, утром выглядит как падение системы. - Не забывать правило для DNS при запрете исходящего трафика.
- Не отчитываться о сетевых политиках, не проверив сетевой плагин. Политики без поддержки плагина ничего не фильтруют.
- Не держать ключи защиты данных .NET во временном томе. После перезапуска пользователей выбросит из системы.
- Не давать
pods/execвсем, у кого есть доступ к кластеру.
Похожая картина у вас?
Разберём ваш контур за 30 минут бесплатного созвона: версия RX, состав сервисов, что болит сильнее всего.
Написать