Многие компании используют сертификаты, подписанные внутренними удостоверяющими центрами (Certificate Authority) для ресурсов в приватных сетях. Поскольку такие сертификаты по умолчанию не могут быть доверенными, почти на каждом этапе вокруг пайплайна могут возникать ошибки такого рода: x509 certificate signed by unknown authority. Из-за этого до каждого компонента необходимо доставлять корневые сертификаты, используемые в компании. В статье расскажу, как это можно сделать. Статья подготовлена на основе моего материала в курсе «CI/CD на примере Gitlab CI», первые его темы доступны в бесплатном мини-курсе. Обратите внимание: настройка инфраструктуры выходит за рамки этой статьи, поэтому в примерах, где не идёт речи о динамическом получении сертификата, я ограничился предположением, что на серверах компании внутренний корневой сертификат всегда расположен по пути /usr/local/share/ca-certificates/RootCA.crt. Поскольку курс в основе этого материала строится вокруг Gitlab-CI, то и нижеследующие разделы рассматривают доставку сертификатов до тех или иных этапов или компонентов, входящих в пайплайн, построенный в гитлабе с раннерами, запущенными внутри корпоративной инфраструктуры. Поехали! Передача корневого сертификата в управляющий компонент gitlab-runner — наиболее простая задача. gitlab-runner открывает HTTPS-соединения только с самим сервером GitLab и, возможно, с кластером Kubernetes. Однако последний явным образом требует указания своего корневого сертификата. Для верификации подлинности сервера GitLab используется сертификат, указанный в config.toml: Если gitlab-runner запущен непосредственно на хосте, этого достаточно. Если он запускается в докер-контейнере, в контейнер нужно смонтировать сертификат с хоста: Наконец, если он запускается в Kubernetes, один из вариантов — монтировать директорию с хоста В спецификации контейнера: В спецификации пода: Если нет прав на монтирование hostPath, сертификат придётся положить в Kubernetes как секрет и монтировать его оттуда, поменяв содержимое ключа volumes. Например, если секрет создан командой ключ volumes будет выглядеть так: gitlab-runner-helper — вспомогательная утилита, которая используется в пайплайнах для клонирования репозитория и работы с кэшем и артефактами. Обычно проблемы с сертификатами возникают тогда, когда используется собственное S3-хранилище (например, на базе ceph или minio). Как правило, происходит обращение к S3-хранилищу по HTTPS, и сервер S3 представляется корпоративным сертификатом. Единственный способ доставить туда сертификаты — пересобрать образ. Helper image используется в тандеме с docker или kubernetes экзекьюторами. Чтобы настроить использование кастомного образа, укажите следующие строки в config.toml: Действия выше выполняются относительно редко, однако если используется Docker или Kubernetes экзекьютор, то каждый джоб будет выполняться в свежесозданном контейнере, в котором снова нет нужных сертификатов. Поэтому необходим способ динамически доставлять сертификаты в контейнеры. Есть несколько таких способов: Если прописать в config.toml то сертификаты будут монтироваться в основные и сервисные контейнеры запускаемых джобов. В случае с докер-экзекьютором аналогичный результат можно получить так: Минус подхода в том, что ответственность за актуальность сертификатов сдвигается на конфигурацию инфраструктуры, поэтому подход удобен, если вы этой инфраструктурой управляете. Если же вы, например, работаете в отдельной команде, которая в многопользовательском кластере Kubernetes сама подняла свой раннер, но не имеет админского доступа туда, вы не можете быть уверены в актуальности сертификатов на хосте, да и можете вовсе не иметь прав на монтирование hostPath. Тогда можно аналогично примеру для gitlab-runner создать в кубе секрет, содержащий ключи (имена файлов сертификатов) и значения (их содержимое): а config.toml исправить следующим образом: Если используется докер-экзекьютор на недоступном вам build-сервере, из этого тут же следует, что у вас нет доступа к конфигурации раннера, если только не используется экзотичный вариант подключения к сокету докер-демона по tcp (подобный доступ почти всегда означает, что и доступ к хосту получить тривиально). Лучше всего договориться с администраторами build-сервера о гарантированном наличии свежих сертификатов в определённой директории, которая будет монтироваться в контейнеры пайплайна. Один из плюсов такого подхода в том, что можно делать следующее: Ключевой шаг, естественно, update-ca-certificates. Поскольку для сервисных контейнеров нельзя прописать последовательность команд аналогично script, а писать длинные скрипты для установки сертификатов под ключом command не очень удобно, меньшим из зол является подобное монтирование сертификатов и сравнительно короткая последовательность команд. Если вы в пайплайнах собираете докер-образы, то, независимо от того, используется docker-in-docker, kaniko или другой сборщик в пайплайне, в контексте сборки сертификаты снова отсутствуют, если их не вшили в базовый образ. Предположим, что каким-либо образом в контейнер пайплайна сертификаты были доставлены (либо примонтированы с хоста, либо получены из другого источника командами в before_script). Тогда конфигурация пайплайнов должна будет выглядеть так: а в докер-файлах придётся всегда указывать что-нибудь вроде  Эти добавления нужны, если во время сборки происходят обращения к внутренним сервисам, которые представляются корпоративными сертификатами. Кроме того, бывает, что выход в интернет осуществляется через MITM-proxy, который для инспекции трафика расшифровывает передаваемые данные и, соответственно, подменяет сертификат. Тогда, если во время сборки нужно обращаться в интернет, например, для доставки каких-либо пакетов, нужно доверять сертификату, который используется MITM-proxy. Поэтому для сборок в начале каждого докер-файла может потребоваться добавлять эти строки: Если источник корневых сертификатов предоставляет удобный API для их получения, можно не утруждать себя хранением сертификатов на своих серверах с последующим монтированием в пайплайны. Допустим, корневой сертификат можно получить командой Пример со сборкой докер-образа примет такой вид: То есть добавился шаг скачивания сертификатов. Если API центра сертификации недостаточно функционален или требует авторизации, которая в пайплайнах слишком громоздкая, можно запустить простейший веб-сервер, откуда сертификаты можно будет скачать почти таким же образом, как в предыдущем примере. Принципиального отличия от получения их из первоисточника нет, поэтому рассмотрим последний, наиболее эффективный вариант. Рано или поздно многие компании обнаруживают потребность контролировать свою инфраструктуру. Админам надоедает отвечать на одинаковые вопросы разработчиков («почему у меня не качает с гитхаба?, «что значит x509?»), безопасникам надоедает наблюдать за тем, как все докер-файлы начинаются со строчки FROM vasyapupkin/zvercd, а разработчикам надоедает решать головоломки с изменчивыми промежуточными сертификатами и прочими вещами вне их области экспертизы. Тогда ответственные за платформу идут и пишут много-много докер-файлов наподобие такого: Эти образы сохраняются в корпоративный репозиторий, а разработчики компании начинают пользоваться только этими образами в качестве базовых. Дополнительным бонусом идёт возможность преднастроить во внутренних образах всевозможные кэши и проксирующие репозитории (nexus, artifactory и прочие). Наличие базовых образов не отменяет необходимость получать сертификаты динамически, любым из остальных перечисленных способов. Это нужно инженерам, поддерживающим актуальность корпоративных базовых образов, чтобы своевременно обновлять в них сертификаты или создавать новые базовые образы. Совместив два-три описанных подхода вы, наконец, сможете спать спокойно и не объяснять всем в сотый раз, что означает x509: certificate signed by unknown authority.