Sigstore Cosign 2026: keyless подписване на контейнери, SLSA и Kyverno в CI/CD

Практическо ръководство за Sigstore Cosign 2026: keyless подписване, SBOM атестации със Syft, SLSA Build Level 3 provenance и налагане с Kyverno admission control. С работещи GitHub Actions snippets, готови за production pipeline.

Sigstore Cosign 2026: keyless ръководство

Обновено: 17 септември 2026 г.

Sigstore Cosign е инструментът, който подписва криптографски контейнерни образи и OCI артефакти през 2026 г., като елиминира нуждата от дългосрочни ключове чрез keyless signing поток, базиран на OIDC идентичност. В това ръководство ще ви покажа целия pipeline: от cosign sign в GitHub Actions, през SBOM атестации със Syft, SLSA provenance, до налагане на верификация в Kubernetes с Kyverno. Всеки раздел завършва с работещ CI snippet, който можете да пуснете директно в production pipeline.

  • Cosign 2.x с keyless signing чрез Fulcio и Rekor е стандартът за подписване на контейнерни образи през 2026 г. Забравете за GPG и KMS ключове в CI secrets.
  • Винаги подписвайте по digest (image@sha256:...), а не по tag. Тагът може да сочи към различен digest всеки път.
  • При cosign verify задължително подавайте --certificate-identity и --certificate-oidc-issuer. Без тях проверката е практически безполезна.
  • SBOM атестации се генерират със syft attest и се верифицират с cosign verify-attestation, което подава SBOM към Grype за сканиране на CVE.
  • Kyverno v1.13+ с type: SigstoreBundle налага политиката на admission ниво и блокира неподписани образи, преди Pod-ът дори да стартира.
  • SLSA Build Level 3 чрез slsa-github-generator добавя провенанс атестация, която обвързва образа с source commit и workflow-а, който го е построил.

Как работи Sigstore Cosign с keyless signing през 2026 г.

Sigstore е OpenSSF проект, който предоставя три компонента, работещи като един trust chain. Първо, Fulcio е безплатен Certificate Authority, който издава краткотрайни X.509 сертификати (валидни около 10 минути). После идва Rekor, публичен transparency log с append-only структура, който записва всяко подписващо събитие. И накрая Cosign, CLI-то, което говори и с двата, и заедно с това знае как да сложи подпис до образ в OCI регистър.

Keyless потокът работи така: pipeline-ът ви (GitHub Actions runner, GitLab job, ArgoCD workflow) поисква OIDC token от identity provider-а. Cosign изпраща token-а на Fulcio, който валидира claim-овете и издава краткотраен сертификат, обвързан с тази идентичност. Cosign подписва sha256 digest-а на образа с ефимерния ключ, публикува подписа в OCI регистъра (като отделен .sig tag, обвързан с digest-а), и накрая записва подписа в Rekor за публичен audit. Ключът се изхвърля секунди след подписването. Няма какво да ротирате, няма какво да изтече.

Честно казано, в production наблюдавам pipeline-и, които преди свободно държаха GPG ключ като GitHub secret. Всеки път, когато някой пусне echo $COSIGN_KEY в debug стъпка, ключът се логваше. Keyless премахва целия този клас проблеми, защото идентичността е записана в подписа, а не в CI secret vault.

Защо keyless подписването е стандартът в 2026 г.

Traditional signing (GPG, KMS-managed key) прехвърля целия си threat model върху съхранението на ключа. Ключът трябва да е в HSM или KMS, достъпен само от build runner-а, ротиран, backup-нат, но не и наличен в log-овете. На практика повечето екипи не се справят с това. През 2024 г. видях compromise, в който атакуващ извлече RSA private key от кеширан docker layer, защото build стъпка беше писала ключа във volatile файл, но той оставаше в предишен слой. Лош ден за всички замесени.

Keyless подписването премества threat model-а върху идентичността. Атакуващ трябва да компрометира вашия GitHub Actions runner, вашия OIDC token или вашия identity provider, а не да открадне статичен ключ, който съществува на диск години наред. Защитата се измества от HSM управление към MFA, branch protection и strict environment approvals. Това е много по-defensible граница, защото компанията ви вече има MFA и SAML/OIDC контроли, докато HSM управлението често е ad-hoc.

Освен това, Rekor transparency log-ът дава audit trail, който традиционното подписване не предлага. Ако утре забележите неоторизиран образ с валиден подпис, можете да го намерите в Rekor и да проследите точния workflow, който го е създал. За compliance разкази (EU Cyber Resilience Act, изисквания на клиенти), този audit trail сам по себе си оправдава миграцията. За системен фон върху runtime защита в контейнери, вижте нашето ръководство за укрепване на Docker контейнери с rootless, Seccomp и AppArmor.

Настройка на CI/CD pipeline за подписване с Cosign

Ето минималния GitHub Actions workflow, който build-ва образ, push-ва го в GHCR и го подписва keyless. Пиновете на action-версиите са важни. @v3 днес и @v3 след половин година не са едно и също нещо, ако някой освободи breaking change в minor версия.

# .github/workflows/build-sign.yml
name: build-and-sign

on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write
  id-token: write   # ЗАДЪЛЖИТЕЛНО за OIDC → Fulcio

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          # digest е това, което ще подпишем, не тагът
          outputs: type=registry,name=target
          provenance: mode=max
          sbom: true

      - uses: sigstore/cosign-installer@v3
        with:
          cosign-release: 'v2.4.1'

      - name: Sign the image by digest
        env:
          IMAGE: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
        run: |
          cosign sign --yes "$IMAGE"

Три неща разграничават този workflow от тези, които ще намерите в остарели tutorial-и. Първо, id-token: write permission-а е задължителен, защото Cosign иска OIDC token от runner-а. Второ, --yes флагът е нужен в non-interactive среди (иначе Cosign чака y/N потвърждение за upload към Rekor). И трето, $IMAGE се строи от steps.build.outputs.digest, а не от тага, което гарантира, че подписваме точно образа, който е бил pushed, дори да е race condition с друг pipeline.

SBOM атестации със Syft и cosign attest

Подписът доказва произход, но не казва какво има вътре в образа. За второто ви трябва SBOM (Software Bill of Materials), списък на всички пакети, версии и лицензи. Syft от Anchore генерира SBOM в CycloneDX или SPDX формат, а Cosign опакова SBOM-а като in-toto атестация, DSSE-обвита и подписана със същата OIDC идентичност.

Ключовата разлика между cosign sign и cosign attest: sign подписва самия образ (subject), докато attest подписва твърдение за образа (predicate). Predicate може да е SBOM, SLSA provenance, vulnerability report или каквото пожелаете. Всяка атестация се съхранява в OCI регистъра до образа със свой .att tag.

# Продължение на workflow-а от предишната секция
      - uses: anchore/sbom-action@v0
        with:
          image: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
          format: cyclonedx-json
          output-file: sbom.cdx.json

      - name: Attest SBOM keyless
        env:
          IMAGE: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
        run: |
          cosign attest --yes \
            --predicate sbom.cdx.json \
            --type cyclonedx \
            "$IMAGE"

      - name: Verify SBOM attestation and scan with Grype
        env:
          IMAGE: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
        run: |
          cosign verify-attestation \
            --type cyclonedx \
            --certificate-identity-regexp "https://github.com/${{ github.repository }}/.*" \
            --certificate-oidc-issuer https://token.actions.githubusercontent.com \
            "$IMAGE" \
            | jq -r '.payload' | base64 -d | jq '.predicate' > verified-sbom.json
          grype sbom:verified-sbom.json --fail-on high

Забележете pipe-а към Grype: сканираме SBOM-а след като сме верифицирали, че той идва от нашия pipeline. Това е важна отбранителна разлика. Сканирането на unverified SBOM може да пропусне пакети, които атакуващ е изтрил от списъка, за да скрие уязвимост. За допълнителни техники за откриване по време на изпълнение, разгледайте статията ни за eBPF сигурност с Falco и Tetragon.

SLSA provenance и Build Level 3

SLSA (Supply-chain Levels for Software Artifacts) е OpenSSF framework, който описва инкрементални нива на build integrity. Той е нужен, защото само подписан образ не доказва, че е построен по правилата. Атакуващ с достъп до CI runner-а може да подпише всичко. SLSA provenance е атестация, която описва как е построен образът: source commit, workflow file, runner изображение, build arguments.

НивоИзискванияРеалистично за
SLSA 1Провенанс е налице, дори без подписВсеки проект
SLSA 2Version control + hosted build + signed provenanceСтандартен CI/CD
SLSA 3Hardened, изолиран build platform, non-forgeable provenanceProduction workloads
SLSA 4Hermetic reproducible builds + two-person reviewКритична инфраструктура

За GitHub Actions, slsa-github-generator action-ът генерира SLSA Level 3 provenance автоматично. Той работи в отделен, изолиран reusable workflow, което прави provenance-а non-forgeable дори от caller workflow-а. Ето минималния пример:

# .github/workflows/slsa-provenance.yml
name: build-with-slsa

on:
  push:
    tags: ['v*']

permissions:
  id-token: write
  contents: read
  packages: write

jobs:
  build:
    outputs:
      image: ${{ steps.build.outputs.image }}
      digest: ${{ steps.build.outputs.digest }}
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - id: build
        # ... build стъпки от предишния workflow

  provenance:
    needs: [build]
    permissions:
      actions: read
      id-token: write
      packages: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
    with:
      image: ${{ needs.build.outputs.image }}
      digest: ${{ needs.build.outputs.digest }}
      registry-username: ${{ github.actor }}
    secrets:
      registry-password: ${{ secrets.GITHUB_TOKEN }}

След като workflow-ът приключи, ще намерите атестация от тип https://slsa.dev/provenance/v1 прикачена към образа. Тя се верифицира със същата cosign verify-attestation команда. Само трябва да сменим --type на slsaprovenance. Референтният SLSA v1.0 спецификационен документ описва точните изисквания на всяко ниво.

Как да наложа проверка на подписи в Kubernetes с Kyverno

Подписването е декоративно, ако Kubernetes кластерът не го налага. Kyverno (CNCF Graduated от ноември 2024 г.) е admission controller, който валидира Cosign подписи, преди Pod-ът да бъде създаден. Ако образът не отговаря на политиката, заявката се отхвърля с ясно съобщение в API server response-а.

Ето ClusterPolicy, който изисква всеки образ от ghcr.io/mycompany/* да бъде подписан от нашия GitHub Actions workflow:

# kyverno-verify-signature.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cosign-signature
spec:
  validationFailureAction: Enforce
  webhookTimeoutSeconds: 30
  failurePolicy: Fail
  rules:
    - name: verify-ghcr-images
      match:
        any:
          - resources:
              kinds: [Pod]
      exclude:
        any:
          - resources:
              namespaces: [kube-system, kyverno, cert-manager]
      verifyImages:
        - imageReferences:
            - "ghcr.io/mycompany/*"
          failureAction: Enforce
          type: SigstoreBundle   # Kyverno v1.13+
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/mycompany/*/.github/workflows/build-sign.yml@refs/heads/main"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

Тествайте политиката с kubectl apply --dry-run=server, преди да я включите в Enforce режим. Първите ви deployments почти сигурно ще ударят непокрити от политиката образи (init контейнери от sidecar инжектори, например). Debug-ването е директно:

kubectl logs -n kyverno -l app.kubernetes.io/name=kyverno \
  --tail=100 | grep -E "image verification (failed|succeeded)"

Как да проверя подпис на контейнер с cosign verify

Ето въпроса, на който отговарят повечето изгубени часове при debug на supply-chain incidents: защо моята cosign verify команда преминава, а образът все още е компрометиран? Отговорът в 90% от случаите е, че сте пропуснали identity constraints. Хванах този bug лично, докато интегрирахме верификация в production admission gate преди около година, и оттогава го предупреждавам всеки колега.

cosign verify без --certificate-identity и --certificate-oidc-issuer валидира само, че някой валиден Sigstore подпис съществува. Тъй като всеки с GitHub акаунт може да си пусне собствен workflow и да подпише каквото си иска, това не ви дава практически нищо.

# НЕПРАВИЛНО: валидира само че има валиден Sigstore подпис
cosign verify ghcr.io/mycompany/api:v1.2.3

# ПРАВИЛНО: валидира че конкретен workflow е подписал образа
cosign verify \
  --certificate-identity "https://github.com/mycompany/api/.github/workflows/build-sign.yml@refs/tags/v1.2.3" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/mycompany/api@sha256:abcd...

За гъвкавост (например при feature branch builds), използвайте --certificate-identity-regexp:

cosign verify \
  --certificate-identity-regexp "https://github.com/mycompany/api/\.github/workflows/build-sign\.yml@refs/heads/(main|release/.+)$" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/mycompany/api@sha256:abcd...

За офлайн среди (напр. air-gapped кластери), Cosign v2 поддържа bundle proofs. Rekor inclusion proof се съхранява до подписа, така че верификацията не изисква жив HTTP достъп до Rekor. Флагът е --experimental-oci11=true при подписване и --offline при верификация. За автоматизиране на audit процес около тези проверки, вижте одит на Linux сигурността с Lynis, OpenSCAP и Ansible.

Често срещани грешки при keyless signing

След две години миграции на pipeline-и към Sigstore, ето кратката ми колекция от footgun-и, които виждам най-често (и които съм правил лично поне веднъж):

  • Подписване на tag вместо digest. cosign sign myimage:latest подписва каквото digest се разрешава от :latest в момента на изпълнение. Race condition с друг push, и подписвате грешното нещо. Винаги използвайте myimage@sha256:....
  • Забравено id-token: write permission. Cosign ще падне с криптично OIDC token error. Проверете permissions: блока в GitHub Actions workflow.
  • Проверка без identity constraints. Обяснено по-горе: намеренията ви са очевидни, но грешката е тиха и опасна.
  • Разчитане на pinned SHA на action от 2023 г. Sigstore-installer action-ите добавят breaking changes между major версии. Използвайте semver pin (@v3) и следвайте release notes.
  • Игнориране на Rekor availability. Ако public Rekor е down, вашият pipeline пада. За production, обмислете self-hosted Rekor или bundle mode за офлайн верификация.
  • Използване на --insecure-ignore-tlog в production. Този флаг казва на Cosign да не изисква Rekor запис, така че губите publicly auditable trail. Полезно за local dev, катастрофа за production.

Референтните команди и update-ната документация са в официалната Sigstore документация, а списъкът с последни release notes е в GitHub releases на sigstore/cosign.

Често задавани въпроси

Каква е разликата между cosign sign и cosign attest?

cosign sign подписва самия digest на образа и доказва произхода. cosign attest подписва in-toto твърдение (predicate) за образа, например SBOM, SLSA provenance или vulnerability scan. Двете се съхраняват като отделни OCI артефакти в регистъра.

Нужен ли ми е Kubernetes, за да използвам Cosign?

Не. Cosign работи с всеки OCI-съвместим регистър: Docker Hub, GHCR, ECR, GAR, Harbor, дори локални регистри. Kubernetes admission control с Kyverno е препоръчителният начин да наложите подписите при deployment, но самото подписване не изисква Kubernetes.

Как да използвам Cosign в air-gapped среда без достъп до Rekor?

Cosign v2 поддържа bundle proofs. Rekor inclusion proof се съхранява до подписа с --experimental-oci11=true. При верификация подавате --offline и cosign verify валидира bundle-а локално, без HTTP заявка към rekor.sigstore.dev. Алтернатива е self-hosted Rekor instance.

Мога ли да мигрирам от GPG signed images към Sigstore keyless постепенно?

Да. Kyverno и Sigstore Policy Controller поддържат множествени attestor блокове. Можете да напишете политика, която приема или валиден GPG подпис, или keyless подпис от вашия workflow, което позволява поетапно миграция без deployment freeze.

Какво ниво на SLSA трябва да таргетирам?

Стремете се към SLSA Build Level 3 за production workloads. Постижимо е с slsa-github-generator без промяна на инфраструктурата. Level 4 (hermetic + two-person review) е реалистично само за критична инфраструктура. Level 2 е абсолютният минимум за 2026 г.

Raj Patel
За Автора Raj Patel

DevSecOps engineer who's gradually turning every CI pipeline he sees into a security-checking machine.