Проверка TLS-соединения при загрузке зависимостей: как убедиться, что пакеты приходят из доверенного источника

Когда сборщик проекта, менеджер пакетов или CI-агент скачивает зависимости, он почти всегда делает это по HTTPS. Проверка TLS-соединения — это то, что отличает загрузку из настоящего реестра пакетов от загрузки через подставной сервер злоумышленника. Если проверка отключена или настроена небрежно, атакующий в той же сети может подменить пакет, и вредоносный код окажется в вашей сборке. Главный принцип: TLS-проверку нельзя отключать ради «чтобы заработало» — вместо этого нужно разобраться, почему сертификат не проходит проверку, и устранить причину.

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

Что именно проверяется при TLS-рукопожатии

TLS (Transport Layer Security) — протокол шифрования, который устанавливается до того, как клиент отправит первый байт HTTP-запроса. При загрузке зависимости клиент (pip, npm, Maven, Gradle, NuGet, Go modules, Docker и другие) выполняет рукопожатие с сервером реестра и проверяет несколько вещей:

  • Цепочку сертификатов. Сертификат сервера должен быть подписан центром сертификации, которому доверяет клиент, и вся цепочка от серверного сертификата до корневого должна быть полной и валидной.
  • Срок действия. Просроченный или ещё не вступивший в силу сертификат приводит к отказу в соединении.
  • Соответствие имени хоста. Имя в сертификате должно совпадать с доменом, к которому обращается клиент. Обращение к реестру по прямому IP-адресу почти всегда провалит эту проверку.
  • Отзыв сертификата. Клиент может проверить по CRL или OCSP, не отозван ли сертификат. Реализация этой проверки зависит от инструмента и платформы.

Если любая из проверок не пройдена, соединение разрывается до передачи данных. Это и есть защита от атак «человек посередине», при которых подменяется и содержимое пакета, и, потенциально, файлы блокировок и контрольные суммы.

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

Типичные ошибки и их причины

Ошибки проверки TLS при загрузке зависимостей сводятся к небольшому числу сценариев. У каждого — своя причина и правильное решение.

Симптом Вероятная причина Правильное решение
«Self-signed certificate» или «certificate verify failed» Трафик перехватывает корпоративный прокси с собственным сертификатом, которого нет в доверенных Добавить корневой сертификат организации в системное хранилище доверия
«Unable to get local issuer certificate» Неполная цепочка: сервер отдаёт не все промежуточные сертификаты, или на клиенте устаревший набор корневых Обновить корневые сертификаты ОС/рантайма; на стороне сервера — отдавать полную цепочку
«Hostname mismatch» Обращение по IP, по внутреннему имени, которого нет в сертификате, или опечатка в URL реестра Использовать имя хоста из сертификата или выпустить сертификат с нужным SAN
«Certificate has expired» Просрочен сертификат сервера или локального прокси Сообщить владельцу инфраструктуры; на своей стороне — обновить сертификат прокси
Ошибка появляется только в Docker-контейнере или CI В образе нет корпоративного корневого сертификата, который есть на рабочей машине Смонтировать или скопировать сертификат в образ и обновить хранилище доверия
Ошибка появилась внезапно у всех Смена сертификата реестра, ротация корневых, обновление прокси Проверить сертификат сервера внешним инструментом, дождаться обновления или обновить локальное доверие

Отдельно стоит сказать про Node.js: он по умолчанию использует собственное встроенное хранилище корневых сертификатов, а не системное. Поэтому корпоративный сертификат, добавленный в доверенные ОС, npm и Node могут не увидеть — его нужно передать через переменную окружения NODE_EXTRA_CA_CERTS с путём к файлу сертификата.

Диагностика: с чего начать

Когда загрузка зависимостей падает с ошибкой TLS, действуйте в следующем порядке — от общего к частному:

  1. Определите, кто на самом деле отвечает на соединение. Выполните запрос к реестру утилитой вроде OpenSSL (openssl s_client -connect host:443 -servername host) и посмотрите, чей сертификат возвращается. Если там сертификат вашей компании, а не реестра — трафик проходит через перехватывающий прокси.
  2. Сравните окружения. Если на локальной машине загрузка работает, а в CI или контейнере нет — почти наверняка разница в наборе доверенных сертификатов. Сравните содержимое хранилищ доверия.
  3. Проверьте срок и цепочку. В выводе OpenSSL видно, истёк ли сертификат и все ли промежуточные звенья отдаёт сервер. Неполная цепочка — частая причина ошибок, которые проявляются не у всех клиентов.
  4. Проверьте имя хоста. Убедитесь, что в конфигурации пакетного менеджера указан тот же домен, что и в сертификате, без опечаток и без прямого IP.
  5. Вспомните, что менялось. Обновление ОС, рантайма, прокси, антивируса с проверкой HTTPS-трафика или смена корпоративного сертификата — типичные триггеры внезапных сбоев.

Как правильно настроить доверие к корпоративному сертификату

Если организация перехватывает HTTPS-трафик (это распространённая практика для контроля и фильтрации), все соединения подписываются сертификатом прокси. Чтобы пакетные менеджеры работали, этот корневой сертификат должен быть доверенным в каждом окружении, где идёт сборка.

  • Linux: скопируйте сертификат в каталог системного доверия (например, /usr/local/share/ca-certificates/ в Debian-подобных системах) и выполните обновление хранилища (update-ca-certificates). Для Python-инструментов дополнительно может понадобиться переменная REQUESTS_CA_BUNDLE или PIP_CERT с путём к файлу.
  • Node.js и npm: задайте NODE_EXTRA_CA_CERTS на путь к PEM-файлу. Для npm также существует конфигурация cafile.
  • Java (Maven, Gradle): сертификат импортируется в хранилище доверия JVM (cacerts) утилитой keytool. У каждой версии JVM своё хранилище — импортировать нужно в то, которое реально используется сборкой.
  • Git: если зависимости подтягиваются из git-репозиториев, укажите путь к пакету сертификатов через http.sslCAInfo.
  • Docker: сертификат добавляется в образ на этапе сборки, а не монтируется только в запущенный контейнер — иначе сборка внутри контейнера снова упадёт.

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

Приватные реестры и самоподписанные сертификаты

Внутренние реестры пакетов (Artifactory, Nexus, GitLab Package Registry и аналоги) часто работают на самоподписанных или внутренних сертификатах. Здесь есть два корректных пути:

  1. Выпустить сертификат через внутренний центр сертификации и добавить его корень в доверие всех сборочных сред. Это основной вариант для организаций: сертификаты можно ротировать централизованно, а клиенты продолжат стандартную проверку.
  2. Использовать публичный сертификат (например, от бесплатного удостоверяющего центра) для внутреннего домена, если он доступен изнутри сети. Тогда дополнительная настройка доверия не нужна, но домен должен быть реально резолвимым и доступным.

Чего делать не стоит — отключать проверку. У большинства инструментов есть флаги вроде —trusted-host у pip, strict-ssl=false у npm, insecure у ряда клиентов. Они превращают HTTPS в «шифрование без проверки подлинности»: соединение всё ещё зашифровано, но кто угодно в сети может представиться реестром. В публичном коде и CI такие флаги — прямая уязвимость в цепочке поставки.

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

Настройка в CI/CD

Сборочные агенты — самое частое место сбоев, потому что окружение там минимальное и часто пересоздаётся. Практический порядок действий:

  1. Добавьте корпоративный корневой сертификат в базовый образ агента или в шаг подготовки окружения — так он будет присутствовать во всех сборках автоматически.
  2. Задайте переменные окружения для всех используемых рантаймов (NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, SSL_CERT_FILE и аналогичные) на уровне шаблона пайплайна, а не в отдельных job.
  3. Проверьте, что прокси-переменные (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) согласованы: если внутренний реестр не должен идти через прокси, его хост должен быть в NO_PROXY.
  4. Периодически проверяйте срок действия сертификата прокси и реестра: его истечение уронит все пайплайны одновременно, и лучше узнать об этом заранее.

Частые ошибки и как их избежать

  • Отключение проверки вместо настройки доверия. Самая опасная ошибка. Правильная альтернатива — добавить корневой сертификат в доверие конкретного рантайма.
  • Добавление сертификата только на своей машине. Ошибка воспроизводится у коллег и в CI, потому что доверие не зафиксировано в образах и конфигурации проекта. Решение — хранить настройку доверия в коде инфраструктуры.
  • Игнорирование различий хранилищ. Node, Python, Java и системные инструменты могут использовать разные источники доверия. Добавили в одно — проверьте остальные.
  • Диагностика «вслепую». Без просмотра реального сертификата (OpenSSL или браузером) невозможно понять, чей сертификат приходит. Всегда начинайте с этого.
  • Забытый промежуточный сертификат на сервере. Если вы администрируете внутренний реестр и часть клиентов падает, а часть работает — проверьте, отдаёт ли сервер полную цепочку, а не только листовой сертификат.
  • Смешение прокси и реестра. Ошибка TLS может приходить от прокси, а не от реестра. Смотрите, какой именно хост указан в сообщении об ошибке.

Сценарии: что делать в вашей ситуации

  • Домашняя или обычная офисная сеть, ошибка у одного инструмента. Проверьте сертификат сервера через OpenSSL; скорее всего, проблема в устаревшем рантайме или в конфигурации конкретного пакетного менеджера. Обновите инструмент и корневые сертификаты.
  • Корпоративная сеть с фильтрацией HTTPS. Получите корневой сертификат организации у ИТ-отдела, добавьте его в доверие всех используемых рантаймов и зафиксируйте это в образах CI.
  • Внутренний реестр с самоподписанным сертификатом. Разверните внутренний центр сертификации, добавьте его корень в доверие сборочных сред. Точечный обход проверки допустим только как временная мера для конкретного хоста.
  • Ошибка только в контейнере. Добавьте сертификат в образ и обновите хранилище доверия на этапе сборки образа; проверьте, что переменные окружения передаются внутрь контейнера.
  • Внезапный массовый сбой. Проверьте срок действия сертификата сервера и прокси и новости от ИТ о ротации сертификатов — обычно причина на стороне инфраструктуры, а не в вашем проекте.

Как проверить, что всё настроено правильно

После настройки доверия выполните контрольную проверку, не дожидаясь продакшн-сборки:

  1. Убедитесь, что при запросе к реестру возвращается ожидаемый сертификат (через OpenSSL или просмотр в браузере).
  2. Выполните минимальную загрузку зависимости тем же инструментом и в том же окружении, где была ошибка.
  3. Проверьте, что в конфигурации проекта и переменных окружения не осталось флагов отключения проверки TLS, добавленных «на время».
  4. Запустите сборку в чистом окружении (свежий контейнер или чистый агент), чтобы убедиться, что доверие зафиксировано воспроизводимо, а не только на вашей машине.

Что запомнить

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

Следующий шаг: воспроизведите ошибку, посмотрите сертификат сервера утилитой OpenSSL, определите источник проблемы из таблицы выше и зафиксируйте настройку доверия в конфигурации проекта и образов сборки, чтобы ошибка не возвращалась в каждом новом окружении.

PEFile.ru