Меня зовут Никита, я технический лидер команды, которая занимаемся управлением безопасностью контейнерных сред — и как отдельным продуктом для внешних клиентов, и как внутренним решением для команд, которые используют Managed Kubernetes под свои сервисы.По работе я вижу примерно одни и те же ошибки у разных клиентов: уязвимые зависимости, зараженные образы, забытые в слоях секреты, дырки в CI/CD. В этой статье разберем четыре тонких места, которые я вижу чаще всего, с конкретными кейсами из практики. И в конце будет минимальный набор защиты, который быстро можно накатить у себя. Читать далее

Привет, Хабр! Меня зовут Никита, я технический лидер команды Evolution Container Security and Addons в Cloud.ru. Мы занимаемся управлением безопасностью контейнерных сред — и как отдельным продуктом для внешних клиентов, и как внутренним решением для команд, которые используют Managed Kubernetes под свои сервисы.
По работе я вижу примерно одни и те же ошибки у разных клиентов: уязвимые зависимости, зараженные образы, забытые в слоях секреты, дырки в CI/CD. В этой статье разберем четыре тонких места, которые я вижу чаще всего, с конкретными кейсами из практики. И в конце будет минимальный набор защиты, который быстро можно накатить у себя.
Тонкое место 1. Уязвимости в зависимостях, которые никто не отслеживаетСамая частая проблема в контейнерных средах — устаревшее ПО с известными CVE и неправильная конфигурация.
Что интересно, здесь есть корреляция с размером бизнеса. Я бы нарисовал такую картину — от простого к сложному.
Мелкий бизнес и физлица. Обновления ставятся легко. Обновить Kubernetes или базовый образ обычно не проблема, так как инфраструктура простая и без тяжелых кастомных обвязок. Часто такие команды живут на актуальных версиях и обновляются ради новых фич и удобства, а не из соображений безопасности. Но здесь же есть и обратная сторона: из-за отсутствия строгих процессов обновления могут легко зависать на старых версиях на годы просто потому, что «работает — не трогай».
Очень крупные компании — энтерпрайз, банки. Сидят на старых версиях, потому что за пять лет обросли собственными самописными решениями, обвязками, форками. Обновиться означает переписать половину инфраструктуры, поэтому не обновляются. Но это компенсируется большим штатом ИБ, который закрывает риски другими способами: политиками доступа, изоляцией от интернета, компенсирующими мерами.
Середина — средний бизнес и компании, которые уже переросли стартап, но еще не превратились в энтерпрайз. Здесь самый неприятный сценарий: обвязки уже мешают обновляться, а штата ИБ и инструментов, которые могли бы компенсировать риски, еще нет.
Использование заведомо уязвимого ПО не означает гарантированного взлома: если сервис недоступен снаружи, часть рисков снимается сама собой. Но именно средний сегмент попадает в худший расклад: уязвимости есть, компенсирующих мер нет, а обновляться некому.
Кейс: GPU-оператор, который управляет доступом к железуВозьмем типичный сценарий: в Kubernetes нужно запустить нагрузку с доступом к GPU — инференс модели, обучение, любой ML-сценарий. Драйверы к видеокарте живут на уровне ядра, а нагрузка от разработчика — в user space. Связующим слоем между ними выступают nvidia devicePlugin и nvidia-container-toolkit. Они позволяют Kubernetes запускать поды, которые получают доступ к GPU-ускорителю. Из-за архитектурной необходимости нагрузка запускается с большими привилегиями, иначе просто не получится дать контейнеру доступ к железу.
В 2025 году в одном из таких компонентов — nvidia-container-toolkit — была обнаружена уязвимость уровня High (CVE-2025-23266). Проблема была связана с механизмом OCI hooks — спецификация позволяет выполнять команды на разных этапах жизненного цикла контейнера.
В случае NVIDIA этот механизм используется как часть «мостика» между контейнером и хостовой GPU-инфраструктурой — драйверами и устройствами. Хуки при этом исполняются как привилегированный процесс на стороне хоста.
В результате даже простой Dockerfile из нескольких строк позволял внедрить вредоносную библиотеку через механизм хуков. Поскольку рабочая директория хука находится в корне файловой системы контейнера, библиотека могла подменяться прямо из образа.
В этом кейсе после выхода за пределы контейнера атакующий оказался на Worker Node. Дальше он получил список подов на этой ноде, нашел под с сервисным аккаунтом с правами nodes/proxy и через kubelet на порту 10250 выполнил exec в другой под на другой ноде. Финальная точка — доступ к примонтированным Kubernetes-секретам и, по сути, контроль над частью кластера.
История примечательна тем, что, во-первых, здесь сошлась цепочка: сама уязвимость + неправильно настроенные привилегии сервисного аккаунта. Убери одно звено — и цепочка бы не сработала. Во-вторых, буквально через месяц-два после того, как этот CVE закрыли, в новой версии этого же компонента нашли еще одну высокую уязвимость. И ее тоже проэксплуатировали.
Как мы это решаем у себяОтсюда вывод: с компонентами, которые работают с высокими привилегиями (GPU-операторы, CNI-плагины, ingress-контроллеры), задержка с патчем в 2–3 дня уже может быть критичной.
Раньше мы проверяли зависимости раз в спринт, то есть раз в две недели. Сегодня этого категорически недостаточно. Сейчас у нас настроена автоматизация. Поэтому, как только выходит новая версия базового образа или появляется CVE, DevOps получает уведомление в канал, и мы смотрим: если уязвимость критическая — не ждем две недели, а выкатываем фикс в тот же день или на следующий.
Чем больше стек, тем чаще эти уведомления. Один-два продукта не проблема, но когда проектов много, отсматривать вручную становится физически невозможно, работает только автоматизация.
У нас в Cloud.ru эту задачу дополнительно закрывает Evolution Container Security. Сервис, в частности, анализирует сервисные аккаунты в кластерах на предмет избыточных привилегий. Ровно тот сценарий, который выше стал точкой эскалации.
Тонкое место 2. Supply chain: когда даже конкретный тег не гарантияКейс: Trivy System Supply Chain CompromiseКлассическая рекомендация: не используйте тег latest — привязывайтесь к конкретному тегу. Но это работает не всегда. Недавняя история 2026 года про сканер уязвимостей Trivy показывает, почему.
Ирония в том, что Trivy — это самый популярный в мире сканер уязвимостей контейнерных образов. Под эгидой CNCF его разрабатывает компания Aqua Security, которая профессионально занимается безопасностью контейнеров. Их и взломали.
Инцидент произошел в начале марта. Злоумышленники получили доступ к персональным учетным записям разработчиков с правами записи в GitHub. Несмотря на то что Aqua Security обнаружила взлом и провела ротацию учетных данных, она оказалась неполной — часть доступов у атакующих сохранилась.
Через некоторое время это привело ко второй фазе атаки: злоумышленники выполнили force push и перепривязали ряд тегов к вредоносным коммитам. В результате более 70 тегов начали указывать на зараженный код.
Внутри вредоносной нагрузки был функционал для поиска и эксфильтрации секретов: сервисных аккаунтов, ключей доступа, приватных ключей и конфигураций VPN. Спустя еще несколько дней последовала вторая волна атаки. Из-за все той же неполной ротации злоумышленники, используя скомпрометированный сервисный аккаунт Aqua Security, опубликовали в Docker Hub образы с уже встроенной вредоносной логикой.
Все, у кого CI/CD был настроен на постоянное подтягивание свежих образов без кеша, могли попасть на зараженную версию. В нашем случае ситуацию спасло сразу несколько факторов. Во-первых, мы использовали приватный registry с кешированием: в нем остались образы, загруженные до инцидента. Во-вторых, любые обновления у нас дополнительно проходят ручной просмотр и прогоняются через отдельные security-сканеры, перед тем как попасть в окружение. Это дало второй слой защиты. Поэтому, даже если бы внешний образ обновился, он бы не прошел дальше без проверки.
Что отсюда следуетПравило «Не используй latest — привязывайся к тегу» больше не работает как единственная защита. Тег можно перевыпустить — и содержимое поменяется под известной версией.
Единственная надежная привязка сегодня — полный SHA256-хеш коммита. Его подделать практически нереально, поскольку коллизия SHA256 — не тот сценарий, за который стоит переживать в первую очередь. Либо тег + полный хеш совместно, либо только хеш.
Отдельная страховка — приватный registry с кешированием, куда зеркалируются все внешние образы. Это защищает не только от подмены, но и от менее драматичных сценариев. У нас, например, был случай с проектом Kata Containers. Они без всякого злого умысла перезалили образ под тем же тегом. Изменения были небольшие, но были затронуты патчи containerd-конфига, из-за чего Kata Containers перестали корректно запускаться. Мы долго не могли понять, что случилось. На нашей стороне ничего не менялось, и оказалось, что изменился сам upstream. С приватным зеркалированием такое просто невозможно.
Этот сценарий закрывается через Artifact Registry — приватный реестр с периодическим фоновым сканированием. Внешние образы зеркалируются к себе, каждый получает отчет по уязвимостям с разбивкой по критичности, и подмена в upstream уже не может дотянуться до кластера.
Инструменты для подписи и проверкиСтандарт де-факто для подписи образов сегодня — Cosign. Он распространен очень широко, делает свою работу и входит в экосистему Sigstore.
Для проверки подписей на стороне кластера мы используем Connaisseur, Kyverno и его новые Image Validating Admission Policies. Они позволяют не пускать в кластер образ, если подпись не проходит проверку. Это функциональность, которую мы у себя внедрили одной из первых.
Здесь важно, что подписи бывают двух типов и работают с небольшим различием.
Для публичных образов используется keyless signing. Это удобная схема для open source: не нужно заранее распространять публичные ключи, так как все завязано на доверенный прозрачный лог. Cosign во время подписи запрашивает короткоживущие ключи через OIDC (например, через Fulcio), использует их для подписи образа и фиксирует результат в transparency log — Rekor.
Rekor в этой схеме выступает как промежуточное хранилище, которому доверяет агент в кластере при валидации образов. После завершения процесса временные ключи не используются повторно, что снижает риск их компрометации.
Для приватных образов внутри компании чаще используется классическая схема с парой открытый/закрытый ключ. Проблема начинается в мультитенантных кластерах, где разные команды подписывают образы разными ключами. Получается куча пар открытых/закрытых ключей, каждый из которых нужно периодически ротировать. И если забыли про ротацию и изменили только закрытый ключ, а открытый ключ в политике остался старым, то вебхук в кластере будет блокировать запуск новых подов, потому что не сможет проверить подпись. Это ловушка, в которую легко попасть в первый же год работы схемы.
И, как всегда, есть ложка дегтя.
Существует класс атак, например gh0stEdit, который усложняет ситуацию. Суть в том, что вредоносный код можно внедрить в образ так, что это не будет видно ни в сканерах, ни в подписи.
Docker при проверке опирается на manifest.json и хеш сжатого слоя. Если хеш совпадает, то образ считается валидным. Но после распаковки файловой системы внутри слоя содержимое может быть изменено таким образом, что изменения не всегда заметны на уровне метаданных. В результате атакующий может отредактировать файлы в распакованной файловой системе, при этом хеш будет выглядеть валидным.
Тонкое место 3. Утечки секретов: почему регулярки до сих пор работаютПро утечки секретов в образах и репозиториях написано много, но паттерн ошибок повторяется годами.
90% инструментов для поиска утекших секретов работают на регулярных выражениях — и, надо отдать должное, работают эффективно. Пароли и токены имеют примерно одинаковую длину и одинаковые паттерны, регулярки их находят с высокой точностью. Из инструментов — OpenGrep, Trivy, SecretScanner, который умеет находить секреты прямо в образах контейнеров, а также Checkov, который, помимо всего прочего, сканирует и конфигурации Dockerfile и инфраструктуры как кода.
Кейсы из практикиПервый случай произошел в одной из команд разработки. DevOps работал с кластером, положил приватный ключ в рабочую директорию (в тот момент был нужен для быстрой отладки). Работу закончил, забыл про файл. Дальше git add . и git push. Проверить, что улетает, не догадался. Ключ попал при билде образа в слой и ушел в приватный реестр.

Репозиторий был закрытый, и это спасло от катастрофы.

Но пришлось все равно срочно ротировать все связанные ключи, потому что предполагать иное было бы наивно.
Второй сценарий из другой команды: тестировщик захардкодил креды в коде — и при билде в пайплайне они попали в образ. Само по себе неприятно, и дальше срабатывает распространенная иллюзия.
Иллюзия «Перекоммитим сверху»Когда люди обнаруживают, что случайно закоммитили секрет, их типичная реакция — «Сейчас перекоммитим сверху — и все будет хорошо». В случае с git это не работает: старый коммит остается в истории. Любой, у кого есть доступ к репозиторию, может откатиться и достать секрет.
С Docker-образами еще хуже: люди пересобирают образ без чувствительных файлов, пушат новую версию в registry и считают проблему решенной. На самом деле старый образ никуда не делся: он лежит в registry как отдельный артефакт. Атакующий, если знает, что искать, скачивает старую версию и достает секреты из слоев.
Почему это трудно заметитьОтсюда правило: если секрет утек, единственная надежная реакция — ротировать сам секрет. Все, кто убрали и переписали без ротации, живут в иллюзии безопасности.
Dockerfile и скрипты, которые запекаются в слои и модифицируют что-либо после распаковки слоя, на код-ревью мало кто смотрит. Причина: если образ собрался и заработал, значит, все сделано правильно. Далеко не все потом будут анализировать лишние зависимости, которые можно было бы и не тащить в итоговый образ.
Добавьте к этому эпоху, когда часть кода пишут ИИ-помощники и вываливают в review тысячами строк. Ревьюер физически не в состоянии внимательно проглядеть весь этот поток, и что-то неизбежно проскакивает.
Что реально работаетНулевой этап, где можно отловить ошибки, — это pre-commit-хуки. Они срабатывают локально, до того как что-то улетело в репозиторий. Разработчик пытается закоммитить, хук пробегает по изменениям, натыкается на паттерн, похожий на пароль или токен, и просто не дает делать коммит.
Это самое дешевое место для отлова секретов. Все, что мы поймали до git push, не требует ротации, не требует извинений перед командой безопасности, не требует правки истории. Просто локальная блокировка.
Но важно не переусердствовать. Pre-commit-хуки — штука полезная, но, если их слишком много и они начинают часто давать ложные срабатывания, это быстро начинает раздражать. Особенно когда разработчик или девопс в пятницу вечером просто хочет закоммитить результат и уйти домой.
В таких условиях есть риск, что проверки начнут обходить или отключать. В нашей практике был даже случай, когда из-за большого количества проверок разработчик не смог запушить изменения вовремя, а потом в выходные ноутбук с незакоммиченными правками вышел из строя — и часть работы просто потерялась.
Тонкое место 4. CI/CD как точка атакиСледующий уровень — сканирование в CI/CD, а затем — сканирование образов в registry.
С атаками на цепочки поставок мы разобрались, но CI/CD — отдельная тема, которая с этим сильно пересекается. Только с марта 2026-го было четыре крупных инцидента. Напрямую нас они не задели, но были максимально близко. То есть мы каждый раз выдыхали, что не попали под раздачу.
Проверка подписей помогает, но не дает гарантии. Если атакующий проник в саму компанию, которая поставляет ПО, и выпускает от ее имени подписанный образ, то подпись честная, а образ вредоносный. Против такого сценария подпись бессильна.
Где меньше контроля — в CI или в registry?Скажу честно: его мало и там, и там.
Registry у большинства компаний — это большая файлопомойка: терабайты образов, тысячи проектов, десятки команд, постоянные пересборки. Разобраться в этом руками невозможно. Плюс реестра в том, что его можно сканировать в фоне. Даже если что-то попало в registry неделю назад, можно пройти по всем образам и проверить их. Найти уязвимые, поместить на карантин и разобраться.
Отдельно важный момент — разграничение доступа. Частая проблема в том, что доступ выдают на весь реестр, а не на конкретные проекты или namespaces. Гораздо безопаснее строить гранулярные права: разные команды должны видеть и пушить только свои образы, без доступа ко всему хранилищу целиком.
С CI сложнее. Он живет на пересечении множества разнородных вещей: код с разными зависимостями (npm-пакеты у фронтенда, Go-модули у бэкенда, C-зависимости в системных сервисах), Dockerfile, манифесты Kubernetes, переменные окружения для разных стендов. Разные команды, разные процессы. Покрыть CI инструментами безопасности сложнее, но именно это дает больше эффекта — проще поймать проблему на этапе сборки, чем разбираться на проде.
Отдельная неприятная механикаФайл GitLab CI или его аналог в другой системе обычно доступен для редактирования команде разработки. Ничто не мешает разработчику временно отключить этап сканирования. Дальше что-то залетает в registry, оттуда в кластер. Но что именно не прошло проверку, уже непонятно.
Именно поэтому одного только сканирования в CI мало. Нужен еще контроль на входе в кластер — сканер образа и admission policy, которая требует выполнения определенных условий.
Проверка манифестов, а не только образовПроверять манифесты можно двумя способами.
В кластере — сканировать уже запущенные поды и применять политики к живой нагрузке.
В CI — до того как манифест поехал в кластер.
Второй способ дешевле, потому что не приходится разбирать проблемы на проде. Из инструментов у нас — Checkov для проверки конфигураций и Kyverno Playground, который позволяет проверить манифест против политики без физического запуска в режиме dry-run.
Это удобная штука. Команда безопасности говорит: «Мы запрещаем создание привилегированных подов». Ты берешь свой deployment, кладешь в песочницу вместе с политикой, нажимаешь кнопку — и он сразу показывает, соответствует манифест политике или нет. Все правки можно внести до того, как что-то доедет до кластера, и это отлично интегрируется в CI.
На стороне кластера логику admission-контроля закрывает все тот же Evolution Container Security — сервис, который управляет политиками допуска, дополнительно сканирует образы уже внутри Kubernetes-кластера и работает как второй уровень защиты после проверок в CI.
Сканирование не только до, но и после деплояЕще одна практика, которую я считаю обязательной, — фоновое сканирование уже запущенной нагрузки в кластере. Потому что образы годами не обновляются. У нас были кластеры, которые не обновлялись несколько лет, где «все работает — зачем трогать». С момента, как эти образы прошли CI, накопилось множество новых CVE, о которых на прошлом сканировании ничего не знали. Фоновое сканирование в проде — единственный способ это увидеть.
Если же нужно запустить недоверенный образ, например для проверки или временного использования, то более безопасный вариант — изоляция. Это может быть отдельная виртуальная машина без сетевого доступа либо в Kubernetes — Kata Containers, которые запускают поды внутри легких VM и дают более сильную изоляцию, чем обычные контейнеры. В Managed Kubernetes у нас также доступен плагин Kata Containers для таких сценариев.
Что с этим делать: минимальный наборСоберу в один блок то, что имеет смысл внедрять в первую очередь. По моему опыту, это тот минимум, который дает максимальный эффект за разумное время.
Сканирование образов — маст-хэв. На этапе сборки в CI, перед деплоем, и фоновое в проде. Без этого дальнейшие уровни просто не имеют смысла.
Статический анализатор кода на регулярках. OpenGrep для кода, Checkov для конфигураций и SecretScanner для поиска секретов в слоях образов. По моей оценке, снимает большое количество потенциальных проблем просто за счет того, что ловит секреты и типовые ошибки до попадания в репозиторий.
Pod Security Standard — из коробки в кубере, не требует стороннего софта, не сильно инвазивен. Большая часть корректно написанной нагрузки под него уже подходит без правок. Это закрывает еще 50% проблем.
SBOM-сканирование (Software Bill of Materials) — позволяет сопоставлять зависимости образа с базами уязвимостей. Важно, что оно помогает не просто находить CVE, а оценивать реальную эксплуатируемость. Например, уязвимый пакет под Windows не создает риска в Linux-среде, и это позволяет не зашумлять алерты лишними срабатываниями.
Validating Admission Policies — новые нативные политики Kubernetes, stable с версии 1.30. К текущему моменту выкачено уже шесть версий. Из коробки, ничего дополнительно ставить не нужно, документация подробная. Логичное следующее место, куда двигаться после PSS.
Runtime monitoring — уровень, который включается уже после деплоя. Это последний слой защиты, который ловит то, что не удалось отследить раньше. Здесь используются инструменты вроде Falco, Tetragon, Tracee или KubeArmor для мониторинга поведения процессов и аномалий в рантайме.
Как это внедрять, чтобы не разругаться со всемиГлавная проблема при внедрении безопасности — это взаимодействие команд разработки и ИБ. Для разработчиков это воспринимается так: зачем нужна дополнительная работа, в которой самое ужасное — false positives?
Типичный сценарий: раньше образы не сканировали, но теперь начали. Просканировали и нашли 120 уязвимостей. Дальше приходит команда разработки и говорит, что никто не пойдет править 120 уязвимостей, релиз на носу. И они правы. Реально критичных из этих 120 будет в лучшем случае десяток. Половина — низкая или средняя критичность. Еще половина неприменимы к конкретной конфигурации, например требуют определенной цепочки условий, которые в вашем сценарии не выполняются.
Проделывать этот путь каждый раз вручную никто не будет, и отсюда вытекает практическое правило.
Внедряйте в два этапа — сначала audit mode, потом enforcement.
Audit — это когда сканирование работает и фиксирует, но ничего не блокирует. Ставится за пару дней, ничего не ломает, разработка не страдает. Все смотрят на результаты, привыкают и начинают править. Через какое-то время переключаете в enforcement. Тогда сборка или деплой падают на критических находках. К этому моменту команда уже адаптировалась.
Дополнительная механика для рутинных обновлений — это Dependabot и его аналоги. Он видит новую версию зависимости с патчем безопасности, автоматически создает PR, прогоняет через CI. Если тесты зеленые, то мержится без участия человека. У большинства крупных проектов на GitHub это уже реализовано.
Здесь есть нюанс: автопатчинг зависимостей работает только при хорошем покрытии тестами. Если тестов мало, ты не можешь понять, что после обновления что-то сломалось, и вся автоматизация превращается в ручной труд. Придется привлекать тестировщика или разработчика к каждому мержу, и никакой автоматизации не остается.
Чек-лист: с чего начать на этой неделеЕсли материал попал в боль, приведу короткий список действий, с которых стоит начать:
Проверьте, привязаны ли ваши базовые образы к тегу или к полному SHA-хешу. Если только к тегу — переведите на хеш хотя бы для критичных внешних зависимостей.
Добавьте pre-commit-хук на поиск секретов. Opengrep или аналог, ставится за несколько минут, снимает больше проблем, чем любой сканер образов в CI.
Настройте фоновое сканирование registry. Даже если сегодня в CI сканирования нет, можно запустить пробег по существующим образам и увидеть картину.

Включите Pod Security Standard в кластере в audit-режиме. Ничего не ломает, но дает понимание, где нагрузка запущена с избыточными привилегиями.
Настройте автоматические уведомления о новых CVE в базовых зависимостях. Даже без автопатчинга — просто чтобы узнавать в тот же день, а не раз в две недели.

Проверьте, у каких сервисных аккаунтов в кластере повышенные привилегии и почему. Как показал разобранный выше кейс с GPU-оператором, именно они могут стать точкой эскалации при эксплуатации других уязвимостей.


Все эти шаги — на день-два работы, все ставятся в audit-режиме и ничего не ломают. Уже этот минимум существенно снижает риск того, что завтра у вас что-то сломается, из-за того что вы сегодня запустили контейнеры без проверки образов.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Организовал весь пентест-арсенал в одном месте: всё под рукой, офлайн и на русском | 5 | 7 | 28-06-2026 |
| 2 | [Перевод] Большинство ошибок при System Design ускользает из виду | 0 | 11.08 | 24-07-2026 |
| 3 | Playwright не спасает от флапающих тестов: разбираемся, как он ждёт на самом деле | 0 | 8.65 | 24-07-2026 |
| 4 | Один файл, одна команда: как мы упростили запуск dev-окружения с помощью Runium | 0 | 12.11 | 24-07-2026 |
| 5 | [Перевод] Должны ли библиотеки запрещать уязвимые версии зависимостей? | 0 | 8.8 | 24-07-2026 |
| 6 | Мобильная разработка за неделю #636 (22 — 28 июня) | 5 | 7 | 28-06-2026 |
| 7 | Harness engineering: как за год собрать фабрику из десятка конвейеров | 0 | 5.82 | 24-07-2026 |
| 8 | Мы вас видим | 0 | 5 | 22-06-2026 |
| 9 | Klipper: опенсорс без сообщества, или почему у вашего принтера никогда не будет тулченджера из коробки | 0 | 11.18 | 24-07-2026 |
| 10 | 460 ГБ, свободно 14: археология диска разработчика | 0 | 11.75 | 24-07-2026 |