Матрица требований и доказательств по Приказу ОАЦ № 66: как собрать рабочую таблицу
Актуализировано 26.09.20264 минуты
РазделБлог
ФорматПрактический разбор
ЦиклGuardCore
Чтение4 минуты
Кратко
Практический шаблон, который связывает требования Приказа ОАЦ № 66 с активами, реализацией, документами, проверочными действиями и доказательствами.
Матрица требований и доказательств — вспомогательный рабочий инструмент, который связывает применимые нормативные требования с активами информационной системы, реализацией, документами, проверочными действиями и результатами. Сам по себе приказ № 66 не устанавливает обязанность вести документ именно под таким названием; матрица не заменяет ТЗ, ЛПА, схемы или протоколы испытаний.
Когда матрица действительно полезна
Матрица нужна там, где одно требование затрагивает несколько компонентов, либо один технический механизм используется для выполнения разных требований. Без такой связки легко получить ситуацию «в ТЗ написано, в интерфейсе что-то включено, но никто не может показать проверяемый результат».
Одна строка должна отвечать на вопросы: что требуется → почему применимо → где выполнено → как проверить → чем подтвердить → что делать при несоответствии. Не пытайтесь заменить этим файлом все проектные документы: храните ссылки и идентификаторы, а не переписывайте в таблицу целые инструкции.
Рекомендуемые поля
Поле
Содержание
ID / пункт
Идентификатор требования и ссылка на действующую редакцию
Класс / применимость
Статус в нормативной таблице и отдельно фактическая применимость
Название, версия и пункт ТЗ, ЛПА, формуляра, инструкции
Проверочное действие
Как именно воспроизвести контроль, а не «сделать скриншот»
Ожидаемый результат
Измеримое условие соответствия
Доказательство
Ссылка на протокол, настройки, журнал, выгрузку, акт
Статус и действие
Не проверено / соответствует / частично / не соответствует; владелец и срок
Важно не смешивать в одной ячейке статус обязательности из таблицы и вывод о применимости в конкретной ИС. Если строка неприменима, укажите причину с привязкой к инфраструктуре и документам. Не подменяйте отсутствие технологии общим утверждением «не требуется» без проверки нормативного порядка определения применимости.
Условная ИС содержит доменные рабочие станции Windows. В техническом задании установлен период бездействия, по истечении которого требуется блокировка сеанса.
Поле
Пример заполнения
Область
Группа «Рабочие станции пользователей», список в реестре активов
Реализация
Доменная политика с заданным временем блокировки; отдельно учтены автономные АРМ
Документ
ТЗ, раздел о управлении доступом; ЛПА по использованию рабочих мест
Проверочное действие
На выбранном АРМ проверить применение GPO, дождаться заданного интервала и попытаться продолжить работу
Ожидаемый результат
Сеанс блокируется; продолжение требует аутентификации
Доказательство
Протокол, идентификатор АРМ, результат применения GPO, снимок состояния без персональных данных
Статус
Соответствует / частично / не соответствует по факту проверки
Одного снимка GPO недостаточно: он подтверждает настройку объекта политики, но не гарантирует получение её конкретным АРМ. И наоборот, демонстрация блокировки на одном узле не доказывает полный охват группы. Нужно отдельно указать метод выбора образцов и проверки распространения политики.
Второй пример: централизованный сбор событий
Здесь одной строкой «SIEM внедрена» ограничиваться нельзя. Разделите области по источникам: серверы, АРМ, сеть, СЗИ, инфраструктурные сервисы. Для каждой группы определите состав событий, канал доставки, контроль потери связи, срок хранения, права доступа и тестовый поиск записи. Доказательства должны позволять пройти путь от события на источнике до хранилища; сам факт установки агента не подтверждает доставку.
Порядок заполнения
Зафиксируйте версию нормативного источника и область ИС; приложите действующее ТЗ и схемы.
Определите нормативную обязательность каждого пункта для установленного класса. Отдельно зафиксируйте применимость с учётом активов и технологий.
Укажите владельца реализации и активы, на которых проверяется выполнение.
Опишите результат меры, а не только название средства защиты.
Сформулируйте проверочное действие, ожидаемый результат и состав доказательств.
Выполните проверку, отметьте дату, результат, исключения и корректирующие действия.
После изменения инфраструктуры или требований актуализируйте затронутые строки и доказательства.
Не следует прятать спорные решения о применимости в примечании. В матрице должно быть ясно, кем, на основании чего и в какой редакции документов принято такое решение.
Правила ведения и защита сведений
Матрица может содержать топологию, идентификаторы активов, сведения о настройках и дефектах защиты. Не публикуйте заполненный рабочий экземпляр без обезличивания; ограничьте доступ, настройте историю изменений, определите ответственного и период пересмотра. Хранение файлов доказательств организуйте отдельно, а в матрице оставляйте идентификаторы и контролируемые ссылки.
Результат: рабочая таблица полезна тогда, когда по любой строке можно пройти от нормативного основания до фактической проверки и увидеть, чем подтверждён вывод. «Выполнено» без указанного проверочного действия — не доказательство.
<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>
<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>
<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>