Последние двенадцать месяцев выдались неспокойными для цепочки поставок (supply chain) программного обеспечения с открытым исходным кодом. Пакет axios был скомпрометирован на npm и в составе внешне нормальных релизов распространял троян удалённого доступа. Пакет LiteLLM на PyPI взломали для кражи переменных окружения. Тайпсквоттинговые форки Trivy публиковались в расчёте на тех, кто ошибается при наборе go install. И классический пример — взлом SolarWinds в 2020 году — по-прежнему остаётся главным поучительным случаем: злоумышленники проникли в систему сборки и через обычные обновления Orion доставили вредоносное ПО примерно 18 000 организациям, в том числе федеральным ведомствам США, НАТО и Microsoft. Вредонос месяцами оставался неактивным, а сам взлом не обнаруживали почти год.
Cilium работает на уровне сетевого стека ядра для миллионов подов Kubernetes. Если бы наша цепочка поставок оказалась скомпрометирована, последствия были бы весьма масштабными. Мы постоянно работаем над защитой проекта от подобных сценариев и решили подробно описать, что именно мы делаем. Большинство практик ниже не специфичны для Cilium: любой проект с открытым исходным кодом, использующий CI/CD на GitHub Actions, может применить их у себя. Мы также честно указали, где у нас ещё есть пробелы — возможно, это послужит кому-то отправной точкой.
Кратко о главном
Если времени читать всё нет, вот что Cilium делает сегодня для защиты цепочки поставок, сгруппировано по уровням конвейера:
| Уровень | Контроль | Что даёт |
|---|---|---|
Кто запускает сборки |
Ariane — бот с разрешительным списком |
Только участники организации могут запускать рабочие процессы CI через комментарии к PR. |
Какой код исполняет CI |
Двухэтапный чекаут для |
Доверенные действия загружаются из базовой ветки; код из PR используется только как контекст сборки Docker, но не исполняется. |
Кто проверяет изменения CI |
CODEOWNERS как обязательные ревьюеры |
|
Какие зависимости подтягивает CI |
Действия и образы, закреплённые по SHA-дайджесту |
|
Какие Go-модули попадают в бинарник |
Вендоринг Go-зависимостей |
|
Как вообще должны выглядеть рабочие процессы |
Статический анализ рабочих процессов |
|
Какие учётные данные доступны |
Разделение CI- и продакшен-учётных данных |
CI-сборки пишут только в теги разработки |
Что могут верифицировать пользователи |
Подписанные релизы |
Sigstore Cosign с беспарольным OIDC; к образам прикреплены SBOM-аттестации. |
Где мы ещё не дотягиваем |
Пробелы, над которыми работаем |
Нет |
Далее мы подробно разбираем каждый пункт: принятые решения и то, от чего мы сознательно отказались (например, от форка всех сторонних действий в наш орг).
Управление тем, кто и что запускает
Первый вопрос в любой истории о безопасности CI — кто может запустить сборку и какой код при этом исполняется? Многие компрометации CI начинаются именно здесь: система обманом запускает код, контролируемый злоумышленником, с повышенными привилегиями.
Ограничения на запуск рабочих процессов с помощью Ariane
Ariane — это GitHub-бот собственной разработки, запускающий рабочие процессы CI по комментариям к PR. Когда мейнтейнер пишет /test или /ci-eks в pull request, Ariane проверяет, состоит ли комментатор в команде organization-members, определяет, какие процессы нужно запустить (включая зависимости — например, тесты, которым сначала нужна свежая сборка образа), и инициирует их через workflow_dispatch.
Ключевой момент — разрешительный список. Запускать процессы могут только верифицированные участники организации, а сам перечень доступных процессов прописан вручную в конфигурации:
allowed-teams:
- organization-members
triggers:
/test\s*:
workflows:
- conformance-aws-cni.yaml
- conformance-clustermesh.yaml
- conformance-eks.yaml
# ...и так далее
depends-on:
- /build-images-dependency
/ci-aks:
workflows:
- conformance-aks.yaml
depends-on:
- /build-images-dependency
Если случайный внешний участник напишет /test в PR — система его проигнорирует. Он не сможет запустить дорогостоящие тесты на облачных провайдерах и не израсходует наши минуты CI.
Разделение доверенного и недоверенного кода в CI
Когда кто-то открывает PR, нам нужно собрать его код — но доверять ему, разумеется, нельзя. Это классическая проблема pull_request_target. Мы избегаем pull_request_target везде, где можно, но в нескольких процессах он всё же необходим — и там мы применяем компенсирующие меры.
Наглядный пример — рабочий процесс сборки образов. Он разбивает чекаут на два этапа:
.github/workflows/build-images-ci.yaml
- name: Checkout base or default branch (trusted)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.base_ref || github.event.repository.default_branch }}
persist-credentials: false
# ...здесь выполняются доверенные шаги настройки, включая загрузку составных действий...
# Предупреждение: поскольку это привилегированный процесс, последующие шаги
# должны избегать исполнения недоверенного кода.
- name: Checkout pull request branch (NOT TRUSTED)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
ref: ${{ steps.tag.outputs.sha }}
Первый чекаут забирает базовую ветку (код, уже прошедший ревью и смёрженный), чтобы загрузить составные действия, скрипты и логику подписи Cosign из заведомо надёжного источника. Лишь после этого процесс переключается на ветку PR, и тот чекаут используется исключительно как контекст сборки для docker build. Ничто из ветки PR не исполняется как скрипт.
Мы регулярно получаем отчёты об уязвимостях по этому паттерну. Автоматические сканеры и добросовестные исследователи видят «pull_request_target плюс второй чекаут» и квалифицируют это как уязвимость. В общем случае они правы. В нашем — процесс спроектирован намеренно, и паттерн безопасен:
-
Ни один блок
run:после второго чекаута не исполняет скрипты из недоверенного чекаута. Все inline-команды написаны прямо в YAML процесса (проверки использования диска, копирование файлов, вывод дайджеста). Ничего из ветки PR не подключается. -
Составные действия тоже не загружаются из недоверенного чекаута. Все составные действия (
set-runtime-image,cosign,set-env-variables) берутся из доверенного чекаута базовой ветки или из сохранённой директории../cilium-base-branch/. Мы также работаем над переносом этих составных действий в отдельный репозиторий, чтобы вовсе не делать чекаут исходников для их запуска. -
Docker BuildKit действительно исполняет недоверенный Dockerfile — это и есть смысл сборки CI-образа из PR. BuildKit работает изолированно: без переменных окружения GitHub Actions, без секретов репозитория, без доступа к хранилищу Docker-учётных данных раннера. Передаваемые аргументы сборки не содержат секретов — только ссылку на образ среды выполнения и название варианта оператора.
-
Недоверенные данные поступают ровно в одно доверенное действие. Файл
runtime-image*.txtиз PR передаётся в доверенное действиеset-runtime-image, которое проверяет, что ссылка на образ начинается сquay.io/cilium/, и убирает символы новой строки — чтобы злоумышленник не мог протащить инъекцию вGITHUB_ENV. Перенаправить сборку куда-либо за пределы пространства имён Cilium невозможно. -
Из учётных данных доступны только CI-кредиты. Docker login использует
QUAY_USERNAME_CI/QUAY_PASSWORD_CI, которые позволяют пушить только вci-реестр для разработки. Продакшен-учётные данные на раннере отсутствуют.
Наихудший сценарий при компрометации сборки из PR — попадание вредоносного CI-образа в реестр разработки. Это тот же радиус поражения, что и у любой CI-системы, собирающей код от контрибьюторов. Мы внимательно читаем каждый репорт и благодарны за них, но данный паттерн применяется намеренно.
CODEOWNERS как обязательные ревьюеры
Мы активно используем CODEOWNERS, чтобы изменения всегда попадали к людям с наибольшей экспертизой. Для конфигурации CI это означает: всё под .github/ находится в ведении @cilium/github-sec (наша команда по безопасности CI) и @cilium/ci-structure, а процесс auto-approve.yaml — в ведении @cilium/cilium-maintainers:
/.github/ @cilium/github-sec @cilium/ci-structure
/.github/ariane-config.yaml @cilium/github-sec @cilium/ci-structure
/.github/renovate.json5 @cilium/github-sec @cilium/ci-structure
/.github/workflows/ @cilium/github-sec @cilium/ci-structure
/.github/workflows/auto-approve.yaml @cilium/cilium-maintainers
Никто не может изменить конвейер CI без явного ревью от команды, ответственной за его безопасность.
Жёсткий контроль зависимостей
Когда вопрос о том, кто запускает сборки, решён, встаёт следующий: какой код они при этом подтягивают? Закреплённый процесс, тянущий скомпрометированную зависимость, сам становится скомпрометированным.
Закрепление GitHub Actions по SHA-дайджесту
Самое эффективное, что может сделать любой проект, — перестать доверять изменяемым тегам.
Каждая директива uses: в наших файлах рабочих процессов ссылается на действия по полному 40-символьному SHA коммита, а человекочитаемая версия указывается в комментарии:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Если кто-то скомпрометирует тег v6 в actions/checkout и форс-пушнет туда вредоносный код, наши процессы его не подтянут — они закреплены на конкретном коммите. То же самое касается каждого стороннего действия: docker/build-push-action, sigstore/cosign-installer, golangci/golangci-lint-action и десятков других. Образы контейнеров, используемые непосредственно в шагах процессов, мы закрепляем аналогично — по дайджесту @sha256:, так что даже инструменты внутри CI адресованы по содержимому.
У закрепления есть один неприятный слепой пятно — транзитивные зависимости. Закрепив actions/checkout@de0fac2e…, мы точно знаем, какой код запустится для этого действия. Но если само actions/checkout ссылается на другое действие по тегу (uses: some-org/some-helper@v1), это разрешается в рантайме и нам невидимо. Злоумышленник, скомпрометировавший вложенную зависимость, всё равно может достучаться до нашего конвейера.
Исправление на подходе: блокировка зависимостей на уровне процессов была анонсирована в плане по безопасности GitHub Actions на 2026 год. Планируется добавление секции dependencies: в YAML процессов, которая будет фиксировать все прямые и транзитивные зависимости по SHA коммита с проверкой хешей перед исполнением — примерно так, как go.mod + go.sum работают для Go. Как только функция появится, мы её внедрим.
Автоматические обновления с контролем доверия
Поддерживать SHA-закрепления вручную — дело неблагодарное, поэтому мы так не делаем. Наша конфигурация Renovate расширяет пресет helpers:pinGitHubActionDigests и глобально устанавливает pinDigests: true. Когда выходит новая версия действия, Renovate открывает PR с обновлением SHA. Мы остаёмся на актуальных версиях, не прибегая к изменяемым ссылкам.
Renovate запускается как self-hosted бот по часовому расписанию, используя специальный GitHub App с точечными правами вместо персонального токена доступа. Включён флаг vulnerabilityAlerts, поэтому известные CVE в дереве зависимостей сразу превращаются в PR.
Мы недавно добавили задержку в Renovate, чтобы не подхватывать только что вышедшие релизы. С учётом нынешнего темпа атак на цепочки поставок именно в эти несколько дней обычно обнаруживают и отзывают скомпрометированные пакеты:
{
// Задержка зависимостей: пропускать версии, опубликованные менее 5 дней назад
"matchUpdateTypes": ["major", "minor", "patch"],
"minimumReleaseAge": "5 days"
},
{
"matchPackageNames": [
"actions/{/,}**", // Официальные действия GitHub
"docker/{/,}**", // Официальные действия Docker
"cilium/{/,}**", // Наша экосистема
"k8s.io/{/,}**", // Официальный Kubernetes
"sigs.k8s.io/{/,}**", // Kubernetes SIGs
"golang.org/x/{/,}**", // Экспериментальные пакеты Go
"github.com/golang/{/,}**", // Официальный орг Go
"github.com/prometheus/{/,}**",
"github.com/hashicorp/{/,}**",
"go.etcd.io/etcd/{/,}**",
// ...сокращено
],
"automerge": true,
"automergeType": "pr",
"groupName": "auto-merge-trusted-deps",
"reviewers": ["ciliumbot"]
}
Обновления из этого разрешительного списка автоматически мёрджатся после прохождения CI. Всё остальное требует ревью живого человека.
Процесс auto-approve добавляет ещё одну защитную проверку: он удостоверяется, что PR создан именно cilium-renovate[bot] и что запрос ревью действительно инициирован самим ботом, а не человеком, притворяющимся им:
if: ${{
github.event.pull_request.user.login == 'cilium-renovate[bot]' &&
(github.triggering_actor == 'cilium-renovate[bot]' ||
github.triggering_actor == 'auto-committer[bot]')
}}
Если условия не выполняются, автоодобрения не происходит.
Вендоринг Go-модулей
Все Go-зависимости вендорятся и фиксируются в репозитории. CI проверяет отсутствие расхождений между go.mod, go.sum и vendor/. Сборки воспроизводимы и не обращаются к внешним прокси модулей во время сборки, поэтому подменённый модуль на прокси до нас не доберётся. Мы также проводим проверки лицензий (go run ./tools/licensecheck), чтобы не допустить в дерево зависимостей пакеты с нежелательными лицензиями.
Стоит ли форкать действия в наш орг?
В теории — да. Если форкнуть каждое стороннее действие в пространство имён cilium/ и закрепиться на SHA собственного форка, взлом апстрима до нас не дойдёт. Ряд высокозащищённых проектов так и делает.
Мы от этого отказались — главным образом потому, что операционные издержки реальны, а выигрыш в безопасности меньше, чем кажется на первый взгляд:
-
Нагрузка на сопровождение. Мы используем десятки сторонних действий. Синхронизация форков с апстримными патчами безопасности превращается в полноценную задачу, а устаревший форк с непропатченными уязвимостями сам по себе является проблемой безопасности.
-
Пропущенные улучшения. Апстримные действия регулярно исправляют баги и добавляют функции безопасности. Форки создают трение при их подхватывании.
-
Усложнение Renovate. Нашему конвейеру обновлений пришлось бы отслеживать апстримные релизы, открывать PR к каждому форку, а затем обновлять потребляющие процессы. Цепочка удвоилась бы в длину.
Закрепление по SHA даёт именно ту гарантию неизменяемости, которая важна: конкретный коммит — это конкретный коммит, вне зависимости от того, в каком орге он хостится. В сочетании с Renovate, предлагающим обновления при выходе новых версий, мы получаем преимущества безопасности без операционных издержек. Если крупный провайдер действий будет компрометироваться систематически, форк наиболее рискованных из них станет разумной эскалацией — но до этой точки мы пока не дошли.
Тот же компромисс применим к Go-зависимостям
Вопрос «стоит ли форкать?» актуален и для дерева Go-зависимостей. Cilium использует сотни Go-модулей: клиентские библиотеки Kubernetes, gRPC, etcd, Prometheus и многое другое. Форкать и поддерживать их все нереалистично.
У Go здесь изначально лучше исходная позиция, чем у npm или PyPI: пути импорта явно включают источник (github.com/stretchr/testify), что полностью исключает класс атак Dependency Confusion (подмена зависимостей). Тайпсквоттинг, тем не менее, остаётся реальной угрозой. Исследование Михаэля Хенриксена обнаружило тайпсквоттинговые Go-пакеты в реальности — в том числе форк urfave/cli, зарегистрированный как utfave (одна переставленная буква), который передавал на удалённый сервер имя хоста, ОС и архитектуру. Заменить этот callback на обратный шелл — правка в одну строку.
И это ещё не худший сценарий. SolarWinds показал, что легитимный, широко доверенный вендор может оказаться со взломанным конвейером сборки — и тогда вредоносное ПО доставляется через штатные обновления. С любым Go-модулем возможно то же самое: злоумышленник, получив доступ к аккаунту мейнтейнера, публикует вредоносный релиз, прокси кеширует его, и все, кто запускает go get, его подтягивают. Именно поэтому мы вендорим: это переносит решение о доверии с момента сборки, где оно невидимо, на момент ревью, где человек видит diff.
Вендоринг — основная защита здесь. Тайпсквоттинговый путь импорта проявляется как diff в vendor/ при код-ревью, а не разрешается тихо из прокси модулей. Это не ловит опечатку в момент её появления (нужно, чтобы ревьюер заметил незнакомый путь в PR), но в сочетании с CODEOWNERS-гейтингом подход хорошо себя зарекомендовал.
Мы также осознанно подходим к выбору зависимостей. В конфигурации Renovate есть явный список отключённых зависимостей, которыми мы управляем вручную — либо потому что они требуют скоординированных обновлений (например, sigs.k8s.io/gateway-api вместе с conformance-тестами), либо потому что мы поддерживаем форк с проектными патчами (например, github.com/cilium/dns), либо потому что это наша собственная разработка и нам важен намеренный контроль над обновлениями (например, github.com/cilium/ebpf — не форк, а самостоятельная Go-библиотека под оргом Cilium). Изменения в vendor/ проверяются выделенной командой @cilium/vendor через тот же механизм CODEOWNERS.
Стоит привести одну Go-пословицу: «Небольшое копирование лучше небольшой зависимости». Мы воспринимаем её серьёзно — не как стилистическую рекомендацию. Мы периодически проводим аудит сторонних библиотек и активно уменьшаем дерево зависимостей. Если зависимость нужна только ради одной небольшой вспомогательной функции — заменяем её несколькими строками встроенного кода. Каждая удалённая зависимость никогда не будет скомпрометирована, дерево вендора становится меньше, а ревью будущих изменений зависимостей — проще. Эффект накапливается.
Выявление ошибок с помощью статического анализа
Даже при правильных политиках ошибки случаются. Добросовестный контрибьютор может добавить процесс без permissions:, или использовать ubuntu-latest вместо закреплённого раннера. Для обнаружения таких ситуаций до ревью мы используем статический анализ.
Там, где процессам нужен доступ на запись (подпись при релизе, OIDC для Cosign), они объявляют только конкретно нужный scope — например, id-token: write или contents: write. Там, где не нужен — объявляют permissions: read-all или permissions: {}, отказываясь от широких значений по умолчанию. Впрочем, полагаться на память мы не стали. CodeQL запускается на каждом пуше и PR с включённым правилом actions/missing-workflow-permissions, и процесс будет завален, если модифицированный файл процесса не задаёт права явно.
Сверх того, actionlint статически проверяет каждый файл процесса на синтаксические ошибки, небезопасные паттерны и неправильные конфигурации. Тот же lint-конвейер следит за проектными соглашениями: каждый джоб и шаг имеет поле name, ни один джоб не использует плавающий тег ubuntu-latest (мы закрепляем ubuntu-24.04), в файлах процессов нет замыкающих пробелов.
Один класс уязвимостей заслуживает особого упоминания — инъекция через выражения GitHub Actions (GitHub Actions expression injection). Синтаксис ${{ }} в YAML процессов — это текстовая подстановка, которая происходит до того, как строку увидит bash. Если злоумышленник контролирует подставляемое значение (заголовок PR, имя ветки), он может внедрить произвольные shell-команды через ;, $(…) или обратные кавычки. Bash понятия не имеет, откуда взялось значение. Исправление: присвоить значение переменной окружения и обращаться к ней как "$MY_VAR" в блоке run:, чтобы bash трактовал его как единую переменную вне зависимости от содержимого. Команда безопасности GitHub однажды сообщила нам об этом, и мы исправили все случаи. Это коварный баг: легко ввести, трудно заметить на ревью — именно поэтому важен статический анализ: и actionlint, и CodeQL сигнализируют об использовании ${{ }} в блоках run:, куда поступают недоверенные данные.
Защита учётных данных
Мы исходим из того, что любой отдельный уровень может дать сбой. Если CI-процесс когда-нибудь окажется скомпрометирован, важно: до чего злоумышленник вообще сможет дотянуться? Ответ должен быть: ни до чего существенного.
Жёсткие настройки по умолчанию
По умолчанию наши токены GITHUB_TOKEN имеют минимальные права на чтение для contents и packages. Процессам, которым нужно что-то большее, приходится явно это запрашивать — так процесс, забывший объявить права, не получает широкий доступ на запись по всей организации.
Разделение CI- и продакшен-учётных данных
Мы держим два отдельных набора учётных данных реестра за разными защищёнными окружениями GitHub:
-
CI-учётные данные позволяют пушить в реестр образов для разработки (
quay.io/cilium/*-ci) и доступны для CI-сборок. Даже если CI-процесс каким-то образом окажется скомпрометирован, эти учётные данные не позволят пушить в продакшен-теги образов. -
Продакшен-учётные данные защищены окружением
release, которое требует явного одобрения мейнтейнера перед тем, как процесс получит к ним доступ. Ни форк, ни feature-ветка, ни CI-сборка эти секреты не увидят. Только сборка релиза, запущенная по тегу и одобренная мейнтейнером, может к ним обратиться.
В худшем случае при компрометации CI злоумышленник сможет опубликовать вредоносный -ci-образ. Опубликовать quay.io/cilium/cilium:v1.x.x или docker.io/cilium/cilium:v1.x.x он не сможет. Учётных данных на раннере попросту нет.
Каждый вызов actions/checkout также устанавливает persist-credentials: false, чтобы GITHUB_TOKEN не оседал в git-конфиге раннера, откуда его мог бы подхватить последующий шаг.
Подписание и аттестация того, что мы выпускаем
Предыдущие разделы посвящены тому, как не допустить вредоносного кода в конвейер. Этот — о том, как дать пользователям возможность верифицировать то, что из него выходит.
Каждый выпускаемый нами образ контейнера (cilium, operator-*, hubble-relay, clustermesh-apiserver) подписывается с помощью Sigstore Cosign с беспарольным OIDC. Никаких долгоживущих ключей подписи, которые можно было бы украсть.
Переиспользуемое составное действие ведёт весь конвейер подписания:
.github/actions/cosign/action.yaml
- name: Install Cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Generate SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
artifact-name: sbom_${{ inputs.sbom_name }}.spdx.json
output-file: ./sbom_${{ inputs.sbom_name }}.spdx.json
image: ${{ inputs.image_tag }}
- name: Sign Container Image
shell: bash
run: cosign sign -y "${{ inputs.image }}"
- name: Attach SBOM Attestation
shell: bash
run: |
cosign attest -y \
--predicate "./sbom_${{ inputs.sbom_name }}.spdx.json" \
--type spdxjson \
"${{ inputs.image }}"
Это действие запускается при каждой сборке образа для релиза, а также для OCI-артефактов Helm-чарта. Инструкции по верификации описаны в документации Cilium.
Релизные сборки также выполняются внутри защищённых окружений (release, release-tool, release-helm), поэтому продакшен-учётные данные реестра закрыты правилами защиты окружения. Запустить релизную сборку из форка или feature-ветки невозможно.
Команда безопасности Cilium
Если вы когда-либо сообщали проекту об уязвимости (через GitHub security advisories или security@cilium.org), вы уже взаимодействовали с командой безопасности Cilium. Помимо разбора отчётов об уязвимостях, команда также занимается операционной стороной безопасности цепочки поставок:
-
Аудит и ротация учётных данных и прав доступа в GitHub-организации.
-
При необходимости — расследование инцидентов и аудиты.
-
Мониторинг паттернов в поступающих security-отчётах и тенденций в индустрии с целью предлагать меры в областях, где наша защищённость недостаточна.
Дополнительные уровни защиты
Несколько небольших, но важных моментов:
-
Неизменяемость тегов. После публикации GitHub-релиза теги и прикреплённые к нему артефакты не могут быть изменены. Настройка находится на странице репозитория Settings → Releases.
-
Обязательный DCO sign-off. Каждый коммит должен содержать строку
Signed-off-by. Наша конфигурация maintainers-little-helper блокирует мёрдж меткойdont-merge/needs-sign-offдо появления sign-off. -
Сторонние аудиты безопасности. Нас аудировала компания ADA Logics; опубликованная модель угроз находится в открытом доступе.
Что мы ещё дорабатываем
Мы провели аудит нашей директории .github/ на соответствие актуальным лучшим практикам (OpenSSF Scorecard, SLSA, рекомендации StepSecurity) и обнаружили ряд реальных пробелов. Крупнейшие из них:
-
Нет SLSA-провенанса (provenance). Каждый вызов
docker/build-push-actionустанавливаетprovenance: false. Мы подписываем образы с помощью Cosign, но не генерируем аттестации провенанса сборки по SLSA. Пользователи могут верифицировать, кто подписал образ, но не как он был собран. Принятиеslsa-framework/slsa-github-generator(или как минимум включение нативного провенанса BuildKit) стоит в планах. -
Нет проверки зависимостей на этапе PR. Мы полагаемся на
vulnerabilityAlertsот Renovate для выявления уязвимых зависимостей — но это реактивный подход. Подключение actions/dependency-review-action позволит перехватывать вредоносные или уязвимые новые зависимости до их мёрджа. -
Нет
govulncheckв CI. Мы используем фаззинг и линтинг, но официальный сканер уязвимостей Go пока не запускаем. Он проверяет, действительно ли наш код вызывает уязвимые функции — не просто присутствует ли уязвимый пакет вgo.sum. -
68 внутренних ссылок на
@main. Ряд conformance- и scale-test-процессов ссылается наcilium/cilium/.github/actions/set-commit-status@main— это изменяемая ссылка на ветку. Риск ниже, чем у стороннего тега, но это противоречит нашей политике закрепления по SHA. Планируем перенести все составные действия из cilium/cilium в отдельный репозиторий — тогда надобность в@mainотпадёт.
Ещё несколько замечаний из того же аудита:
-
Нет процесса OpenSSF Scorecard для непрерывного мониторинга состояния безопасности цепочки поставок.
-
Наш
SECURITY-INSIGHTS.ymlистёк в январе 2025 года и не обновлялся. (Мы обнаружили это, пока писали этот пост.) -
Нет шага
go mod verifyдля проверки целостности директории vendor по контрольным суммам изgo.sum.
Если любой из этих пунктов выглядит как хороший first issue и вы готовы отправить PR — мы его примем.
Дорожная карта GitHub Actions по безопасности на 2026 год и как она соотносится с нашей работой
В апреле 2026 года GitHub опубликовал свою дорожную карту по безопасности Actions, описывающую изменения на уровне платформы в трёх слоях: экосистема, поверхность атаки и инфраструктура. Читая её, мы ощутили: это признание тех самых проблем, с которыми мы работали годами, и сигнал, что платформа наконец нагоняет потребности крупных open source проектов. Вот как это соотносится с тем, что мы делаем сегодня.
Блокировка зависимостей: закрепление по SHA становится первоклассным механизмом
Мы закрепляем каждое действие по SHA и полагаемся на Renovate для актуализации закреплений, но у нас остаётся слепое пятно для транзитивных ссылок. Планируемая GitHub секция dependencies: в YAML процессов заблокирует все прямые и транзитивные зависимости по SHA коммита с проверкой хешей до начала исполнения. Это закроет пробел.
Управление исполнением через политики: централизация того, что мы сегодня задаём в каждом файле
Мы ограничиваем, кто может запускать процессы (разрешительный список Ariane), какие события разрешены (конфигурация на уровне процесса) и кто одобряет релизы (защищённые окружения). Всё это сейчас закодировано в десятках YAML-файлов плюс кастомный бот, и для полной картины нужно прочитать каждый файл.
Планируемые GitHub защиты исполнения процессов, основанные на rulesets, позволят централизованно задать эти контроли на уровне организации: какие акторы могут запускать процессы, какие события допустимы, на какие репозитории распространяются правила. Мы сможем запретить pull_request_target на уровне орга, кроме тех процессов, где намеренно спроектирован безопасный двухэтапный чекаут, — вместо того чтобы полагаться на ревью кода и CODEOWNERS.
Областные секреты: устранение неявного наследования
Разделение CI- и продакшен-учётных данных — один из наших сильнейших контролей, но в рамках конкретного окружения секреты по-прежнему доступны довольно широко: любой процесс в этом окружении может к ним обратиться.
Областные секреты позволят привязать учётные данные к конкретным путям процессов, веткам или даже отдельным переиспользуемым процессам. Релизный credential можно будет ограничить не просто окружением release, но конкретным файлом release.yaml, — тогда новый процесс, добавленный в это окружение (случайно или злоумышленником), не унаследует учётные данные. Это существенный шаг вперёд по сравнению с тем, что дают одни лишь защищённые окружения.
В дорожной карте также предусмотрено разделение управления секретами и прав на запись в репозиторий. Сегодня все, кто имеет write-доступ к репозиторию, могут управлять его секретами. GitHub планирует вынести управление секретами в отдельную кастомную роль — что соответствует принципу минимальных привилегий, который мы уже применяем к правам процессов, но пока не можем применить к администрированию секретов.
Нативный файрвол исходящего трафика
Планируемый GitHub нативный файрвол исходящего трафика ограничит сетевой доступ наружу с GitHub-hosted раннеров. Он работает за пределами ВМ раннера на уровне L7, поэтому остаётся неизменяемым даже если злоумышленник получил root внутри раннера. Организации смогут задавать разрешённые домены, IP-диапазоны и HTTP-методы; всё остальное будет заблокировано.
Для Cilium это менее критично, чем остальное. Наши наиболее чувствительные с точки зрения безопасности процессы (релизные сборки, подпись образов) уже работают с изоляцией учётных данных и минимальными правами, что ограничивает возможный ущерб от скомпрометированного шага даже при неограниченном сетевом доступе. Составление точного egress-разрешительного списка для проекта, взаимодействующего с реестрами контейнеров, прокси Go-модулей, облачными API и Sigstore, потребует значительных усилий. Публичный предварительный просмотр ожидается через 6–9 месяцев — тогда и оценим.
Actions Data Stream: наблюдаемость CI
Наши процессы производят логи, но централизованной телеметрии по ним у нас нет. Если процесс начнёт вести себя странно (разрешать неожиданные зависимости, работать дольше обычного, делать подозрительные сетевые вызовы) — мы заметим это лишь вручную.
Actions Data Stream будет доставлять телеметрию исполнения в режиме близком к реальному времени во внешние системы (S3, Azure Event Hub): детали выполнения процессов, паттерны разрешения зависимостей и в перспективе сетевую активность. Для open source проекта с сотнями запусков процессов в день — это значимое слепое пятно.
В чём суть
Безопасность цепочки поставок — это в основном практика постоянного вопроса: «а что будет, если то, чему я доверяю, окажется скомпрометировано?» — и добавления уровня, ограничивающего последствия, когда это всё же произойдёт.
Мы старались выстроить эшелонированную защиту (defense in depth): контроль доступа, чтобы только доверенные люди запускали сборки; закреплённые дайджесты, чтобы взломанный тег до нас не добрался; минимальные права, чтобы вредоносное действие не могло похитить секреты; изоляция учётных данных, чтобы CI никогда не дотянулся до продакшена; и подписи, чтобы пользователи могли проверить то, что запускают.
Ничто из этого не делает нас неуязвимыми. Но безопасность через неизвестность — не настоящая безопасность, и обратное тоже верно: чем открытее open source проекты делятся своими механизмами защиты, тем выше коллективная планка для злоумышленников. Мы показали вам наше — включая то, что пока не на должном уровне. Если вы ведёте CI/CD для open source проекта и решили что-то, что у нас пока не решено, — откройте issue, напишите свой пост или приходите к нам в Slack. Цепочка поставок open source сильна ровно настолько, насколько силён её самый слабый проект, — и укрепить её можно только сообща.
Полезные ресурсы: OpenSSF Scorecard · SLSA Framework · Sigstore · StepSecurity Harden Runner · GitHub Actions Security Hardening · GitHub Actions 2026 Security Roadmap