Когда менеджер пакетов скачивает библиотеку или обновление из внешнего репозитория, он должен убедиться, что соединение действительно ведёт к легитимному серверу, а данные по пути никто не подменил. За это отвечает проверка TLS-сертификата. Если её отключить «чтобы всё заработало», вы теряете защиту от перехвата трафика и подмены пакетов — а это один из самых опасных сценариев компрометации сборочной инфраструктуры. В этой статье разберём, как работает проверка сертификатов в популярных менеджерах пакетов, какие ошибки с ней чаще всего возникают и как их решать, не жертвуя безопасностью.
- Что именно проверяется при HTTPS-соединении с репозиторием
- Почему проверку сертификатов иногда отключают — и почему это плохая идея
- Как настроить доверие к сертификатам в разных менеджерах пакетов
- Python и pip
- Node.js и npm
- Системные менеджеры пакетов Linux
- Java-инструменты (Maven, Gradle)
- Сравнение источников доверия
- Проверка сертификата вручную: как диагностировать проблему
- Проверка сертификата — не единственный рубеж защиты
- Типичные ошибки и как их избежать
- Что делать, если ошибка проверки появилась внезапно
- Практические выводы
Что именно проверяется при 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.
Порядок действий при ошибке проверки сертификата:
- Определите, какой сертификат предъявляет сервер. Это можно сделать утилитой openssl: подключиться к хосту репозитория и вывести цепочку сертификатов.
- Если в цепочке присутствует сертификат вашей компании (признак TLS-инспекции на прокси) — получите корневой сертификат компании у IT-отдела.
- Добавьте его в доверенные. Варианты: переменная окружения REQUESTS_CA_BUNDLE или PIP_CERT, указывающая на файл с цепочкой; параметр cert в конфигурации pip; либо дописывание корпоративного корня в файл cacert.pem внутри certifi (менее удобный вариант, так как файл перезаписывается при обновлении пакета).
- Проверьте результат повторной установкой пакета без флагов отключения проверки.
Отдельно стоит упомянуть переменную 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-файла важно, чтобы цепочка шла от конечного сертификата к корню, а файл был в правильной кодировке. Ошибки формата проявляются как загадочные сбои проверки.
Что делать, если ошибка проверки появилась внезапно
Если раньше установка работала, а теперь падает с ошибкой сертификата, действуйте по порядку:
- Проверьте, не изменилась ли сеть: новый прокси, VPN, смена провайдера. TLS-инспекция может появиться только в определённой сети.
- Посмотрите цепочку сертификатов через openssl — это сразу покажет, кто сейчас издатель.
- Проверьте дату и время на машине.
- Обновите хранилище корневых сертификатов (пакет ca-certificates или certifi).
- Если проблема на стороне сервера (неполная цепочка, просроченный сертификат репозитория) — сообщите владельцам зеркала или используйте официальный источник до исправления.
Практические выводы
Главный принцип: ошибку проверки сертификата нужно устранять добавлением доверия, а не отключением проверки. В 90% случаев решение сводится к тому, чтобы корневой сертификат реального издателя (чаще всего — корпоративного прокси) оказался в том хранилище, которое использует конкретный инструмент. Системное хранилище покрывает apt, curl и git, для pip нужен certifi или переменные окружения, для npm — NODE_EXTRA_CA_CERTS, для Maven — cacerts JDK.
Конкретные следующие шаги:
- Определите, какие менеджеры пакетов используются в ваших проектах и CI, и проверьте, откуда каждый берёт корневые сертификаты.
- Если работаете за корпоративным прокси — запросите у IT корневой сертификат и распространите его централизованно (в образах Docker, в конфигурации CI, в документации онбординга).
- Найдите и удалите из проектов и пайплайнов флаги отключения проверки SSL — замените их корректной настройкой доверия.
- Добавьте к TLS-проверке фиксацию хэшей зависимостей и, по возможности, внутренний прокси-репозиторий.
Такой подход занимает больше времени, чем один флаг в конфиге, но сохраняет защиту от подмены пакетов — а именно через цепочку поставки ПО сегодня реализуется значительная часть атак на разработчиков и сборочные системы.
Материал носит информационный характер. Конкретные настройки зависят от вашей инфраструктуры, версий инструментов и политик безопасности организации; при работе в корпоративной среде согласуйте изменения конфигурации с ответственной командой ИБ или ИТ.
