open atlas
↑ К треку
Безопасность облака и инфраструктуры CLOUD · 02 · 04

Network Policy and Pod Security

Свежий под Kubernetes полностью открыт: любой под достаёт любой другой по плоской сети, а без SecurityContext контейнер работает под root с доступом к хосту. NetworkPolicy и Pod Security Standards — два opt-in барьера, превращающих эту позицию default-allow в default-deny.

CLOUD Middle ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior

Пентестер пробивает один малоценный под — ресайзер картинок за публичным эндпоинтом загрузки, уязвимый к SSRF. На захардененном хосте на этом бы всё и кончилось: один скомпрометированный процесс в одном контейнере. Но это дефолтный кластер Kubernetes, поэтому две двери уже открыты. Первая: под может открыть TCP-соединение к каждому другому поду в кластере — к базе, внутреннему admin-API, эндпоинту метрик, что течёт токенами, к сервису облачных метаданных на 169.254.169.254. Файрвола между нагрузками нет, потому что его никто не написал. Вторая: контейнер пода работает под UID 0 с дефолтным SecurityContext, так что цепочка SSRF→RCE отдаёт атакующему root внутри контейнера, который может mount, грузить модули ядра через щедрый набор capabilities и зондировать ноду. За двадцать минут ресайзер становится точкой пивота во весь кластер. Kubernetes сделал ровно то, что обещал: запланировал под и дал ему говорить со всеми. Никто не велел ему этого не делать.

К концу урока ты сможешь написать NetworkPolicy с default-deny и прочитать существующую на предмет дыры, что тихо открывает её обратно, применить профиль Pod Security Standards, блокирующий побеги root/privileged/hostPath, и объяснить, почему оба барьера opt-in и почему этот дефолт кусает команды каждый раз.

Две двери default-allow: сеть и спека пода

У безопасности Kubernetes повторяющаяся форма: платформа поставляется в режиме default-allow, а безопасность — это то, что ты добавляешь. Две такие двери — тема этого урока, и они независимы: закрытие одной ничего не делает для другой.

Дверь первая — плоская сеть. Сетевая модель Kubernetes требует, чтобы каждый под мог достать любой другой под по IP, через все неймспейсы, без NAT. Это не баг; это контракт, который обязан выполнять CNI-плагин. Следствие: east-west-трафик (под-под) по умолчанию полностью открыт. NetworkPolicy — это opt-in объект, который его ограничивает, но только если твой CNI реализует enforcement NetworkPolicy (Calico, Cilium и большинство managed-CNI это делают; легаси-дефолт flannel — нет, так что политика, которую ты kubectl apply, успешно применяется и тихо не enforce-ит ничего).

Дверь вторая — SecurityContext пода. Контейнер без securityContext работает под тем UID, который задаёт образ — часто root (UID 0) — наследует дефолтный набор Linux-capabilities, может получить privileged: true, монтировать тома hostPath и делить с хостом namespace PID или сети. Каждое из этого — задокументированный примитив побега из контейнера. Pod Security Standards (PSS) определяют три профиля — privileged (без ограничений), baseline (блокировать известные побеги) и restricted (жёстко закрыт: non-root, без добавленных capabilities, seccomp включён) — а Pod Security Admission (PSA) — это встроенный admission-контроллер, enforce-ящий выбранный профиль для неймспейса через метки.

NetworkPolicy: аддитивный allow и паттерн default-deny

NetworkPolicy только разрешающая и аддитивная, ровно как RBAC — deny-правила нет. Трюк, заставляющий её вести себя как файрвол, — это селектор: в момент, когда любая NetworkPolicy выбирает под для заданного направления (ingress или egress), этот под перещёлкивается с default-allow на default-deny для этого направления, и пропускается только тот трафик, что явно перечислен в любой выбирающей его политике. Объединение всех совпавших политик — это allow-список.

Поэтому каноничный приём хардненинга — это default-deny на неймспейс: политика, что выбирает каждый под, но не перечисляет разрешённых соседей:

ПолитикаСелектор + направлениеЭффектГрабли при пропуске
default-deny-allpodSelector: {}, оба Ingress + EgressКаждый под в неймспейсе дропает весь трафик, пока не разрешеноПлоская сеть — пробитый под достаёт весь кластер
allow-dnsegress к kube-dns по UDP/TCP 53Даёт подам резолвить имена, когда egress запрещёнКаждый под ломается — DNS первым убивает default-deny
allow-from-frontendingress к app: api от app: webТолько web-ярус может достать API-ярусЛюбой под бьёт прямо в API в обход края
block-metadataegress deny к 169.254.169.254/32Останавливает кражу облачных кредов через SSRF из любого подаSSRF в одном поде читает IAM-роль / креды ноды

Неочевидный режим отказа живёт во второй строке. В тот миг, когда ты применяешь default-deny egress-политику, ты заодно блокируешь DNS-запросы пода к kube-dns, потому что это egress к другому поду на порт 53. Каждый запрос тогда падает не с «connection refused», а с ошибкой резолва имени, что отправляет инженеров отлаживать не тот слой на час. Корректный default-deny поэтому всегда пара: deny-all плюс явное allow-DNS egress-правило. Это самая частая причина, по которой команды откатывают NetworkPolicy и объявляют её «слишком сложной».

Частая ошибка

NetworkPolicy, которую kubectl apply принимает, — не то же самое, что NetworkPolicy, которая enforce-ится. NetworkPolicy — это API-объект, хранимый в etcd; enforcement — работа CNI-плагина, и не каждый CNI её делает. На кластере с голым flannel (или с неверным режимом Cilium/Calico) твой тщательно написанный default-deny сохранён, виден в kubectl get netpol и дропает ровно ноль пакетов. Всегда проверяй enforcement эмпирически — kubectl exec в под, который политика должна изолировать, и попробуй достать соседа, которого не должна — а не доверяй тому, что объект существует. «Политика применена» и «трафик заблокирован» — два разных утверждения.

Pod Security Standards: блокировать побег, а не только сеть

NetworkPolicy сдерживает латеральное движение между подами. Она ничего не делает с подом, который сбегает вниз к ноде — а это разрешает неограниченный SecurityContext. Три побега, что важны, и что делает с каждым профиль restricted:

  1. Работа под root. runAsNonRoot: true плюс ненулевой runAsUser означает, что побег из контейнера высаживает атакующего под непривилегированным UID, а не root, резко ограничивая, что он может на ноде, если вырвется из namespace.
  2. privileged: true и добавленные capabilities. У privileged-контейнера по сути все capabilities и доступ к устройствам — это почти прямой путь к хосту. restricted запрещает privileged, требует сбросить ALL capabilities и возвращает обратно только NET_BIND_SERVICE.
  3. Host-namespaces и hostPath. hostPID: true / hostNetwork: true делят процессный или сетевой вид ноды; том hostPath может примонтировать / и прочитать креды kubelet. restricted и baseline оба это блокируют.

Pod Security Admission enforce-ит профиль по метке неймспейса, в трёх режимах — enforce (отклонять несоответствующие поды), audit (логировать их) и warn (предупреждать применяющего) — так что можно выкатить сперва в warn/audit и посмотреть, что сломалось бы, перед переключением на enforce:

Выбери лучший вариант

Ты хардишь неймспейс stateless-подов приложения, которым никогда не нужны root, доступ к хосту или лишние capabilities. Выбери позицию Pod Security.

Викторина

Ты применяешь default-deny `Ingress` + `Egress` NetworkPolicy к неймспейсу, и сразу каждый под падает с ошибками резолва имени. Что произошло?

Викторина

Неймспейс enforce-ит профиль `restricted` Pod Security, но CNI кластера не реализует NetworkPolicy. Под скомпрометирован через RCE. Каков реалистичный следующий ход атакующего?

Расставь шаги по порядку

Упорядочь шаги, чтобы безопасно захарденить неймспейс без простоя, от первого к последнему:

  1. 1 Пометь неймспейс `warn`/`audit` для профиля `restricted` и смотри, что было бы отклонено
  2. 2 Почини SecurityContext подов, помеченных audit (runAsNonRoot, сброс capabilities)
  3. 3 Переключи Pod Security на `enforce: restricted`, когда audit чист
  4. 4 Примени default-deny ingress+egress NetworkPolicy плюс явное allow-DNS правило
  5. 5 Добавь узкие allow-правила для реального трафика, затем проверь enforcement через kubectl exec
Вспомните перед уходом
  1. 01
    Пройди шаг за шагом, почему `kubectl apply` NetworkPolicy может успешно пройти и всё равно блокировать ноль трафика, и как бы ты доказал enforcement.
  2. 02
    Почему NetworkPolicy и Pod Security Standards дополняют друг друга, а не дублируют, и что каждый не покрывает?
Итог

У свежего пода Kubernetes две независимые двери default-allow. Сеть плоская: каждый под достаёт любой другой по IP через неймспейсы, так что east-west-трафик нараспашку, пока NetworkPolicy его не ограничит. NetworkPolicy аддитивна и только разрешающая (deny-правила нет) — выбор пода перещёлкивает его в default-deny для этого направления, и каноничный хардненинг — это пара default-deny: deny-all ingress+egress плюс явное allow-DNS egress-правило, потому что первое, что ломает наивный запрет egress, — резолв имён к kube-dns. Критично: NetworkPolicy enforce-ится, только если CNI её реализует, так что «применено» надо проверять против «enforce-ится», тестируя dataplane через kubectl exec. Вторая дверь — SecurityContext пода: без ограничения контейнер работает под root с дефолтным набором capabilities и может запросить privileged, hostPath и host-namespaces — каждое примитив побега на ноду. Pod Security Standards определяют профили privileged/baseline/restricted, а Pod Security Admission enforce-ит один на неймспейс через метку, в режимах enforce/audit/warn, так что ты стейджишь выкат перед тем, как он начнёт отклонять поды. Эти два контроля ортогональны: NetworkPolicy сдерживает латеральное движение, PSS блокирует побег на ноду, а компрометации нужна лишь одна открытая дверь — поэтому применяй оба, плюс минимальный RBAC у ServiceAccount. Когда в следующий раз будешь ревьюить неймспейс, три вопроса: есть ли default-deny NetworkPolicy (и enforce-ит ли её CNI), какой профиль Pod Security enforce-ится и может ли любой под всё ещё достать эндпоинт метаданных?

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 6 завершено
Связанные уроки
углубляется в

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.