Когда сборщик проекта, менеджер пакетов или CI-агент скачивает зависимости, он почти всегда делает это по HTTPS. Проверка TLS-соединения — это то, что отличает загрузку из настоящего реестра пакетов от загрузки через подставной сервер злоумышленника. Если проверка отключена или настроена небрежно, атакующий в той же сети может подменить пакет, и вредоносный код окажется в вашей сборке. Главный принцип: TLS-проверку нельзя отключать ради «чтобы заработало» — вместо этого нужно разобраться, почему сертификат не проходит проверку, и устранить причину.
Ниже разобрано, как устроена эта проверка, какие ошибки встречаются чаще всего, как их диагностировать и как правильно настроить загрузку зависимостей за корпоративным прокси, с приватным реестром и в изолированных средах.
- Что именно проверяется при TLS-рукопожатии
- Типичные ошибки и их причины
- Диагностика: с чего начать
- Как правильно настроить доверие к корпоративному сертификату
- Приватные реестры и самоподписанные сертификаты
- Настройка в CI/CD
- Частые ошибки и как их избежать
- Сценарии: что делать в вашей ситуации
- Как проверить, что всё настроено правильно
- Что запомнить
Что именно проверяется при 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, действуйте в следующем порядке — от общего к частному:
- Определите, кто на самом деле отвечает на соединение. Выполните запрос к реестру утилитой вроде OpenSSL (openssl s_client -connect host:443 -servername host) и посмотрите, чей сертификат возвращается. Если там сертификат вашей компании, а не реестра — трафик проходит через перехватывающий прокси.
- Сравните окружения. Если на локальной машине загрузка работает, а в CI или контейнере нет — почти наверняка разница в наборе доверенных сертификатов. Сравните содержимое хранилищ доверия.
- Проверьте срок и цепочку. В выводе OpenSSL видно, истёк ли сертификат и все ли промежуточные звенья отдаёт сервер. Неполная цепочка — частая причина ошибок, которые проявляются не у всех клиентов.
- Проверьте имя хоста. Убедитесь, что в конфигурации пакетного менеджера указан тот же домен, что и в сертификате, без опечаток и без прямого IP.
- Вспомните, что менялось. Обновление ОС, рантайма, прокси, антивируса с проверкой 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 и аналоги) часто работают на самоподписанных или внутренних сертификатах. Здесь есть два корректных пути:
- Выпустить сертификат через внутренний центр сертификации и добавить его корень в доверие всех сборочных сред. Это основной вариант для организаций: сертификаты можно ротировать централизованно, а клиенты продолжат стандартную проверку.
- Использовать публичный сертификат (например, от бесплатного удостоверяющего центра) для внутреннего домена, если он доступен изнутри сети. Тогда дополнительная настройка доверия не нужна, но домен должен быть реально резолвимым и доступным.
Чего делать не стоит — отключать проверку. У большинства инструментов есть флаги вроде —trusted-host у pip, strict-ssl=false у npm, insecure у ряда клиентов. Они превращают HTTPS в «шифрование без проверки подлинности»: соединение всё ещё зашифровано, но кто угодно в сети может представиться реестром. В публичном коде и CI такие флаги — прямая уязвимость в цепочке поставки.
Если самоподписанный сертификат нужен временно, ограничьте область: включайте обход проверки только для конкретного внутреннего хоста и только в конкретном окружении, а не глобально в конфигурации пользователя.
Настройка в CI/CD
Сборочные агенты — самое частое место сбоев, потому что окружение там минимальное и часто пересоздаётся. Практический порядок действий:
- Добавьте корпоративный корневой сертификат в базовый образ агента или в шаг подготовки окружения — так он будет присутствовать во всех сборках автоматически.
- Задайте переменные окружения для всех используемых рантаймов (NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, SSL_CERT_FILE и аналогичные) на уровне шаблона пайплайна, а не в отдельных job.
- Проверьте, что прокси-переменные (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) согласованы: если внутренний реестр не должен идти через прокси, его хост должен быть в NO_PROXY.
- Периодически проверяйте срок действия сертификата прокси и реестра: его истечение уронит все пайплайны одновременно, и лучше узнать об этом заранее.
Частые ошибки и как их избежать
- Отключение проверки вместо настройки доверия. Самая опасная ошибка. Правильная альтернатива — добавить корневой сертификат в доверие конкретного рантайма.
- Добавление сертификата только на своей машине. Ошибка воспроизводится у коллег и в CI, потому что доверие не зафиксировано в образах и конфигурации проекта. Решение — хранить настройку доверия в коде инфраструктуры.
- Игнорирование различий хранилищ. Node, Python, Java и системные инструменты могут использовать разные источники доверия. Добавили в одно — проверьте остальные.
- Диагностика «вслепую». Без просмотра реального сертификата (OpenSSL или браузером) невозможно понять, чей сертификат приходит. Всегда начинайте с этого.
- Забытый промежуточный сертификат на сервере. Если вы администрируете внутренний реестр и часть клиентов падает, а часть работает — проверьте, отдаёт ли сервер полную цепочку, а не только листовой сертификат.
- Смешение прокси и реестра. Ошибка TLS может приходить от прокси, а не от реестра. Смотрите, какой именно хост указан в сообщении об ошибке.
Сценарии: что делать в вашей ситуации
- Домашняя или обычная офисная сеть, ошибка у одного инструмента. Проверьте сертификат сервера через OpenSSL; скорее всего, проблема в устаревшем рантайме или в конфигурации конкретного пакетного менеджера. Обновите инструмент и корневые сертификаты.
- Корпоративная сеть с фильтрацией HTTPS. Получите корневой сертификат организации у ИТ-отдела, добавьте его в доверие всех используемых рантаймов и зафиксируйте это в образах CI.
- Внутренний реестр с самоподписанным сертификатом. Разверните внутренний центр сертификации, добавьте его корень в доверие сборочных сред. Точечный обход проверки допустим только как временная мера для конкретного хоста.
- Ошибка только в контейнере. Добавьте сертификат в образ и обновите хранилище доверия на этапе сборки образа; проверьте, что переменные окружения передаются внутрь контейнера.
- Внезапный массовый сбой. Проверьте срок действия сертификата сервера и прокси и новости от ИТ о ротации сертификатов — обычно причина на стороне инфраструктуры, а не в вашем проекте.
Как проверить, что всё настроено правильно
После настройки доверия выполните контрольную проверку, не дожидаясь продакшн-сборки:
- Убедитесь, что при запросе к реестру возвращается ожидаемый сертификат (через OpenSSL или просмотр в браузере).
- Выполните минимальную загрузку зависимости тем же инструментом и в том же окружении, где была ошибка.
- Проверьте, что в конфигурации проекта и переменных окружения не осталось флагов отключения проверки TLS, добавленных «на время».
- Запустите сборку в чистом окружении (свежий контейнер или чистый агент), чтобы убедиться, что доверие зафиксировано воспроизводимо, а не только на вашей машине.
Что запомнить
Проверка TLS при загрузке зависимостей — это подтверждение того, что вы общаетесь именно с тем реестром, к которому обращались. Ошибка проверки почти всегда означает одну из понятных причин: перехватывающий прокси, неполная или просроченная цепочка, несоответствие имени хоста или отсутствие корпоративного сертификата в конкретном окружении. Правильный путь — диагностировать, чей сертификат приходит, и добавить нужный корень в доверие всех задействованных рантаймов, включая CI и контейнеры. Отключение проверки — допустимо лишь как временное, ограниченное конкретным хостом решение, и его следует воспринимать как технический долг с реальным риском подмены пакетов.
Следующий шаг: воспроизведите ошибку, посмотрите сертификат сервера утилитой OpenSSL, определите источник проблемы из таблицы выше и зафиксируйте настройку доверия в конфигурации проекта и образов сборки, чтобы ошибка не возвращалась в каждом новом окружении.
