
При выявлении компрометации скомпрометированных СА был создан внутренний инцидент компании. 20 октября в 21:20 в чат инцидента прилетела ссылка на GitHub-репозиторий с закрытыми ключами восьми промежуточных удостоверяющих центров eMudhra (бренд emSign). Меньше чем через час мы объявили фриз всех выкаток в облаке. Через неделю мы всё ещё находили образы, которые считались исправленными, но несли встроенные бандлы со старыми корнями. Любой из них принял бы поддельный сертификат на любой домен, а выпустить такой сертификат с опубликованными ключами можно на обычном ноутбуке.
Последние дни я много говорил с коллегами из других компаний, и почти все отвечают одно и то же: «Браузеры уже заблокировали, сертификатов eMudhra у нас нет, нас это не касается». К сожалению, касается. Браузеры действительно защищены, потому что получают списки отзыва по своим каналам. А CI-раннеры, контейнеры, JVM, Python-скрипты и Go-бинарники доверяют тому набору корней, который лежит рядом с ними, и отзыв не проверяют.
Меня зовут Алексей Федулаев. Я руковожу командой Cloud-Native Security в MWS Cloud Platform. Расскажу, где в инфраструктуре прячутся доверенные корни, как мы их вычищали и на чём спотыкались.
Как так получилось?
Ключи не украли злоумышленники. Причиной стала ошибка в скрипте генерации ключей в апреле 2025 года: приватный ключ оказался числом от 0 до 255 вместо случайного 384-битного. Закрытые ключи восстанавливаются прямо из публичных сертификатов, по словам исследователей, за доли секунды.
Затронуты восемь промежуточных CA:
TLS: EM DV TLS CA — G3B/G3C, EM OV TLS CA — G3B/G3C, EM EV TLS CA — G3B/G3C. Цепочка идёт через emSign Root TLS CA — G3 к корню emSign ECC Root CA — G3.
Подпись кода: EM OV CS CA — G3B/G3C. Цепочка идёт к корню emSign Root CS CA — G3, которому доверяет Microsoft.
У каждого из восьми есть кросс-подпись от Sectigo под старым корнем AAA Certificate Services (в бандлах он часто лежит как Comodo AAA Services root). Про этот второй путь к доверию легко забыть.
Дата (2026) | Событие |
|---|---|
17 октября | Исследователь сообщает в Chrome и Mozilla, в тот же вечер браузеры блокируют сертификаты через CRLSet и OneCRL |
18–19 октября | Отзыв в CCADB с причиной cACompromise, Sectigo отзывает кросс-подписи, Apple блокирует их |
19–20 октября | Выходят Go 1.27.3 и 1.26.10 с жёстким блок-листом восьми ключей (CVE-2026-111943), обновления ca-certificates в Ubuntu, Debian и Alpine, certifi 2026.10.19, Microsoft блокирует их |
20 октября, вечер | Закрытые ключи лежат на GitHub, исследователь выпускает себе валидный сертификат на поддомен google.com. Mozilla удаляет emSign ECC Root CA — G3 из NSS |
21 октября | Выходят Node.js 22.23.4, 24.22.1, 26.12.1, Oracle и Azul выпускают исправленные сборки JDK |
23 октября | Для JVM список блокируемых отпечатков вырастает с 16 до 48 |
27 октября | Chrome удаляет emSign ECC Root CA — G3 из своего root store |
Браузеры закрылись за несколько часов. Для остального софта исправления выходили всю неделю, и каждое приходится применять руками: обновить пакет, пересобрать бинарник или образ, перезапустить процесс.
Почему это касается вас, даже если у вас нет ни одного сертификата eMudhra
А проблема в том, что если процесс доверяет emSign ECC Root CA — G3 или AAA Certificate Services, он примет поддельный сертификат на registry-1.docker.io, registry.npmjs.org, proxy.golang.org, github.com или на ваш собственный API. Выпустить такой сертификат сейчас может любой, кто скачал опубликованные ключи. Чей сертификат стоит на вашем сайте, здесь роли не играет.
Браузеры защищены: блокировки к ним приходят через CRLSet и OneCRL. Кроме того, Chrome требует Certificate Transparency, а до блокировки ни одного сертификата от этих CA в CT-логах не было, так что через браузер атака не пройдёт.
Почти всё остальное не защищено: curl и wget, docker и containerd, pip, npm, Go-бинарники, JVM, gRPC, Terraform-провайдеры, агенты мониторинга. Отзыв и CT они не проверяют и верят статическому файлу с корнями. Таких файлов в одном образе мы находили по пять-шесть штук.
Примерив это на себя, мы нашли критичный сценарий атаки:
Атакующий выпускает сертификат на registry.npmjs.org и registry-1.docker.io.
Оказывается на пути трафика CI-раннера: анонс чужих префиксов по BGP, подмена DNS, скомпрометированный транзитный узел.
Раннер по расписанию делает npm install или docker pull и получает модифицированный пакет. TLS-проверка проходит.
postinstall выполняется на раннере. У раннера есть секреты CI, права на push в реестр и нередко креды к проду. Дальше закладка едет в образ и расходится по всем стендам.
В чате инцидента сразу возразили: для MITM нужна позиция в сети, а её ещё надо добыть. Это правда, и вероятность конкретной атаки на конкретный раннер невысока. Мы всё равно решили считать по последствиям. Один заражённый раннер даёт доступ к сборкам, секретам и проду всего облака. И никто не знает, сколько людей восстановили эти ключи до того, как их выложили: сертификаты лежат в открытом доступе с апреля 2025 года.
Немного матчасти про доверенне корни
За неделю мы насчитали десятки хранилищ доверенных корней, и у каждого свой способ обновления. Обновление ca-certificates на хостах закрывает только верхний слой.
Из бандлов нужно убрать два корня: emSign ECC Root CA — G3 и AAA Certificate Services (он же Comodo AAA Services root). Мы проверили на 12 тестовых цепочках: после снятия этих двух отвергаются все. emSign Root CA — C1, emSign ECC Root CA — C3 и emSign Root CA — G1 к инциденту не относятся. Имейте в виду, что без AAA могут перестать строиться старые легитимные цепочки Sectigo. Для JVM есть путь без удаления корней: блок-лист отпечатков, см. таблицу рантаймов ниже.
Инфраструктура
Слой | Где доверие | Что делать | Подвох |
|---|---|---|---|
ОС хостов | /etc/ssl/certs/ca-certificates.crt, /etc/pki/ca-trust | Обновить ca-certificates (Ubuntu 22.04: 20260816ubuntu0.22.04.1) или снять два корня вручную | Локальный прокси репозиториев может отдавать старую версию пакета |
containerd, dockerd | Корни читаются при старте и держатся в памяти | Рестарт после обновления стора | Без рестарта docker pull продолжает доверять старому |
Ноды вне конфиг-менеджмента | Ноды control plane, балансировщиков, новые хосты из наливки | Пересобрать golden image, добавить обновление в наливку | Новые хосты приезжают уже уязвимыми |
CI-раннеры (ВМ, Kubernetes, автомасштабируемые) | Хост раннера, образы runner и helper, golden image и стор образа каждого джоба | Обновить пакет на хосте, пересобрать образы runner и helper и golden image, смонтировать в джобы бандл хоста | Джоб всё равно идёт через стор своего образа |
Базовые и сборочные образы | Стор ОС образа + сторы рантаймов | Пересобрать по цепочке все производные, выпустить с новым тегом | Пересборка на непропатченном раннере бессмысленна. Первую исправленную версию сборочного образа приходится собирать под старым тегом |
Nexus | Truststore JVM (свой или cacerts) | Удалить корни через keytool, рестарт | Уже закешированные артефакты нужно сверить |
Harbor | У каждого микросервиса свой стор (core, jobservice, registry, registryctl, trivy-adapter, exporter) | Слой поверх официальных образов, удаляющий корни | caBundleSecretName в чарте только добавляет корни, убрать им ничего нельзя |
GitLab | Образы компонентов (pull mirroring, вебхуки, интеграции) | Пересобрать образы | GitLab сам ходит наружу по HTTPS |
Service mesh, ингрессы | Образы Istio, Envoy | Пересобрать, затем рестартовать ингрессы | Сайдкар подставляется автоматически, команды его не контролируют |
Legacy-реестры и proxy-cache | Старые реестры и зеркала Docker Hub | Инвентаризировать, перевести на свой реестр | Неизвестно, чему доверяет сам прокси |
PaaS-воркеры, БД | Образы менеджед-сервисов | Обновлять в maintenance window | Регламент выкатки образов БД идёт неделями |
Windows и macOS | Системное хранилище; на macOS /etc/ssl/cert.pem для curl | Удалить корни вручную | Системный curl на macOS не проверяет отзыв |
Клиентские ВМ и Terraform | Стор гостевой ОС и раннеров клиента | Рассылка с инструкцией, обновлённые публичные образы | За ОС отвечает клиент, мы можем только предупредить |
Рантаймы и библиотеки
Стек | Где доверие | Исправление |
|---|---|---|
Go | Системный стор + блок-лист в crypto/x509; x509roots/fallback вшивает корни в бинарник | Go 1.27.3 / 1.26.10 и пересборка. Уже собранные бинарники не защищены, Go 1.25 и более ранние исправления не получат |
JVM | cacerts (JKS) и blocked.certs | Исправленный JDK (Oracle и Azul с 21.10) или 48 отпечатков в blocked.certs. Deb-пакеты Temurin строят cacerts из стора ОС при установке |
Python | certifi, копия certifi внутри pip (pip/_vendor/certifi), botocore/cacert.pem, boto/cacerts/cacerts.txt | certifi 2026.10.19, pip 26.2.2 или симлинки на системный бандл; REQUESTS_CA_BUNDLE, AWS_CA_BUNDLE. Старый boto2 читает только свой файл |
Node.js | Корни вшиты в бинарник | Node.js 22.23.4, 24.22.1, 26.12.1 или NODE_OPTIONS=--use-openssl-ca |
NSS | libnssckbi.so со встроенным списком корней | NSS 3.131.1 или подмена на p11-kit-trust.so |
gRPC | /usr/share/grpc/roots.pem | Обновить пакет или симлинк на системный бандл |
Rust, PHP и др. | webpki-roots, composer/ca-bundle | webpki-roots 1.0.10, ca-bundle 1.4.16 / 1.6.0 |
Подпись кода | emSign Root CS CA — G3 в доверенных у Microsoft | Отдельная проверка систем, которые проверяют подписи |
Как мы всё это исправляли?
Ниже хронология из чата инцидента. За неделю в нём набралось больше тысячи сообщений от десятков команд: инфраструктуры, платформы сборки, Kubernetes, хранилищ, сети, PaaS, поддержки и продуктов.
День 0, 20 октября: оценка и фриз
Инцидент мы завели в 19:04 с низким приоритетом. На тот момент история выглядела как демонстрация от исследователей по безопасности: есть сайт с тестовыми сертификатами, есть сертификат на поддомен google.com, браузеры уже заблокировали. План был такой: утром раздать командам задачи по удалению корней.
В 21:20 в чат пришла ссылка на репозиторий с опубликованными закрытыми ключами, и тут стало понятно, что до утра ждать нельзя. Дальше спорили, что делать ночью. Будить все команды и за ночь перекатывать облако от железа до control plane не стали: так можно уронить больше, чем защитить. До утра мы успели три вещи:
Объявили фриз всех выкаток с 22:09. Почти сразу в чате заметили, что запрещать надо и сборки: образ с закладкой можно собрать сегодня и выкатить после фриза. Наутро мораторий расширили на все операции CI/CD.
Подготовили текст для клиентов: что случилось, чем грозит, что сделать с ВМ, контейнерами, CI/CD и Terraform. Сначала там была ссылка на сайт исследователя с инструкциями по дистрибутивам, но коллеги справедливо заметили, что это личный сайт и неизвестно, что там будет завтра. Заменили своим вложением.
Расставили приоритеты на утро: сборочная инфраструктура и раннеры, затем базовые образы, прод, инструменты, preprod, dev.
Воркеры PaaS и CDN ночью не трогали. Воркеры можно менять только в maintenance window, а для подмены origin у CDN атакующему нужно ещё и увести трафик.
День 1, 21 октября: раннеры, Nexus, Harbor
Утро началось с инвентаризации. Раннеров оказалось около шестисот, шести типов: общие сборочные пулы на ВМ, автомасштабируемые в облаке, командные сборочные, командные деплойные в Kubernetes, деплойные на своих ВМ и раннеры для Salt. У нас есть команда платформы разработки (Development Platform), и управляет она тремя из них, остальные принадлежат командам.
На сборочных раннерах на VM через Ansible обновили ca-certificates и поправили конфиг gitlab-runner, чтобы контейнеры джобов брали корни с хоста (конфиг есть ниже, в граблях). Первая же попытка на Ubuntu 22.04 ответила ca-certificates is already the newest version, а тестовая точка продолжала открываться. Выяснилось, что завис наш прокси-репозиторий и отдавал старый индекс пакетов. Автомасштабируемые раннеры просто остановили до пересборки golden image.
С Nexus разобрались быстро: нашли truststore JVM, удалили два корня через keytool и перезапустили.
С Harbor провозились весь день и вечер. Как сказали в чате, он написан «в лучших традициях опенсорса»: несколько сервисов на Photon OS и у каждого свой стор. Параметр caBundleSecretName в helm-чарте только добавляет корни, а стартовый скрипт при каждом запуске возвращает базовый бандл на место. В итоге сделали просто: взяли официальные образы и добавили слой, который удаляет корни. Ещё одна деталь: свои же образы Harbor из себя не вытянет, поэтому их пришлось выкладывать в отдельный bootstrap-реестр.
На хостах обновлённый ca-certificates раскатили с рестартом containerd. Перед этим отдельно убедились, что рестарт демона не убивает работающие поды.
День 2, 22 октября: базовые образы и частичное снятие фриза
В полночь проверка перед сборкой базового образа показала emSign ECC Root CA — G3 на раннере, который считался исправленным. Причина: две группы раннеров Ansible только проверил в режиме --check, но не задеплоил. Пакет и недоступность тестового сайта проверку прошли, а конфиг раннера остался старым. К двум часам ночи выпустили новые базовые образы Ubuntu 22.04 и 24.04 и Go-платформу на Go 1.26.10.
Утром посыпались сборки на Java: монтирование /etc/ssl/certs с хоста ломает truststore Temurin (подробности в граблях). Мы оставили монтирование как временную схему на несколько недель, а в платформу сборки добавили пересборку cacerts.
К вечеру платформа сборки выпустила все рантаймы и сборочные образы, и в 20:30 мы сняли фриз для проектов на ней. В анонсе был список обязательных версий образов и ссылка на чекер. Остальные команды сначала обновляют свои образы и прогоняют их через чекер, потом выкатываются.
Последним в этот день был кеш.Тут мы попали в замкнутый круг. Чтобы собрать базовый образ с новым тегом, нужен уже исправленный сборочный образ, а он сам собирается из предыдущей версии. Поэтому сначала мы выпустили исправленные образы под прежними тегами, включая дефолтные образы раннеров. В реестре под старым тегом оказался исправленный образ, а в кешах нод остался уязвимый с тем же тегом, и по тегу их не отличить. imagePullPolicy: Always на весь парк отвергли: тысячи compute-нод при массовом рестарте положили бы реестр. Вместо этого выпустили образы с новыми тегами и точечно вычистили старые со всех доступных хостов. Обход занял около четырёх часов и задел больше двух тысяч хостов, старый образ нашёлся на шести.
Дни 3–8: вторая волна и длинный хвост
Как только команды стали прогонять свои образы через чекер, вылезли сторы, о которых мы не думали. Даже на рекомендованных базовых образах находились libnssckbi.so, certifi внутри pip, botocore/cacert.pem, boto/cacerts/cacerts.txt и /usr/share/grpc/roots.pem. Каждый из этих файлов приезжает с пакетом и не зависит от ca-certificates.
23 октября список отпечатков для JVM вырос с 16 до 48, и команды спросили, не начинать ли всё заново. Не нужно. Java сравнивает сертификат по отпечатку, а один и тот же сертификат можно перекодировать так, что подпись останется валидной, а отпечаток изменится. Отсюда 16 исходных и 32 перекодированных. Если в образе уже нет двух корней, цепочка не строится при любой кодировке.
Грабли, на которые мы наступили
1. Раннер обновлён, а джоб нет
Джоб выполняется в контейнере из image:, и TLS внутри него идёт по стору этого образа. Обновив пакет на хосте, вы защитили только docker pull самого раннера. На переходный период мы пробросили стор хоста во все контейнеры:
[[runners]] environment = [ "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt", "REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt", "NODE_OPTIONS=--use-openssl-ca", ] [runners.docker] volumes = [ "/cache", "/etc/ssl/certs:/etc/ssl/certs:ro", "/usr/share/ca-certificates:/usr/share/ca-certificates:ro", ]
gitlab-runner перечитывает config.toml сам, рестарт не нужен.
2. Монтировать только /usr/share/ca-certificates бесполезно
Из этих файлов update-ca-certificates только собирает бандл. При проверке TLS их никто не читает, а готовый /etc/ssl/certs/ca-certificates.crt в образе уже содержит оба корня. Проверили на практике: curl на тестовую точку спокойно получает 200.
3. Монтирование /etc/ssl/certs ломает Java
В Debian и Ubuntu cacerts у Temurin — симлинк в /etc/ssl/certs/adoptium/cacerts. Когда каталог перекрыт стором хоста, симлинк смотрит в пустоту, и джоб падает с the trustAnchors parameter must be non-empty. Лечится пересборкой cacerts из смонтированного стора, так же как это делает пакет при установке:
J=/usr/lib/jvm/temurin-21-jdk-amd64/lib/security/cacerts rm -f "$J" trust extract --format=java-cacerts "$J"
Проще сразу монтировать не каталог, а только файл бандла: "/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro". Тогда Java не ломается. От граблей № 4 это не спасает: заменить смонтированный файл update-ca-certificates тоже не сможет.
4. Оно же ломает сборки, которые добавляют свои корни
Многие образы кладут корпоративные CA и запускают update-ca-certificates. На read-only каталоге он падает с Failed to open temporary file /etc/ssl/certs/bundleXXXXXX. При монтировании одного файла ошибка другая: заменить смонтированный файл нельзя и mv падает с Device or resource busy. Поэтому монтирование у нас временное и снимать его будем вместе с командой платформы сборки. Если просто выключить его на всех раннерах, разом упадут сотни пайплайнов.
5. Прокси репозиториев тоже кеширует
apt честно сообщил, что стоит последняя версия ca-certificates. Последняя с точки зрения зависшего кеша Nexus. Сверяйте версию с бюллетенем дистрибутива, а не с выводом apt.
6. Сurl из пода ничего не говорит о Go-процессе
В Harbor curl уже падал с ошибкой доверия, а сам Harbor продолжал ходить на скомпрометированный адрес. Проверяйте самим сервисом. Для Harbor это тестовый эндпоинт и proxy-cache проект на скомпрометированный адрес, а в логах core должна появиться x509: certificate signed by unknown authority.
7. Пересборка под тем же тегом ничего не даёт
При IfNotPresent нода спокойно запустит старую версию из кеша. Исправленные образы мы выпускаем с новым тегом, а старые теги удаляем из реестра. Совсем без пересборки под старым тегом не обойтись: первую исправленную версию сборочного образа приходится собирать под прежним тегом (см. День 2). Поэтому старые теги вычищаем и из реестра, и из кешей нод, даже если в реестре под ними уже лежит исправленный образ. Необновлённые пайплайны упадут с понятной ошибкой, и это лучше, чем тихо уехать с уязвимым образом. По ходу дела выяснилось, что фактические дефолтные образы раннеров были совсем не те, которые мы патчили первыми. И ещё один момент: Kubernetes executor не пишет в лог digest образа, так что задним числом не всегда понятно, что именно запускалось.
8. У Python корни лежат в трёх-четырёх местах
requests и urllib3 берут certifi.where(). botocore берёт certifi, а если его нет, то свой cacert.pem. Старый boto2 (на нём, например, до сих пор работает часть инструментов бэкапа в S3) всегда читает свой файл и на переменные окружения не смотрит. Надёжнее всего заменить такие файлы симлинками на системный бандл (pip из пакета Debian и Ubuntu трогать не нужно: его вендоренный certifi и так смотрит в системный бандл), например для botocore:
RUN ln -sf /etc/ssl/certs/ca-certificates.crt "$(python3 -c 'import botocore, os; print(os.path.join(os.path.dirname(botocore.__file__), "cacert.pem"))')"
Как проверить, уязвимы ли вы?
Фикс мы засчитывали, только когда сервис реально отвергал скомпрометированную цепочку. То, что файла больше нет в /etc/ssl/certs, ещё ничего не доказывает.
Исследователь держит тестовые точки с сертификатами от скомпрометированных CA:
https://dv-g3b-emudhra.we-have-a-problem.com: цепочка через emSign ECC Root CA — G3;
https://dv-g3b-aaa-emudhra.we-have-a-problem.com: через кросс-подпись AAA Certificate Services;
https://emudhra.we-have-a-problem.com: контрольная точка, она должна открываться.
Если к первым двум адресам соединение устанавливается, клиент уязвим. Если падает с ошибкой доверия, всё в порядке.
Для образов мы сделали чекер: пайплайн принимает имя образа, запускает его и ищет корни во всех известных сторах. Упрощённая версия, которую можно запустить в любом контейнере или на хосте:
#!/usr/bin/env bash # Ищет доверие к emSign ECC Root CA — G3 и AAA Certificate Services BAD='emSign ECC Root CA — G3|AAA Certificate Services' rc=0 # 1. Реальная проверка TLS for u in https://dv-g3b-emudhra.we-have-a-problem.com \ https://dv-g3b-aaa-emudhra.we-have-a-problem.com; do if curl -s -o /dev/null --max-time 10 "$u"; then echo "VULN curl доверяет $u"; rc=1 else echo "OK curl отверг $u" fi done curl -s -o /dev/null --max-time 10 https://emudhra.we-have-a-problem.com \ || echo "WARN контрольная точка недоступна, проверка TLS недостоверна" # 2. PEM-бандлы: системный, certifi, pip, botocore, boto, gRPC и другие find / -xdev -type f \( -name '*.pem' -o -name '*.crt' -o -name 'cacerts.txt' -o -name 'ca-bundle*' \) 2>/dev/null | while read -r f; do awk '/BEGIN CERT/{c=""} {c=c $0 "\n"} /END CERT/{print c | "openssl x509 -noout -subject"; close("openssl x509 -noout -subject")}' "$f" 2>/dev/null \ | grep -Eq "$BAD" && { echo "VULN $f"; rc=1; } done # 3. Встроенные корни NSS find / -xdev -name libnssckbi.so 2>/dev/null | while read -r so; do strings "$so" | grep -Eq "$BAD" && echo "VULN $so" done # 4. JVM truststore if command -v keytool >/dev/null; then find / -xdev -name cacerts -path '*security*' 2>/dev/null | while read -r ks; do keytool -list -v -keystore "$ks" -storepass changeit 2>/dev/null \ | grep -Eq "Owner:.*($BAD)" && echo "VULN $ks" done fi exit $rc
Учтите, что упрощённый чекер ищет сами корни: для JVM он покажет VULN и там, где проблема закрыта через blocked.certs, а на pip из пакета Debian или Ubuntu, сработает ложно: файл pip/_vendor/certifi/cacert.pem там есть, но Debian патчит pip так, что он берёт системный бандл. Кроме образов, стоит отдельно посмотреть на четыре вещи.
Версию тулчейна, которым собраны Go-бинарники: её покажет go version -m ./your-binary. Нужна 1.26.10 или 1.27.3 и выше. Заодно поищите в зависимостях golang.org/x/crypto/x509roots/fallback.
JVM-сервисы после рестарта: keytool -printcert -sslserver dv-g3b-emudhra.we-have-a-problem.com, запущенный тем же JRE, что и сервис.
Harbor: создайте эндпоинт на тестовую точку с включённым Verify Remote Cert. TEST CONNECTION должен падать, pull через proxy-cache проект тоже, а в логах core должна появиться x509: certificate signed by unknown authority.
Ноды и раннеры: мы добавили метрику наличия корней в экспортер харденинга и проверку перед джобой, чтобы не ходить по парку руками.
Какие выводы мы сделали?
У доверенных корней появится владелец. Собственный ca-bundle облака становится частью харденинга и раскатывается одним механизмом на хосты, базовые образы, раннеры и рантаймы. Следующий подобный инцидент с каким-нибудь CA хочется закрыть одним релизом.
Сборка без прямого интернета. Весь внешний трафик сборки идёт только через прокси-репозитории артефактов, и это обеспечивается техническим контролем, а не только рекомендациями. Деплойным раннерам сетевые политики оставляют DNS, Kubernetes API и GitLab, а прямые curl и wget за бинарниками в Dockerfile отсекает проверка в CI.
У каждого раннера есть владелец и зафиксированная версия образа, без этого раннер не регистрируется. В конфигах раннеров нет дефолтных образов, чтобы не было неявных зависимостей.
Все кластеры получают образы только из внутреннего реестра, и это закреплено политикой Kyverno в режиме enforce. Переезд со старого реестра доводим до конца и выводим его из оборота.
Исправленный образ всегда выходит с новым тегом, старый тег удаляется вместе с промежуточными пересборками под ним, внешние образы пинятся по digest. Инвентарь образов в кешах нод собирается автоматически.
Фриз становится техническим рубильником в CI. Исключения даёт владелец риска по понятным критериям. Инструкции для клиентов должны публиковаться в обход замороженного CI/CD.
С чего начать у себя
Я бы шёл в таком порядке:
Запустите curl на тестовую точку из CI-джоба, а не с ноутбука.
Обновите ca-certificates на хостах и рестартуйте containerd и docker.
Пересоберите базовые и сборочные образы и golden image, выпустите их с новыми тегами и вычистите старые образы из кешей нод и реестра. Начните со сборочных образов: первую исправленную версию придётся собрать под старым тегом, так что вычищать нужно всё, что выходило под старыми тегами.
Проверьте свои Nexus, Artifactory и Harbor тем же способом, каким они ходят в upstream.
Пересоберите Go-сервисы на 1.26.10+ или 1.27.3+.
Найдите в образах все встроенные бандлы: certifi, pip, botocore, gRPC, NSS, JVM cacerts, обновите Node.js или включите --use-openssl-ca.
Решите, что делать со всем, что было собрано с внешними зависимостями после 17 октября. В идеале стоит посмотреть и на собранное с апреля 2025 года.
Нам на всё это понадобилась неделя и десятки инженеров, и хвост мы добираем до сих пор. Спасибо всем, кто сидел в этом чате ночью. Если вы нашли у себя стор, которого нет в нашей карте, напишите в комментариях, дополним.

