linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • Симулятор
  • База знаний
  • Собеседование
Index
Categories
All entries
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
home/linux/kb/Контейнеры (бонус)/image-signing-cosign

kb/containers ── Контейнеры (бонус) ── intermediate

Cosign и подпись container images (sigstore)

cosign подписывает container images. Sigstore = ekosistema: rekor (transparency log), fulcio (CA для keyless OIDC). Подписи хранятся как OCI-объекты рядом с image. Verify - часть admission control в k8s через policy-controller или Kyverno.

view as markdownaka: cosign, sigstore, image-signing, supply-chain, keyless-signing

Зачем подписывать image

Container image, это просто tar.gz с manifest'ом, его кто угодно может pushнуть с тем же тегом. Если злоумышленник получил доступ к registry или подменил image на промежуточном hop'е, ваш k8s спокойно его pull'нет, никаких проверок authenticity по умолчанию нет.

Атаки в реале: SolarWinds, codecov, ua-parser-js. Все, supply chain, где доверенный артефакт оказался компромисом.

Подпись image решает:

  1. Authenticity, image создан тем, кому мы доверяем
  2. Integrity, image не изменён после подписи (подпись над digest'ом)
  3. Non-repudiation, подписант не может отказаться (если в transparency log)

sigstore, экосистема

Раньше использовали Notary v1 (Docker Content Trust). Не взлетело: сложно, требует key management, плохо интегрируется. В 2021 Linux Foundation запустила sigstore, современная альтернатива.

Компоненты:

КомпонентНазначение
cosignCLI для sign/verify
fulcioбесплатный CA, выдаёт короткоживущие ([[tls-certificates
rekorappend-only transparency log всех подписей (как certificate transparency)
policy-controller / gatekeeper / kyvernoenforcement в k8s

Идея: никаких долгоживущих ключей. Подписант аутентифицируется через OIDC (GitHub Actions, GCP, любой OIDC IdP), fulcio выдаёт cert на 10 минут, cosign им подписывает, подпись + cert + log-entry публикует. Проверка, verify подписи по cert, проверка cert по fulcio root, проверка inclusion в rekor.

Способы подписи

1. Keyless (OIDC-based), рекомендуемый

bash
# Подписать
cosign sign ghcr.io/myorg/myapp:v1.2.3
# Откроется браузер → OIDC login → fulcio выдаст cert →
# подпись push'нется в registry, log-entry в rekor
# Проверить (cosign 2.0+: keyless verify дефолт, COSIGN_EXPERIMENTAL не нужен)
cosign verify \
  --certificate-identity 'https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/myorg/myapp:v1.2.3

Identity, это OIDC subject, для GitHub Actions workflow path. Так verify проверяет: подпись сделана конкретным workflow, не кем угодно с доступом к registry.

2. Static keypair

Когда OIDC недоступен (air-gap, on-prem без OIDC):

bash
cosign generate-key-pair                 # cosign.key + cosign.pub
cosign sign --key cosign.key ghcr.io/myorg/myapp:v1.2.3
cosign verify --key cosign.pub ghcr.io/myorg/myapp:v1.2.3

Минус: надо защищать cosign.key. Вариант, KMS: --key gcpkms://projects/.../keys/... или awskms://....

3. KMS-backed key

bash
cosign sign --key awskms:///alias/cosign-key ghcr.io/myorg/myapp:v1

Ключ никогда не покидает KMS, подпись делает облачный провайдер. Лучший компромисс между keyless (slick) и static (control).

Где живёт подпись

cosign push'ит подпись как отдельный OCI-объект в тот же registry, по соглашению с тегом sha256-<digest>.sig:

ghcr.io/myorg/myapp:v1.2.3              ← image
ghcr.io/myorg/myapp:sha256-abc...sig    ← signature
ghcr.io/myorg/myapp:sha256-abc...att    ← attestation (опционально)

Никакой схемы, никакой БД. registry умеет хранить любой OCI-blob, поэтому всё работает с любым реестром (ECR, GHCR, Artifactory, ACR, Harbor, Quay).

Attestations, больше, чем просто подпись

cosign умеет подписывать произвольные claim'ы про image, а не только digest. Это attestation в формате in-toto:

bash
# Подписать SBOM (CycloneDX/SPDX)
cosign attest --predicate sbom.spdx.json --type spdx \
  ghcr.io/myorg/myapp:v1.2.3
# SLSA provenance (откуда собрался)
cosign attest --predicate provenance.json --type slsaprovenance \
  ghcr.io/myorg/myapp:v1.2.3
# Кастомный predicate
cosign attest --predicate vuln-scan.json --type custom \
  ghcr.io/myorg/myapp:v1.2.3

Verify:

bash
cosign verify-attestation --type slsaprovenance \
  --certificate-identity ... ghcr.io/myorg/myapp:v1.2.3

Применение: «доверять только image со SLSA provenance level 3, со свежим vuln-scan'ом, и собранным из main-ветки нашего репо».

Enforcement в k8s

Подпись бесполезна, если её никто не проверяет. В k8s, admission controller:

sigstore policy-controller

yaml
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata: { name: must-be-signed-by-myorg }
spec:
  images:
  - glob: "ghcr.io/myorg/**"
  authorities:
  - keyless:
      url: https://fulcio.sigstore.dev
      identities:
      - issuer: https://token.actions.githubusercontent.com
        subjectRegExp: 'https://github.com/myorg/.+'

Любой pod, который пытается pull'нуть ghcr.io/myorg/... без подписи, отклоняется на admission.

Kyverno

yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: verify-images }
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-image
    match:
      any: [{ resources: { kinds: [Pod] } }]
    verifyImages:
    - imageReferences: ["ghcr.io/myorg/*"]
      attestors:
      - entries:
        - keyless:
            subject: "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main"
            issuer: "https://token.actions.githubusercontent.com"

CI-интеграция (GitHub Actions, типичный пример)

yaml
# .github/workflows/release.yml
permissions:
  id-token: write       # для OIDC
  contents: read
  packages: write
jobs:
  build-sign:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: sigstore/cosign-installer@v3
    - uses: docker/login-action@v3
      with: { registry: ghcr.io, username: ${{ github.actor }}, password: ${{ secrets.GITHUB_TOKEN }} }
    - run: |
        docker buildx build --tag ghcr.io/myorg/myapp:${{ github.sha }} --push .
    - run: |
        cosign sign --yes ghcr.io/myorg/myapp:${{ github.sha }}

--yes пропускает интерактивный confirm. OIDC-token берётся из GitHub Actions environment автоматически.

Cosign vs Notary v2 vs GPG

| | cosign | notary v2 | [[gpg-pgp|GPG]] | |---|--------|-----------|------| | Keyless OIDC | yes | no | no | | Хранилище | OCI registry | OCI registry | вне registry | | Transparency log | rekor (built-in) | optional | no | | Подписи как OCI artifacts | yes (с _.sig тегом, OCI 1.1 referrers) | yes (referrers) | no | | Adoption | де-факто стандарт 2026 | в спеке OCI 1.1 | legacy |

В 2026 практически весь индустри-стек выбрал cosign. Notary v2 присутствует в спеке OCI Distribution 1.1 как alternative artifact type.

Когда что-то пошло не так

  • Error: no matching signatures, image подписан другим identity или другим issuer. Проверь --certificate-identity и --certificate-oidc-issuer точно.
  • Verify висит на Searching rekor, rekor.sigstore.dev недоступен, фолбек: --insecure-ignore-tlog (НО потеря transparency-проверки, не для prod).
  • signature not found, подпись push'нулась в один registry, image копировали в другой без подписи. Используй cosign copy src dst (копирует image + подпись + attestations).
  • unable to verify signed entity, fulcio root cert устарел, обновить cosign.
  • OIDC failure в CI, permissions: id-token: write забыто, или federated identity не настроен в registry.
  • Air-gap deployment, нужен self-hosted fulcio + rekor либо static keys через KMS.
  • Подписи раздувают registry, каждый image-tag = +200KB на подпись. Не критично, но для тысяч tag'ов заметно.

SLSA, связанная инициатива

SLSA (Supply-chain Levels for Software Artifacts), стандарт по уровням «как защищён ваш build». L1 = есть provenance, L2 = подписан, L3 = build на изолированном builder'е, L4 = hermetic.

cosign attest со SLSA-predicate, основной механизм публикации provenance для удовлетворения L2/L3 требований. GitHub Actions

  • cosign + slsa-github-generator из коробки даёт SLSA L3.

§ команды

bash
cosign sign ghcr.io/myorg/app:v1

Подписать image через keyless OIDC - откроется браузер для аутентификации

bash
cosign sign --key cosign.key ghcr.io/myorg/app:v1

Подписать static-key вариант - для air-gap или без OIDC

bash
cosign verify --certificate-identity foo --certificate-oidc-issuer bar img:tag

Проверить подпись с обязательной валидацией identity подписанта

bash
cosign attest --predicate sbom.json --type spdx ghcr.io/myorg/app:v1

Прикрепить SBOM как подписанный attestation к image

bash
cosign tree ghcr.io/myorg/app:v1

Показать все подписи и attestations связанные с image

bash
cosign copy ghcr.io/myorg/app:v1 internal-registry/app:v1

Скопировать image вместе с подписями и attestations в другой registry

bash
cosign generate-key-pair --kms awskms:///alias/cosign

Сгенерировать ключ в AWS KMS - private key никогда не покидает KMS

§ см. также

  • oci-specOCI spec - стандарт контейнеровOCI - три спеки: Image (слои + manifest), Runtime (config.json + rootfs для runc), Distribution (registry API). Стандарт после Docker'а; runc, podman, containerd, CRI-O - всё OCI-compatible.
  • tls-certificatesTLS-сертификаты - X.509, цепочка доверия, Let's EncryptTLS cert - X.509 объект с public key + identity (CN/SAN) + подписью CA. Цепочка: leaf → intermediate → root (доверенный OS). Let's Encrypt = бесплатный CA через ACME (HTTP-01/DNS-01 challenge). В k8s - cert-manager.
  • gpg-pgpGPG/PGP - подпись, шифрование, web of trustGPG = open-source реализация OpenPGP. Пара ключей (RSA/ECDSA), публичный для verify/encrypt, приватный для sign/decrypt. keyring локально, web-of-trust между людьми. Используется в git-commit signing, package-signing (apt/rpm), email.
  • runc-and-runscrunc, runsc, kata - container runtimesrunc - стандартный OCI-runtime, namespaces+cgroups+seccomp. runsc/gVisor - userspace-ядро для дополнительной изоляции. kata - облегчённая VM на контейнер. Performance ↔ isolation trade-off.
  • kubernetes-pod-lifecycleKubernetes pod lifecycle - от Pending до TerminatedPod проходит фазы Pending → Running → Succeeded/Failed/Unknown. Init-containers выполняются последовательно до основных. Probes: startup → readiness/liveness. SIGTERM + grace period при удалении.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки