Пиков / КАПО
Как читать авторский курс и нормативные материалы

Это учебные материалы, а не заключение о соответствии конкретного продукта. Организационные модели курса отделяйте от терминов стандарта: ППК в опубликованном проекте — машиночитаемый перечень, а живой реестр его версий является практикой сопровождения. Термин OSA в разных материалах имеет разный охват. Статус и обязательность нормативного документа проверяются отдельно; проект КАПО опубликован как проект первой редакции.

П14. Подпись SBOM через Sigstore

Что именно решает эта практика SBOM сам по себе — обычный JSON-файл, который кто угодно может подменить между сборкой и проверкой. Подпись через Sigstore привязывает SBOM к конкретному образу как attestation (in-toto DSSE-конверт), фиксирует, кто и в каком pipeline его выпустил (OIDC-identity вместо приватного ключа), и заносит факт подписи в публичный transparency log Rekor. Без этого этапа любой downstream-потребитель доверяет SBOM «на слово».

Цель

Получить верифицируемую привязку CycloneDX SBOM к контейнерному образу: образ подписан keyless-подписью Sigstore, SBOM приложен как attestation типа cyclonedx, а cosign verify-attestation возвращает корректный DSSE-конверт с проверенным OIDC-identity и записью в Rekor. Проверяемый результат — это пара «образ + attestation», которую можно скачать из реестра на другой машине и заново верифицировать без секретов.

Команда или действие

Подготовка: образ уже в реестре (например, ghcr.io/org/app@sha256:...), SBOM сгенерирован (см. П38. Честный SBOM контейнерного образа) в файл bom.cdx.json.

# 1. Авторизация в реестре (cosign использует те же креды, что docker)
cosign login ghcr.io -u <user> --password-stdin <<< "$GHCR_TOKEN"

# 2. Прикрепить SBOM как attestation. В cosign v2 keyless-режим включён по умолчанию:
#    Fulcio выдаёт короткоживущий сертификат на OIDC-identity, Rekor логирует факт.
cosign attest \
  --predicate bom.cdx.json \
  --type cyclonedx \
  --yes \
  ghcr.io/org/app@sha256:<digest>

# 3. Проверка attestation с привязкой к ожидаемому identity и issuer
GITHUB_REPOSITORY='<github-owner>/<repository>'
EXPECTED_CERT_IDENTITY="^https:\/\/github\.com\/${GITHUB_REPOSITORY}\/.+$"

cosign verify-attestation \
  --type cyclonedx \
  --certificate-identity-regexp "$EXPECTED_CERT_IDENTITY" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/org/app@sha256:<digest> | jq -r '.payload' | base64 -d | jq .

https://token.actions.githubusercontent.com здесь не справочная страница, а значение OIDC issuer в сертификате Fulcio. Если нужно проверить саму конфигурацию issuer, смотрите https://token.actions.githubusercontent.com/.well-known/openid-configuration.

Адресуйте образ по digest, а не по тегу Подпись и attestation в Sigstore привязываются к digest (@sha256:...), а не к тегу. Тег :latest можно переназначить на другой образ — и ваша подпись «отвалится». В CI всегда получайте digest сразу после push (docker inspect --format '{{index .RepoDigests 0}}') и работайте с ним.

Что вы должны увидеть

При cosign attest в терминал выводится подтверждение публикации в Rekor:

Generating ephemeral keys...
Retrieving signed certificate...
...
tlog entry created with index: 148203715

cosign verify-attestation сначала печатает блок верификации, затем выдаёт DSSE-конверт. После декодирования payload вы увидите in-toto statement с вашим SBOM внутри predicate:

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "predicateType": "https://cyclonedx.org/bom",
  "subject": [
    { "name": "ghcr.io/org/app", "digest": { "sha256": "<digest>" } }
  ],
  "predicate": {
    "bomFormat": "CycloneDX",
    "specVersion": "1.5",
    "components": [
      {
        "type": "library",
        "name": "zlib",
        "version": "1.3.1"
      }
    ]
  }
}

Ключевой признак успеха в выводе verify: строки Certificate subject, соответствующие шаблону ^https:\/\/github\.com\/<github-owner>\/<repository>\/.+$, и Certificate issuer URL: https://token.actions.githubusercontent.com — они подтверждают, что подписал именно ваш CI-pipeline, а не посторонний.

Если не получилось

Типичные ошибки и как их распознать - Error: no matching attestations при verify — либо вы проверяете другой digest (тег уехал на новый образ), либо attestation так и не загрузился в реестр. Сверьте digest и убедитесь, что шаг attest отработал без ошибок. - error verifying certificate: ... no matching subject — --certificate-identity-regexp/--certificate-oidc-issuer не совпали с тем, кто реально подписал. Это не баг, а сработавшая проверка: либо ошиблись в regexp, либо образ подписан не тем pipeline. Никогда не «чините» это, убрав флаги identity — verify без них принимает любую подпись. - getting signer: getting key from Fulcio: ... 401 — в CI нет валидного OIDC-токена (например, в GitLab не задан id_tokens/SIGSTORE_ID_TOKEN, в GitHub Actions не выставлен permissions: id-token: write). Keyless-подпись без OIDC невозможна. - Подпись прошла локально с браузерной авторизацией, но падает в CI — локально вы прошли interactive OIDC-flow, а в pipeline токена нет. Проверяйте именно CI-окружение, ради которого gate и делается.

Критерий зачёта

Показать преподавателю: 1. Лог шага cosign attest с непустым tlog entry created with index. 2. Вывод cosign verify-attestation с зелёной верификацией и видимыми Certificate subject + Certificate issuer URL. 3. Декодированный in-toto statement, где subject.digest.sha256 совпадает с digest проверяемого образа, а predicateType = https://cyclonedx.org/bom. 4. Устно: почему verify с привязкой к identity/issuer — это контроль, а verify без них — фикция доверия.

Нормативная связка