Безопасность сборки и CI/CD
Пайплайн CI/CD — это мост между исходником и продом, и потому одна из самых ценных целей для атаки.
Безопасность сборки — это защита самого процесса CI/CD: его секретов, прав, изоляции и целостности выходного артефакта, чтобы между «коммит прошёл ревью» и «релиз поехал в прод» никто не подменил код.
Парадокс CI/CD: пайплайн по определению имеет высокие привилегии — доступ к секретам, к реестрам, к продакшену. При этом он выполняет код, который может прийти из зависимостей, форков или скриптов. Если атакующий контролирует сборку, он может внедрить вредоносный код прямо в подписанный, «доверенный» артефакт — и обычное код-ревью этого не заметит.
Зачем это знать защитнику
Компрометация CI/CD — это атака на цепочку поставок изнутри. Злоумышленнику не нужно ломать прод напрямую: достаточно протащить изменение в сборку, и оно само разъедется по всем потребителям с легитимной подписью. Поэтому пайплайн защищают как продакшен-систему: минимум прав, изоляция, защита секретов и контроль того, что попадает в артефакт.
Изоляция сборки
Каждый запуск сборки должен идти в чистом, эфемерном окружении, которое уничтожается после завершения. Зачем: если сборки делят одну машину, вредоносный шаг в одном проекте может оставить «закладку» (модифицированный кеш, подменённый бинарь компилятора), которая повлияет на следующую сборку. Эфемерность разрывает эту связь — каждая сборка начинается с чистого листа.
Дополнительно изолируют сеть: продакшен-сборке обычно не нужен произвольный доступ в интернет. Ограничив исходящие соединения, вы мешаете вредоносному шагу выгрузить секреты наружу или подтянуть постороннюю нагрузку.
Защита секретов в пайплайне
Секреты (токены реестра, ключи деплоя, пароли БД) — главная добыча. Базовые правила:
- Никогда не хардкодьте секреты в YAML пайплайна или в репозитории. Используйте хранилище секретов CI или внешний vault, подставляя значения как переменные окружения на момент шага.
- Маскируйте вывод. CI должен затирать значения секретов в логах, иначе
echo $TOKENили случайный дамп окружения утечёт в общедоступные логи сборки. - Минимальный охват. Секрет должен быть доступен только тому шагу/окружению, которому он реально нужен, и иметь как можно более узкие права (например, токен только на push в один конкретный репозиторий).
- Короткоживущие учётки. Вместо вечного токена в настройках лучше получать временные креды через OIDC-федерацию: пайплайн предъявляет облаку свою подписанную личность и получает токен на минуты. Утекать там почти нечему.
# Иллюстрация: секрет берётся из хранилища, а не из репозитория
steps:
- name: Deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} # из защищённого хранилища CI
run: ./deploy.sh # внутри НЕ печатать $DEPLOY_TOKEN в лог
Права раннеров: принцип наименьших привилегий
Раннер (агент, выполняющий сборку) часто имеет слишком широкие права «на всякий случай». Это опасно. Применяйте least privilege:
- Токен пайплайна по умолчанию делайте read-only; повышайте права точечно для конкретной задачи (например, право на запись пакета — только в job публикации).
- Не давайте раннерам для сборки прав на изменение настроек репозитория, защиту веток или управление другими пайплайнами.
- Изолируйте сборки из форков и недоверенных PR: они не должны иметь доступа к боевым секретам. Иначе кто угодно открывает PR со скриптом, читающим ваши токены.
Защита артефакта от внедрения
Цель атакующего — чтобы вредоносный код оказался в финальном артефакте незаметно. Защита:
- Пиннингуйте инструменты сборки. Сторонние шаги/плагины пайплайна фиксируйте по точному коммиту (хешу), а не по изменяемому тегу — иначе обновлённый «доверенный» шаг может оказаться скомпрометированным.
- Двухфазная модель. Разделяйте шаги, которые исполняют недоверенный код (тесты, сборка из PR), и шаги, у которых есть секреты/право на релиз. Первые не должны иметь доступа ко вторым.
- Фиксируйте provenance. Пусть сборка выпускает подписанное утверждение «этот артефакт собран из коммита C пайплайном P» (см. предыдущий урок). Тогда подмену артефакта на финальном шаге можно обнаружить проверкой.
- Защищайте ветки. Релиз должен собираться только из защищённой ветки, в которую нельзя пушить напрямую — лишь через ревью. Это закрывает путь «протолкнуть код в сборку мимо ревью».
Как это работает под капотом
Уязвимость CI/CD возникает на стыке привилегий и недоверенного ввода. Пайплайн запускается с правами, достаточными для деплоя, но среди его шагов есть исполнение кода, который контролирует не только владелец репозитория: команды из package.json-скриптов зависимостей, содержимое тестов из присланного PR, сторонние actions/плагины. Если хоть один такой шаг видит боевые секреты или может писать в артефакт, граница доверия нарушена. Защита — это всегда разрыв этой связи: либо лишить недоверенный шаг привилегий (least privilege, изоляция форков), либо лишить привилегированный шаг недоверенного ввода (двухфазная модель, пиннинг шагов).
Как защититься
- Запускайте сборки в чистом эфемерном окружении с ограниченной сетью.
- Храните секреты в vault/хранилище CI, маскируйте их в логах, давайте узкий охват и предпочитайте короткоживущие OIDC-креды.
- Раннеры — по принципу наименьших привилегий: read-only по умолчанию, изоляция форков и недоверенных PR от боевых секретов.
- Защищайте артефакт: пиннинг сторонних шагов по хешу, двухфазная модель, provenance, защищённые ветки для релиза.
Юридическое напоминание: несанкционированное вмешательство в работу информационных систем и внедрение вредоносного кода наказуемы (ст. 272–274 УК РФ). Все техники здесь рассматриваются для защиты собственных пайплайнов; тестировать чужие CI/CD можно только с письменного разрешения владельца.
Итоги
- CI/CD — привилегированный мост в прод, и компрометация сборки позволяет внедрить вредоносный код в подписанный артефакт мимо код-ревью.
- Изоляция (эфемерные раннеры, ограниченная сеть) разрывает связь между сборками и мешает выгрузке секретов.
- Секреты защищают хранилищем, маскированием, узким охватом и короткоживущими OIDC-кредами вместо вечных токенов.
- Целостность артефакта обеспечивают least privilege раннеров, изоляция форков, пиннинг шагов по хешу, двухфазная модель и provenance.