MWS Cloud рассказала, как после публикации приватных ключей восьми промежуточных центров сертификации eMudhra удаляла связанные доверенные корни из облачной инфраструктуры. Практический урок для команд CI прост и не слишком приятен: обновлённый пакет сертификатов на хосте ещё не означает, что контейнеры, рантаймы и кэшированные образы перестали доверять скомпрометированным центрам.

По описанию компании, инцидент стал срочным вечером 20 октября, когда в чат команды прислали ссылку на репозиторий GitHub с закрытыми ключами. MWS объявила заморозку развёртываний, а позднее распространила ограничения и на CI/CD. Работа заняла неделю: доверие пришлось проверять на разных уровнях — от узлов и базовых образов до хранилищ JVM и отдельных бандлов сертификатов в библиотеках.

По материалу MWS, первопричиной стала ошибка в скрипте генерации ключей: в апреле 2025 года приватный ключ оказался числом от 0 до 255, а не случайным значением длиной 384 бит. Исследователи смогли восстановить ключи по публичным сертификатам. Под угрозой оказались восемь промежуточных CA бренда emSign, использовавшихся для TLS и подписи кода.

Почему блокировки в браузерах не решают проблему

Браузеры получили блокировки через собственные механизмы, в частности CRLSet и OneCRL; для Chrome важную роль также играет Certificate Transparency. Но многие инструменты разработки не используют те же каналы отзыва. Они проверяют сертификат по локальному набору доверенных корней, который может находиться в системе, контейнере или быть встроен в исполняемый файл.

Поэтому отсутствие сертификатов eMudhra на собственном сайте — недостаточная проверка. Если клиент доверяет emSign ECC Root CA — G3 или AAA Certificate Services, он может принять цепочку, построенную через скомпрометированный ключ. Среди потенциально затронутых компонентов MWS называет curl и wget, Docker и containerd, npm, pip, Go-бинарники, JVM, gRPC и провайдеры Terraform. В зависимости от места такого клиента в цепочке CI это может привести к подмене зависимости при загрузке.

Сценарий, который рассматривала команда, начинается с перехвата трафика раннера к реестру или репозиторию пакетов. Если TLS-клиент доверяет скомпрометированному корню, он может принять поддельный сертификат; затем раннер загружает изменённый пакет, а скрипт установки выполняется в среде сборки. MWS отдельно отмечает, что атакующему сначала нужно занять позицию на сетевом пути. Однако команда оценивала не только вероятность, но и возможные последствия для секретов и последующих сборок.

Вот где заканчивается удобная модель «пакет сертификатов обновлён — значит, всё в порядке»: один образ контейнера может содержать пять или шесть независимых хранилищ доверия. Ручной инвентаризации хватает примерно до первого сервиса, который читает другой файл.

Где искали доверие и что меняли

Команда определила два корня, которые нужно удалить из соответствующих бандлов: emSign ECC Root CA — G3 и AAA Certificate Services, также известный как корень Comodo AAA Services. Второй путь доверия проходил через кросс-подписи Sectigo, поэтому проверить только основную цепочку было недостаточно. MWS сообщает, что после удаления корней проверила 12 тестовых цепочек; при этом предупреждает, что без AAA некоторые старые легитимные цепочки Sectigo могут перестать строиться.

Обновление ca-certificates на узлах было лишь первым шагом. containerd и dockerd хранят сертификаты в памяти после запуска, поэтому после изменения системного хранилища их перезапускали. Новые узлы тоже требовали исправлений: команда обновляла golden image и процедуры их подготовки, иначе хост мог появиться уже со старым набором корней.

Больше всего работы было связано с CI. В MWS насчитали около шестисот раннеров шести типов, часть из которых принадлежала отдельным командам. Обновление хоста не исправляло контейнер джобы: его TLS-запросы продолжали использовать хранилище образа. В качестве временной меры в конфигурацию раннеров добавили передачу системного бандла хоста в контейнеры и настроили переменные для отдельных клиентов.

У этого обходного решения есть ограничения. Смонтированный каталог сертификатов конфликтовал с расположением cacerts в Temurin на Debian и Ubuntu: символическая ссылка оказывалась без целевого файла, и процессы Java завершались с ошибкой. Монтирование только файла бандла не ломало Java таким же образом, но мешало образам обновлять хранилище или добавлять собственные корпоративные CA. Поэтому MWS оставила этот способ временным и параллельно добавила сборку Java-хранилища из актуального бандла.

Обнаружились и менее очевидные копии корней. Среди них — certifi в Python и вендорная копия в pip, собственный файл CA в botocore и boto2, системный файл корней gRPC, а также NSS и Java cacerts. Иными словами, исправление системного пакета само по себе не меняет файлы, поставляемые вместе с библиотекой или рантаймом. Для части стеков нужны обновлённые версии, для других — настройка использования системного бандла или отдельное обновление хранилища.

В материале приведены конкретные версии исправлений: Go 1.27.3 и 1.26.10, Node.js 22.23.4, 24.22.1 и 26.12.1, certifi 2026.10.19 и NSS 3.131.1. Для JVM описаны два варианта: исправленный JDK или блокировка отпечатков сертификатов через blocked.certs. Уже собранные Go-бинарники требовали повторной сборки, а для ранних веток Go исправления, согласно материалу, не планировались.

Образы, теги и проверка результата

Пересборка образов создала отдельную проблему зависимостей: исправленный образ для сборки сам поначалу требовал исправленной базы. Команда выпустила первые исправленные образы под прежними тегами, а затем — под новыми. Из-за кэшей узлов тег не гарантировал, что запустится именно новое содержимое. MWS отказалась массово включать imagePullPolicy: Always, чтобы не создавать одновременную нагрузку на реестр, и вместо этого точечно очищала кэши. Проверка охватила более двух тысяч хостов; старый образ нашли на шести.

Для проверки команда не полагалась на то, что файл исчез из известного каталога. Исправление засчитывали, когда конкретный сервис отклонял тестовую TLS-цепочку. Для образов использовали чекер, который запускал образ и искал нежелательные корни в известных хранилищах. При этом авторы предупреждают: упрощённый поиск может дать ложное срабатывание, например на JVM, где проблему решает список отпечатков, или на пакете pip в Debian и Ubuntu, который использует системный бандл.

В итоге MWS связывает долгосрочное исправление с централизованным управлением доверенными корнями, инвентаризацией образов и ответственным за каждый раннер. Команда также описывает переход сборок на прокси-репозитории, внутренний реестр для кластеров и использование новых тегов для исправленных образов. Для быстрой первичной проверки авторы советуют запускать тест TLS именно из CI-джобы: состояние ноутбука не показывает, какому хранилищу доверяет контейнер сборки.