Перейти к содержимому
GUARDCORE Инженерная база знаний Поиск

Раздел

Блог

76 материалов Последняя публикация: 22.09.2026
Блог

Безопасность цепочки поставки и runtime: где остановить риск, а где его увидеть

<p>Контейнерный образ — это не просто файл для запуска. Он несёт код, зависимости, настройки и историю сборки; затем один и тот же артефакт проходит registry, CI/CD, Kubernetes API и runtime узла. Поэтому разговор о «сканировании образов» недостаточен. Нужна цепочка решений: какой артефакт мы допускаем, кто это проверяет до запуска, что ограничивает сам Pod и какие события после запуска требуют реакции.</p> <p>Важная оговорка: Kubernetes предоставляет механизмы для запуска образов, admission control и Pod Security Admission, но не даёт готовой универсальной системы сканирования, SBOM, проверки подписи или runtime detection. Эти части выбираются отдельно. Хорошая программа безопасности описывает границы продукта, а не обещает, что настройка Kubernetes сама собой проверит весь supply chain.</p> <h2>От тега к проверяемому артефакту</h2> <p>Тег образа удобен человеку (<code>payments:1.8</code>), но не является неизменяемой идентичностью: тот же тег может указывать на другой образ. Для развертывания и расследования полезно фиксировать digest — точное содержимое образа. Это даёт ответ на вопрос «какой именно артефакт работал», но само по себе не говорит, кто его собрал и безопасен ли он.</p> <p>Практическая цепочка выглядит так:</p> <ol> <li>CI собирает образ из контролируемого исходного кода и фиксирует версию зависимостей.</li> <li>Сканер анализирует образ и формирует результат по уязвимостям; при необходимости создаётся SBOM.</li> <li>Артефакт попадает в registry, где для него сохраняются digest, результат проверки и, если выбрано решение для provenance, подпись/аттестация.</li> <li>Deployment ссылается на согласованный digest. Admission-проверка сверяет, что объект соответствует минимальным правилам организации.</li> <li>После запуска Pod Security Admission и <code>securityContext</code> ограничивают опасные параметры запуска, а runtime-датчик либо аудит собирают события процесса и сети.</li> </ol> <p>Каждый этап отвечает на свой вопрос. Сканер отвечает «известна ли уязвимость на момент анализа». Подпись или аттестация — «есть ли проверяемое происхождение». Admission — «можно ли создать этот объект сейчас». Runtime-наблюдение — «что процесс делает после старта». Смешивать эти ответы означает оставлять дыры: например, без admission уязвимый или непроверенный образ всё равно может быть создан вручную, а без runtime можно не заметить эксплуатацию уже запущенного процесса.</p> <h2>Сканирование: риск, а не автоматический приговор</h2> <p>У сканера есть ложные совпадения, неполные базы и уязвимости без исправления. Поэтому правильная политика не звучит как «запрещать всё с CVE». Она определяет контекст: критичность, достижимость компонента, наличие фиксированной версии, публичность сервиса и срок исключения. Исключение должно быть ограниченным по времени, иметь владельца и ссылку на риск-решение. Иначе список исключений превращается в постоянный обход защиты.</p> <p>Пример рабочего gate: критическая уязвимость в образе, доступном из Internet, блокирует продвижение до исправления или формально оформленного временного исключения. Средняя уязвимость в неиспользуемой утилите не блокирует релиз автоматически, но попадает в backlog с датой пересмотра. Такой подход сохраняет безопасность и не заставляет команды обходить CI.</p> <p>Проверяйте не только «сканер запустился», а способность восстановить картину: для работающего Deployment можно назвать digest, источник сборки, время сканирования, результат политики и владельца исключения. Если хотя бы одного звена нет, расследование будет опираться на догадки.</p> <h2>Admission — точка, где можно предотвратить запуск</h2> <p>Admission controllers обрабатывают запрос к Kubernetes API до сохранения объекта. Это полезная точка для общих правил: допустимые registries, запрет mutable-тегов, минимальные поля <code>securityContext</code>, проверка подписи выбранным внешним валидатором. Но admission не заменяет CI: он не должен заново выполнять тяжёлый анализ при каждом <code>kubectl apply</code> и не видит дальнейшее поведение процесса.</p> <p>Начните с малой, объяснимой политики. Например: рабочие namespaces принимают образы только из утверждённого registry и требуют digest; исключение возможно только в специально отмеченном namespace с владельцем и сроком. Сначала включите режим аудита или предупреждения, соберите реальные нарушения и исправьте шаблоны Helm/Kustomize. Блокирующий режим включайте по namespace, а не одним изменением для всей production-среды.</p> <p>Не делайте admission-webhook единственной критической точкой без плана доступности. Нужно заранее решить, что происходит при его недоступности, кто обновляет сертификаты webhook, как откатить ошибочное правило и где увидеть отклонённые запросы. «Fail-open» снижает защиту во время сбоя; «fail-closed» может заблокировать развёртывания. Выбор зависит от риска и должен быть осознанным.</p> <h2>Pod Security Admission: встроенная защита запуска Pod</h2> <p>Kubernetes имеет встроенный Pod Security Admission (PSA), который применяет уровни Pod Security Standards на уровне namespace. У PSA три режима: <code>warn</code> показывает пользователю предупреждение, <code>audit</code> добавляет аннотацию в audit event, <code>enforce</code> отклоняет Pod с нарушением. Стандарты имеют уровни <code>privileged</code>, <code>baseline</code> и <code>restricted</code>.</p> <p>Это хороший базовый контроль опасных параметров Pod, но не политика всего бизнеса. PSA не знает список разрешённых образов, не оценивает CVE и не определяет, имеет ли микросервис право обращаться к базе. Его задача уже и яснее: задать согласованный минимум безопасных параметров запуска.</p> <p>Начните с наблюдения в одном прикладном namespace. Версию стандарта лучше закрепить под используемую версию Kubernetes: так обновление кластера не меняет требования неожиданно.</p> <pre><code class="language-bash">kubectl label namespace payments \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/audit=restricted \ pod-security.kubernetes.io/warn-version=v<ваша-версия-Kubernetes> \ pod-security.kubernetes.io/audit-version=v<ваша-версия-Kubernetes> </code></pre> <p>Плейсхолдер <code>v<ваша-версия-Kubernetes></code> нужно заменить конкретной поддерживаемой minor-версией, например <code>v1.37</code> только если именно эта политика выбрана для кластера. После исправления предупреждений добавляют <code>enforce=restricted</code> на пилотный namespace. Перед этим проверьте DaemonSet, Jobs и контроллеры: Pods часто создаются не напрямую, а из шаблонов workload, и принуждение в итоге применяется к созданному Pod.</p> <h2>Runtime: обнаружение не равно предотвращению</h2> <p>Даже проверенный и ограниченный образ может быть скомпрометирован уязвимостью приложения. Runtime-слой ищет сигналы уже после запуска: неожиданный shell, запись в чувствительный путь, запуск бинарника из временного каталога, обращение к необычному адресу, попытку получить привилегии. Конкретный охват зависит от ОС, runtime, датчика и его настроек.</p> <p>Сильный runtime-сигнал должен вести к готовой процедуре, а не только к алерту. Минимальный сценарий:</p> <ol> <li>Зафиксировать namespace, Pod, контейнер, образ по digest, узел и время события.</li> <li>Оценить критичность и сохранить логи/метаданные до перезапуска.</li> <li>Ограничить воздействие: изолировать workload сетевой политикой, остановить rollout или удалить скомпрометированный Pod — действие выбирается по runbook и критичности сервиса.</li> <li>Проверить соседние workloads с тем же digest и доступы service account.</li> <li>Устранить первопричину: обновить образ, правило или секрет, затем задокументировать выводы.</li> </ol> <p>Автоматическое удаление Pod по любому сигналу может усугубить инцидент или дать атакующему простой сервиса. Автоматизировать можно только заранее проверенные, обратимые и ограниченные действия; всё остальное требует контекста дежурного.</p> <h2>Как понять, что программа работает</h2> <p>Измеряйте не число установленных инструментов, а закрытие пути. Доля production-workloads с digest, доля образов с актуальным результатом сканирования, число временных исключений старше срока, доля namespaces с PSA в <code>enforce</code>, время от runtime-сигнала до triage и полнота связи «Pod → digest → сборка» — полезные показатели. Они показывают, где защита реальна, а где остаётся декларацией.</p> <p>Итог: supply chain предотвращает попадание неподходящего артефакта, PSA ограничивает опасный способ его запуска, а runtime даёт шанс обнаружить ситуацию, которую ранние фильтры не остановили. Все три слоя нужны, но у каждого своя честная граница.</p> <h2>Официальные источники</h2> <ul> <li>Kubernetes: Images — <a href="https://kubernetes.io/docs/concepts/containers/images/">https://kubernetes.io/docs/concepts/containers/images/</a></li> <li>Kubernetes: Admission controllers — <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/">https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/</a></li> <li>Kubernetes: Pod Security Admission — <a href="https://kubernetes.io/docs/concepts/security/pod-security-admission/">https://kubernetes.io/docs/concepts/security/pod-security-admission/</a></li> <li>Kubernetes: Pod Security Standards — <a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/">https://kubernetes.io/docs/concepts/security/pod-security-standards/</a></li> </ul>

7 минут Открыть →
Блог

Ingress и Egress в Kubernetes: почему закрыть вход недостаточно

<p><strong>Цикл:</strong> «Сетевая безопасность Kubernetes: от основ до практической защиты» · материал 8 из 12<br><strong>Формат:</strong> практический разбор<br><strong>Для кого:</strong> ИБ-специалист, DevOps-инженер, архитектор и разработчик.</p> <h2>Кратко</h2> <p>В Kubernetes словом <em>ingress</em> называют и входящий трафик к workload, и API-объект для публикации HTTP/HTTPS. Это не одно и то же. Ingress API направляет внешний HTTP(S)-трафик к Service; он не заменяет NetworkPolicy, WAF, RBAC и контроль исходящих соединений.</p> <p>Egress — трафик из Pod к другим системам: DNS, БД, Kubernetes API, внутреннему proxy, облачному metadata service или Интернету. Без отдельной egress-политики скомпрометированный Pod сохраняет канал для загрузки инструмента, командного управления и вывода данных, даже если вход к приложению хорошо защищён.</p> <p>Безопасная цель — не «запретить Интернет везде», а описать каждую необходимую связь: источник, назначение, протокол, порт, владелец, способ проверки и допустимый путь. Для стандартной NetworkPolicy это обычно Pod/namespace/IP/CIDR и порт; правило «разрешить только <code>api.example.com</code>» требует функции конкретного CNI либо контролируемого proxy/gateway.</p> <h2>Не смешивайте три границы</h2> <p>Рассмотрим опубликованное приложение <code>frontend</code>.</p> <pre><code class="language-text">Internet → Gateway / Ingress controller → frontend Pod → backend Pod → внешняя платёжная система </code></pre> <p>Здесь есть три разные задачи:</p> <table> <thead> <tr> <th>Граница</th> <th>Вопрос</th> <th>Типичные механизмы</th> </tr> </thead> <tbody><tr> <td>Внешняя публикация</td> <td>Кто и по какому HTTP(S)-маршруту приходит к приложению?</td> <td>Gateway API или Ingress controller, WAF, TLS, LoadBalancer</td> </tr> <tr> <td>Внутренний ingress</td> <td>Какие источники могут подключиться к Pod?</td> <td>NetworkPolicy ingress, Service mesh, CNI</td> </tr> <tr> <td>Egress</td> <td>Куда Pod может установить соединение?</td> <td>NetworkPolicy egress, egress proxy/gateway, CNI-specific policy, NGFW</td> </tr> </tbody></table> <p>Ingress API предназначен для HTTP/HTTPS-доступа к Service извне. Kubernetes рекомендует для новой функциональности Gateway API: Ingress API остаётся поддерживаемым, но заморожен и не развивается. Это не повод срочно переписывать работающий Ingress, но новый дизайн лучше строить с учётом Gateway API и конкретной реализации контроллера.</p> <p>Главная ошибка — считать, что закрытый Ingress закрывает приложение целиком. NetworkPolicy ingress не ограничивает egress, а <code>default deny</code> только для ingress не меняет исходящие соединения.</p> <h2>Почему egress — реальная граница ущерба</h2> <p>После выполнения кода в контейнере нарушитель действует от имени этого Pod. Если он может обращаться куда угодно, ему доступны внешние каналы. В зависимости от окружения это могут быть загрузка дополнительного кода, обратное соединение, передача данных, обращение к внутренним административным сервисам и туннелирование через DNS.</p> <p>Но и без атаки открытый egress мешает расследованию: нет ожидаемого набора направлений, поэтому новое TLS-соединение к незнакомому адресу трудно отличить от рабочей зависимости. Egress allow-list одновременно уменьшает достижимость и делает аномалию заметной.</p> <p>Это не означает, что нужно выдавать один шаблон <code>deny all</code> всему кластеру. У приложений есть зависимости, которые команда часто не учитывает: DNS, proxy, IdP, БД, мониторинг, журналирование, backup, внутренний API, обновление сертификатов. Блокировка без карты потоков быстро приводит к аварийному <code>allow all</code>.</p> <h2>Начинайте с карты, а не с YAML</h2> <p>Для одного workload составьте минимальную таблицу до изменения политики.</p> <table> <thead> <tr> <th>Источник</th> <th>Назначение</th> <th align="right">Протокол/порт</th> <th>Зачем</th> <th>Допустимый путь</th> <th>Как проверить</th> </tr> </thead> <tbody><tr> <td><code>frontend</code></td> <td>CoreDNS</td> <td align="right">UDP+TCP/53</td> <td>разрешение имён</td> <td>прямо к DNS Pod</td> <td><code>nslookup</code></td> </tr> <tr> <td><code>frontend</code></td> <td><code>backend</code></td> <td align="right">TCP/8080</td> <td>API приложения</td> <td>Service внутри кластера</td> <td>HTTP-тест</td> </tr> <tr> <td><code>backend</code></td> <td>PostgreSQL</td> <td align="right">TCP/5432</td> <td>данные</td> <td>private subnet</td> <td>сценарий приложения</td> </tr> <tr> <td><code>backend</code></td> <td>платёжный API</td> <td align="right">TCP/443</td> <td>бизнес-функция</td> <td>только через proxy</td> <td>тестовая транзакция</td> </tr> </tbody></table> <p>«Нужен Интернет» — не строка матрицы. Нужны конкретная роль, направление и путь. Если назначение меняет IP или обслуживается SaaS, сначала определяют, чем именно будут выражать правило: устойчивым private endpoint, корпоративным proxy, egress gateway или FQDN-функцией конкретного CNI. Стандартный Kubernetes API здесь не даёт универсального решения.</p> <h2>Базовая модель egress в NetworkPolicy</h2> <p>Pod становится изолированным для egress, когда его выбирает политика с <code>policyTypes: [Egress]</code>. Разрешения всех применимых политик складываются. Следовательно, политика с пустым <code>egress</code> не является «приоритетным запретом», а переводит выбранные Pod в состояние, где допускаются только соединения, разрешённые хотя бы одной подходящей egress-политикой.</p> <p>Минимальный запрет исходящего трафика для одного namespace выглядит так:</p> <pre><code class="language-yaml">apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-egress namespace: app spec: podSelector: {} policyTypes: - Egress </code></pre> <p>Этот манифест не затрагивает ingress. Он также блокирует DNS. Поэтому применять его в production до готовых разрешений и тестового окна нельзя.</p> <h2>DNS — первое обязательное разрешение</h2> <p>В кластере приложения обычно используют имена Service. После egress deny <code>curl http://backend:8080</code> может сообщать не об ошибке backend, а о невозможности резолвинга. Kubernetes прямо предупреждает: default deny egress требует отдельной политики к cluster DNS.</p> <p>Ниже — типовой вариант только для кластера, где DNS Pod находятся в <code>kube-system</code> и имеют метку <code>k8s-app: kube-dns</code>. Перед применением проверьте реальные labels и endpoint своего DNS.</p> <pre><code class="language-bash">kubectl -n kube-system get svc kube-dns kubectl -n kube-system get pods -l k8s-app=kube-dns --show-labels </code></pre> <pre><code class="language-yaml">apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns namespace: app 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 </code></pre> <p>Проверка должна идти из Pod целевого namespace, а не с node администратора:</p> <pre><code class="language-bash">kubectl -n app run dnsutils --image=registry.k8s.io/e2e-test-images/agnhost:2.39 \ --restart=Never --command -- sleep infinity kubectl -n app wait --for=condition=Ready pod/dnsutils --timeout=60s kubectl -n app exec dnsutils -- nslookup backend.app.svc.cluster.local </code></pre> <p>Если <code>dnsutils</code> не проходит, не добавляйте <code>0.0.0.0/0</code>. Сначала проверьте selector, namespace, UDP и TCP/53, фактические DNS endpoints и поддержку NetworkPolicy используемым CNI.</p> <h2>Как разрешить конкретный внешний адрес</h2> <p>Для стабильного корпоративного сервиса или egress proxy стандартный API позволяет использовать <code>ipBlock</code>. Пример ниже не является готовой конфигурацией: адрес <code>10.90.10.25</code> и порт 443 должны быть заменены на подтверждённые параметры вашего proxy.</p> <pre><code class="language-yaml">apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: backend-to-corporate-proxy namespace: app spec: podSelector: matchLabels: app: backend policyTypes: [Egress] egress: - to: - ipBlock: cidr: 10.90.10.25/32 ports: - protocol: TCP port: 443 </code></pre> <p>Такое правило ограничивает destination IP и порт, но не доказывает, что на 443 расположен именно ожидаемый сервис и не описывает HTTP host/path. Проверку проводят настоящим бизнес-запросом через предназначенный proxy, а затем повторяют отрицательный тест к другому адресу/порту. Для <code>ipBlock</code> не используйте Pod CIDR: IP Pod динамичны, а поведение при Service/NAT зависит от реализации сети.</p> <h2>FQDN, proxy и egress gateway: где заканчивается стандарт Kubernetes</h2> <p>Стандартная NetworkPolicy не умеет правило по DNS-имени. Если внешний API часто меняет адреса, выбор между вариантами архитектурный:</p> <ul> <li><strong>Corporate egress proxy.</strong> Workload соединяется только с одним контролируемым proxy, а разрешения по имени, TLS и журналирование выполняются там. Это самый переносимый способ для HTTP(S).</li> <li><strong>Private endpoint или фиксированный CIDR.</strong> Подходит, если поставщик гарантирует сеть и есть процесс изменения адресов.</li> <li><strong>FQDN policy конкретного CNI.</strong> Например, Cilium имеет DNS-based policy, но это расширение CNI: нужно отдельно проверять DNS-путь, кэширование, наблюдаемость и отказоустойчивость.</li> <li><strong>Egress gateway конкретной реализации.</strong> Он может вывести выбранные Pod через выделенный node/IP. Это не встроенная функция Kubernetes NetworkPolicy и не заменяет policy на самом workload.</li> </ul> <p>Не выбирайте FQDN policy только потому, что она звучит точнее. DNS становится частью security boundary: нужно сохранять события разрешения имени, понимать время жизни ответа и проверять, что приложение не обходит ожидаемый DNS/proxy путь.</p> <h2>Проверка до и после изменения</h2> <p>Минимальная приёмка egress-policy состоит из пяти наблюдений:</p> <ol> <li>Разрешённый внутренний вызов приложения проходит.</li> <li>DNS резолвит нужное имя.</li> <li>Разрешённая внешняя зависимость проходит только ожидаемым путём.</li> <li>Неподтверждённое назначение или порт не проходят.</li> <li>В flow telemetry или журнале CNI видны источник, назначение, время и verdict.</li> </ol> <p>Не используйте <code>ping</code> вместо проверки приложения: для ICMP поведение NetworkPolicy зависит от сетевой реализации. Проверяйте TCP/UDP-протокол, который реально использует зависимость. И не тестируйте только один Pod: после пересоздания Deployment повторите allow/deny-проверки, чтобы убедиться, что policy привязана к labels, а не к исчезнувшему IP.</p> <h2>Что часто ломают</h2> <p>Самая опасная ошибка — начать с <code>allow all egress</code>, а потом обещать «ужесточить позднее». Это почти всегда становится постоянным исключением. Вторая ошибка — полагаться только на внешний firewall, который видит worker node как источник и не различает Pod/ServiceAccount. Третья — разрешить <code>0.0.0.0/0:443</code> «потому что всё сейчас HTTPS». Это оставляет почти неограниченный канал наружу.</p> <p>Отдельно проверяйте cloud metadata service. Kubernetes рекомендует ограничивать доступ Pod к metadata API, если он не нужен: такие данные могут дать доступ к инфраструктурным учётным данным. Это отдельная строка матрицы и отдельное отрицательное испытание.</p> <h2>Итог</h2> <p>Ingress определяет, как запрос приходит к приложению. Egress определяет, куда приложение может уйти. Устойчивая защита требует обеих границ, карты зависимостей и функциональных allow/deny-проверок.</p> <p>Начинайте с одного namespace и одной понятной зависимости. Сначала наблюдение, затем узкие разрешения, пилот и только потом enforcement. Следующая статья покажет, какую телеметрию нужно собрать, чтобы не строить эти политики вслепую.</p> <h2>Официальные источники</h2> <ul> <li><a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">Kubernetes: Network Policies</a></li> <li><a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Kubernetes: Ingress</a></li> <li><a href="https://kubernetes.io/docs/concepts/security/security-checklist/">Kubernetes: Security Checklist</a></li> <li><a href="https://kubernetes.io/docs/concepts/security/multi-tenancy/">Kubernetes: Multi-tenancy and network isolation</a></li> <li><a href="https://docs.cilium.io/en/latest/security/dns/">Cilium: DNS-based policies</a></li> <li><a href="https://docs.cilium.io/en/latest/network/egress-gateway/">Cilium: Egress Gateway</a></li> </ul> <hr> <p><strong>Навигация по циклу:</strong><br>← <a href="https://guardcore.by/serviceaccount-rbac-bezopasnost-kubernetes-api/">ServiceAccount и RBAC: почему сетевая защита Kubernetes невозможна без контроля API</a> · <a href="https://guardcore.by/pochemu-privychnaya-setevaya-bezopasnost-ne-rabotaet-v-kubernetes/">Начало цикла</a> · <a href="https://guardcore.by/observability-kubernetes-flow-logs-audit-dns/">Как увидеть, что происходит внутри Kubernetes: flow logs, Audit, DNS и observability →</a></p>

8 минут Открыть →
Блог

ServiceAccount и RBAC: почему сетевая защита Kubernetes невозможна без контроля API

<p>Можно идеально ограничить сетевые соединения pod, но оставить приложению право менять Kubernetes API.</p> <p>Тогда сетевой контроль будет неполным.</p> <p>Поэтому Kubernetes нужно защищать минимум в двух плоскостях:</p> <pre><code class="language-text">сеть + API управления </code></pre> <h2>Что такое ServiceAccount</h2> <p>ServiceAccount — идентичность для workload.</p> <p>Это не пользователь приложения и не системный администратор.</p> <p>Он нужен, чтобы pod мог аутентифицироваться в Kubernetes API.</p> <h2>Default ServiceAccount</h2> <p>В каждом namespace существует default ServiceAccount.</p> <p>Если pod не указал другой, он может использовать его.</p> <p>Это удобно, но не означает, что всем приложениям действительно нужен API token.</p> <h2>ServiceAccount token</h2> <p>Современный Kubernetes использует краткоживущие bound tokens.</p> <p>Но даже короткоживущий credential остаётся credential.</p> <p>Если приложение Kubernetes API не использует, разумно отключить автоматическое монтирование:</p> <pre><code class="language-yaml">automountServiceAccountToken: false </code></pre> <h2>RBAC</h2> <p>RBAC расшифровывается Role-Based Access Control.</p> <p>ServiceAccount определяет <strong>кто</strong>.</p> <p>RBAC определяет <strong>что ему разрешено</strong>.</p> <p>Основные объекты:</p> <ul> <li>Role;</li> <li>ClusterRole;</li> <li>RoleBinding;</li> <li>ClusterRoleBinding.</li> </ul> <h2>Пример минимального права</h2> <p>Приложению нужно читать только ConfigMap в своём namespace.</p> <p>Ему не требуется:</p> <ul> <li>читать Secrets всего кластера;</li> <li>создавать pods;</li> <li>менять NetworkPolicy;</li> <li>создавать RoleBinding;</li> <li>удалять namespaces.</li> </ul> <p>Именно это и есть least privilege.</p> <h2>Почему широкий RBAC опасен</h2> <p>Представим:</p> <pre><code class="language-text">уязвимость frontend ↓ ServiceAccount token ↓ широкий RBAC ↓ Kubernetes API </code></pre> <p>Если ServiceAccount может создавать pods, нарушитель потенциально может запустить собственный workload.</p> <p>Если может читать Secrets — получить credentials.</p> <p>Если может менять NetworkPolicy — изменить сетевую модель.</p> <p>Если может создавать ClusterRoleBinding — последствия ещё серьёзнее.</p> <h2>NetworkPolicy не заменяет RBAC</h2> <p>NetworkPolicy отвечает:</p> <blockquote> <p>может ли workload установить сетевое соединение?</p> </blockquote> <p>RBAC отвечает:</p> <blockquote> <p>может ли identity выполнить действие через Kubernetes API?</p> </blockquote> <p>Это разные уровни.</p> <h2>Ограничение доступа к API</h2> <p>Кроме RBAC полезно контролировать:</p> <ul> <li>каким workload API вообще доступен;</li> <li>используется ли API через штатный endpoint;</li> <li>есть ли telemetry обращений;</li> <li>какие ServiceAccount обращаются к API;</li> <li>соответствует ли это назначению приложения.</li> </ul> <p>Необычный доступ обычного web-приложения к Kubernetes API — полезный detection signal.</p> <h2>Кто может менять NetworkPolicy</h2> <p>Это важный аудиторский вопрос.</p> <p>Допустим, ИБ создала строгие NetworkPolicy.</p> <p>Если обычный DevOps pipeline может бесконтрольно удалить их, архитектура неустойчива.</p> <p>Необходимо определить:</p> <ul> <li>кто владелец policy;</li> <li>кто утверждает изменения;</li> <li>где хранится policy;</li> <li>есть ли GitOps;</li> <li>кто имеет права <code>create/update/delete</code>.</li> </ul> <h2>Кто может менять labels</h2> <p>NetworkPolicy часто опирается на labels.</p> <p>Например:</p> <pre><code class="language-text">role=backend </code></pre> <p>Если приложение или пользователь может произвольно присвоить себе security-sensitive label, он может попасть под другое правило.</p> <p>Поэтому labels, используемые в политике, тоже являются частью security boundary.</p> <h2>RoleBinding vs ClusterRoleBinding</h2> <p>По возможности права следует ограничивать namespace.</p> <p>Cluster-wide permissions нужны не всем.</p> <p>Особенно внимательно стоит относиться к:</p> <ul> <li><code>cluster-admin</code>;</li> <li>wildcard actions;</li> <li>wildcard resources;</li> <li>доступу к Secrets;</li> <li>правам impersonate;</li> <li>bind/escalate;</li> <li>созданию privileged workloads.</li> </ul> <h2>Что проверять</h2> <p>Для критичных namespace:</p> <ol> <li>какие ServiceAccount существуют;</li> <li>какой используется каждым Deployment;</li> <li>где включён automount token;</li> <li>какие RoleBinding и ClusterRoleBinding привязаны;</li> <li>есть ли wildcard permissions;</li> <li>кто читает Secrets;</li> <li>кто меняет NetworkPolicy;</li> <li>кто может создавать privileged pod.</li> </ol> <h2>Практическая модель</h2> <pre><code class="language-text">workload | ServiceAccount | RBAC | Kubernetes API </code></pre> <p>И отдельно:</p> <pre><code class="language-text">workload | NetworkPolicy | network </code></pre> <p>Компрометация одного уровня не должна автоматически давать полный контроль над другим.</p> <h2>Главное</h2> <p>В Kubernetes identity становится такой же важной частью безопасности, как IP и firewall.</p> <p>Поэтому зрелая микросегментация — это не только <code>frontend → backend</code>, но и минимальные API-полномочия конкретного ServiceAccount.</p> <p>Следующая статья посвящена ingress и особенно egress — исходящим соединениям, которые часто остаются полностью открытыми.</p>

3 минуты Открыть →
Блог

NetworkPolicy с нуля: как реально изолировать приложения в Kubernetes

<p><strong>Цикл:</strong> «Сетевая безопасность Kubernetes: от основ до практической защиты» · материал 5 из 12<br><strong>Формат:</strong> практический разбор<br><strong>Для кого:</strong> ИБ-специалист, DevOps-инженер, архитектор и разработчик, которым нужно безопасно начать сегментацию Kubernetes-кластера.</p> <h2>Кратко</h2> <p>NetworkPolicy — это не «один firewall на кластер» и не универсальный YAML-шаблон. Это набор разрешений для выбранных Pod. В стандартной модели Kubernetes Pod, который не выбран политикой для направления ingress или egress, не изолирован в этом направлении; после <code>default deny</code> приложение перестаёт работать, пока явно не описаны его реальные зависимости. Поэтому безопасный путь — сначала восстановить карту потоков, затем изолировать один namespace, разрешить DNS и необходимые соединения, проверить разрешённый и запрещённый трафик и только после этого расширять охват.</p> <p>В статье разберём воспроизводимый стенд <code>client → backend</code>, а затем перенесём этот приём на цепочку <code>frontend → backend → PostgreSQL</code>. Покажем минимальные манифесты, команды проверки и признаки ошибок. Примеры используют стандартный API <code>networking.k8s.io/v1</code>; фактическое применение зависит от CNI-плагина.</p> <h2>Что NetworkPolicy делает, а что нет</h2> <p><code>NetworkPolicy</code> описывает правила L3/L4 для входящего и исходящего трафика Pod. Она выбирает Pod по меткам и определяет, от кого и на какие порты разрешены соединения. Сам Kubernetes хранит объект политики, но пакет блокирует или пропускает сетевой datapath выбранного CNI. Если установленный CNI не поддерживает enforcement, объект будет принят API, но изоляции не произойдёт. Это первое, что нужно проверять до любой работы; ссылка на первоисточник приведена в конце статьи.</p> <p>NetworkPolicy не заменяет:</p> <ul> <li>RBAC и ServiceAccount: политика не запрещает разрешённому workload менять объекты через Kubernetes API;</li> <li>Ingress-controller, WAF или внешний NGFW: они отвечают за другие границы;</li> <li>защиту самого образа и runtime detection;</li> <li>контроль L7-содержимого, DNS-имён или TLS-идентичности, если эти функции не реализует конкретный CNI или отдельный компонент.</li> </ul> <p>Правильная модель проста: <strong>NetworkPolicy уменьшает сетевую достижимость после компрометации Pod, но не устраняет саму компрометацию.</strong></p> <h2>Результат, который нужно получить</h2> <p>Для production-сценария в namespace должны выполняться только три связи:</p> <pre><code class="language-text">frontend → backend:8080 backend → postgres:5432 frontend/backend → cluster DNS:53 UDP/TCP </code></pre> <p>Не должны выполняться:</p> <pre><code class="language-text">frontend → postgres:5432 frontend → произвольный Pod в namespace backend → Internet и произвольные внутренние адреса </code></pre> <p>Этот пример намеренно небольшой. В реальной системе к нему добавятся мониторинг, логирование, IdP, registry, NTP, backup, proxy и внешние API. Их нельзя разрешать «на всякий случай»: каждая связь должна иметь владельца и назначение.</p> <h2>До начала: четыре обязательные проверки</h2> <h3>1. Зафиксируйте границу изменения</h3> <p>Не начинайте с <code>default deny</code> для всего кластера. Выберите один не критичный namespace или подготовьте отдельный тестовый namespace. Нужны:</p> <ul> <li>доступ к API через <code>kubectl</code> с правами создавать Deployment, Service и NetworkPolicy;</li> <li>список CNI и его версии;</li> <li>способ посмотреть заблокированные потоки или хотя бы воспроизвести соединение из тестового Pod;</li> <li>окно изменения и понятный откат.</li> </ul> <p>Минимальная инвентаризация:</p> <pre><code class="language-bash">kubectl get nodes -o wide kubectl get pods -A -o wide kubectl get networkpolicy -A kubectl get ds -n kube-system </code></pre> <p>Последняя команда часто помогает увидеть используемый CNI, но не заменяет проверку его документации и конфигурации. Нельзя делать вывод «CNI поддерживает NetworkPolicy» только по имени DaemonSet.</p> <h3>2. Убедитесь, что политика реально применяется</h3> <p>Создайте отдельный namespace:</p> <pre><code class="language-bash">kubectl create namespace np-lab </code></pre> <p>До включения изоляции разверните два временных Pod и подтвердите, что они могут взаимодействовать. После применения <code>default deny</code> повторите тот же тест. Если соединение продолжает работать, не переходите к следующим этапам: либо CNI не применяет NetworkPolicy, либо тест обращается не туда, куда вы считаете.</p> <h3>3. Составьте матрицу потоков</h3> <p>До YAML нужна не коллекция селекторов, а таблица зависимостей.</p> <table> <thead> <tr> <th>Источник</th> <th>Получатель</th> <th align="right">Порт/протокол</th> <th>Зачем нужен</th> <th>Владелец</th> <th>Как проверяем</th> </tr> </thead> <tbody><tr> <td>client (в стенде) / frontend (в production)</td> <td>backend</td> <td align="right">TCP/8080</td> <td>API приложения</td> <td>команда приложения</td> <td>HTTP-запрос</td> </tr> <tr> <td>backend</td> <td>postgres</td> <td align="right">TCP/5432</td> <td>хранение данных</td> <td>владелец БД</td> <td><code>pg_isready</code>/тест приложения</td> </tr> <tr> <td>оба</td> <td>CoreDNS</td> <td align="right">UDP+TCP/53</td> <td>разрешение имён сервисов</td> <td>платформа</td> <td><code>nslookup</code></td> </tr> </tbody></table> <p>Если строку нельзя объяснить, её нельзя автоматически разрешать. Именно на этой стадии обнаруживаются скрытые зависимости.</p> <h3>4. Не забывайте DNS</h3> <p>Pod обычно обращаются к Service по DNS-имени, а не по постоянному IP. Kubernetes создаёт DNS-записи для Service и настраивает DNS Pod через kubelet. Блокировка доступа к DNS часто выглядит как «backend недоступен», хотя проблема не в backend. Ссылка на официальное описание DNS приведена в конце статьи.</p> <p>IP и метки CoreDNS различаются между кластерами. Не копируйте порт и <code>namespaceSelector</code> из примера без проверки своих объектов:</p> <pre><code class="language-bash">kubectl get svc -n kube-system kubectl get pods -n kube-system -l k8s-app=kube-dns --show-labels </code></pre> <h2>Лабораторный стенд</h2> <p>Ниже — минимальный стенд. Образы намеренно простые; это не production-манифесты.</p> <pre><code class="language-yaml">apiVersion: v1 kind: Namespace metadata: name: np-lab --- apiVersion: apps/v1 kind: Deployment metadata: name: backend namespace: np-lab spec: replicas: 1 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: hashicorp/http-echo:1.0 args: ["-text=backend-ok", "-listen=:8080"] ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: backend namespace: np-lab spec: selector: app: backend ports: - port: 8080 targetPort: 8080 --- apiVersion: apps/v1 kind: Deployment metadata: name: client namespace: np-lab spec: replicas: 1 selector: matchLabels: app: client template: metadata: labels: app: client spec: containers: - name: client image: curlimages/curl:8.10.1 command: ["sleep", "infinity"] </code></pre> <p>Примените и дождитесь готовности:</p> <pre><code class="language-bash">kubectl apply -f lab.yaml kubectl -n np-lab get pods -w </code></pre> <p>Проверка до изоляции:</p> <pre><code class="language-bash">kubectl -n np-lab exec deploy/client -- curl -fsS --max-time 3 http://backend:8080 </code></pre> <p>Ожидаемый результат: <code>backend-ok</code>. Команда <code>kubectl exec</code> выполняет указанную программу внутри контейнера.</p> <h2>Шаг 1. Включите default deny в одном namespace</h2> <pre><code class="language-yaml">apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress-egress namespace: np-lab spec: podSelector: {} policyTypes: - Ingress - Egress </code></pre> <p><code>podSelector: {}</code> выбирает все Pod в <code>np-lab</code>. Пустые правила <code>ingress</code> и <code>egress</code> не добавляют разрешений; после применения каждый выбранный Pod изолирован для соответствующего направления. Явно указанный <code>policyTypes</code> исключает неоднозначность поведения: API допускает <code>Ingress</code>, <code>Egress</code> или оба значения.</p> <pre><code class="language-bash">kubectl apply -f 01-default-deny.yaml kubectl -n np-lab exec deploy/client -- curl -fsS --max-time 3 http://backend:8080 || echo "blocked as expected" </code></pre> <p>Ожидаемый результат: соединение не проходит, а команда выводит <code>blocked as expected</code>. Если она успешно получает <code>backend-ok</code>, остановитесь и разберите enforcement. Если она зависает, это нормально для сетевой блокировки; всегда ставьте небольшой <code>--max-time</code>.</p> <h2>Шаг 2. Сначала восстановите DNS</h2> <p><code>default deny</code> для egress также блокирует DNS. Поэтому повторно проверять приложение по имени до отдельного разрешения на DNS бессмысленно: ошибка резолвинга будет маскироваться под ошибку доступа к backend.</p> <p>Сначала определите фактический DNS Service и его endpoints:</p> <pre><code class="language-bash">kubectl -n kube-system get svc kube-dns kubectl -n kube-system get endpoints kube-dns </code></pre> <p>После этого создайте разрешение на UDP и TCP/53 для Pod DNS. Вариант ниже применим только если DNS Pod находятся в <code>kube-system</code> и имеют метку <code>k8s-app: kube-dns</code>; это распространённая, но не универсальная конфигурация.</p> <pre><code class="language-yaml">apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns namespace: np-lab 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 </code></pre> <p>Сохраните манифест как <code>02-allow-dns.yaml</code> и примените его:</p> <pre><code class="language-bash">kubectl apply -f 02-allow-dns.yaml </code></pre> <p>Проверьте DNS из отдельного диагностического Pod. Официальная документация Kubernetes использует для этого образ <code>agnhost</code> и <code>nslookup</code>; аналогичный тест безопаснее выполнять именно в целевом namespace.</p> <pre><code class="language-bash">kubectl -n np-lab run dnsutils \ --image=registry.k8s.io/e2e-test-images/agnhost:2.39 \ --restart=Never --command -- sleep infinity kubectl -n np-lab wait --for=condition=Ready pod/dnsutils --timeout=60s kubectl -n np-lab exec dnsutils -- nslookup backend.np-lab.svc.cluster.local </code></pre> <p>Если тест не проходит, не открывайте egress в <code>0.0.0.0/0</code> ради «быстрого исправления». Проверьте адрес DNS, метки Pod DNS, namespace, <code>resolv.conf</code> внутри Pod и правила CNI.</p> <h2>Шаг 3. Разрешите одну связь в обоих направлениях политики</h2> <p>Для соединения от одного Pod к другому после <code>default deny</code> должны разрешить <strong>оба</strong> конца: egress у источника и ingress у получателя. Ниже два объекта в одном файле <code>03-client-backend.yaml</code>.</p> <pre><code class="language-yaml">apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-client-egress-to-backend namespace: np-lab spec: podSelector: matchLabels: app: client policyTypes: - Egress egress: - to: - podSelector: matchLabels: app: backend ports: - protocol: TCP port: 8080 --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-client-ingress-to-backend namespace: np-lab spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: client ports: - protocol: TCP port: 8080 </code></pre> <p>Первая политика выбирает источник — Pod <code>client</code> — и разрешает ему egress к Pod <code>backend</code>. Вторая выбирает получателя <code>backend</code> и разрешает ingress только от <code>client</code>. Оба <code>podSelector</code> без <code>namespaceSelector</code> относятся к namespace самой политики: здесь это <code>np-lab</code>. Для связи между namespace добавляют <code>namespaceSelector</code> и проверяют, что он находится в том же элементе списка, что и <code>podSelector</code>.</p> <p>NetworkPolicy — аддитивная модель: несколько политик складывают разрешения, а правило «запретить поверх разрешения» стандартный API не создаёт. Поэтому аудитируют итоговый набор политик для конкретного Pod, а не только последний применённый файл.</p> <pre><code class="language-bash">kubectl apply -f 03-client-backend.yaml kubectl -n np-lab exec deploy/client -- curl -fsS --max-time 3 http://backend:8080 </code></pre> <p>Ожидаемый результат: снова <code>backend-ok</code>.</p> <p>Проверьте, что другой Pod не получил доступ только из-за нахождения в том же namespace:</p> <pre><code class="language-bash">kubectl -n np-lab run other --image=curlimages/curl:8.10.1 --restart=Never --command -- sleep 300 kubectl -n np-lab wait --for=condition=Ready pod/other --timeout=60s kubectl -n np-lab exec other -- curl -fsS --max-time 3 http://backend:8080 || echo "blocked as expected" </code></pre> <p>Если <code>other</code> проходит, значит селекторы слишком широки, существует другая разрешающая политика или NetworkPolicy фактически не применяется.</p> <h2>Как диагностировать проблему после включения политики</h2> <p>Диагностика идёт в строгом порядке:</p> <ol> <li><strong>Pod жив и имеет ожидаемые labels.</strong> <code>kubectl -n np-lab get pod --show-labels</code>.</li> <li><strong>Service выбирает нужные endpoints.</strong> <code>kubectl -n np-lab get endpoints backend</code>.</li> <li><strong>Имя разрешается.</strong> <code>nslookup backend.np-lab.svc.cluster.local</code>.</li> <li><strong>Политика выбирает адресата.</strong> Селектор в <code>podSelector</code> проверяется по labels backend, а не client.</li> <li><strong>Источник соответствует <code>from</code>.</strong> Namespace и labels должны совпадать одновременно, если используются оба селектора.</li> <li><strong>Порт совпадает с фактическим containerPort/Service targetPort.</strong></li> <li><strong>Нет другой политики с неожиданным широким разрешением.</strong> <code>kubectl -n np-lab get networkpolicy -o yaml</code>.</li> <li><strong>Есть данные выбранного CNI.</strong> Для Cilium это, например, policy verdict/flow; для другого CNI путь будет иным.</li> </ol> <p>Не используйте <code>ping</code> как универсальную проверку: ICMP и его обработка не одинаковы у разных реализаций policy. Проверяйте тот же TCP/UDP-протокол, который использует приложение.</p> <h2>Как безопасно откатить изменение</h2> <p>Для лаборатории:</p> <pre><code class="language-bash">kubectl delete namespace np-lab </code></pre> <p>Для production не удаляют все политики одним действием. Откат должен быть заранее зафиксирован: имя конкретной политики, владелец, допустимое окно и тест после удаления. Если <code>default deny</code> уже защищает namespace, его удаление резко меняет границу безопасности. Предпочтительнее временно добавить узкое разрешение для подтверждённой зависимости, задокументировать его и затем пересмотреть.</p> <h2>Что переносить в production</h2> <p>После успешной лаборатории последовательность такая:</p> <ol> <li>Выбрать один namespace и владельца приложения.</li> <li>Собрать реальные потоки из телеметрии и документации приложения.</li> <li>Согласовать матрицу связей с ИТ, разработкой и ИБ.</li> <li>Ввести режим наблюдения, если его поддерживает выбранный инструмент, либо пилотировать на не критичной среде.</li> <li>Включить <code>default deny</code> только после появления минимального набора узких разрешений.</li> <li>Проверить бизнес-сценарии, DNS, мониторинг, логи, backup и аварийный доступ.</li> <li>Хранить политики в Git и ограничить права на их изменение через RBAC.</li> <li>Периодически пересматривать исключения и широкие селекторы.</li> </ol> <h2>Типичные ошибки</h2> <ul> <li><strong>Политика есть в API, но CNI её игнорирует.</strong> Наличие объекта ничего не доказывает без отрицательного теста.</li> <li><strong><code>default deny</code> включён без карты зависимостей.</strong> Команда чинит сбой широким <code>allow all</code> и теряет смысл сегментации.</li> <li><strong>Разрешён только UDP/53.</strong> Часть DNS-сценариев использует TCP.</li> <li><strong>Селекторы основаны на неуправляемых labels.</strong> Любой, кто может менять security-relevant label, может повлиять на границу доступа.</li> <li><strong>Проверяется только успешный сценарий.</strong> Нужно показать и разрешённое, и запрещённое соединение.</li> <li><strong>Правила не имеют владельца.</strong> После смены приложения никто не знает, какое исключение больше не нужно.</li> </ul> <h2>Итог</h2> <p>Практическая ценность NetworkPolicy появляется не после применения YAML, а после доказанной работы ограничений. Для каждого критичного взаимодействия должны существовать: назначение, точный источник и получатель, порт, владелец, проверка allow/deny и понятный откат. Именно так NetworkPolicy становится частью управляемой микросегментации, а не набором манифестов в репозитории.</p> <h2>Официальные источники</h2> <ul> <li><a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">Kubernetes: Network Policies</a></li> <li><a href="https://kubernetes.io/docs/reference/kubernetes-api/networking/network-policy-v1/">Kubernetes API: NetworkPolicy v1</a></li> <li><a href="https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/">Kubernetes: DNS for Services and Pods</a></li> <li><a href="https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/">Kubernetes: Debugging DNS Resolution</a></li> <li><a href="https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/">Kubernetes: Declare Network Policy</a></li> </ul> <hr> <p><strong>Навигация по циклу:</strong><br>← <a href="https://guardcore.by/ataka-vnutri-kubernetes-ot-pod-do-lateral-movement/">Атака внутри Kubernetes: lateral movement и blast radius</a> · <a href="https://guardcore.by/pochemu-privychnaya-setevaya-bezopasnost-ne-rabotaet-v-kubernetes/">Начало цикла</a> · <a href="https://guardcore.by/cni-ebpf-kto-filtruet-trafik-kubernetes/">CNI и eBPF: кто на самом деле фильтрует трафик Kubernetes →</a></p>

12 минут Открыть →
Блог

Как развивается атака внутри Kubernetes: от уязвимого pod до других сервисов

<p>Сетевую сегментацию проще понять не через определения, а через сценарий атаки.</p> <p>Представим интернет-магазин:</p> <pre><code class="language-text">Internet | frontend | backend | database </code></pre> <p>Frontend опубликован наружу. В нём обнаруживается уязвимость, позволяющая выполнить команду внутри контейнера.</p> <p>Что происходит дальше?</p> <h2>Первый этап: выполнение кода в контейнере</h2> <p>Нарушитель уже находится внутри workload.</p> <p>Это ещё не означает компрометацию Kubernetes. На этом этапе он действует примерно как на обычном Linux-сервере, но внутри ограниченного окружения контейнера.</p> <p>Его интересуют переменные окружения, конфигурационные файлы, доступные сетевые сервисы, DNS, mounted secrets, ServiceAccount token и возможность обращения к Kubernetes API.</p> <h2>Второй этап: исследование сети</h2> <p>Если east-west не ограничен, скомпрометированный frontend может видеть больше сервисов, чем требуется бизнес-логике.</p> <p>Например:</p> <pre><code class="language-text">frontend +-- backend +-- database +-- redis +-- internal-admin-api +-- monitoring </code></pre> <p>Для работы frontend, возможно, нужен только backend.</p> <p>Все остальные связи увеличивают blast radius.</p> <h2>Lateral movement</h2> <p>Lateral movement — перемещение от первой скомпрометированной точки к другим системам.</p> <p>В Kubernetes это может быть:</p> <pre><code class="language-text">frontend → backend → database </code></pre> <p>или:</p> <pre><code class="language-text">frontend → internal service → external infrastructure </code></pre> <p>NetworkPolicy не предотвращает сам первоначальный взлом приложения, но способна существенно ограничить следующий шаг.</p> <h2>Blast radius</h2> <p>Blast radius — масштаб потенциальных последствий одной компрометации.</p> <p>Если один pod может обращаться ко всему кластеру и в Интернет, blast radius большой.</p> <p>Если pod видит только один backend и DNS, он заметно меньше.</p> <p>Это один из главных смыслов микросегментации.</p> <h2>ServiceAccount</h2> <p>Pod может иметь ServiceAccount token.</p> <p>Этот токен используется для аутентификации в Kubernetes API.</p> <p>Но важно понимать:</p> <p><strong>ServiceAccount token не равен правам администратора.</strong></p> <p>Фактические возможности определяет RBAC.</p> <p>Хорошая конфигурация:</p> <pre><code class="language-text">frontend ServiceAccount | +-- нет доступа к Secrets +-- нет создания Pod +-- нет изменения NetworkPolicy +-- нет ClusterRoleBinding </code></pre> <p>Плохая конфигурация:</p> <pre><code class="language-text">frontend ServiceAccount | cluster-admin </code></pre> <p>В последнем случае обычная уязвимость приложения может превратиться в серьёзную компрометацию управления.</p> <h2>Нужно ли монтировать токен вообще</h2> <p>Если приложение не использует Kubernetes API, ему часто не нужен ServiceAccount token.</p> <p>Можно использовать:</p> <pre><code class="language-yaml">automountServiceAccountToken: false </code></pre> <p>Это уменьшает количество credentials внутри контейнера.</p> <h2>Попытка выхода на node</h2> <p>Следующий уровень — попытка выйти за границы контейнера.</p> <p>Риск возрастает, если workload запускается privileged, с hostNetwork, с hostPID, с опасными capabilities, с hostPath или с доступом к container runtime socket.</p> <p>Поэтому NetworkPolicy нельзя рассматривать отдельно от Pod Security.</p> <h2>Что будет при компрометации node</h2> <p>Если нарушитель получил контроль над worker node, последствия существенно серьёзнее.</p> <p>На node могут находиться pod разных приложений. Кроме того, node является частью инфраструктуры управления кластером.</p> <p>Именно поэтому высокопривилегированные и недоверенные workload не следует смешивать без необходимости.</p> <h2>Supply chain</h2> <p>Есть и другой сценарий: вредоносный код попадает в контейнер ещё до запуска.</p> <p>Например, через скомпрометированную зависимость, CI/CD, container registry или подменённый image.</p> <p>В этом случае Kubernetes честно запустит уже проблемный workload.</p> <p>Удаление pod не поможет: новый будет создан из того же image.</p> <p>Это показывает, почему безопасность Kubernetes состоит из нескольких слоёв.</p> <h2>Модель эшелонированной защиты</h2> <pre><code class="language-text">Supply chain ↓ Admission ↓ Pod Security ↓ NetworkPolicy ↓ RBAC ↓ Runtime detection ↓ SIEM/SOC </code></pre> <p>Ни один слой не заменяет остальные.</p> <h2>Простой пример правильной минимизации</h2> <p>Пусть frontend должен только:</p> <ul> <li>принимать запросы через ingress;</li> <li>обращаться к backend на TCP/8080;</li> <li>использовать DNS.</li> </ul> <p>Тогда его сеть должна примерно соответствовать этой логике.</p> <p>Не нужно заранее разрешать ему БД, мониторинг и весь Интернет «на случай необходимости».</p> <h2>Что проверять</h2> <p>Для каждого критичного workload полезно ответить:</p> <ol> <li>Какие входящие соединения ему действительно нужны?</li> <li>Куда он действительно должен ходить?</li> <li>Нужен ли ему Kubernetes API?</li> <li>Какие Secrets он получает?</li> <li>Какие capabilities используются?</li> <li>Есть ли hostPath/hostNetwork?</li> <li>Что произойдёт, если этот workload будет полностью скомпрометирован?</li> </ol> <p>Последний вопрос особенно полезен.</p> <p>Защищённая архитектура предполагает компрометацию одного компонента и пытается не позволить ей автоматически стать компрометацией всей системы.</p> <p>В следующей статье перейдём к основному встроенному механизму сетевой сегментации Kubernetes — NetworkPolicy.</p>

4 минуты Открыть →
Блог

North-South и East-West: какую часть трафика Kubernetes не видит периметровый firewall

<p>В обычной инфраструктуре межсетевой экран часто расположен между сетевыми сегментами. Если сервер приложений хочет обратиться к БД, пакет проходит через firewall, который может проверить правило.</p> <p>В Kubernetes значительная часть сетевых взаимодействий может происходить внутри cluster datapath.</p> <p>Чтобы не смешивать эти потоки, используют два понятия: north-south и east-west.</p> <h2>North-South</h2> <p>North-south — взаимодействие между Kubernetes и внешней инфраструктурой.</p> <p>Например:</p> <pre><code class="language-text">Internet → WAF → Ingress → frontend </code></pre> <p>или:</p> <pre><code class="language-text">backend → external API </code></pre> <p>В этих точках традиционные средства защиты по-прежнему очень полезны: NGFW, WAF, reverse proxy, load balancer, IDS/IPS, secure web gateway и egress proxy.</p> <h2>East-West</h2> <p>East-west — внутренние взаимодействия:</p> <pre><code class="language-text">frontend → backend backend → payment-service backend → logging-service </code></pre> <p>Такие соединения могут оставаться полностью внутри pod network.</p> <p>Если external firewall стоит только перед кластером, он не участвует в этих соединениях.</p> <h2>Пример</h2> <pre><code class="language-text">Internet | WAF | Ingress | frontend | backend | database </code></pre> <p>WAF видит запрос пользователя к frontend. После этого frontend инициирует новый сетевой запрос к backend.</p> <p>Если backend находится внутри того же кластера, этот поток может пройти напрямую через CNI datapath.</p> <p>Следовательно, успешная фильтрация north-south ничего не говорит о том, насколько ограничен east-west.</p> <h2>Почему это важно</h2> <p>Если frontend скомпрометирован, нарушитель начинает действовать уже изнутри.</p> <p>Периметровый WAF больше не является основной точкой контроля.</p> <p>Дальше важны: достижимость внутренних сервисов, NetworkPolicy, egress control, права ServiceAccount и видимость внутренних flow.</p> <h2>Ingress — не firewall всего кластера</h2> <p>Ingress предназначен прежде всего для публикации HTTP/HTTPS-сервисов.</p> <p>Здесь удобно реализовывать TLS, WAF, аутентификацию, rate limiting и правила публикации.</p> <p>Но Ingress не контролирует автоматически взаимодействие между backend-сервисами.</p> <h2>Где должен происходить east-west контроль</h2> <p>Основные варианты:</p> <ol> <li>стандартный NetworkPolicy;</li> <li>расширенные политики CNI;</li> <li>service mesh authorization;</li> <li>специализированные средства сетевой защиты;</li> <li>комбинация подходов.</li> </ol> <p>Базовым механизмом обычно является NetworkPolicy.</p> <h2>Ошибка: правила только на IP worker nodes</h2> <p>Внешний firewall часто видит Kubernetes примерно так:</p> <pre><code class="language-text">worker-1 worker-2 worker-3 </code></pre> <p>Если разрешить worker-сети широкий доступ к БД, фактически доступ могут получить многие workload на этих nodes.</p> <p>Это слишком грубая модель.</p> <h2>Что проверить</h2> <ul> <li>какие Service доступны извне;</li> <li>где используются LoadBalancer/NodePort;</li> <li>какие Ingress/Gateway существуют;</li> <li>есть ли обход центрального ingress;</li> <li>какие pod могут произвольно выходить в Интернет;</li> <li>какие внутренние приложения связаны без NetworkPolicy;</li> <li>какие внешние БД доступны со всех worker nodes;</li> <li>есть ли telemetry east-west.</li> </ul> <h2>Главное</h2> <p>Периметр Kubernetes нужно защищать так же серьёзно, как обычную инфраструктуру. Но одной периметровой защиты недостаточно.</p> <p>Архитектура должна отдельно отвечать на два вопроса:</p> <blockquote> <p>кто имеет право войти в кластер?</p> </blockquote> <p>и</p> <blockquote> <p>кто внутри кластера имеет право взаимодействовать с кем?</p> </blockquote> <p>Следующая статья посвящена тому, что происходит, когда один pod всё-таки скомпрометирован.</p>

3 минуты Открыть →