защитить конвейер сборки от зависимости до подписанного деплоя

Безопасность цепочки поставок ПО в DevOps: от реестра компонентов до подписи образов

14 минут

Промышленное ПО собирается из сотен зависимостей, базовых образов и инструментов сборки. Без SBOM, подписей и admission-политик команда не докажет, что выкатила, и не остановит подменённый артефакт до попадания в кластер.

Почему атаки на цепочку поставок обгоняют защиту на уровне приложения

Современные сервисы собираются, а не пишутся с нуля. Один контейнерный образ тянет сотни транзитивных библиотек, мутабельный тег базового образа, сторонние build actions и манифесты деплоя из Git. Инциденты SolarWinds, Log4Shell и бэкдор в xz Utils показывают: противник бьёт по системам сборки и зависимостям, а не только по прикладному коду. Платформенные команды часто не могут перечислить все пакеты в образе, доказать, что бинарник в продакшене совпадает с выходом CI, и восстановить provenance, когда аудитор спрашивает, кто собрал компонент. EO 14028 и EU Cyber Resilience Act проталкивают SBOM и обработку уязвимостей в закупки и compliance даже вне госсектора. Решение — не один сканер, а видимость через SBOM, целостность через подпись, обнаружение через гейты в CI и принуждение через admission в Kubernetes.

Четыре столпа: SBOM, подпись, сканирование и политики

Зрелая позиция строится на четырёх слоях, которые усиливают друг друга. SBOM — машиночитаемый инвентарь в SPDX или CycloneDX каждого компонента артефакта сборки. Подпись привязывает криптографическую идентичность к образу или бинарнику, чтобы реестр и кластер проверили: артефакт создан ожидаемым пайплайном и не изменён по пути. Сканирование уязвимостей на этапе сборки сопоставляет пакеты SBOM с базами CVE до push, когда исправление дешевле всего. Политики в кластере отклоняют неподписанные образы, образы выше порога severity или без обязательных attestations. Типичный поток: push в Git, сборка и тесты в CI, параллельно SBOM, скан Grype или Trivy, подпись Cosign через OIDC CI-провайдера, push в реестр с SBOM и provenance, затем Kyverno или OPA Gatekeeper проверяют подпись и политики уязвимостей при admission.

Генерация и публикация SBOM через Syft на каждой сборке

Ручной или квартальный экспорт SBOM бесполезен во время инцидента. Генерируйте SBOM для каждого образа в CI, храните как артефакт сборки и прикрепляйте к записи реестра рядом с образом. Syft от Anchore инвентаризирует пакеты ОС и зависимости приложения и отдаёт SPDX или CycloneDX JSON. Зафиксируйте версию sbom-action, сканируйте digest собранного образа — не мутабельный тег — и выгружайте SBOM в хранилище артефактов для аудиторов. OCI-совместимые реестры принимают SBOM через Cosign referrers, чтобы сканеры непрерывно связывали digest образа со списком компонентов.

YAML · permissions GitHub Actions и генерация SBOM
permissions:
  contents: read
  packages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: |
          docker build -t "${REGISTRY}/${IMAGE}:${GITHUB_SHA}" .
          docker push "${REGISTRY}/${IMAGE}:${GITHUB_SHA}"
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: "${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}"
          format: cyclonedx-json
          output-file: sbom.cdx.json
      - name: Upload SBOM artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.cdx.json
Dockerfile · фиксация base image по digest
# Mutable tags change under you; pin digest for reproducible builds
FROM python:3.12-slim@sha256:abc123def456...
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER 10001
CMD ["python", "-m", "app.main"]

Подпись образов Cosign keyless через OIDC и прикрепление SBOM

Долгоживущие ключи подписи — операционный риск. Keyless-подпись Sigstore использует короткоживущие сертификаты, привязанные к OIDC-идентичности CI — GitHub Actions, GitLab CI или облачный OIDC, — и пишет события в transparency-лог Rekor для аудита. Выдайте permission id-token write, установите Cosign, подпишите digest образа после push и прикрепите SBOM или опубликуйте CycloneDX attestation. Перед включением enforcement в кластере проверьте локально через cosign verify. В изолированных или регулируемых средах используйте пары ключей через KMS вместо keyless.

YAML · keyless-подпись Cosign и attach SBOM
- uses: sigstore/cosign-installer@v3

- name: Sign container image
  run: |
    cosign sign --yes \
      "${REGISTRY}/${IMAGE}@${DIGEST}"

- name: Attach SBOM to image
  run: |
    cosign attach sbom --sbom sbom.cdx.json \
      "${REGISTRY}/${IMAGE}@${DIGEST}"

- name: Verify signature in CI
  run: |
    cosign verify \
      --certificate-identity-regexp "https://github.com/${GITHUB_REPOSITORY}/.*" \
      --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
      "${REGISTRY}/${IMAGE}@${DIGEST}"

Гейт CVE через Grype и принудительная проверка подписи в Kubernetes

Сканируйте digest образа в CI до промоушена. Grype и Trivy сопоставляют содержимое SBOM с CVE; прерывайте пайплайн на critical severity или по allow list принятых рисков. При деплое не полагайтесь на ручную проверку подписи разработчиками. Kyverno verifyImages валидирует подписи Cosign и может требовать SLSA- или vulnerability-attestations. Сначала ограничьте политики паттернами приватного реестра, затем расширяйте. Тестируйте в режиме audit до enforce, чтобы не заблокировать системные namespace.

YAML · гейт CVE Grype в CI
- name: Scan image for vulnerabilities
  uses: anchore/scan-action@v3
  with:
    image: "${REGISTRY}/${IMAGE}:${GITHUB_SHA}"
    fail-build: true
    severity-cutoff: critical

# Alternative: Grype CLI
- name: Grype scan
  run: |
    grype "${REGISTRY}/${IMAGE}@${DIGEST}" \
      --fail-on critical
YAML · ClusterPolicy Kyverno для keyless Cosign
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: verify-image-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
      verifyImages:
        - imageReferences:
            - "registry.example.com/*"
          mutateDigest: true
          required: true
          attestors:
            - entries:
                - keyless:
                    issuer: "https://token.actions.githubusercontent.com"
                    subject: "https://github.com/your-org/your-repo/.github/workflows/build.yml@refs/heads/main"
                    rekor:
                      url: https://rekor.sigstore.dev

Операционные практики: SLSA, provenance, hardening CI и гигиена зависимостей

Внедряйте уровни SLSA постепенно: сначала attestation provenance сборки, затем изолированные hardened builders, затем нефальсифицируемый provenance для релизных артефактов. Используйте GitHub attest-build-provenance или cosign attest с in-toto predicates, где платформа это поддерживает. Фиксируйте базовые образы и GitHub Actions по digest или неизменяемым тегам версий. Автоматизируйте обновление зависимостей через Renovate или Dependabot и гоняйте тесты на каждый bump. Относитесь к CI-раннерам как к продакшен-инфраструктуре: least-privilege IAM, контроль исходящего трафика, защита веток и аудит изменений workflow. Периодически просматривайте записи Rekor на неожиданные идентичности подписи. Храните SBOM и отчёты сканирования с тем же сроком, что и релизные артефакты, чтобы при инциденте ответить, что выкатилось в конкретную дату. Безопасность цепочки поставок — практика на весь жизненный цикл: видимость без enforcement деградирует, enforcement без SBOM оставляет слепые зоны при раскрытии CVE.

Admission-политики дополняют hardening кластера из практического гайда по безопасности Kubernetes в продакшене.

Идентичности подписи и учётные данные реестра зависят от того, как секреты проходят через CI — это разобрано в гайде по управлению секретами в DevOps и CI/CD.