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

Как внедрить защиту Kubernetes: план с владельцами, контрольными точками и доказательствами

<p>Стратегия безопасности Kubernetes проваливается не потому, что в ней мало инструментов, а потому что она превращается в список пожеланий: «внедрить NetworkPolicy», «настроить мониторинг», «добавить сканирование». У такого списка нет владельца, порядка, критерия готовности и безопасного пути отката. Ниже — план, который переводит цикл статей в управляемую программу работ.</p> <p>Он не является обязательным стандартом Kubernetes и не заменяет оценку рисков. Конкретный CNI, service mesh, registry, CI/CD и требования регулятора отличаются. Но для каждого этапа сохраняется один принцип: не объявлять контроль внедрённым, пока нет наблюдаемого результата и владельца его сопровождения.</p> <h2>Модель ответственности</h2> <p>Без разделения ролей платформа превращается в единственную точку ожиданий, а разработчики воспринимают безопасность как внешний запрет. Базовое распределение удобно зафиксировать до технических изменений.</p> <table> <thead> <tr> <th>Роль</th> <th>Отвечает за результат</th> </tr> </thead> <tbody><tr> <td>Platform/SRE</td> <td>CNI, кластеры, RBAC-границы, шаблоны, доступность контролей и rollback</td> </tr> <tr> <td>Владелец сервиса</td> <td>карта зависимостей, корректность workload-манифестов, функциональная проверка и устранение нарушений</td> </tr> <tr> <td>Security/DevSecOps</td> <td>модель угроз, требования к policy, исключения, supply-chain gates и контроль доказательств</td> </tr> <tr> <td>SOC/дежурная команда</td> <td>правила обнаружения, triage, runbook, эскалация и упражнения по инцидентам</td> </tr> <tr> <td>Руководитель продукта/риска</td> <td>приоритизация, принятие остаточного риска, выделение времени на исправления</td> </tr> </tbody></table> <p>Один человек может совмещать роли в маленькой команде, но обязанность всё равно должна быть названа. Например, «владелец Platform» — лучше, чем «команда Kubernetes», если решение нужно принять ночью во время инцидента.</p> <h2>Этап 0. Зафиксировать исходное состояние</h2> <p><strong>Цель.</strong> Понять, что именно защищается и какие изменения вообще допустимы.</p> <p><strong>Входы:</strong> перечень кластеров, namespaces и критичных сервисов; используемый CNI; способы входа/выхода трафика; service accounts; регистры образов; текущие логи, метрики и audit; требования к доступности.</p> <p><strong>Работа.</strong> Соберите карту «сервис → namespace → владельцы → зависимости → данные → внешний вход/выход». Не пытайтесь сразу описать каждый пакет. Достаточно выявить критичные потоки: пользователь → ingress → API, API → база, workload → DNS/внешний API, CI/CD → Kubernetes API. Отдельно отметьте системные namespaces: к ним нельзя применять прикладные правила без отдельной проверки.</p> <p><strong>Артефакт:</strong> версионируемый реестр сервисов и потоков, инвентарь текущих политик и известные исключения.</p> <p><strong>Контрольная точка:</strong> для каждого пилотного сервиса есть владелец и хотя бы один подтверждённый способ проверить его здоровое взаимодействие. Без этого запрещающие политики не вводятся.</p> <h2>Этап 1. Сделать изменения наблюдаемыми и обратимыми</h2> <p><strong>Цель.</strong> До ужесточения иметь данные, которые объяснят отказ, и практический откат.</p> <p><strong>Работа.</strong> Включите централизованный сбор событий Kubernetes, логов приложения и минимальных сетевых наблюдений, доступных в выбранном CNI. Согласуйте единый набор полей для расследования: кластер, namespace, workload, Pod, service account, образ по digest, узел, время и идентификатор релиза. Создайте короткий runbook: кто смотрит отклонённый admission-запрос, где видно нарушение NetworkPolicy и как временно вернуть работоспособность без расширения прав навсегда.</p> <p><strong>Артефакт:</strong> дашборд/запросы для пилота, запись проверенного rollback и контакт дежурного.</p> <p><strong>Контрольная точка:</strong> команда может отличить «сервис не запустился из-за PSA» от «сервис не подключается из-за NetworkPolicy» за согласованное время. Если это неизвестно, следующим этапом будет неконтролируемый поиск причины.</p> <h2>Этап 2. Пилот минимальных сетевых прав</h2> <p><strong>Цель.</strong> Ввести allow-list без риска заблокировать весь кластер.</p> <p><strong>Работа.</strong> Выберите один namespace с понятными зависимостями. Сначала подтвердите, что CNI действительно применяет Kubernetes NetworkPolicy; сам объект <code>NetworkPolicy</code> без поддержки провайдера не создаёт защиту. Затем добавьте default deny для пилотного namespace и минимальные правила: DNS, нужные входящие сервисы, нужные исходящие зависимости. Каждое правило должно ссылаться на владельца сервиса и проверяемую потребность, а не на догадку по IP.</p> <p><strong>Проверка:</strong> разрешённый сценарий работает; заранее выбранный запрещённый сценарий не проходит; события и поток видны в телеметрии. Выполните проверку до и после изменения, чтобы не принять случайный кэш за доказательство.</p> <p><strong>Контрольная точка:</strong> нет неучтённых широких <code>0.0.0.0/0</code> и «разрешить всё» в пилоте, кроме документированных временных исключений с датой удаления.</p> <h2>Этап 3. Укрепить входящий и исходящий периметр</h2> <p><strong>Цель.</strong> Сделать ingress и egress управляемыми, а не побочным эффектом Service/LoadBalancer.</p> <p><strong>Работа.</strong> Определите, кто вправе создавать внешние точки входа, какие hostnames/TLS-сертификаты разрешены и как проверяется маршрут до backend. Для egress составьте реестр внешних направлений с бизнес-владельцем и способом идентификации: имя сервиса, gateway или другой механизм выбранного CNI. Не обещайте доменную фильтрацию, если конкретная реализация NetworkPolicy её не умеет.</p> <p><strong>Артефакт:</strong> правила входа и выхода, схема DNS/egress gateway, процедура добавления нового направления.</p> <p><strong>Контрольная точка:</strong> внешнее направление можно связать с workload и владельцем; неизвестный workload не получает широкий выход только потому, что он находится в «доверенном» namespace.</p> <h2>Этап 4. Ограничить способ запуска и происхождение образов</h2> <p><strong>Цель.</strong> Не допускать небезопасный Pod и непонятный образ в production.</p> <p><strong>Работа.</strong> Сначала включите Pod Security Admission в режимах <code>warn</code> и <code>audit</code> для пилотного namespace, исправьте шаблоны Deployment/Job, затем включите <code>enforce</code> с закреплённой версией политики. Параллельно договоритесь о registry, digest, сканировании и сроках исключений. Admission-политики для registry/подписей вводите поэтапно: аудит → исправление → блокировка.</p> <p><strong>Проверка:</strong> тестовый манифест с запрещённым securityContext отклоняется; корректный workload запускается; отчёт CI связывается с digest работающего Pod. Это важнее, чем формальное наличие PSA labels.</p> <p><strong>Контрольная точка:</strong> исключения видимы, имеют владельца и срок; блокирующие правила не применены к системным или legacy-нагрузкам без проверенного плана миграции.</p> <h2>Этап 5. Добавить идентичность и шифрование там, где это обосновано</h2> <p><strong>Цель.</strong> Защитить чувствительный сервисный трафик без ложного ощущения, что mTLS решает всё.</p> <p><strong>Работа.</strong> Для сервисов с высокой ценностью данных выберите модель: шифрование CNI, TLS приложения или конкретный service mesh. Зафиксируйте владельца PKI, модель идентичности workload, ротацию и наблюдаемость. Миграцию выполняйте на одном сервисном контуре; если продукт поддерживает переходный режим, используйте его только для инвентаризации plaintext-клиентов и с конечной датой.</p> <p><strong>Контрольная точка:</strong> разрешённый клиент проходит с ожидаемой идентичностью и шифрованием, а неподходящий клиент не может выдать себя за него. При этом NetworkPolicy и прикладная авторизация остаются отдельными контролями.</p> <h2>Этап 6. Связать detection с реакцией</h2> <p><strong>Цель.</strong> Сократить время от сигнала до понятного действия.</p> <p><strong>Работа.</strong> Выберите несколько высокоценных сценариев: запуск shell в production-контейнере, привилегированный Pod, неожиданный egress, использование неподходящего образа, необычный вызов Kubernetes API. Для каждого укажите качество сигнала, приоритет, куда он поступает, кому назначается и какую информацию нужно сохранить. Проведите tabletop или безопасное упражнение: пройти инцидент от алерта до фиксации digest и решения об изоляции.</p> <p><strong>Артефакт:</strong> runbooks, правила корреляции, журнал проведённого упражнения и список улучшений.</p> <p><strong>Контрольная точка:</strong> дежурный может найти владельца, образ и недавние изменения workload без доступа «cluster-admin» и без ручного опроса нескольких команд.</p> <h2>Этап 7. Расширять покрытие только после стабильности</h2> <p>Пилот не надо копировать на все namespaces одним коммитом. Расширяйте по волнам: похожие сервисы, затем более сложные, затем legacy. Для каждой волны повторяются те же входы, тесты и окно отката. Метрики прогресса должны описывать качество: доля namespaces с проверенным default deny, доля production workloads с digest и действующим PSA, число активных исключений, время triage и число неизвестных внешних направлений.</p> <p>Финальный результат — не «кластер настроен», а работающий цикл: изменения проходят через проверяемый путь, нарушения видны, исключения не бессрочны, а владельцы могут безопасно развивать систему. Именно такая последовательность позволяет усилить Kubernetes без хрупкой миграции и без иллюзии, что один инструмент закрывает весь риск.</p> <h2>Официальные источники</h2> <ul> <li>Kubernetes Security Checklist: <a href="https://kubernetes.io/docs/concepts/security/security-checklist/">https://kubernetes.io/docs/concepts/security/security-checklist/</a></li> <li>Kubernetes NetworkPolicy: <a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">https://kubernetes.io/docs/concepts/services-networking/network-policies/</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 Auditing: <a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/">https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/</a></li> </ul>

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

Безопасность цепочки поставки и 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 минут Открыть →
Инструменты

mTLS и service mesh: где шифрование действительно добавляет защиту

<p>Внутри Kubernetes трафик между сервисами часто выглядит «внутренним», а потому по умолчанию считается безопасным. Это опасная модель. Pod может оказаться на другом узле, сеть может быть скомпрометирована, а ошибочно выданные сетевые права позволяют обращаться к соседнему сервису. На этом фоне mTLS — взаимная TLS-аутентификация — решает две конкретные задачи: шифрует соединение и позволяет сторонам удостовериться, с кем они установили связь. Но это не универсальный «режим безопасного Kubernetes».</p> <p>Важно начать с границы ответственности. Kubernetes не включает service mesh как базовую функцию. Сетевые политики Kubernetes определяют, каким Pod разрешены соединения, но шифрование и способ его реализации зависят от CNI либо отдельного продукта. Kubernetes Security Checklist прямо рекомендует NetworkPolicy и допускает service mesh как вариант шифрования внутрикластерного трафика, если выбранный CNI этого не делает. Поэтому слова «включим mTLS» всегда должны сопровождаться названием продукта, моделью его внедрения и владельцем сертификатов.</p> <h2>Что mTLS меняет, а чего не меняет</h2> <p>При обычном TLS клиент проверяет сервер. При mTLS проверяются обе стороны: клиент предъявляет сертификат, сервер предъявляет свой. В service mesh это обычно происходит не в прикладном коде, а в прокси или другом компоненте data plane. Контрольный компонент выдаёт короткоживущие учётные данные и распространяет правила. В результате приложение <code>payments</code> может получить криптографически подтверждаемую идентичность вызывающего, а трафик между точками обработки становится зашифрованным.</p> <p>Это полезно, но это не заменяет следующие уровни:</p> <table> <thead> <tr> <th>Вопрос</th> <th>Что отвечает</th> </tr> </thead> <tbody><tr> <td>Может ли Pod установить сетевое соединение?</td> <td>NetworkPolicy и её реализация CNI</td> </tr> <tr> <td>Кто действительно подключился?</td> <td>mTLS и идентичность workload</td> </tr> <tr> <td>Разрешено ли этому субъекту вызвать именно этот метод?</td> <td>прикладная или mesh-авторизация</td> </tr> <tr> <td>Кто может создать, изменить или прочитать объект Kubernetes?</td> <td>RBAC и защита API</td> </tr> <tr> <td>Что произошло после запуска процесса?</td> <td>журналирование, мониторинг и runtime detection</td> </tr> </tbody></table> <p>Например, сервис <code>frontend</code> имеет разрешённый egress к <code>orders</code>, а <code>orders</code> принимает соединения только от идентичности <code>frontend</code>. Здесь NetworkPolicy уменьшает число возможных путей, а mTLS не позволяет любому Pod, оказавшемуся на допустимом IP-пути, выдать себя за <code>frontend</code>. Однако если у самого <code>frontend</code> есть уязвимость и он легитимно обращается к <code>orders</code>, mTLS не определит, что бизнес-запрос вредоносен. Нужны авторизация, валидация входа и наблюдаемость.</p> <h2>Не начинайте с режима strict на всём кластере</h2> <p>Самый частый риск внедрения — превратить сильную цель в массовый простой. В ряде mesh-реализаций есть миграционный режим, в котором приёмник временно допускает и обычный трафик, и mTLS. В Istio такой режим называется <code>PERMISSIVE</code>; <code>STRICT</code> принимает только mTLS. Это свойство Istio, а не API Kubernetes, поэтому названия и точное поведение обязательно проверяют в документации выбранного продукта.</p> <p>Безопасный порядок работы выглядит так:</p> <ol> <li>Выберите один непубличный сервисный контур и зафиксируйте все его клиенты: workloads, Jobs, интеграции извне кластера, probes, gateways и администрирование.</li> <li>Определите, какая идентичность соответствует каждому workload. Нужен не просто список имён сервисов, а понятная связь «service account / namespace → допустимый сервис».</li> <li>Включите mesh в пилотном namespace и соберите наблюдаемость: сколько соединений уже mTLS, какие остаются plaintext и почему.</li> <li>Исправьте исключения: включите proxy там, где это предусмотрено моделью продукта, либо оформите контролируемый gateway для внешнего клиента. Не маскируйте проблему постоянным глобальным исключением.</li> <li>Лишь после подтверждения всех путей переведите <em>приёмник</em> пилотного сервиса в строгий режим. Затем проверьте доступность, трассы, ошибки TLS и автоматическую ротацию сертификатов.</li> </ol> <p>На практике «сначала permissive, затем strict» — не цель сама по себе, а способ увидеть реальный legacy-трафик до блокировки. Если у выбранного решения нет такого режима, пилот должен быть ещё меньше: отдельный сервис, контролируемые клиенты и заранее подготовленный откат.</p> <h2>Минимальная проверка перед принуждением</h2> <p>До изменения режима владелец сервиса и платформа должны ответить на вопросы ниже не предположениями, а наблюдаемыми данными.</p> <ul> <li>Все ли исходящие клиенты известны, включая CronJob и отладочные утилиты?</li> <li>Все ли конечные точки на пути поддерживают модель mesh: сам workload, ingress/egress gateway, внешняя интеграция?</li> <li>Есть ли отдельный ответственный за корневое доверие, выдачу, срок жизни и отзыв сертификатов?</li> <li>Видна ли в телеметрии идентичность peer и признак шифрования, а не только HTTP-код?</li> <li>Проверяли ли ротацию сертификата в рабочем окружении или реплике production?</li> </ul> <p>Проверка результата должна быть двусторонней. Позитивный тест: <code>frontend</code> вызывает разрешённый API <code>orders</code>, приложение получает ожидаемый ответ, а данные mesh показывают зашифрованное соединение и ожидаемую идентичность клиента. Негативный тест: тестовый Pod с другой service account пытается обратиться к тому же маршруту и получает отказ по политике идентичности либо по NetworkPolicy. Если виден только успешный HTTP 200, это ещё не доказательство mTLS: запрос мог пройти plaintext или обойти data plane.</p> <p>Для Istio минимальная строгая политика приёмника имеет вид ниже. Это <strong>пример именно Istio</strong>, а не универсальный манифест Kubernetes; применять его стоит только в отдельном пилотном namespace после инвентаризации клиентов.</p> <pre><code class="language-yaml">apiVersion: security.istio.io/v1 kind: PeerAuthentication metadata: name: orders-strict namespace: pilot spec: selector: matchLabels: app: orders mtls: mode: STRICT </code></pre> <p>После применения не ограничивайтесь тем, что объект создался. Проверьте, что клиент действительно использует data plane выбранного mesh, что запросы от <code>frontend</code> проходят, а известный plaintext-клиент перестал проходить предсказуемо. Затем отмените или скорректируйте политику в рамках заранее согласованного rollback, если отказ затронул критичный путь.</p> <h2>Сертификаты — не деталь установки</h2> <p>mTLS надёжен ровно настолько, насколько надёжны его идентичности и доверие. Требуется определить:</p> <ul> <li>кто владеет CA и имеет право менять trust bundle;</li> <li>как часто выдаются и автоматически обновляются workload-сертификаты;</li> <li>что происходит при компрометации service account, узла или корневого ключа;</li> <li>где доступны журналы выпуска и ошибок валидации;</li> <li>как проходит смена CA без одновременного отключения всех сервисов.</li> </ul> <p>Нельзя переносить административный корневой сертификат кластера в mesh «для простоты» без отдельной архитектурной оценки. У разных продуктов разные механизмы доверия, хранение ключей и интеграция с внешним PKI. Это отдельная зона контроля: в ней должны быть аудит, ограниченный доступ и проверенный сценарий ротации.</p> <h2>Когда service mesh оправдан</h2> <p>Mesh уместен, когда ценность идентичности workload и шифрования выше его операционной цены: есть много внутренних сервисов, чувствительные данные, несколько команд, требования к аудитам или необходимость вводить авторизацию между сервисами единообразно. Для небольшой системы с несколькими сервисами иногда достаточно TLS в приложении или возможности CNI шифровать трафик, плюс аккуратные NetworkPolicy. Это не «хуже» и не «лучше»: нужно сравнить покрытие угроз, сложность сопровождения и ответственность за инциденты.</p> <p>Итоговая архитектура должна быть слоистой. Сначала минимальные сетевые права, затем криптографическая идентичность и шифрование, затем авторизация действий и полноценная телеметрия. Если хотя бы один из этих уровней не проверен, надпись «mTLS enabled» остаётся настройкой, а не доказанной мерой защиты.</p> <h2>Официальные источники</h2> <ul> <li>Kubernetes Security Checklist: <a href="https://kubernetes.io/docs/concepts/security/security-checklist/">https://kubernetes.io/docs/concepts/security/security-checklist/</a></li> <li>Kubernetes NetworkPolicy: <a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">https://kubernetes.io/docs/concepts/services-networking/network-policies/</a></li> <li>Istio Security and mTLS: <a href="https://istio.io/latest/docs/concepts/security/">https://istio.io/latest/docs/concepts/security/</a></li> </ul>

6 минут Открыть →
Инструменты

Наблюдаемость Kubernetes: как связать flow logs, Audit и DNS в доказательства

<p><strong>Цикл:</strong> «Сетевая безопасность Kubernetes: от основ до практической защиты» · материал 9 из 12<br><strong>Формат:</strong> практический разбор</p> <h2>Кратко</h2> <p>Нельзя строить NetworkPolicy по догадкам. Но и один network flow не объясняет инцидент: IP Pod быстро меняется, flow не показывает действие через Kubernetes API, а DNS-запрос не доказывает установление соединения. Нужна связка источников: сетевые потоки для фактической связности, Audit для действий через API, DNS для имён, runtime для процесса-инициатора и perimeter/proxy для внешнего направления.</p> <p>Цель наблюдаемости не «собирать все логи». Она должна позволять ответить на конкретные вопросы: какой workload установил соединение, было ли оно разрешено политикой, кто изменил NetworkPolicy или RBAC, обращался ли Pod к новому имени и как это повлияло на бизнес-сценарий.</p> <h2>Пять источников — пять разных ответов</h2> <table> <thead> <tr> <th>Источник</th> <th>На что отвечает</th> <th>Чего не знает сам по себе</th> </tr> </thead> <tbody><tr> <td>Network flow</td> <td>кто с кем соединялся, протокол, порт, allow/deny</td> <td>кто изменил Kubernetes-объект</td> </tr> <tr> <td>Kubernetes Audit</td> <td>кто и как обращался к API</td> <td>проходил ли TCP-пакет между Pod</td> </tr> <tr> <td>DNS</td> <td>какое имя запрашивал workload</td> <td>был ли затем успешный HTTPS/TCP-сеанс</td> </tr> <tr> <td>Runtime telemetry</td> <td>какой процесс в контейнере действовал</td> <td>полную сетевую политику и API-авторизацию</td> </tr> <tr> <td>Proxy/NGFW</td> <td>куда ушёл внешний трафик</td> <td>какой Pod стоит за source IP без Kubernetes metadata</td> </tr> </tbody></table> <p>Именно поэтому строки вида <code>10.244.2.15 → 10.244.3.10</code> недостаточно. Через час IP может принадлежать другому Pod. В момент создания flow должны сохраняться namespace, имя Pod, workload, node, destination, порт, направление и verdict. Если CNI умеет связать flow с конкретной policy — сохраняйте и её имя, но не объявляйте это универсальной функцией Kubernetes.</p> <h2>Сначала определите вопросы, затем включайте сбор</h2> <p>Минимальный набор use cases для пилотного namespace:</p> <ol> <li>Показать реальные зависимости перед <code>default deny</code>.</li> <li>Найти поток, заблокированный после изменения policy.</li> <li>Обнаружить новый egress во внешний адрес или домен.</li> <li>Установить, кто создал, изменил или удалил NetworkPolicy, RoleBinding и Service.</li> <li>Сопоставить создание/обновление Deployment с новым Pod и его сетевым поведением.</li> </ol> <p>Если для каждого случая нет ответственного, правила хранения и действия по сигналу, это не детектирование, а дорогой архив.</p> <h2>Kubernetes Audit: журнал API, а не сетевой лог</h2> <p>Kubernetes Audit создаётся в <code>kube-apiserver</code>: запрос может записываться на нескольких стадиях, а audit policy определяет, что именно попадёт в выбранный backend. Поэтому Audit не появляется «сам по себе в SIEM» только от факта работы Kubernetes.</p> <p>Для ИБ в первую очередь нужны действия с <code>networkpolicies</code>, <code>roles</code>, <code>rolebindings</code>, <code>clusterrolebindings</code>, <code>serviceaccounts</code>, <code>secrets</code>, <code>deployments</code>, <code>daemonsets</code>, <code>pods</code>, <code>services</code> и <code>ingresses</code>/Gateway-объектами. Для чувствительных объектов чаще достаточно метаданных запроса; не включайте тела ответов к Secrets ради удобства расследования — это создаст новый канал раскрытия данных. Точную audit policy согласовывают с владельцем платформы и проверяют на тестовом изменении.</p> <p>Проверочный сценарий прост: в учебном namespace создать или изменить одну именованную NetworkPolicy, затем найти в Audit время, пользователя/ServiceAccount, verb, URI ресурса, namespace и результат. Если записи нет, сначала исправляют policy, backend или доставку в SIEM; не утверждают, что «аудит включён».</p> <h2>DNS: контекст имени и отдельная зависимость</h2> <p>Kubernetes создаёт DNS-записи для Service и настраивает DNS Pod. После egress deny ошибка разрешения имени часто выглядит как недоступность backend. Поэтому в процессе изменения policy собирают не только flow, но и проверку <code>nslookup</code> из самого namespace.</p> <p>Для внешнего egress DNS полезен как ранний сигнал: workload начал спрашивать неизвестный домен. Но DNS не равен соединению: запрос мог завершиться ошибкой, адрес мог быть кэширован, а приложение может обращаться к IP напрямую. Коррелируйте DNS с последующим flow и proxy/NGFW-событием по времени, workload и destination IP.</p> <p>Не используйте Kubernetes <code>Event</code> как основной журнал. Официальная документация определяет их как best-effort информацию с ограниченным хранением; они полезны для оперативной диагностики, но не для доказательств инцидента.</p> <h2>Практический минимум для CNI с flow telemetry</h2> <p>Сначала проверьте возможности именно установленного CNI: формат, доступность verdict, metadata, экспорт и retention. Например, Hubble — средство наблюдаемости Cilium; оно не появляется в Calico, OVN или другом CNI автоматически.</p> <p>В стенде из статьи 5 зафиксируйте одну разрешённую и одну запрещённую связь:</p> <pre><code class="language-bash">kubectl -n np-lab exec deploy/client -- \ curl -fsS --max-time 3 http://backend:8080 kubectl -n np-lab exec other -- \ curl -fsS --max-time 3 http://backend:8080 || echo "blocked as expected" </code></pre> <p>В консоли или экспортируемом потоке CNI должны различаться хотя бы source Pod, destination Pod/IP, TCP/8080, время и verdict. Если policy correlation доступна, дополнительно проверяется имя политики. Если telemetry не показывает deny, это не доказывает, что deny отсутствует, но означает, что этим источником нельзя подтверждать блокировку.</p> <h2>Корреляция, которая имеет смысл</h2> <p>Полезная цепочка расследования выглядит так:</p> <pre><code class="language-text">Audit: изменён Deployment → Kubernetes: создан новый Pod → DNS: Pod запросил незнакомое имя → Flow: Pod открыл TLS-соединение наружу → Proxy/NGFW: подтверждено внешнее назначение </code></pre> <p>Это гипотеза, а не автоматическое доказательство компрометации. Проверяющий должен установить владельца Deployment, ожидаемость новой версии и назначение домена. Сильная корреляция не отменяет контекст.</p> <h2>Retention, доступ и стоимость</h2> <p>Хранение определяют для каждого источника отдельно. Audit может быть малым по объёму, но критичным для расследования изменений. Flow обычно объёмнее. DNS может содержать чувствительные сведения о сервисах и поведении пользователей. Нужно заранее определить срок хранения, защиту от изменения, экспорт, резервирование, доступ по ролям и способ поиска по namespace/workload.</p> <p>Не включайте L7, packet capture и все debug-события навсегда. Сначала измерьте поток событий в пилоте, оцените CPU/память на node, пропускную способность collector и стоимость хранения. При перегрузке приоритетом остаются metadata и security use cases, а не бесконечная детализация.</p> <h2>Итог</h2> <p>Observability предшествует строгому enforcement. Сначала наблюдают реальные зависимости, затем формируют узкие policy, после чего отслеживают deny и новые исключения. Flow, Audit и DNS дают разные части картины; только их корреляция позволяет объяснить, что произошло и кто должен принять решение.</p> <p>Следующая статья покажет, где нужен mTLS и service mesh, а где эти механизмы только усложняют простую L3/L4-сегментацию.</p> <h2>Официальные источники</h2> <ul> <li><a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/">Kubernetes: Auditing</a></li> <li><a href="https://kubernetes.io/docs/concepts/security/controlling-access/">Kubernetes: Controlling Access to the API</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/reference/kubernetes-api/core/event-v1/">Kubernetes API: Event</a></li> <li><a href="https://docs.cilium.io/en/stable/overview/intro/">Cilium: Hubble</a></li> <li><a href="https://docs.cilium.io/en/latest/observability/hubble/hubble-cli/">Cilium: Inspecting network flows</a></li> </ul> <hr> <p><strong>Навигация по циклу:</strong><br>← <a href="https://guardcore.by/ingress-egress-kubernetes-pochemu-zakryt-vhod-nedostatochno/">Ingress и egress в Kubernetes: почему закрыть вход недостаточно</a> · <a href="https://guardcore.by/pochemu-privychnaya-setevaya-bezopasnost-ne-rabotaet-v-kubernetes/">Начало цикла</a> · <a href="https://guardcore.by/servis-mesh-mtls-kubernetes/">mTLS и service mesh: где заканчивается NetworkPolicy →</a></p>

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

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 минуты Открыть →
Инструменты

CNI и eBPF в Kubernetes: как проверить, кто применяет сетевую политику

<p><strong>Цикл:</strong> «Сетевая безопасность Kubernetes: от основ до практической защиты» · материал 6 из 12<br><strong>Формат:</strong> практический разбор<br><strong>Для кого:</strong> ИБ-специалист, DevOps-инженер, архитектор и разработчик, которым нужно понять, где в Kubernetes реально принимается решение «разрешить или заблокировать пакет».</p> <h2>Кратко</h2> <p><code>NetworkPolicy</code> — это объект Kubernetes API с описанием желаемого правила. Он сам не фильтрует ни одного пакета. Реальную работу выполняет реализация pod-сети: она подключает Pod к сети, отслеживает изменения в API и программирует datapath на worker node. Без поддержки NetworkPolicy со стороны этой реализации YAML можно успешно создать, но трафик останется открытым.</p> <p>eBPF — один из способов построить такой datapath в Linux. Он не является самостоятельным firewall, не заменяет RBAC, защиту образов, аудит или реагирование. Его польза возникает только в связке: корректные правила, поддерживаемая версия ядра, контролируемая конфигурация и возможность доказать по телеметрии, почему поток был разрешён или заблокирован.</p> <p>Ниже — методика: определить используемую реализацию сети, проверить enforcement на стенде из предыдущей статьи, собрать минимальный паспорт datapath и понять, что именно проверять перед выбором или изменением CNI.</p> <h2>Сначала разделим три разные вещи</h2> <p>Термины часто смешивают, из-за чего CNI превращается в «магический сетевой компонент». На практике это разные уровни.</p> <table> <thead> <tr> <th>Уровень</th> <th>Что делает</th> <th>Что не доказывает</th> </tr> </thead> <tbody><tr> <td>Kubernetes API</td> <td>Хранит <code>NetworkPolicy</code>, Service, Pod и их labels</td> <td>Что пакет был отфильтрован</td> </tr> <tr> <td>Реализация pod-сети / CNI</td> <td>Подключает Pod к сети и может реализовать policy, маршрутизацию и Service</td> <td>Что правила безопасны и полны</td> </tr> <tr> <td>Datapath на node</td> <td>Обрабатывает конкретный пакет: маршрут, трансляция, allow/drop</td> <td>Кто и зачем создал широкое правило</td> </tr> <tr> <td>Observability</td> <td>Связывает поток с Pod, namespace, Service и verdict</td> <td>Что блокировка не сломала бизнес-сценарий</td> </tr> </tbody></table> <p>Официальная документация Kubernetes прямо отделяет API от реализации: pod-сеть и NetworkPolicy обычно обеспечиваются внешней реализацией сети; часть простых реализаций не поддерживает NetworkPolicy вовсе. Поэтому наличие ресурсов <code>networkpolicy.networking.k8s.io</code> в кластере — ещё не доказательство защиты.</p> <h2>Что такое CNI без маркетинга</h2> <p>CNI (Container Network Interface) — спецификация и набор плагинов, через которые container runtime на node настраивает сеть sandbox Pod. Kubernetes требует совместимый сетевой плагин для реализации своей модели pod-сети. Но CNI не означает фиксированный набор функций и не является названием конкретного продукта.</p> <p>Одна реализация может только подключать интерфейс и выдавать IP. Другая дополнительно реализует межузловую маршрутизацию, NetworkPolicy, балансировку Service, шифрование, DNS/FQDN-политику или flow telemetry. Поэтому вопрос «у нас есть CNI?» бесполезен. Нужен другой вопрос: <strong>какая реализация pod-сети установлена, какие функции включены и где они применяются?</strong></p> <p>Не путайте эту роль с <code>kube-proxy</code>. Kubernetes даёт стандартную реализацию Service proxy через <code>kube-proxy</code>, но часть сетевых решений реализует Service handling внутри собственного datapath. Для расследования это важно: один и тот же вызов <code>backend.default.svc.cluster.local</code> может сначала быть виден как Service, а затем как соединение с конкретным endpoint Pod.</p> <h2>Как пакет связан с предыдущей статьёй</h2> <p>Возьмём стенд <code>client → backend</code> из материала о NetworkPolicy. После <code>default deny</code> успешное обращение по TCP/8080 возможно только когда:</p> <ol> <li>DNS разрешил имя <code>backend</code>;</li> <li>Service направил соединение к endpoint <code>backend</code>;</li> <li>datapath источника разрешил egress от <code>client</code>;</li> <li>datapath получателя разрешил ingress к <code>backend</code>;</li> <li>обратный трафик был обработан как ответ разрешённого соединения.</li> </ol> <p>Схема не означает, что каждый CNI применяет правила в одинаковых точках ядра. Она показывает границы проверки. Если поток не проходит, нельзя заключать «NetworkPolicy сломана», пока не разделены DNS, Service/endpoints, policy и сама связность между node.</p> <pre><code class="language-text">client Pod → DNS / Service → datapath на source node → сеть между node → datapath на destination node → backend Pod │ │ │ имя/endpoint egress policy ingress policy </code></pre> <h2>Почему одинаковый YAML может работать по-разному</h2> <p>Стандартный API NetworkPolicy задаёт ожидаемый результат на L3/L4: какой Pod выбран, откуда или куда разрешён TCP/UDP/SCTP. Он не предписывает конкретный внутренний механизм. Реализация может использовать Linux routing, <code>iptables</code>/<code>nftables</code>, виртуальные интерфейсы, overlay-туннель, BGP-маршрутизацию или eBPF. Эти технологии не являются взаимозаменяемыми пунктами настройки: в одном кластере может сочетаться несколько из них.</p> <p>Из этого следуют два практических правила.</p> <p>Первое: нельзя переносить инструкции «посмотрите правило в iptables» в другой кластер и считать их универсальной проверкой. Второе: нельзя оценивать CNI только по YAML совместимости. Нужно проверять фактический поток в конкретной версии CNI, Kubernetes и ядра Linux.</p> <h2>Минимальная инвентаризация без доступа к node</h2> <p>Начните с read-only команд. Они не раскрывают внутреннее устройство datapath полностью, но дают основу для дальнейшего запроса к платформенной команде.</p> <pre><code class="language-bash">kubectl get nodes -o wide kubectl -n kube-system get daemonset kubectl -n kube-system get pods -o wide kubectl get networkpolicy -A kubectl get svc -A </code></pre> <p>Зафиксируйте результат в паспорте кластера, а не в личных заметках:</p> <table> <thead> <tr> <th>Поле</th> <th>Что записать</th> <th>Зачем</th> </tr> </thead> <tbody><tr> <td>CNI и версия</td> <td>точное имя реализации и release</td> <td>определяет документацию и известные ограничения</td> </tr> <tr> <td>Kubernetes и ядро node</td> <td>версии control plane, worker OS/kernel</td> <td>важны для совместимости и eBPF-функций</td> </tr> <tr> <td>Pod/Service CIDR</td> <td>фактические диапазоны</td> <td>нужны для маршрутизации, egress и расследований</td> </tr> <tr> <td>Межузловая схема</td> <td>direct routing, overlay, BGP или иное</td> <td>определяет путь трафика и возможные слепые зоны</td> </tr> <tr> <td>Service handling</td> <td><code>kube-proxy</code> или замена CNI</td> <td>объясняет трансляцию Service→endpoint</td> </tr> <tr> <td>NetworkPolicy</td> <td>поддержка, режим enforcement, scope</td> <td>отвечает на вопрос, действует ли default deny</td> </tr> <tr> <td>Телеметрия</td> <td>источник flows, поля, retention, экспорт</td> <td>делает policy проверяемой и расследуемой</td> </tr> </tbody></table> <p>Названия DaemonSet и Pod могут подсказать поставщика, но это не доказательство включённых функций. Версию и режим policy нужно подтвердить конфигурацией и документацией именно этого CNI.</p> <h2>Проверка, что policy действительно действует</h2> <p>Не заменяйте функциональную проверку просмотром объектов. Возьмите отдельный namespace <code>np-lab</code> из статьи 5 и выполните три теста в таком порядке:</p> <pre><code class="language-bash"># 1. До изоляции: client должен достичь backend kubectl -n np-lab exec deploy/client -- \ curl -fsS --max-time 3 http://backend:8080 # 2. После default deny: тот же поток не должен пройти kubectl -n np-lab exec deploy/client -- \ curl -fsS --max-time 3 http://backend:8080 || echo "blocked as expected" # 3. После DNS и узких ingress/egress policy: поток снова проходит kubectl -n np-lab exec deploy/client -- \ curl -fsS --max-time 3 http://backend:8080 </code></pre> <p>Это не тест производительности и не полная приёмка CNI. Он отвечает на базовый вопрос: выбранная реализация изменила судьбу реального соединения после изменения NetworkPolicy или нет.</p> <p>Для отрицательного теста добавьте Pod без разрешающей метки и убедитесь, что он не получает доступ к <code>backend</code>. Успешный allow-тест сам по себе слабое доказательство: соединение могло пройти из-за слишком широкого правила, другой политики или отсутствия enforcement.</p> <p>Результат нужно фиксировать как артефакт изменения: версия CNI, namespace, применённые манифесты, время, allow/deny-результат и ссылка на flow/event, если он доступен.</p> <h2>eBPF: что это и чего от него не ждать</h2> <p>eBPF — механизм Linux kernel, позволяющий исполнять проверенные программы в разрешённых точках ядра. В сетевом datapath он может обрабатывать пакет на раннем этапе, сопоставлять его с картами состояний, применять policy или собирать metadata. Это механизм исполнения, а не отдельная политика безопасности.</p> <p>Например, Cilium использует eBPF-программы, привязанные к сетевым hook. Его документация описывает проверку policy на veth/TC-пути и сопоставление пакета с identity и policy. Это описание относится к Cilium; его нельзя объявлять устройством любого CNI.</p> <p>У eBPF есть реальные преимущества для динамичной среды: решение может связать flow не только с краткоживущим IP, но и с Kubernetes identity; Service lookup и policy могут использовать общую картину endpoints. Но «у нас eBPF» не отвечает ни на один из критичных вопросов: есть ли default deny, кто может менять правила, включена ли телеметрия, хватает ли ресурсов или защищены ли сами node.</p> <h2>Cilium и Hubble: отдельный, а не обязательный вариант</h2> <p>Если кластер действительно использует Cilium, проверяйте его как конкретный продукт. Cilium документирует identity-aware L3/L4 policy для Pod-to-Pod; для расширенных возможностей использует собственные CRD. Hubble строится поверх Cilium и предоставляет распределённую observability: потоки, DNS-события и verdict policy.</p> <p>Это полезно для ИБ, потому что расследование перестаёт опираться только на IP. Но Hubble не компенсирует ошибочную политику и не превращает наблюдение в блокировку. Сначала нужна минимальная связность, подтверждённая allow/deny-тестами; затем — осмысленный экспорт нужных событий и срок хранения.</p> <p>Ниже команды допустимы <strong>только после подтверждения, что установлен Cilium и разрешён доступ к его агенту</strong>:</p> <pre><code class="language-bash">kubectl -n kube-system get pods -l k8s-app=cilium kubectl -n kube-system exec ds/cilium -- cilium status --wait </code></pre> <p>Не копируйте эти команды в кластер с другим CNI. Для него используйте штатную диагностику поставщика и проверьте: отображается ли источник, назначение, DNS-имя (если оно нужно), порт, verdict, причина/политика и время события.</p> <h2>Как выбрать нужную глубину контроля</h2> <p>Стандартная NetworkPolicy подходит, когда нужно управлять Pod/namespace/IP и портом на L3/L4. Это базовый переносимый минимум. Запросы «разрешить только конкретное DNS-имя», «только HTTP <code>GET /health</code>» или «видеть HTTP-код ответа» выходят за пределы стандартного API и требуют расширения конкретного CNI, egress proxy, Gateway/API gateway или service mesh.</p> <p>Не начинайте с L7 только потому, что оно доступно в интерфейсе продукта. Более глубокий контроль добавляет компоненты, правила, сертификаты, нагрузку и точки отказа. Он оправдан, когда L3/L4 не выражает необходимую границу риска: например, один общий порт обслуживает и разрешённый, и опасный API-метод.</p> <h2>Нагрузка, отказ и обновление</h2> <p>Вопрос «eBPF быстрее iptables?» не является критерием проектного решения. Итог зависит от режима CNI, ядра, числа endpoints, Service, policy, включённого шифрования и объёма наблюдения. Нужны измерения на репрезентативной нагрузке, а не обещание из презентации.</p> <p>Перед включением глубоких flow logs или L7-observability определите:</p> <ol> <li>какие события действительно нужны для защиты и эксплуатации;</li> <li>ожидаемый поток событий и retention;</li> <li>CPU, память и сетевой трафик на node;</li> <li>куда экспортируются события и кто имеет к ним доступ;</li> <li>поведение при недоступности collector или переполнении буфера;</li> <li>порядок обновления CNI и проверку rollback.</li> </ol> <p>Особый риск — изменение CNI или его datapath одновременно с <code>default deny</code>. Это две переменные в одном инциденте. Сначала докажите базовую связность и policy enforcement в пилотном namespace, затем отдельно меняйте CNI/режим и повторяйте те же тесты.</p> <h2>Чек-лист приёмки CNI с точки зрения ИБ</h2> <p>Перед тем как считать CNI частью контролируемой защиты, должны быть положительные ответы на вопросы:</p> <ol> <li>Поддерживает ли текущая версия именно стандартный Kubernetes NetworkPolicy и включён ли enforcement?</li> <li>Подтверждён ли отрицательный тест <code>default deny</code> на реальном Pod-трафике?</li> <li>Проверены ли ingress и egress, DNS, Service и связь между разными node?</li> <li>Известны ли режим маршрутизации, Service handling и поведение egress/NAT?</li> <li>Есть ли диагностика deny и корректная связь IP с Kubernetes metadata?</li> <li>Ограничены ли права на изменение CNI-конфигурации, labels и NetworkPolicy?</li> <li>Зафиксированы ли ресурсные лимиты, обновление, rollback и ответственные?</li> </ol> <p>Если на несколько пунктов ответ «не знаю», CNI нельзя считать проверенным средством сегментации. Это не основание немедленно менять продукт; это основание закрыть неизвестность тестом, документацией поставщика и измерением в своём кластере.</p> <h2>Итог</h2> <p>NetworkPolicy описывает, что разрешено. Реализация pod-сети и её datapath решают, как это будет применено. Observability позволяет увидеть, что произошло. eBPF может быть сильной технической основой для этих задач, но не является заменой архитектуры безопасности.</p> <p>Следующая статья переходит к другой границе: ServiceAccount и RBAC. Даже идеальная сетевая сегментация не спасает, если скомпрометированный Pod может через Kubernetes API читать Secrets или менять политики.</p> <h2>Официальные источники</h2> <ul> <li><a href="https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/">Kubernetes: Network Plugins</a></li> <li><a href="https://kubernetes.io/docs/concepts/services-networking/">Kubernetes: Services, Load Balancing, and Networking</a></li> <li><a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">Kubernetes: Network Policies</a></li> <li><a href="https://docs.cilium.io/en/stable/overview/intro/">Cilium: Introduction to Cilium and Hubble</a></li> <li><a href="https://docs.cilium.io/en/stable/network/ebpf/intro/">Cilium: eBPF datapath</a></li> <li><a href="https://docs.cilium.io/en/stable/network/kubernetes/intro/">Cilium: Kubernetes networking</a></li> </ul> <hr> <p><strong>Навигация по циклу:</strong><br>← <a href="https://guardcore.by/networkpolicy-s-nulya-kak-izolirovat-prilozheniya-kubernetes/">NetworkPolicy с нуля: как реально изолировать приложения в Kubernetes →</a> · <a href="https://guardcore.by/pochemu-privychnaya-setevaya-bezopasnost-ne-rabotaet-v-kubernetes/">Начало цикла</a> · <a href="https://guardcore.by/serviceaccount-rbac-bezopasnost-kubernetes-api/">ServiceAccount и RBAC: почему сетевая защита Kubernetes невозможна без контроля API →</a></p>

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

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 минуты Открыть →