Kubernetes 1.23 и PSP: начинаем миграцию на Pod Security Standards
Kubernetes 1.23 GA: PodSecurityPolicy deprecated, Pod Security Admission в beta. Разбираем разницу подходов и что нашли в клиентских кластерах при первичном аудите.
Kubernetes 1.23 GA (декабрь 2021): PodSecurityPolicy официально deprecated, Pod Security Admission переходит в beta
Kubernetes 1.23 вышел в декабре, и главная новость с точки зрения безопасности - не новая фича, а таймер. PodSecurityPolicy теперь официально deprecated. Pod Security Admission перешёл из alpha в beta и включён по умолчанию. Срок жизни PSP ограничен: по плану её уберут в 1.25.
Мы начали проходить по клиентским кластерам, которые ведём в рамках managed-сервиса, и смотреть что там с PSP. Картина оказалась предсказуемой, но от этого не менее неприятной.
Что нашли при аудите
В нескольких кластерах PSP живёт в одном из трёх состояний. Первое - политики настроены и реально работают: есть продуманные ограничения, матрица RBAC, команда понимает что там. Таких меньшинство. Второе - есть одна permissive-политика на весь кластер, привязанная ко всем ServiceAccount через system:authenticated. Де-факто PSP включена, де-факто ничего не ограничивает. Третье - PSP-контроллер активирован в admission-плагинах, политики есть, но никто уже не помнит зачем именно такие.
Все три варианта требуют разной работы, но одинаково требуют работы.
Отдельный привет - кластеры, которые обновляли последовательно начиная с давних версий. В них встречаются политики, написанные ещё когда PSP только появилась в alpha (Kubernetes 1.3-1.4), с полями, которые уже устарели, и комментариями вида # TODO: разобраться с этим. TODO не разобрались.
Чем PSP плоха и почему её убирают
PSP - концептуально правильная идея: ограничить что pod может делать на уровне хоста. Запрет privileged-контейнеров, hostPID, hostNetwork, опасных capabilities, монтирование hostPath. Всё это нужно.
Но реализация запутана. Политика выбирается через RBAC - admission-контроллер проверяет, какие PSP доступны ServiceAccount пода или пользователю, сделавшему запрос. Если доступных несколько - берётся первая по алфавиту, которая валидирует pod. Это поведение неочевидно и регулярно порождает сюрпризы: pod неожиданно получает менее строгую политику, чем предполагалось, просто потому что имя другой политики начинается раньше по алфавиту.
Ещё одна проблема - отсутствие нейтральных режимов. PSP либо блокирует, либо нет. Нет нативного способа сказать «посмотри что нарушает эта политика, но не блокируй». Это делает тестирование сложным: в staging всё работает, в production чего-то нет - и ищи разницу в RBAC.
Как устроены Pod Security Standards
Pod Security Admission работает иначе. Вместо произвольных политик - три встроенных профиля:
- privileged - без ограничений. Для системных namespace-ов: kube-system, мониторинг, CNI.
- baseline - базовые ограничения. Запрещает privileged-контейнеры, hostPID/hostNetwork/hostIPC, небезопасные capabilities, небезопасные volume-типы.
- restricted - строгий профиль. Всё из baseline плюс: запуск от root запрещён, seccomp обязателен (RuntimeDefault или Localhost), capabilities ограничены до минимума.
Профиль назначается через лейблы на namespace, и здесь ключевое отличие от PSP - три режима применения:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
enforce - pod отклоняется. warn - pod создаётся, но в ответе API приходит предупреждение (его видно в kubectl-выводе). audit - событие пишется в audit log, pod проходит. Эти режимы независимы и могут быть с разными профилями - например, enforce: baseline и audit: restricted.
Это меняет процесс миграции принципиально: можно поставить warn: restricted и просто деплоить как обычно, собирая предупреждения. Никакого риска что что-то сломается.
Как мы делаем миграцию
Схема, которую мы сейчас применяем по кластерам:
Первое - инвентаризация PSP и привязок. kubectl get psp даёт список политик. Но важно понять кто их использует: kubectl get clusterrolebinding,rolebinding -A -o json и ищем ссылки на PSP. Строим матрицу: политика - namespace - ServiceAccount.
Второе - включить warn для всех production-namespace-ов. Профиль restricted как warn даёт картину что нарушает строгий профиль. Для менее критичных пространств можно начать с baseline. Режим warn работает в 1.23 без дополнительных настроек - PSA в beta, включён по умолчанию.
Третье - разбор нарушений. Типичное что мы находим: контейнеры без явного securityContext, запускающиеся от root. Поля runAsNonRoot: true, runAsUser, allowPrivilegeEscalation: false, capabilities.drop: [ALL] - их просто не добавляли, потому что и так работало. Для restricted ещё нужен seccompProfile. Всё это правится в манифестах деплоев.
Четвёртое - отдельная история с system-компонентами. Для kube-system, CNI (Cilium, Calico), мониторинга (node-exporter, Prometheus agent) - профиль privileged. Это не лень, это реальность: node-exporter монтирует hostPath и нужен доступ к сетевому стеку хоста. Никакой другой профиль там не пройдёт.
Пятое - переключение на enforce. После того как warn не показывает новых нарушений в течение нескольких дней реальных деплоев - переключаем на enforce.
Где PSA не дотягивает до PSP
Честно: не везде PSA - прямая замена. PSP позволяла гибко разрешать конкретные вещи конкретным ServiceAccount. Например, разрешить монтировать определённый hostPath-путь только для одного DaemonSet. В PSA такого нет - профиль применяется к namespace целиком.
Если нужна тонкая гранулярность - в бой идут внешние инструменты: OPA/Gatekeeper или Kyverno. Они умеют писать произвольные политики с доступом к полному контексту запроса. PSA и сторонний движок политик не конкурируют - они дополняют друг друга: PSA закрывает базовые профили, Kyverno/Gatekeeper - специфику.
Несколько кластеров с нетривиальными PSP-политиками мы пока оставили как есть и разбираем отдельно. Срок до 1.25 есть, но он не бесконечный - и наш опыт с Kubernetes 1.22 показал, что PSP-конфигурации оказываются сложнее, чем выглядят снаружи.
Где сейчас
Миграция идёт, не завершена. Простые кластеры - те, где была permissive-политика «лишь бы работало» - переводятся быстро: PSP там была чисто номинальной, реальных ограничений не добавляла. Кластеры с реально настроенными политиками требуют больше времени: нужно разобраться с каждым исключением и принять осознанное решение.
Полезный побочный эффект: аудит PSP вскрыл деплои, у которых вообще нет securityContext. Это были бы проблемы и без всякой миграции.
- Kubernetes 1.22: Ingress в GA и прощание с PodSecurityPolicy · 5 августа 2021
- Kubernetes 1.23: HPA v2 в GA и KEDA для event-driven autoscaling · 22 ноября 2021