Перейти к содержанию

Цепочка поставок

Начиная с версии 1.0.0 образы контейнеров, которые Turbo EA публикует в GHCR, несут проверяемые метаданные цепочки поставок, чтобы операторы могли убедиться, что образ создан CI этого проекта, прежде чем разворачивать его в продакшене.

На этой странице описано, что подписывается, какая версия cosign вам нужна, как проверить образ и Helm-чарт, где находится SBOM и как в это вписывается шлюз Trivy.


Что подписывается

Каждый образ, собранный .github/workflows/docker-publish.yml и отправленный в ghcr.io/vincentmakes/turbo-ea/<image>, подписан с помощью cosign по схеме бесключевого OIDC: долгоживущего ключа подписи не существует. Сертификат выдаётся Fulcio от Sigstore для идентичности рабочего процесса (https://github.com/vincentmakes/turbo-ea/.github/workflows/docker-publish.yml@<ref>), фиксируется в публичном журнале прозрачности Rekor и уничтожается сразу после создания подписи.

Подписанные образы:

  • ghcr.io/vincentmakes/turbo-ea/db
  • ghcr.io/vincentmakes/turbo-ea/backend
  • ghcr.io/vincentmakes/turbo-ea/frontend
  • ghcr.io/vincentmakes/turbo-ea/nginx
  • ghcr.io/vincentmakes/turbo-ea/mcp-server

Helm-чарт ghcr.io/vincentmakes/turbo-ea/charts/turbo-ea подписывается тем же способом рабочим процессом .github/workflows/helm-publish.yml при каждом теге релиза (идентичность …/helm-publish.yml@<ref>).

Образ ollama пересобирается вручную вне матрицы и в настоящее время не подписан; если вы используете встроенный профиль Ollama и вам нужна проверка, соберите его из исходного кода.

Подпись относится к дайджесту списка манифестов OCI, поэтому одна подпись прозрачно покрывает и linux/amd64, и linux/arm64. Отдельных подписей для каждой платформы искать не нужно.


Формат подписи и требуемая версия cosign

Проверяйте с помощью cosign 2.6 или новее либо любой версии 3.x. Более старые клиенты — cosign 2.5 и ниже — сообщают no signatures found для каждого образа и чарта, опубликованного начиная с 1.37.0, хотя подпись на месте.

Причина — смена формата хранения, а не самой подписи. До 1.36.0 рабочий процесс публикации использовал cosign 2, который сохранял подпись под тегом sha256-<digest>.sig рядом с образом. Начиная с 1.37.0 (июнь 2026 года, когда установщик cosign перешёл на cosign 3) подпись представляет собой Sigstore bundle: OCI 1.1 referrer образа. GHCR не реализует referrers API, поэтому cosign хранит bundle под резервным индексным тегом sha256-<digest> — без суффикса .sig — то есть именно там, куда клиент версии ниже 2.6 никогда не заглядывает. cosign tree показывает, что прикреплено к образу:

cosign tree ghcr.io/vincentmakes/turbo-ea/backend:<version>
Релизы Формат подписи Проверяется с помощью
1.0.0 – 1.36.0 устаревший тег sha256-<digest>.sig любой cosign
1.37.0 и новее, а также все Helm-чарты Sigstore bundle (OCI referrer) cosign ≥ 2.6 или 3.x

Теперь каждая публикация проверяет собственную подпись с помощью cosign 2.6 — самого старого клиента, который обещает эта страница, — прежде чем задание станет зелёным, поэтому будущая смена формата приведёт к сбою CI, а не вашего развёртывания. Проект намеренно выпускает одну подпись в текущем формате Sigstore: если контроллер допуска или движок политик в вашем кластере всё ещё читает только устаревший тег, обновите его, а не ждите второй подписи.


Проверка образа

Установите cosign 2.6 или новее, затем:

cosign verify \
  --certificate-identity-regexp 'https://github.com/vincentmakes/turbo-ea/.+' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/vincentmakes/turbo-ea/backend:<version>

Что делают флаги:

  • --certificate-identity-regexp — принимает любой путь рабочего процесса внутри этого репозитория, поэтому одна и та же команда работает независимо от того, опубликован ли образ из docker-publish.yml в main или по тегу. Если нужна большая строгость, замените на --certificate-identity 'https://github.com/vincentmakes/turbo-ea/.github/workflows/docker-publish.yml@refs/tags/v<version>'.
  • --certificate-oidc-issuer — закрепляет издателя OIDC за конечной точкой токенов GitHub. Подпись, выданная любым другим издателем (например, CI форка), не пройдёт проверку.

Успешная проверка выводит подписанную нагрузку и запись журнала прозрачности Rekor. Неудача завершается ненулевым кодом выхода и диагностикой — останавливайте на ней своё развёртывание. Если диагностика гласит no signatures found, сначала проверьте cosign version: см. раздел выше.

Можно также проверять по дайджесту — это самая строгая форма (не зависит от переназначения тегов):

DIGEST=$(docker buildx imagetools inspect ghcr.io/vincentmakes/turbo-ea/backend:<version> --format '{{ .Manifest.Digest }}')
cosign verify \
  --certificate-identity-regexp 'https://github.com/vincentmakes/turbo-ea/.+' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/vincentmakes/turbo-ea/backend@${DIGEST}

Проверка Helm-чарта

Чарт — это OCI-артефакт в том же реестре, и проверяется он той же командой:

cosign verify \
  --certificate-identity-regexp 'https://github.com/vincentmakes/turbo-ea/.+' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/vincentmakes/turbo-ea/charts/turbo-ea:<version>

Одно исключение в идентичности тега: чарт 2.141.0, первый релиз чарта, был отправлен, но не подписан (его шаг подписи не прошёл аутентификацию в реестре) и был подписан позднее из ветки main, поэтому идентичность его сертификата — …/helm-publish.yml@refs/heads/main, а не ссылка на тег. Приведённое выше регулярное выражение принимает оба варианта; строгий --certificate-identity для этой единственной версии должен указывать refs/heads/main.


SBOM

Спецификация состава программного обеспечения в формате SPDX автоматически формируется buildkit (sbom: true на шаге сборки) и прикрепляется к каждому образу как OCI referrer. Устанавливать ничего дополнительно не нужно — она хранится в реестре рядом с образом.

Получить её можно так:

docker buildx imagetools inspect --format '{{ json .SBOM }}' \
  ghcr.io/vincentmakes/turbo-ea/backend:<version> | jq .

SBOM перечисляет каждый пакет, который buildkit обнаружил в итоговом образе (пакеты apk, Python wheels, модули Node и т. д.), с версиями и URL источников. Полезные входные данные для вашего сканера уязвимостей, инструментов лицензионного соответствия или реестра компонентов.


Сканирование уязвимостей (Trivy)

Рабочий процесс публикации запускает Trivy для каждого собранного образа в два шага:

  • Наблюдение — находки уровней HIGH и CRITICAL загружаются в формате SARIF на вкладку Security репозитория в GitHub. Этот шаг никогда не приводит к сбою задания.
  • Шлюз — любая находка уровня CRITICAL с доступным исправлением приводит к сбою публикации, если только CVE не внесена в .github/trivy-allowlist с письменным обоснованием (каждая запись пересматривается ежеквартально и удаляется, как только апстрим выпускает патч).

Те же два шага ежедневно повторяются для реально опубликованных манифестов :latest, а исправимая находка HIGH или CRITICAL в опубликованном образе запускает пересборку на свежих репозиториях Alpine. Находки уровня HIGH пока остаются только под наблюдением: базовые образы построены на alpine (python:3.12-alpine, postgres:18-alpine, nginx:alpine) и регулярно несут фоновые находки по musl-libc и транзитивным зависимостям apk, до которых не доходит ни один путь кода Turbo EA, но о которых Trivy всё равно сообщает.

Операторам: шлюз защищает опубликованные образы, но всё же запускайте собственный сканер по загруженному образу — ваша политика может отличаться от нашей. Опубликованный SBOM — чистые входные данные для него.

Участникам: если вы обнаружили находку, действительно эксплуатируемую в сценарии использования Turbo EA, сообщите о ней через приватное уведомление о безопасности, а не в комментарии к публичному issue. См. SECURITY.md.


Закрепление Actions по SHA

Каждый GitHub Action, используемый рабочим процессом публикации, закреплён за 40-символьным SHA коммита, а не за плавающим мажорным тегом. Это означает, что скомпрометированный апстрим-мейнтейнер или тайпсквоттер не сможет незаметно изменить то, что выполняется в нашем CI, без видимого diff в этом репозитории. Обновления приходят через экосистему github-actions Dependabot с ежемесячной периодичностью, так что обновление продолжается — просто проходит через ревью.