# Cosign и подпись container images (sigstore) _Контейнеры (бонус) · LinuxLab Knowledge Base_ **TL;DR:** cosign подписывает container images. Sigstore = ekosistema: rekor (transparency log), fulcio (CA для keyless OIDC). Подписи хранятся как OCI-объекты рядом с image. Verify - часть admission control в k8s через policy-controller или Kyverno. ## Зачем подписывать 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**, современная альтернатива. Компоненты: | Компонент | Назначение | |-----------|------------| | **cosign** | CLI для sign/verify | | **fulcio** | бесплатный CA, выдаёт короткоживущие ([[tls-certificates|x509]]) сертификаты на основе OIDC-token'а | | **rekor** | append-only transparency log всех подписей (как certificate transparency) | | **policy-controller** / **gatekeeper** / **kyverno** | enforcement в 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-.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 spec - стандарт контейнеров](/kb/oci-spec.md) - [TLS-сертификаты - X.509, цепочка доверия, Let's Encrypt](/kb/tls-certificates.md) - [GPG/PGP - подпись, шифрование, web of trust](/kb/gpg-pgp.md) - [runc, runsc, kata - container runtimes](/kb/runc-and-runsc.md) - [Kubernetes pod lifecycle - от Pending до Terminated](/kb/kubernetes-pod-lifecycle.md)