Глава 15. Безопасность CI/CD
«Если злоумышленник контролирует вашу пайплайн-систему сборки, он контролирует всё, что вы поставляете пользователям.» — NSA/CISA, «Defending Continuous Integration/Continuous Delivery (CI/CD) Environments», июнь 2023
CI/CD-пайплайн — это привилегированная инфраструктура: он имеет доступ к исходному коду, секретам, реестрам артефактов и облачным аккаунтам. В модели Zero Trust каждый этап пайплайна — потенциальный вектор атаки, требующий верификации идентичности, минимальных привилегий и аудируемости.
15.1. Зачем: CI/CD как привилегированный вектор
Атаки на CI/CD-пайплайны
| Инцидент | Дата | Вектор | Последствия |
|---|---|---|---|
| Codecov Bash Uploader | Янв–Апр 2021 | Модификация скрипта загрузки через утечку credentials | Эксфильтрация CI-переменных 29 000 клиентов |
| CircleCI | Дек 2022 — Янв 2023 | Infostealer → кража 2FA session cookie инженера | Утечка customer secrets (env vars, API tokens, SSH keys) |
| tj-actions/changed-files (CVE-2025-30066) | Мар 14, 2025 | Компрометация PAT @tj-actions-bot, модификация тегов | Извлечение секретов из памяти Runner, 23 000+ репозиториев |
| reviewdog/action-setup (CVE-2025-30154) | Мар 2025 | Скомпрометированный контрибьютор с write-доступом | Часть цепочки tj-actions, дамп секретов в логи |
Общий паттерн: злоумышленник получает доступ к системе сборки и через неё — к секретам, артефактам и облачным ресурсам. NSA/CISA выделяют три ключевых сценария угроз (CSI «Defending CI/CD Environments», 28 июня 2023):
- Компрометация учётных данных разработчика для доступа к репозиторию.
- Supply chain compromise библиотеки или образа в пайплайне.
- Компрометация самой CI/CD-среды — модификация конфигов, инъекция зависимостей.
Принцип: нулевое доверие к пайплайну
Каждый компонент CI/CD должен следовать принципам Zero Trust:
15.2. OIDC-федерация: CI/CD без секретов
15.2.1. Проблема статических credentials
Традиционно CI/CD-системы хранят долгоживущие секреты (AWS Access Keys, GCP Service Account JSON, Azure Client Secret) в переменных среды. Это создаёт риски:
- Секреты не ротируются автоматически.
- Утечка одного секрета даёт постоянный доступ к облаку.
- Нет связи между конкретным запуском и использованным credential.
OIDC-федерация решает эту проблему: CI-система выступает поставщиком идентичности (IdP), а облачный провайдер обменивает короткоживущий OIDC-токен на временные credentials.
15.2.2. GitHub Actions OIDC
GitHub Actions предоставляет OIDC-токен через встроенный провайдер https://token.actions.githubusercontent.com.
Ключевые характеристики токена:
| Параметр | Значение |
|---|---|
| Время жизни | 5 минут (только для обмена, не для прямого использования) |
| Обязательное разрешение | permissions: id-token: write |
| Issuer | https://token.actions.githubusercontent.com |
| Subject (примеры) | repo:ORG/REPO:environment:prod, repo:ORG/REPO:ref:refs/heads/main |
| Аудитория (по умолчанию) | URL организации/владельца |
Ключевые claims:
repository— полное имя репозиторияrepository_owner— организацияref— ветка/тег, вызвавший запускenvironment— среда деплоя (если задана)job_workflow_ref— ссылка на reusable workflow (для SLSA L3)runner_environment—github-hostedилиself-hosted
Кастомизация sub claim (REST API):
# На уровне организации
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
https://api.github.com/orgs/MY-ORG/actions/oidc/customization/sub \
-d '{"include_claim_keys": ["repo", "context", "job_workflow_ref"]}'15.2.3. Настройка для AWS
Шаг 1: OIDC-провайдер в IAM
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1Примечание: Один OIDC-провайдер на issuer URL на аккаунт.
Шаг 2: IAM Role с trust policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:MY-ORG/MY-REPO:environment:production"
}
}
}
]
}Безопасность: Используйте
StringEqualsдляaudи максимально специфичныйsub—environment:productionвместо*. Паттернrepo:ORG/*даёт доступ любому репозиторию в организации.
Шаг 3: Workflow
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
# role-duration-seconds: 900 # по умолчанию 3600
- run: aws sts get-caller-identitySTS-сессия по умолчанию — 1 час, диапазон 15 минут – 12 часов.
15.2.4. Настройка для GCP
Workload Identity Federation:
# 1. Пул Workload Identity
gcloud iam workload-identity-pools create "github-pool" \
--location="global" --project="$PROJECT_ID"
# 2. OIDC-провайдер
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--location="global" \
--workload-identity-pool="github-pool" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,\
attribute.repository=assertion.repository" \
--attribute-condition="assertion.repository=='MY-ORG/MY-REPO'"
# 3. Привязка к сервисному аккаунту
gcloud iam service-accounts add-iam-policy-binding \
deploy-sa@$PROJECT_ID.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/\
projects/$PROJECT_NUMBER/locations/global/\
workloadIdentityPools/github-pool/\
attribute.repository/MY-ORG/MY-REPO"# GitHub Actions workflow
- uses: google-github-actions/auth@v3
with:
workload_identity_provider: "projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
service_account: "deploy-sa@PROJECT_ID.iam.gserviceaccount.com"15.2.5. Настройка для Azure
Azure использует Entra ID Federated Credentials:
- Зарегистрируйте приложение (или создайте Managed Identity) в Entra ID.
- Добавьте federated credential с issuer
https://token.actions.githubusercontent.com. - Subject должен точно совпадать с
subclaim из GitHub-токена.
# GitHub Actions workflow
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}Flexible Federated Identity Credentials (preview, март 2025): поддержка wildcards в
subиjob_workflow_refclaims. Пример:claims['sub'] matches 'repo:contoso/*:environment:*'. Доступно только для Application objects (не для Managed Identity).
15.2.6. GitLab CI/CD OIDC
GitLab использует ключевое слово id_tokens (с GitLab 15.7). CI_JOB_JWT deprecated в 15.9, удалён в 17.0.
deploy:
id_tokens:
AWS_TOKEN:
aud: https://sts.amazonaws.com
VAULT_TOKEN:
aud: https://vault.example.com
script:
- >
export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
$(aws sts assume-role-with-web-identity
--role-arn arn:aws:iam::ACCOUNT:role/GitLabRole
--role-session-name "gitlab-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
--web-identity-token "$AWS_TOKEN"
--query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]"
--output text))Sub claim GitLab: project_path:GROUP/PROJECT:ref_type:branch:ref:BRANCH_NAME.
15.2.7. Безопасность OIDC-федерации
Исследование Unit 42 «OH-MY-DC» (DEF CON 32, август 2024) выявило типичные ошибки:
| Ошибка | Риск | Исправление |
|---|---|---|
sub: "repo:ORG/*" | Любой репозиторий организации получает доступ | Указать конкретный репозиторий и ветку/environment |
Нет проверки aud | Token reuse между провайдерами | Всегда проверять audience |
sub без ветки | Любая ветка может деплоить | repo:ORG/REPO:ref:refs/heads/main или environment:production |
| Fork PR с OIDC | Внешний контрибьютор получает credentials | GitHub не генерирует OIDC-токены для форков (по умолчанию) |
Сводка времени жизни credentials:
| Компонент | TTL | Примечание |
|---|---|---|
| GitHub OIDC-токен | 5 минут | Только для обмена |
| AWS STS-сессия | 1 час (по умолчанию) | До 12 часов |
| GCP Access Token | 1 час | По умолчанию |
| Azure Entra ID-токен | 1 час | По умолчанию |
15.3. SLSA: верификация цепочки сборки
15.3.1. Фреймворк SLSA
SLSA (Supply-chain Levels for Software Artifacts) — фреймворк OpenSSF для обеспечения целостности цепочки поставок ПО. В отличие от SBOM (инвентаризация), SLSA отвечает на вопрос: кто, как и из чего построил этот артефакт?
Версии спецификации:
| Версия | Дата | Статус | Ключевое изменение |
|---|---|---|---|
| v1.0 | Апрель 2023 | Approved | Build Track L0-L3; концепция треков |
| v1.1 | Апрель 2025 | Approved | Уточнения Build Track, VSA |
| v1.2 | Ноябрь 2025 | Approved | Source Track (authoring, review, management) |
15.3.2. Build Track: уровни
| Уровень | Требования | Пример |
|---|---|---|
| Build L0 | Нет гарантий | Сборка на ноутбуке разработчика |
| Build L1 | Provenance существует | Автоматическая генерация provenance (может быть unsigned) |
| Build L2 | Hosted + signed | Сборка на хостинговой платформе, provenance подписан платформой |
| Build L3 | Hardened + isolated | Builds изолированы друг от друга; ключи подписи недоступны для пользовательских шагов |
15.3.3. Source Track (v1.2)
SLSA v1.2 (24 ноября 2025) ввёл Source Track, адресующий атаки типа xz-utils:
| Уровень | Фокус |
|---|---|
| Source L0 | Нет требований |
| Source L1 | Source provenance существует |
| Source L2 | История и provenance (VCS с верифицированным происхождением) |
| Source L3 | Непрерывное применение технических контролей |
15.3.4. Provenance: формат in-toto
SLSA provenance использует формат in-toto attestation с предикатом https://slsa.dev/provenance/v1:
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{
"name": "my-app",
"digest": { "sha256": "abc123..." }
}],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": { "ref": "refs/heads/main", "repository": "..." }
},
"resolvedDependencies": [
{ "uri": "git+https://github.com/...", "digest": {"gitCommit": "..."} }
]
},
"runDetails": {
"builder": { "id": "https://github.com/actions/runner" },
"metadata": {
"invocationId": "https://github.com/.../actions/runs/12345",
"startedOn": "2026-01-15T10:00:00Z"
}
}
}
}15.3.5. SLSA на практике
GitHub Actions: Artifact Attestations (GA июнь 2024)
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
attestations: write
steps:
- uses: actions/checkout@v4
- run: go build -o my-app .
- uses: actions/attest-build-provenance@v2
with:
subject-path: my-app- По умолчанию: SLSA Build Level 2 (hosted + signed).
- С reusable workflows: SLSA Build Level 3 (изоляция сборки от вызывающего workflow).
Верификация:
gh attestation verify my-app --repo MY-ORG/MY-REPOGoogle Cloud Build: нативный SLSA L3
Cloud Build автоматически генерирует provenance, соответствующий SLSA Build Level 3. Верификация интегрирована с Binary Authorization для GKE.
Docker BuildKit:
docker buildx build --provenance=true -t my-app:v1.0 .Attestation сохраняется в OCI-манифесте образа.
Принятие в экосистеме (Sigstore-signed SLSA provenance):
| Платформа | Уровень | Дата GA |
|---|---|---|
| GitHub Actions | Build L2/L3 | Июнь 2024 |
| Google Cloud Build | Build L3 | Нативно |
| npm | Build L2 | Октябрь 2023 |
| PyPI | Build L2 | Ноябрь 2024 |
| Homebrew | Build L2 | Май 2024 |
| Maven Central | Build L2 | Январь 2025 |
15.4. Защита CI/CD-раннеров
15.4.1. GitHub Actions
GITHUB_TOKEN: принцип минимальных привилегий
Организации, созданные после 2 февраля 2023, получают GITHUB_TOKEN с read-only доступом по умолчанию. Для старых организаций — переключите в Settings → Actions → General.
# Явно объявляйте необходимые разрешения
permissions:
contents: read
packages: write
id-token: write # для OIDCEphemeral runners:
# Регистрация self-hosted runner как ephemeral
./config.sh --url https://github.com/ORG/REPO \
--token $REG_TOKEN \
--ephemeralGitHub назначает ровно один job ephemeral runner'у; после завершения — автоматическая дерегистрация.
Actions Runner Controller (ARC) на Kubernetes:
apiVersion: actions.github.com/v1alpha1
kind: AutoscalingRunnerSet
metadata:
name: production-runners
spec:
githubConfigUrl: "https://github.com/MY-ORG"
githubConfigSecret: github-secret
maxRunners: 10
minRunners: 0
template:
spec:
containers:
- name: runner
image: ghcr.io/actions/actions-runner:latest
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: trueARC-раннеры ephemeral по умолчанию — новый Pod для каждого Job.
Рекомендация: Раннер-поды — в отдельном namespace от оператора. НЕ размещайте на production-кластере.
15.4.2. GitLab Runner
Новая модель регистрации (GitLab 18+, май 2025):
Старые registration tokens (GR1348941...) удалены. Используйте runner authentication tokens (префикс glrt-):
gitlab-runner register \
--url https://gitlab.example.com \
--token glrt-XXXXXXXXXXXXXXXXXXXX \
--executor kubernetesKubernetes executor: создаёт новый Pod для каждого CI-задания (аналог ephemeral runners). Рекомендация: namespace_per_job для изоляции на уровне namespace.
15.4.3. Принципы hardening
| Принцип | Реализация |
|---|---|
| Ephemeral | Один job — один runner. Нет persistent state |
| Network isolation | Запретить доступ к metadata-сервисам (169.254.169.254), production DB |
| Least privilege | GITHUB_TOKEN: read, явные permissions |
| Secret masking | Автоматическая маскировка *** в логах (best-effort) |
| Pin versions | uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 |
| Мониторинг | StepSecurity Harden-Runner (обнаружил tj-actions, ноябрь 2025) |
| Runner groups | Изоляция по trust level: production vs staging vs PR |
15.4.4. Tekton Chains
Для Kubernetes-нативных пайплайнов Tekton Chains — контроллер, который автоматически генерирует SLSA provenance после завершения TaskRun/PipelineRun.
# Настройка Tekton Chains
kubectl patch configmap chains-config -n tekton-chains \
-p='{"data":{
"artifacts.taskrun.format":"slsa/v2alpha3",
"artifacts.taskrun.storage":"oci",
"transparency.enabled":"true",
"signers.x509.fulcio.enabled":"true"
}}'Keyless-подпись через Fulcio (OIDC Kubernetes ServiceAccount → Fulcio certificate → Rekor log). Достижимый уровень: SLSA Build Level 2.
15.5. Лаборатория: OIDC-федерация + Sigstore
Цель
Настроить GitHub Actions workflow, который:
- Аутентифицируется в AWS без секретов через OIDC.
- Собирает контейнерный образ с SLSA provenance.
- Подписывает образ через Sigstore keyless.
- Верифицирует подпись и provenance локально.
Предварительные требования
- GitHub-репозиторий с разрешением Actions
- AWS-аккаунт с правами на IAM
- Docker registry (ECR / ghcr.io)
Шаг 1: Создание OIDC-провайдера в AWS
# Terraform
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
resource "aws_iam_role" "github_actions" {
name = "github-actions-deploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.github.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:MY-ORG/MY-REPO:ref:refs/heads/main"
}
}
}]
})
}
resource "aws_iam_role_policy_attachment" "ecr_push" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser"
}Шаг 2: GitHub Actions workflow
# .github/workflows/build-sign-deploy.yaml
name: Build, Sign & Deploy
on:
push:
branches: [main]
permissions:
id-token: write # OIDC
contents: read
packages: write # ghcr.io push
attestations: write # SLSA provenance
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-sign:
runs-on: ubuntu-latest
environment: production
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
# 1. Checkout
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
# 2. Аутентификация в AWS (OIDC, без секретов)
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: us-east-1
# 3. Login в container registry
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 4. Сборка и push образа
- uses: docker/build-push-action@v6
id: build
with:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
provenance: true # SLSA provenance в OCI-манифесте
# 5. SLSA Build Provenance (GitHub Artifact Attestations)
- uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
# 6. Подпись образа через Sigstore keyless
- uses: sigstore/cosign-installer@v3
- run: |
cosign sign --yes \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}Шаг 3: Локальная верификация
# Верификация SLSA provenance через GitHub CLI
gh attestation verify \
oci://ghcr.io/MY-ORG/MY-REPO@sha256:abc123... \
--repo MY-ORG/MY-REPO
# Верификация подписи Cosign (keyless)
cosign verify \
--certificate-identity-regexp="https://github.com/MY-ORG/MY-REPO/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/MY-ORG/MY-REPO@sha256:abc123...Шаг 4: Admission control (Kyverno)
Интеграция с Kyverno (глава 13) для блокировки неподписанных образов:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-cosign-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-signature
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["ghcr.io/MY-ORG/*"]
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/MY-ORG/*"
mutateDigest: true
verifyDigest: true
required: true15.6. Чеклист безопасности CI/CD
| Категория | Контроль | Приоритет |
|---|---|---|
| Credentials | OIDC-федерация для всех облаков | Высокий |
| Credentials | Нет долгоживущих секретов в CI-переменных | Высокий |
| Credentials | Ротация оставшихся секретов < 90 дней | Средний |
| Runners | Ephemeral runners (один job — один runner) | Высокий |
| Runners | GITHUB_TOKEN: read по умолчанию | Высокий |
| Runners | Pin actions по SHA (не по тегу) | Высокий |
| Supply chain | SLSA provenance для артефактов | Средний |
| Supply chain | Sigstore keyless signing | Средний |
| Supply chain | Admission control (верификация подписей) | Средний |
| Supply chain | SBOM-генерация в CI (см. главу 13) | Средний |
| Access | Branch protection rules + required reviews | Высокий |
| Access | Deployment environments с approval gates | Средний |
| Мониторинг | Audit log для CI/CD actions | Средний |
| Мониторинг | Network egress мониторинг раннеров | Низкий |
15.7. Итоги
| Принцип Zero Trust | Реализация в CI/CD |
|---|---|
| Никогда не доверяй | OIDC-федерация вместо статических секретов |
| Всегда проверяй | SLSA provenance, подпись артефактов, admission control |
| Минимальные привилегии | Scoped GITHUB_TOKEN, специфичный sub claim, ephemeral runners |
| Предполагай взлом | Pin actions по SHA, мониторинг раннеров, автоматическая ротация |
Связь с другими главами:
- Глава 7 (SPIFFE/SPIRE): Workload identity для CI/CD → облако.
- Глава 13 (Безопасность приложений): SBOM, Sigstore, admission control.
- Глава 14 (Kubernetes): Vault CSI для секретов, RBAC для CI ServiceAccount.
Источники
- NSA/CISA: Defending CI/CD Environments (June 28, 2023): media.defense.gov/2023/Jun/28/2003249466/-1/-1/0/CSI_DEFENDING_CI_CD_ENVIRONMENTS.PDF
- SLSA v1.2: slsa.dev/spec/v1.2/
- GitHub Actions OIDC: docs.github.com/en/actions/concepts/security/openid-connect
- GitHub Artifact Attestations: docs.github.com/en/actions/concepts/security/artifact-attestations
- AWS OIDC для GitHub: docs.github.com/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
- GCP Workload Identity Federation: cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines
- Azure Federated Credentials: learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation
- Unit 42 OH-MY-DC: unit42.paloaltonetworks.com/oidc-misconfigurations-in-ci-cd/
- Tekton Chains: tekton.dev/docs/chains/signed-provenance-tutorial/
- Sigstore: docs.sigstore.dev/