Проверка сертификатов при загрузке пакетов из внешних источников

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

Что именно проверяется при HTTPS-соединении с репозиторием

Большинство современных репозиториев пакетов — PyPI, npm-registry, репозитории Maven, NuGet, официальные зеркала Linux-дистрибутивов — работают поверх HTTPS. Протокол TLS решает две задачи: шифрует трафик и подтверждает подлинность сервера. Вторая задача выполняется через цепочку сертификатов.

Когда клиент подключается к серверу, тот предъявляет сертификат. Клиент проверяет несколько условий:

  • Цепочку доверия. Сертификат сервера должен быть подписан промежуточным или корневым сертификатом из локального хранилища доверенных корневых центров сертификации (CA). В Linux это обычно хранилище уровня системы (например, каталог с файлами CA в формате PEM), в Windows — системное хранилище сертификатов.
  • Соответствие домену. Имя хоста, к которому вы подключаетесь, должно совпадать с одним из имён в поле Subject Alternative Name сертификата. Проверка по устаревшему полю Common Name многими клиентами уже не выполняется.
  • Срок действия. Сертификат не должен быть просрочен и не должен действовать «раньше времени».
  • Статус отзыва. Клиент может проверить через OCSP или CRL, не отозван ли сертификат. На практике многие менеджеры пакетов делают это не всегда или допускают «soft fail» — продолжают работу, если сервер проверки недоступен.

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

Почему проверку сертификатов иногда отключают — и почему это плохая идея

Типичная ситуация: разработчик за корпоративным прокси видит ошибку вида «SSL: CERTIFICATE_VERIFY_FAILED» или «self signed certificate in certificate chain» и гуглит решение. В первых же результатах — советы добавить флаг отключения проверки. Для Python это переменная PYTHONHTTPSVERIFY=0 или параметр —trusted-host в pip, для npm — strict-ssl=false, для curl и wget — флаги -k / —no-check-certificate.

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

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

Как настроить доверие к сертификатам в разных менеджерах пакетов

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

Python и pip

pip использует библиотеку certifi — собственный комплект корневых сертификатов, поставляемый вместе с пакетом. Это значит, что обновление системного хранилища CA само по себе может не помочь: pip смотрит в файл certifi.

Порядок действий при ошибке проверки сертификата:

  1. Определите, какой сертификат предъявляет сервер. Это можно сделать утилитой openssl: подключиться к хосту репозитория и вывести цепочку сертификатов.
  2. Если в цепочке присутствует сертификат вашей компании (признак TLS-инспекции на прокси) — получите корневой сертификат компании у IT-отдела.
  3. Добавьте его в доверенные. Варианты: переменная окружения REQUESTS_CA_BUNDLE или PIP_CERT, указывающая на файл с цепочкой; параметр cert в конфигурации pip; либо дописывание корпоративного корня в файл cacert.pem внутри certifi (менее удобный вариант, так как файл перезаписывается при обновлении пакета).
  4. Проверьте результат повторной установкой пакета без флагов отключения проверки.

Отдельно стоит упомянуть переменную SSL_CERT_FILE и SSL_CERT_DIR: они влияют на стандартный модуль ssl в Python. Часть инструментов читает именно их, поэтому в сложных окружениях иногда приходится задавать несколько переменных сразу.

Node.js и npm

npm по умолчанию проверяет сертификаты и использует собственную связку CA, но умеет читать переменную NODE_EXTRA_CA_CERTS, в которой можно указать путь к файлу с дополнительными корневыми сертификатами. Это самый чистый способ добавить корпоративный корень, не отключая проверку.

Параметр cafile в конфигурации npm работает аналогично. А вот strict-ssl=false отключает проверку полностью — используйте его только как временную диагностику и никогда не фиксируйте в репозитории проекта.

У Node.js есть нюанс: приложения на этой платформе не всегда используют системное хранилище сертификатов. Если ошибка возникает не в npm, а в самом приложении при загрузке зависимостей в рантайме, проверьте, как именно ваш HTTP-клиент (axios, node-fetch, встроенный https-модуль) настраивает CA.

Системные менеджеры пакетов Linux

apt, dnf, yum и zypper используют системное хранилище сертификатов. В Debian и Ubuntu это пакет ca-certificates и каталог с PEM-файлами; в RHEL-подобных системах — механизм update-ca-trust. Чтобы добавить корпоративный корень:

  • в Debian/Ubuntu: положите сертификат в формате PEM в каталог /usr/local/share/ca-certificates с расширением .crt и выполните update-ca-certificates;
  • в RHEL/CentOS/Fedora: поместите файл в /etc/pki/ca-trust/source/anchors/ и выполните update-ca-trust.

После этого доверие получат все инструменты, использующие системное хранилище: apt, curl, wget, git и большинство языковых рантаймов, собранных с привязкой к системе.

Java-инструменты (Maven, Gradle)

JVM использует собственное хранилище cacerts внутри установленной JDK, а не системное. Корпоративный корень добавляется утилитой keytool в хранилище конкретной JDK. Это частая причина того, что в одной и той же системе curl работает, а Maven — падает с ошибкой сертификата.

Сравнение источников доверия

Инструмент Откуда берёт корневые сертификаты Способ добавить корпоративный корень
pip Пакет certifi (свой файл) Переменные REQUESTS_CA_BUNDLE / PIP_CERT или параметр cert в конфигурации
npm Встроенная связка + NODE_EXTRA_CA_CERTS Переменная NODE_EXTRA_CA_CERTS или параметр cafile
apt / dnf / curl / wget Системное хранилище CA update-ca-certificates или update-ca-trust в зависимости от дистрибутива
Maven / Gradle Хранилище cacerts внутри JDK Импорт через keytool в cacerts используемой JDK
Git (HTTPS) Системное хранилище или параметр http.sslCAInfo Параметр http.sslCAInfo в конфигурации git

Из таблицы виден практический вывод: в смешанном окружении одного обновления системного хранилища недостаточно. Если в CI-пайплайне работают и pip, и npm, и Maven, корпоративный корень нужно добавить в каждое из перечисленных мест.

Проверка сертификата вручную: как диагностировать проблему

Прежде чем менять конфигурацию, полезно понять, что именно не так. Утилита openssl позволяет увидеть цепочку сертификатов, которую отдаёт сервер:

Команда вида openssl s_client -connect registry.example.org:443 -showcerts выведет предъявленные сертификаты. По выводу можно определить:

  • кто издатель корневого сертификата — публичный CA или внутренний центр вашей компании;
  • не просрочен ли сертификат и корректны ли даты;
  • совпадает ли домен в поле Subject Alternative Name с тем, к которому вы подключаетесь.

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

Отдельный случай — самоподписанные сертификаты на внутренних зеркалах репозиториев. Их тоже можно добавить в доверенные как отдельный корневой или конечный сертификат, не отключая проверку целиком. Это безопаснее, чем —trusted-host, потому что доверие ограничено конкретным сертификатом, а не «любым сертификатом этого хоста».

Проверка сертификата — не единственный рубеж защиты

Даже корректно работающая TLS-проверка не защищает от всех угроз при загрузке пакетов. Дополните её следующими практиками:

  • Фиксация версий и хэшей. В Python используйте файлы требований с хэшами (pip поддерживает —require-hashes), в npm — package-lock.json, в других экосистемах — аналогичные файлы блокировки. Это защищает от подмены содержимого пакета даже при компрометации зеркала.
  • Внутренний прокси-репозиторий. В организациях разумно развернуть кэширующий прокси (Nexus, Artifactory или аналоги) и направить все менеджеры пакетов на него. Тогда исходящие соединения с репозиториями идут с одной контролируемой точки, а разработчики вообще не зависят от прямого доступа в интернет.
  • Минимизация привилегий при установке. Запуск установки пакетов от root или с правами администратора расширяет последствия компрометации: постинсталляционные скрипты выполняются с этими правами.
  • Проверка новых версий. Автоматическое обновление до свежих версий без периода «выдержки» увеличивает риск получить только что опубликованный вредоносный релиз. В критичных проектах имеет смысл задерживать принятие новых версий на несколько дней.
  • Контроль конфигурации. Флаги отключения проверки SSL не должны попадать в Dockerfile, скрипты CI и файлы конфигурации проекта. Регулярно проверяйте репозиторий на их наличие — они часто остаются после отладки.

Типичные ошибки и как их избежать

Несколько сценариев, которые на практике приводят к проблемам:

  • Отключение проверки «навсегда» после разовой отладки. Флаг добавлен в CI-конфиг для обхода временной ошибки и остался в коде на годы. Решение: относиться к таким флагам как к временному костылю и фиксировать в задаче, когда он будет убран.
  • Добавление корпоративного корня только в одно хранилище. Системное хранилище обновили, но pip продолжает использовать certifi, а Maven — cacerts JDK. Ошибки остаются, и команда снова тянется к отключению проверки. Решение: покрыть все инструменты из таблицы выше.
  • Устаревшее хранилище корней в контейнере. Минимальные Docker-образы часто идут без пакета ca-certificates или с устаревшей версией. Решение: явно устанавливать и обновлять ca-certificates в образе.
  • Игнорирование системных часов. Некорректное время на машине или в виртуальной машине делает любой сертификат «просроченным». Если ошибки проверки возникают массово на всех хостах, проверьте синхронизацию времени.
  • Смешение сертификатов в одном файле без порядка. При ручной сборке PEM-файла важно, чтобы цепочка шла от конечного сертификата к корню, а файл был в правильной кодировке. Ошибки формата проявляются как загадочные сбои проверки.

Что делать, если ошибка проверки появилась внезапно

Если раньше установка работала, а теперь падает с ошибкой сертификата, действуйте по порядку:

  1. Проверьте, не изменилась ли сеть: новый прокси, VPN, смена провайдера. TLS-инспекция может появиться только в определённой сети.
  2. Посмотрите цепочку сертификатов через openssl — это сразу покажет, кто сейчас издатель.
  3. Проверьте дату и время на машине.
  4. Обновите хранилище корневых сертификатов (пакет ca-certificates или certifi).
  5. Если проблема на стороне сервера (неполная цепочка, просроченный сертификат репозитория) — сообщите владельцам зеркала или используйте официальный источник до исправления.

Практические выводы

Главный принцип: ошибку проверки сертификата нужно устранять добавлением доверия, а не отключением проверки. В 90% случаев решение сводится к тому, чтобы корневой сертификат реального издателя (чаще всего — корпоративного прокси) оказался в том хранилище, которое использует конкретный инструмент. Системное хранилище покрывает apt, curl и git, для pip нужен certifi или переменные окружения, для npm — NODE_EXTRA_CA_CERTS, для Maven — cacerts JDK.

Конкретные следующие шаги:

  • Определите, какие менеджеры пакетов используются в ваших проектах и CI, и проверьте, откуда каждый берёт корневые сертификаты.
  • Если работаете за корпоративным прокси — запросите у IT корневой сертификат и распространите его централизованно (в образах Docker, в конфигурации CI, в документации онбординга).
  • Найдите и удалите из проектов и пайплайнов флаги отключения проверки SSL — замените их корректной настройкой доверия.
  • Добавьте к TLS-проверке фиксацию хэшей зависимостей и, по возможности, внутренний прокси-репозиторий.

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

Материал носит информационный характер. Конкретные настройки зависят от вашей инфраструктуры, версий инструментов и политик безопасности организации; при работе в корпоративной среде согласуйте изменения конфигурации с ответственной командой ИБ или ИТ.

PEFile.ru