При выявлении компрометации скомпрометированных СА был создан внутренний инцидент компании. 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 они не проверяют и верят статическому файлу с корнями. Таких файлов в одном образе мы находили по пять-шесть штук.

Примерив это на себя, мы нашли критичный сценарий атаки:

  1. Атакующий выпускает сертификат на registry.npmjs.org и registry-1.docker.io.

  2. Оказывается на пути трафика CI-раннера: анонс чужих префиксов по BGP, подмена DNS, скомпрометированный транзитный узел.

  3. Раннер по расписанию делает npm install или docker pull и получает модифицированный пакет. TLS-проверка проходит.

  4. 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 не стали: так можно уронить больше, чем защитить. До утра мы успели три вещи:

  1. Объявили фриз всех выкаток с 22:09. Почти сразу в чате заметили, что запрещать надо и сборки: образ с закладкой можно собрать сегодня и выкатить после фриза. Наутро мораторий расширили на все операции CI/CD.

  2. Подготовили текст для клиентов: что случилось, чем грозит, что сделать с ВМ, контейнерами, CI/CD и Terraform. Сначала там была ссылка на сайт исследователя с инструкциями по дистрибутивам, но коллеги справедливо заметили, что это личный сайт и неизвестно, что там будет завтра. Заменили своим вложением.

  3. Расставили приоритеты на утро: сборочная инфраструктура и раннеры, затем базовые образы, прод, инструменты, 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:

Если к первым двум адресам соединение устанавливается, клиент уязвим. Если падает с ошибкой доверия, всё в порядке.

Для образов мы сделали чекер: пайплайн принимает имя образа, запускает его и ищет корни во всех известных сторах. Упрощённая версия, которую можно запустить в любом контейнере или на хосте:

#!/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.

  • Ноды и раннеры: мы добавили метрику наличия корней в экспортер харденинга и проверку перед джобой, чтобы не ходить по парку руками.

Какие выводы мы сделали?

  1. У доверенных корней появится владелец. Собственный ca-bundle облака становится частью харденинга и раскатывается одним механизмом на хосты, базовые образы, раннеры и рантаймы. Следующий подобный инцидент с каким-нибудь CA хочется закрыть одним релизом.

  2. Сборка без прямого интернета. Весь внешний трафик сборки идёт только через прокси-репозитории артефактов, и это обеспечивается техническим контролем, а не только рекомендациями. Деплойным раннерам сетевые политики оставляют DNS, Kubernetes API и GitLab, а прямые curl и wget за бинарниками в Dockerfile отсекает проверка в CI.

  3. У каждого раннера есть владелец и зафиксированная версия образа, без этого раннер не регистрируется. В конфигах раннеров нет дефолтных образов, чтобы не было неявных зависимостей.

  4. Все кластеры получают образы только из внутреннего реестра, и это закреплено политикой Kyverno в режиме enforce. Переезд со старого реестра доводим до конца и выводим его из оборота.

  5. Исправленный образ всегда выходит с новым тегом, старый тег удаляется вместе с промежуточными пересборками под ним, внешние образы пинятся по digest. Инвентарь образов в кешах нод собирается автоматически.

  6. Фриз становится техническим рубильником в CI. Исключения даёт владелец риска по понятным критериям. Инструкции для клиентов должны публиковаться в обход замороженного CI/CD.

С чего начать у себя

Я бы шёл в таком порядке:

  1. Запустите curl на тестовую точку из CI-джоба, а не с ноутбука.

  2. Обновите ca-certificates на хостах и рестартуйте containerd и docker.

  3. Пересоберите базовые и сборочные образы и golden image, выпустите их с новыми тегами и вычистите старые образы из кешей нод и реестра. Начните со сборочных образов: первую исправленную версию придётся собрать под старым тегом, так что вычищать нужно всё, что выходило под старыми тегами.

  4. Проверьте свои Nexus, Artifactory и Harbor тем же способом, каким они ходят в upstream.

  5. Пересоберите Go-сервисы на 1.26.10+ или 1.27.3+.

  6. Найдите в образах все встроенные бандлы: certifi, pip, botocore, gRPC, NSS, JVM cacerts, обновите Node.js или включите --use-openssl-ca.

  7. Решите, что делать со всем, что было собрано с внешними зависимостями после 17 октября. В идеале стоит посмотреть и на собранное с апреля 2025 года.

Нам на всё это понадобилась неделя и десятки инженеров, и хвост мы добираем до сих пор. Спасибо всем, кто сидел в этом чате ночью. Если вы нашли у себя стор, которого нет в нашей карте, напишите в комментариях, дополним.

Ссылки