- BrainTools - https://www.braintools.ru -

Cilium работает в сетевом пути уровня ядра в миллионах Kubernetes-pod’ов: от облачных провайдеров до собственных кластеров банков и телекомов. Если бы кто-то скомпрометировал сборочный пайплайн Cilium, зона поражения была бы сопоставима с инцидентом SolarWinds, но в облачно-нативной экосистеме. Поэтому подход проекта к безопасности CI/CD интересен не только мейнтейнерам других опенсорс-проектов: те же паттерны полезны любой команде, которая собирает прод-артефакты в GitHub Actions. Команда VK Cloud [1] перевела статью с конкретными YAML-конфигами, дизайн-решениями и честным списком того, что у Cilium пока не сделано.
Последние двенадцать месяцев выдались тяжёлыми для цепочки поставок open source. Axios скомпрометировали в npm — внутри нормальных на вид релизов поставлялся троян удалённого доступа (RAT). PyPI-пакет LiteLLM угнали, чтобы воровать переменные окружения. Опубликовали тайпсквоттинговые форки Trivy — расчёт на тех, кто опечатается при наборе go install. И канонический пример — взлом SolarWinds 2020 года — до сих пор остаётся поучительной историей, к которой все возвращаются: атакующие проникли в систему сборки и распространили вредоносное ПО через обычные обновления Orion примерно для 18 000 организаций, включая федеральные агентства США, NATO и Microsoft. Вредонос бездействовал месяцами. Взлом не замечали большую часть года.
Cilium работает в сетевом пути уровня ядра миллионов Kubernetes-pod’ов. Если бы нашу цепочку поставок скомпрометировали, зона поражения была бы немаленькой. Усиление защиты от такого сценария — постоянная работа, и мы хотим подробно записать, что именно делаем. Большая часть описанного не привязана к Cilium: любой open source проект, запускающий CI/CD на GitHub Actions, может применить эти паттерны. Мы также отметили места, где пока не дотягиваем, — вдруг что-то послужит полезной отправной точкой для других.
Если нет времени читать целиком, вот что Cilium делает для укрепления цепочки поставок сегодня, по уровням пайплайна:
|
Уровень |
Контроль |
Что делает |
|
Кто запускает сборки |
Контроль триггеров через Ariane |
Только верифицированные члены организации могут запускать CI-workflow из комментариев к PR, по явному списку разрешённых workflow. |
|
Какой код выполняет CI |
Двухфазные чекауты для |
Доверенный код ( |
|
Кто ревьюит изменения CI |
Гейты |
Всё под |
|
Какие зависимости подтягивает CI |
Закреплённые по SHA actions и образы |
Каждая ссылка |
|
Какие Go-модули попадают в бинарь |
Вендорённые Go-зависимости |
Всё закоммичено в |
|
Как вообще могут выглядеть workflow |
Статический анализ workflow |
CodeQL требует обязательного указания |
|
Какие учётные данные доступны |
Изоляция учётных данных CI и production |
Учётные данные CI могут пушить только в |
|
Что могут проверить потребители |
Подписанные релизы |
Каждый релизный образ и Helm-чарт подписан Sigstore Cosign через keyless OIDC, с прикреплёнными аттестациями SBOM (Software Bill of Materials, спецификация состава ПО). |
|
Где мы ещё не дотягиваем |
Пробелы, которые закрываем |
Нет SLSA-провенанса, нет ревью зависимостей во время PR, нет govulncheck в CI, и несколько внутренних ссылок |
Дальше каждая строка разобрана подробнее — с дизайн-решениями и тем, что мы намеренно решили не делать (например, форкать каждый сторонний action в свою организацию).
Первый вопрос в любой истории про CI цепочки поставок: кто может запустить сборку и какой код она выполняет? Множество компрометаций CI начинаются именно здесь — с обмана системы, чтобы она запустила контролируемый атакующим код с повышенными привилегиями.
Ariane — это GitHub-бот, написанный нами внутри, чтобы запускать (dispatch) CI-workflow из комментариев к PR. Когда мейнтейнер пишет /test или /ci-eks в пул-реквесте, Ariane проверяет, что комментатор состоит в команде organization-members, определяет, какие workflow запускать (включая зависимости — например, тесты, которым сначала нужна свежая сборка образа), и запускает их через workflow_dispatch.
Интересная часть — это allow-list (список разрешённых). Запускать workflow могут только верифицированные члены организации, а набор доступных workflow перечислен в конфиге вручную:
# .github/ariane-config.yaml
allowed-teams:
- organization-members
triggers:
/tests*:
workflows:
- conformance-aws-cni.yaml
- conformance-clustermesh.yaml
- conformance-eks.yaml
# ...and so on
depends-on:
- /build-images-dependency
/ci-aks:
workflows:
- conformance-aks.yaml
depends-on:
- /build-images-dependency
Случайный внешний комментатор с /test в PR будет проигнорирован. Он не запустит дорогие conformance-наборы облачных провайдеров и не сожжёт наши CI-минуты.
Когда кто-то открывает PR, нам нужно собрать его код, но доверять ему нельзя. Это классическая проблема pull_request_target. Мы избегаем pull_request_target, где это возможно, но нескольким workflow он всё ещё нужен — и мы оборачиваем их защитными мерами.
Workflow сборки образа — канонический пример. Он делит чекаут на два:
# .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
# ...trusted setup steps run here, including loading composite actions...
# Warning: since this is a privileged workflow, subsequent workflow job
# steps must take care not to execute untrusted code.
- name: Checkout pull request branch (NOT TRUSTED)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
ref: ${{ steps.tag.outputs.sha }}
Первый чекаут забирает базовую ветку — код, который уже отревьюен и влит, — чтобы можно было загрузить composite actions, скрипты и логику подписания Cosign из проверенного источника. И только после этого workflow выполняет checkout head PR — причём этот чекаут используется чисто как контекст сборки для docker build. Ничто из ветки PR не выполняется как скрипт.
Отчёты о безопасности на этот паттерн приходят регулярно. Автоматизированные сканеры и доброжелательно настроенные исследователи видят «pull_request_target плюс второй чекаут» и помечают это как уязвимость. В типичном случае они правы. В нашем — workflow намеренно спроектирован так, чтобы паттерн оставался безопасным:
Никакие шаги run: не выполняют скрипты из недоверенного чекаута. Каждый shell-блок после второго чекаута написан инлайн в YAML workflow (проверки использования диска, копирование файлов, вывод digest). Ничего не подключается из ветки PR.
Никакие composite actions тоже не загружаются из недоверенного чекаута. Все composite actions (set-runtime-image, cosign, set-env-variables) приходят из доверенного чекаута базовой ветки или из сохранённой директории ../cilium-base-branch/. Мы также работаем над переносом этих composite actions в выделенный репозиторий, чтобы для их запуска вообще не приходилось делать чекаут исходного кода.
Docker BuildKit действительно выполняет недоверенный Dockerfile — в этом и весь смысл сборки CI-образа из PR. BuildKit работает в изоляции: ни переменных окружения GitHub Actions, ни секретов репозитория, ни доступа к Docker credential store раннера. В аргументах сборки, которые мы передаём, нет секретов — только ссылка на runtime-образ и имя варианта оператора.
Недоверенные данные попадают ровно в один доверенный action. Файл runtime-image*.txt из PR подаётся в доверенный action set-runtime-image, который проверяет, что ссылка на образ начинается с quay.io/cilium/, и удаляет переводы строк — чтобы атакующий не мог протащить инъекцию GITHUB_ENV. Перенаправить сборку на что-либо вне namespace Cilium невозможно.
В области видимости — только учётные данные CI. Docker login использует QUAY_USERNAME_CI / QUAY_PASSWORD_CI, которые могут пушить только в -ci реестр разработки. Production-учётных данных на раннере нет вообще.
Худший возможный исход скомпрометированной сборки PR — вредоносный CI-образ в реестре разработки. Это та же зона поражения, которую несёт любая CI-система, собирающая код контрибьюторов. Мы ценим каждый отчёт и внимательно читаем все, но этот паттерн намеренный.
Мы довольно сильно опираемся на CODEOWNERS, чтобы изменения всегда попадали к тем, кто лучше всего разбирается в этой области. Для CI-конфигурации это означает, что всё под .github/ принадлежит @cilium/github-sec (нашей CI-команде, отвечающей за безопасность) плюс @cilium/ci-structure, а workflow auto-approve.yaml принадлежит @cilium/cilium-maintainers:
# CODEOWNERS
/.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-пайплайн без явного ревью от команды, которая отвечает за его безопасность.
Как только вы контролируете, кто запускает сборки, следующий вопрос — какой код эти сборки подтягивают. Закреплённый workflow, который подтягивает скомпрометированную зависимость, остаётся скомпрометированным workflow.
Самое выгодное, что любой проект может сделать здесь, — перестать доверять изменяемым тегам.
Каждая директива uses: в наших workflow-файлах ссылается на actions по полному 40-символьному SHA коммита, с человекочитаемой версией в комментарии в конце (закрепление action по полному SHA коммита, SHA pinning):
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Если кто-то скомпрометирует тег v6 в actions/checkout и форс-пушнет вредоносный код, наши workflow его не подтянут. Они закреплены на конкретный коммит. Та же история для каждого стороннего action, который мы используем: docker/build-push-action, sigstore/cosign-installer, golangci/golangci-lint-action, ещё дюжины. Контейнерные образы, которые используются напрямую в шагах workflow, мы закрепляем так же — по @sha256: digest, чтобы даже инструменты, запускаемые внутри CI, адресовались по содержимому.
У закрепления есть одно раздражающее слепое пятно — транзитивные зависимости. Когда мы закрепляем actions/checkout@de0fac2e..., мы точно знаем, какой код выполняется для этого action. Но если actions/checkout сам ссылается на другой action по тегу (uses: some-org/some-helper@v1), разрешение происходит во время выполнения и невидимо для нас. Атакующий, скомпрометировавший вложенную зависимость, всё ещё может дотянуться до нашего пайплайна.
Исправление на подходе: блокировку зависимостей на уровне workflow анонсировали в дорожной карте безопасности GitHub Actions на 2026 год. В YAML workflow добавится секция dependencies:, которая блокирует все прямые и транзитивные зависимости actions по SHA коммита — аналогично тому, что go.mod + go.sum делают для Go. Подключим, как только появится.
Поддерживать SHA-пины вручную было бы мучительно, поэтому мы этого не делаем. Конфигурация Renovate расширяет пресет helpers:pinGitHubActionDigests и устанавливает pinDigests: true глобально. Когда выходит новая версия action, Renovate открывает PR с обновлением SHA. Мы остаёмся актуальными и не откатываемся к изменяемой ссылке.
Renovate работает как self-hosted бот по часовому расписанию, через выделенное GitHub App с гранулярными разрешениями вместо персонального токена доступа. vulnerabilityAlerts включены, так что известные CVE в дереве зависимостей сразу превращаются в PR.
Недавно мы добавили период ожидания (cooldown) Renovate, чтобы не подхватывать совсем новые релизы в момент их появления. Учитывая текущий темп атак на цепочку поставок, нескольких дней обычно хватает, чтобы скомпрометированный пакет заметили и отозвали (yanked):
# .github/renovate.json5
{
// Dependency cooldown: skip versions published less than 5 days ago
"matchUpdateTypes": ["major", "minor", "patch"],
"minimumReleaseAge": "5 days"
},
{
"matchPackageNames": [
"actions/{/,}**", // GitHub's official actions
"docker/{/,}**", // Official Docker actions
"cilium/{/,}**", // Our own ecosystem
"k8s.io/{/,}**", // Kubernetes official
"sigs.k8s.io/{/,}**", // Kubernetes SIGs
"golang.org/x/{/,}**", // Go experimental
"github.com/golang/{/,}**", // Go official org
"github.com/prometheus/{/,}**",
"github.com/hashicorp/{/,}**",
"go.etcd.io/etcd/{/,}**",
// ...trimmed
],
"automerge": true,
"automergeType": "pr",
"groupName": "auto-merge-trusted-deps",
"reviewers": ["ciliumbot"]
}
Обновления из этого списка разрешённых автоматически мержатся после прохождения CI. Всё остальное требует ревью человека.
Workflow 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-зависимости вендорятся и коммитятся в репозиторий. CI проверяет, что нет расхождений между go.mod, go.sum и vendor/. Сборки воспроизводимы и не общаются с внешними прокси модулей во время сборки, так что подделанный модуль на прокси до нас никогда не дойдёт. Мы также запускаем проверки лицензий (go run ./tools/licensecheck), чтобы держать зависимости с нежелательными лицензиями вне дерева.
В теории — да. Если бы мы форкнули каждый сторонний action в cilium/ и закрепили его на SHA нашего форка, компрометация upstream до нас бы не дошла вообще. Некоторые проекты с высокими требованиями к безопасности так и делают.
Мы решили не делать этого — в основном потому, что операционная стоимость реальна, а выигрыш в безопасности меньше, чем кажется на первый взгляд:
Нагрузка на поддержку. Мы используем десятки сторонних actions. Синхронизация форков с upstream-патчами безопасности становится постоянной заботой, а устаревший форк с непропатченными уязвимостями сам по себе — проблема безопасности.
Упущенные улучшения. Upstream-actions регулярно исправляют баги и поставляют функции безопасности. Форки добавляют трение и мешают это подхватывать.
Сложность Renovate. Пайплайну обновлений пришлось бы отслеживать upstream-релизы, открывать PR против каждого форка и потом обновлять потребляющие workflow. Цепочка удваивается.
SHA-пиннинг даёт нам действительно важную гарантию неизменности: конкретный коммит — это конкретный коммит, независимо от того, в чьей организации он лежит. В сочетании с Renovate, который предлагает обновления по мере выхода новых версий, мы получаем выигрыш в безопасности без операционного налога. Если бы крупный поставщик actions неоднократно компрометировался, форкнуть высокорискованные — это разумная эскалация, но до этого дело пока не дошло.
Вопрос «должны ли мы это форкнуть?» применим и к нашему дереву Go-зависимостей. Cilium подтягивает сотни Go-модулей: клиентские библиотеки Kubernetes, gRPC, etcd, Prometheus, и прочее. Форкать и поддерживать их все нереалистично.
У Go стартовая позиция немного лучше, чем у npm или PyPI, потому что пути импорта явно включают источник (github.com/stretchr/testify) — это полностью устраняет класс атак путаницы зависимостей. Тайпсквоттинг, однако, всё ещё реальная угроза. Исследование Michael Henriksen нашло тайпсквоттинговые 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-пословица, которую стоит процитировать: «Немного копирования лучше, чем немного зависимости». Мы относимся к этому серьёзно — за пределами стиля. Мы периодически аудитим сторонние библиотеки и активно сокращаем дерево. Если зависимость существует только для маленькой утилитарной функции, мы заменяем её несколькими строками, скопированными инлайн. Каждая удалённая зависимость — это та, которую никогда не смогут скомпрометировать, дерево vendor становится меньше, и ревью будущих изменений зависимостей упрощается. Эффект накапливается.
Даже с правильными политиками ошибки [3] случаются. Доброжелательно настроенный контрибьютор может добавить workflow без permissions: или использовать ubuntu-latest вместо закреплённого раннера. Мы используем статический анализ, чтобы ловить такое до ревью.
Где workflow нужен write-доступ (подписание релизов, OIDC для Cosign), там декларируется только конкретная область — например, id-token: write или contents: write. Где не нужен — там permissions: read-all или permissions: {}, чтобы явно отключить более широкие умолчания. Но на память [4] мы при этом не полагаемся. CodeQL запускается на каждом push и PR с включённым правилом actions/missing-workflow-permissions, и workflow валит любой изменённый workflow-файл, в котором permissions не указаны явно.
Поверх этого actionlint статически проверяет каждый workflow-файл на синтаксические ошибки, небезопасные паттерны и неправильные конфигурации. Тот же lint-пайплайн ещё и требует обязательного соблюдения соглашений проекта: каждый job и step имеет имя, ни один job не использует плавающий тег раннера ubuntu-latest (мы закрепляем на ubuntu-24.04), нет trailing whitespace в workflow-файлах.
Один класс уязвимости стоит выделить — инъекция выражений GitHub Actions. Синтаксис ${{ }} в YAML workflow — это текстовая подстановка, которая происходит до того, как bash вообще увидит строку. Если атакующий контролирует подставляемое значение (заголовок PR, имя ветки), он может инъектировать произвольные shell-команды через ;, $(...) или backticks. Bash понятия не имеет, откуда пришло значение. Исправление — сначала присвоить значение переменной окружения и ссылаться на неё как "$MY_VAR" в блоке run:, чтобы bash трактовал её как одну переменную независимо от содержимого. Команда по безопасности GitHub сообщила нам об этом некоторое время назад, и мы исправили каждый случай. Это тонкий баг — легко внести и сложно заметить на ревью, поэтому статический анализ так важен: и actionlint, и CodeQL помечают использование ${{ }} в блоках run:, куда втекает недоверенный ввод.
Мы исходим из того, что любой отдельный уровень может отказать. Если CI-workflow всё-таки скомпрометируют, вопрос становится таким: до чего атакующий реально доберётся? Ответ должен быть: ни до чего, что имеет значение.
По умолчанию наши GITHUB_TOKEN ограничены минимальными read-разрешениями на contents и packages. Workflow, которым нужно больше, должны явно включить (opt in) дополнительные права — так что workflow, в котором забыли указать permissions, не оказывается с широким org-wide write-доступом.
Мы держим два отдельных набора учётных данных реестра за отдельными защищёнными окружениями GitHub:
Учётные данные CI могут пушить в наш реестр образов разработки (quay.io/cilium/*-ci) и доступны CI-сборкам. Даже если CI-workflow каким-то образом скомпрометируют, эти учётные данные не смогут пушить в production-теги образов.
Production-учётные данные находятся за окружением release, которое требует явного одобрения мейнтейнера, прежде чем запуск workflow сможет их коснуться. Ни форк, ни 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 через keyless OIDC. Нет долгоживущих ключей подписания, которые можно украсть.
Переиспользуемый composite action обрабатывает пайплайн подписания:
# .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), так что учётные данные production-реестра ограничены правилами защиты окружений. Запустить релизную сборку из форка или feature-ветки нельзя.
Если вы когда-либо сообщали о проблеме безопасности проекту (через GitHub security advisories или security@cilium.org [5]), вы уже взаимодействовали с Security-командой Cilium. Помимо первичной обработки отчётов об уязвимостях, команда ведёт и операционную сторону безопасности цепочки поставок:
Аудит и ротация учётных данных и разрешений по всей GitHub-организации.
Расследования инцидентов и аудиты — когда это необходимо.
Мониторинг паттернов в наших обращениях по безопасности и развития индустрии — чтобы предлагать смягчения и контроли в областях, где уровень защиты слабее.
Несколько меньших вещей, которые стоит упомянуть:
Неизменность тегов. Как только 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-провенанса. Каждый вызов docker/build-push-action устанавливает provenance: false. Мы подписываем образы Cosign, но не генерируем аттестации происхождения (provenance) сборки SLSA. Потребители могут проверить, кто подписал образ, но не как он был собран. Принятие slsa-framework/slsa-github-generator (или как минимум включение нативного provenance BuildKit) — в списке.
Нет ревью зависимостей во время PR. Мы полагаемся на vulnerabilityAlerts Renovate, чтобы помечать известные уязвимые зависимости, но это реактивно. Подключение actions/dependency-review-action ловило бы вредоносные или уязвимые новые зависимости до того, как они вольются.
Нет govulncheck в CI. Мы запускаем фаззинг и линтер, но пока не запускаем официальный сканер уязвимостей Go, который проверяет, действительно ли наш код вызывает уязвимые функции, а не просто появляется ли уязвимый пакет в go.sum.
68 внутренних ссылок @main. Множество conformance- и scale-test-workflow ссылаются на cilium/cilium/.github/actions/set-commit-status@main — это изменяемая ссылка на ветку. Риск ниже, чем у стороннего тега, но это непоследовательно по отношению к нашей политике SHA-пиннинга. План — перенести все composite actions из cilium/cilium в выделенный репозиторий, что устранит необходимость в @main.
Несколько меньших пунктов из того же аудита:
Нет workflow OpenSSF Scorecard для непрерывного мониторинга здоровья цепочки поставок.
Наш SECURITY-INSIGHTS.yml истёк в январе 2025 и не был обновлён. (Мы фактически заметили это, пока писали этот пост.)
Нет шага go mod verify для валидации целостности директории vendor против контрольных сумм go.sum.
Если что-то из этого выглядит как good first issue и вы хотите прислать PR — возьмём.
В апреле 2026 года GitHub опубликовал дорожную карту безопасности Actions — изменения на уровне платформы по трём уровням: экосистема, поверхность атаки и инфраструктура. Чтение ощущалось как валидация проблем, которые мы обходили годами, и реальный сигнал, что платформа наконец-то догоняет то, что нужно крупным open source проектам. Вот как она соотносится с тем, что делаем мы сегодня.
Мы закрепляем каждый action по SHA и опираемся на Renovate, чтобы держать пины актуальными, но слепое пятно для транзитивных ссылок осталось. Запланированная в GitHub секция dependencies: в YAML workflow заблокирует все прямые и транзитивные зависимости по SHA коммита, с верификацией хешей до старта выполнения. Это закрывает пробел.
Мы ограничиваем, кто может запускать workflow (allow-list Ariane), какие события разрешены (per-workflow конфигурация) и кто может одобрять релизы (защищённые окружения). Сейчас всё это закодировано в десятках YAML-файлов плюс кастомный бот, и аудит полной картины означает прочитать каждый файл.
Запланированные GitHub защиты выполнения workflow, построенные на rulesets, позволят определить эти контроли централизованно — на уровне организации: какие акторы могут запускать workflow, какие события разрешены, к каким репозиториям применяются правила. Можно будет запретить pull_request_target по всей организации, кроме workflow, где мы намеренно спроектировали безопасный двухфазный чекаут, — вместо того чтобы полагаться на ревью кода и CODEOWNERS для его принудительного применения.
Изоляция учётных данных CI и production — один из самых сильных наших контролей, но внутри одного окружения секреты всё ещё ограничены довольно широко: любой workflow, выполняющийся в этом окружении, может до них добраться.
Scoped-секреты позволят привязать учётные данные к конкретным путям workflow, веткам или даже отдельным переиспользуемым workflow. Учётные данные релиза можно будет ограничить не просто окружением release, а конкретным файлом workflow release.yaml — так что новый workflow, добавленный в это окружение (случайно или атакующим), не унаследует учётные данные. Это значимый шаг за пределы того, что предоставляют одни только защищённые окружения.
Дорожная карта также отделяет управление секретами от write-доступа к репозиторию. Сегодня любой с write-доступом к репозиторию может управлять его секретами. GitHub планирует перенести управление секретами в выделенную кастомную роль — это соответствует принципу минимальных привилегий, который мы уже применяем к разрешениям workflow, но пока не можем применить к администрированию секретов.
Запланированный нативный egress-файрвол GitHub ограничит исходящий сетевой доступ из раннеров, хостимых GitHub. Он работает за пределами VM раннера на L7, поэтому остаётся неизменным, даже если атакующий получит root внутри раннера. Организации будут определять разрешённые домены, диапазоны IP и HTTP-методы, всё остальное блокируется.
Для Cilium это менее критично, чем остальное. Наши самые критичные для безопасности workflow (релизные сборки, подписание образов) уже выполняются с изоляцией учётных данных и разрешениями минимальных привилегий — это ограничивает то, что мог бы сделать скомпрометированный шаг даже с неограниченным сетевым доступом. Построить точный egress-allow-list (список разрешённых) для проекта, который общается с контейнерными реестрами, прокси Go-модулей, облачными API и Sigstore, — это значительный кусок работы. Публичный preview ожидается через 6–9 месяцев, тогда и оценим.
Наши workflow пишут логи, но централизованной телеметрии для них у нас нет. Если workflow начнёт вести себя странно (разрешать неожиданные зависимости, выполняться дольше обычного, делать странные сетевые вызовы), нам пришлось бы заметить это вручную.
Actions Data Stream будет доставлять телеметрию выполнения в близком к реальному времени во внешние системы (S3, Azure Event Hub) — детали выполнения workflow, паттерны разрешения зависимостей и в конечном счёте сетевую активность. Для open source проекта с сотнями запусков workflow в день это слепое пятно, которое стоит закрыть.
Безопасность цепочки поставок — это в основном практика многократного вопроса «что если то, чему я доверяю, будет скомпрометировано?» и добавления уровня, который ограничивает зону поражения, когда это произойдёт.
Мы попытались построить эшелонированную защиту:
контроли доступа, чтобы только доверенные люди могли запускать сборки;
закреплённые digest’ы, чтобы скомпрометированный тег нас не достал;
разрешения минимальных привилегий, чтобы мошеннический action не смог украсть секреты;
изоляция учётных данных, чтобы CI никогда не касался production;
подписи, чтобы пользователи могли проверить, что они запускают.
Ничто из этого не делает нас неуязвимыми. Но безопасности через сокрытие по-настоящему не существует, и обратное тоже верно: чем больше open source проектов открыто делятся защитами, тем выше коллективная планка для атакующих. Мы показали свои — включая части, которые ещё не очень хороши. Если вы запускаете CI/CD для open source проекта и решили что-то, чего не решили мы, — откройте issue, напишите свой пост или напишите нам в Slack. Open source цепочка поставок настолько сильна, насколько силён её самый слабый проект, и единственный способ её укрепить — вместе.
Автор: levashove
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/30931
URLs in this post:
[1] VK Cloud: https://cloud.vk.com/
[2] логика: http://www.braintools.ru/article/7640
[3] ошибки: http://www.braintools.ru/article/4192
[4] память: http://www.braintools.ru/article/4140
[5] security@cilium.org: mailto:security@cilium.org
[6] Источник: https://habr.com/ru/companies/vktech/articles/1040540/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1040540
Нажмите здесь для печати.