Представим условную ситуацию. Платёжная платформа выпускает контейнер checkout:2026.10. В реестре есть подпись, в Kubernetes установлен проверяющий вебхук, а в отчёте релиза написано «образ верифицирован». Через сутки команда реагирования узнаёт, что релиз собран из ошибочной ревизии. На вопрос, где он сейчас работает, ответы расходятся: GitOps показывает тег, часть подов создана до включения политики, ARM-узлы получили другой платформенный манифест, а исключение для служебного пространства имён никто не связал со сроком действия.
Сценарий вымышленный, но он показывает точный предмет проверки. Безопасность контейнерной инфраструктуры в контексте выпуска образов — это способность доказать цепочку от одобренного неизменяемого содержимого до фактически работающих экземпляров, а затем блокировать новые совпавшие операции допуска, контролировать перезапуски существующих контейнеров и заменить их без несогласованного простоя. Подпись, политика допуска и список подов нужны вместе; по отдельности ни один из этих артефактов не отвечает на весь вопрос.
Как собрать цепочку доказательств релиза
Полезная проверка начинается не с вопроса «включена ли проверка подписей», а с пяти связанных утверждений. Команда одобрила конкретный digest. Подпись этого digest соответствует ожидаемым издателю и идентичности подписанта. Аттестация соответствует содержательной политике релиза. Все нужные API-запросы действительно проходят через принудительный контроль допуска. Инвентаризация показывает, что именно одобренные digest работают сейчас.
Проверку допуска образов можно включить в аудит безопасности Kubernetes вместе с оценкой прав доступа, секретов и сетевой изоляции. Она связана и с анализом безопасности цепочки поставок ПО: сведения об исходном коде и сборщике становятся условиями выпуска конкретного образа. Здесь сосредоточимся на этой связи и доказательствах, которые заказчик должен получить.
У каждого утверждения должен быть свой артефакт: карточка релиза с digest, результат проверки подписи и аттестации, версия политики, событие допуска, снимок фактических образов и запись решения об исключении либо отзыве. Формулировка «cosign прошёл» не говорит, какая идентичность была разрешена, какие поля аттестации проверялись и применялась ли та же политика в нужном кластере.
- ОдобрениеВладелец релиза фиксирует digest, платформы, ожидаемого подписанта, сборщик и допустимые параметры сборки.
- ПроверкаВерификатор связывает подпись и аттестацию с digest и сравнивает их с версионируемой политикой.
- ДопускКластер блокирует не прошедшие политику запросы во всех согласованных ресурсах, версиях API и пространствах имён.
- СверкаИнвентаризация сопоставляет желаемые ссылки, разрешённые digest и идентификаторы образов у работающих контейнеров.
- ОтзывКоманда блокирует совпавшие новые Pod, учитывает перезапуски контейнеров в существующих Pod и по плану заменяет или изолирует экземпляры.
Digest, тег и образ для конкретной платформы
Тег — удобное имя, но владелец реестра может переназначить его на другое содержимое. Digest — идентификатор содержимого: клиент проверяет полученные байты по хешу. Если в ссылке присутствуют и тег, и digest, Kubernetes использует digest для загрузки. Поэтому карточка релиза и правило допуска должны опираться на digest, а тег можно сохранить как человекочитаемую метку.
Для многоплатформенного образа одной строки недостаточно. OCI image index — верхнеуровневый документ со списком дескрипторов платформенных манифестов. Например, один digest индекса может указывать на отдельный манифест для linux/amd64 и отдельный — для linux/arm64; уже платформенный манифест ссылается на конфигурацию и слои. Подпись индекса и отдельная подпись дочернего манифеста относятся к разным объектам. Проверенная цепочка digest связывает подписанный индекс с дочерним содержимым; отдельная подпись каждого дочернего манифеста нужна только тогда, когда этого требует выбранная политика.
Приёмочный пакет поэтому должен хранить digest индекса, набор разрешённых платформ и ожидаемый дочерний digest для каждой из них. В тестовом кластере нужны узлы либо эмуляция всех разрешённых платформ. Проверка только на amd64 не доказывает, что arm64-узел выберет ожидаемый дочерний манифест. Инвентаризация после запуска должна показать и исходную ссылку из спецификации Pod (PodSpec), и идентичность образа, которую определила среда выполнения; простого перечня тегов недостаточно для расследования отзыва.
Что именно доказывает подпись и что должна решать политика
Криптографически корректная подпись показывает, что определённая ключевая или сертифицированная идентичность подписала конкретное содержимое. Она не решает, следует ли вашей организации доверять этой идентичности. В keyless-схеме проверяются как минимум ожидаемый subject сертификата и ожидаемый OIDC issuer; чрезмерно широкий шаблон любого из полей превращает сильную криптографию в слабое правило авторизации. При ключевой схеме аналогичный вопрос звучит так: какой ключ разрешён для какого репозитория и релизного канала, кто может изменить это соответствие и как происходит ротация.
Политика допуска должна содержать больше, чем signature valid. Для конкретного продукта полезно зафиксировать:
- допустимый реестр и путь образа, digest или правила его получения из утверждённой карточки релиза;
- идентичность подписанта и issuer, доверенный корень и режим проверки журнала прозрачности, если он входит в выбранную схему;
- тип требуемой аттестации, её привязку к subject образа и ожидаемые поля;
- канонический репозиторий исходного кода, доверенный
builder.id,buildTypeи разрешённыеexternalParameters; - разрешённые платформы, окружения и типы Kubernetes-ресурсов;
- список запрещённых digest, правила исключений, срок действия и владельца решения;
- поведение при отсутствии совпавшей политики и при недоступности проверяющего сервиса.
SLSA описывает полезную последовательность проверки provenance: проверить подпись оболочки, совпадение subject с digest артефакта, доверие к идентичности сборщика, а затем сравнить buildType, источник и внешние параметры с ожиданиями. Но provenance не является анализом безопасности кода и не обещает полноту всех зависимостей. Подлинная аттестация может честно описывать сборку нежелательной ревизии, если политика допускает этот источник или параметр. Поэтому принятие аттестации без проверки её полей подтверждает наличие документа, а не соответствие релиза.
Где реально срабатывает допуск и где остаются обходы
Контроль допуска оценивает API-запросы, которые совпали с его правилами. Это принципиально отличается от непрерывной проверки всего работающего состояния. Для вебхука нужно перечислить ресурсы и операции, версии API, matchPolicy, namespaceSelector, objectSelector и matchConditions. Исключённый селектором запрос не будет проверен независимо от значения failurePolicy.
У failurePolicy есть более узкий смысл, чем ему часто приписывают. Fail отклоняет запрос, когда вызов совпавшего вебхука завершился ошибкой или таймаутом; Ignore позволяет API-серверу пропустить такую ошибку. Если вебхук успешно получил запрос и вернул allowed: false, это явный отказ, а не ошибка вызова, и Ignore не превращает его в разрешение. И наоборот, Fail не спасает запрос, который вообще не совпал с rules, версией API или селектором.
Проверять нужно не только непосредственный Pod. Deployment, StatefulSet, DaemonSet, Job и CronJob содержат шаблоны Pod, а контроллер позже создаёт реальные экземпляры. Конкретный механизм политики может проверять верхнеуровневые ресурсы, Pod либо оба уровня. Приёмочный тест должен показать ожидаемое поведение для каждого разрешённого пути создания и обновления, включая контейнеры инициализации, вспомогательные контейнеры (sidecar), временные отладочные контейнеры и поддерживаемые тома на основе образов. Если движок их не анализирует, это зафиксированная граница, а не молчаливое предположение.
Отдельно тестируется режим «образ не совпал ни с одной политикой». Значения allow, warn и deny дают разные гарантии. Предупреждение полезно при внедрении, но не является запретом. Пространства имён с opt-in тоже требуют отрицательного теста: новый namespace без нужной метки не должен становиться незаметным коридором в продакшен.
Допущенный запрос ещё не доказывает текущее состояние
Допуск отвечает на вопрос о конкретной операции в конкретный момент. Он не переоценивает автоматически Pod, созданный вчера, после добавления нового запрещённого digest или изменения доверенной идентичности. Поэтому доказательство рабочего состояния строится отдельной сверкой.
Минимальная запись инвентаризации содержит кластер, пространство имён, нагрузку и владельца, имя Pod, контейнер и его роль, ссылку из спецификации, разрешённый digest, идентификатор образа в статусе среды выполнения, платформу узла, время запуска и версию политики, действовавшую при допуске. Нужны обычные контейнеры, контейнеры инициализации и долгоживущие вспомогательные контейнеры (sidecar); отладочные контейнеры отмечаются отдельно. Снимок следует связывать с карточкой релиза, а расхождения — с конкретной задачей и сроком. Отдельно проверяют свежесть данных: статус Pod может отставать, особенно при потере связи узла с управляющей плоскостью. Такой экземпляр нельзя считать проверенным только по последнему сохранённому статусу; в приёмке нужно предусмотреть подтверждение состояния узла и сопоставление его образа с цепочкой digest.
Даже полное совпадение digest не доказывает неизменность всего содержимого процесса после старта. Образ предоставляет неизменяемые слои, но у контейнера может быть записываемый слой; в Pod монтируются emptyDir, persistent volume, ConfigMap, Secret и другие тома. Приложение также способно загрузить и выполнить данные после запуска. Эти байты не становятся частью digest основного образа. Если такая загрузка допустима, политика должна отдельно ограничивать источник, целостность и исполнение; если она запрещена, это проверяют egress-контролем и поведением приложения. Здесь важно не обещать больше: подпись образа подтверждает проверенный объект образа, а не каждое состояние работающего процесса.
Отзыв образа без несогласованного простоя
Добавление digest в список запрета, удаление доверия к подписанту или ужесточение политики происхождения должны блокировать будущие совпавшие операции допуска. Эти действия сами по себе не останавливают уже работающие контейнеры. Более того, когда kubelet перезапускает контейнер по restartPolicy внутри того же Pod на том же узле, новый объект Pod через API не создаётся и повторного допуска Pod не происходит. Значит, один список запрета не гарантирует прекращения запуска отозванных байтов. Автоматически удалить все такие Pod — тоже плохое универсальное правило: для кворумного сервиса, одиночного компонента или StatefulSet это может превратить событие безопасности в аварию доступности.
План отзыва начинается с двух списков: где digest фактически работает и какой одобренный digest может его заменить. Затем владелец риска выбирает действие по контексту: немедленная изоляция при активной компрометации; контролируемое поэтапное обновление для уязвимого, но не эксплуатируемого образа; временное исключение для незаменимого компонента с компенсирующими мерами и коротким сроком. План должен отдельно учесть перезапуск контейнера внутри существующего Pod, создание нового Pod контроллером и исключённые либо статические пути. Решение должно быть явным, а не следствием отсутствующего запасного образа.
Для Deployment доступность при обновлении определяют стратегия поэтапного обновления, maxUnavailable, maxSurge, проверки готовности и способность новой версии обслуживать трафик. PodDisruptionBudget ограничивает добровольное выселение Pod, однако сам по себе не управляет поэтапным обновлением контроллера нагрузки. Если команда отзывает экземпляры через Eviction API, PDB и период корректного завершения участвуют; прямое массовое удаление обходит полезный механизм координации. Для StatefulSet нужны порядок, сохранность данных и кворум; для DaemonSet — минимальный набор узлов; для Job — правила повтора и идемпотентность.
Откат разрешается только на заранее известный digest, который всё ещё соответствует текущей политике. Откат «на предыдущий тег» может вернуть другой объект, если тег переназначили. Перед заменой сначала проверяют запасной digest через тот же путь допуска, затем запускают контрольный экземпляр или малую волну, оценивают здоровье и бизнес-сигналы и лишь после этого расширяют обновление. Удалять отозванный объект из реестра до завершения замены опасно: новый Pod может остаться без образа, пока старые экземпляры продолжают работать и создают ложное ощущение завершённого отзыва.
Как ограничить аварийные исключения
В инфраструктуре могут встречаться системные дополнения, аварийные релизы или вендорские образы, которые пока не проходят общую политику. Исключение безопаснее широкого отключения только тогда, когда оно машиночитаемо и узко. В записи нужны точный digest, кластер и namespace, типы ресурсов, причина, владелец, согласовавший риск, время начала и окончания, компенсирующие меры и проверенный путь выхода.
Исключение по тегу или по всему реестру слишком широко: его будущий смысл может измениться без изменения самой записи. Регулярное выражение для идентичности подписанта следует проверять отрицательными примерами. Аварийный режим требует двух независимых следов — кто разрешил обход и кто применил его — плюс последующей сверки всех созданных в окно Pod. Истечение срока должно закрывать исключение автоматически либо создавать блокирующий сигнал владельцу; календарное напоминание без технического контроля легко пропустить.
Исключения входят в те же тесты отзыва. Команда должна заранее знать, заблокирует ли список запрета исключённый digest, кто имеет право продлить срок и можно ли безопасно заменить нагрузку. Иначе именно самый критичный компонент окажется вне управляемого процесса в момент инцидента.
Что включить в закупку оценки и разрешённый пентест
Покупателю нужен не скриншот успешной проверки, а воспроизводимый комплект. В техническое задание стоит включить карту реестров и кластеров, экспорт версионируемых политик, матрицу правил сопоставления, карточки тестовых digest и платформ, результаты положительных и отрицательных тестов, инвентаризацию желаемых и фактических образов, перечень исключений, сценарий отзыва и протокол пробного отката. В отчёте каждое утверждение должно быть помечено как подтверждённое тестом, выведенное из конфигурации либо сообщённое владельцем.
Разрешённое тестирование может проверить обходы через другой тип нагрузки или версию API, пространство имён без метки, несовпадающий селектор объекта, отсутствие совпавшей политики образов, таймаут вебхука, неверные issuer и subject, подпись правильной идентичности над неразрешённым digest, аттестацию с неожиданным buildType, иной платформенный манифест и расхождение между спецификацией Pod и инвентаризацией работающих образов. Отдельный тест завершает контейнер и проверяет, может ли kubelet снова запустить тот же образ внутри существующего Pod без нового допуска. Для тестов отказа нужен согласованный стенд или окно, потому что закрытый при отказе режим способен остановить легитимные развёртывания, а открытый — временно пропустить непроверенный запрос.
Границы разрешения должны отдельно назвать, можно ли создавать тестовые нагрузки, намеренно перегружать или отключать вебхук, отправлять заведомо некорректные аттестации, читать метаданные реестра, завершать контейнер для проверки повторного запуска и инициировать безопасное поэтапное обновление. Удаление рабочих образов, массовая остановка Pod, доступ к приватным ключам и нагрузочное воздействие на API-сервер не следуют автоматически из слова «пентест». Если активные проверки затрагивают поставщика реестра или управляемый кластер, требуется разрешение соответствующего владельца.
С Pentest.red можно согласовать такой технический охват через форму описания пентеста: кластеры, реестры, разрешённые обходные сценарии, тестовые digest, окна и критерии остановки. Описание услуг по тестированию на проникновение помогает понять формат работы, а пример структуры отчёта по пентесту — заранее договориться о доказательствах. Результат пентеста — технические находки в согласованных границах; он не является сертификацией и не доказывает безопасность каждого образа вне протестированной выборки.
Матрица приёмки: какие утверждения нужно доказать
Матрицу полезно заполнять для каждого продуктивного кластера и каждого пути выпуска. «Пройдено» означает, что сохранено воспроизводимое доказательство, а не только устное подтверждение команды.
Листайте таблицу по горизонтали
| Утверждение | Положительный тест | Отрицательный тест | Артефакт приёмки |
|---|---|---|---|
| Тег разрешается в одобренный digest | Развёртывание утверждённого релиза фиксирует ожидаемый digest | Переназначенный тег не меняет допущенное содержимое | Карточка релиза и нормализованная спецификация Pod |
| Все платформы одобрены | amd64 и arm64 получают ожидаемые дочерние манифесты | Неизвестная платформа или дочерний digest отклоняется | Карта index → platform manifest |
| Подписант авторизован | Совпадают subject, issuer и область репозитория | Чужой subject либо issuer отклоняется при валидной подписи | Результат верификации и версия trust policy |
| Provenance соответствует ожиданиям | Совпадают subject, builder, source, buildType и параметры | Неофициальный fork или неожиданный параметр блокируется | Аттестация и отчёт policy evaluation |
| Все пути создания покрыты | Разрешённые Deployment, Job и Pod проходят одинаковое правило | Обход через другой ресурс или API-версию не проходит | Матрица resources/operations/versions |
| Селекторы не создают коридор | Каждое рабочее пространство имён попадает под принудительную проверку | Пространство без метки и поддельная метка дают ожидаемый отказ | Экспорт настроек вебхука и привязок политики, журнал теста |
| Ошибка вебхука обработана осознанно | Допустимый запрос ведёт себя по выбранной модели доступности | Таймаут показывает именно Fail или Ignore, а явный отказ остаётся отказом | Протокол теста недоступности вебхука |
| Работающие образы совпадают с разрешёнными | Заданная ссылка и идентичность работающего образа сопоставлены для каждого контейнера | Старый Pod или Pod с исключением виден в результатах сверки | Датированная инвентаризация с владельцами |
| Отзыв блокирует новые Pod по охваченным API-путям | Запрещённый digest отклоняется при создании нового совпавшего Pod | Перезапуск контейнера внутри существующего Pod выявляется как отдельный путь без нового допуска | Список запрета, перечень влияния и события допуска |
| Замена сохраняет согласованную доступность | Контрольный экземпляр и обновление достигают критериев здоровья | Неуспешная новая версия останавливает волну и запускает одобренный откат | План действий, метрики решения и протокол отката |
| Исключение ограничено | Точный digest работает только в заданном охвате и сроке | После срока или вне namespace обход закрыт | Реестр исключений и доказательство истечения |
После первичной оценки находки стоит превратить в проверяемые задачи: владелец, точный объект, условие исправления и повторный тест. Практика такой приёмки подробнее разобрана в материале о проверке устранения уязвимостей.
Решение о готовности: четыре вопроса владельцу релиза
Перед выпуском владелец должен уметь назвать точные digest индекса и платформенных манифестов; показать, почему подписант, issuer, сборщик и параметры разрешены; воспроизвести отрицательный тест на каждом пути создания нагрузки; получить датированный список фактически работающих образов. Перед отзывом добавляются одобренная замена, оценка влияния на доступность, проверка перезапуска контейнера в существующем Pod, порядок волн и критерий остановки.
Если хотя бы одного ответа нет, это не всегда означает немедленный запрет бизнеса. Это означает, что решение принимается с недостающим доказательством и должно быть оформлено как ограниченное исключение с владельцем и сроком. Так безопасность контейнерной инфраструктуры становится управляемой системой решений: команда может доказать, что именно допустила, найти, где это работает, и заменить отозванный релиз без импровизации в момент инцидента.
PENTEST.RED
Обсудить объём и стоимость пентеста
Сначала согласуем системы, доступы, ограничения и ожидаемый результат.
Статья подготовлена на основе глубокого исследования и прошла редакционную переработку.