Подписание образов в Kubernetes: Cosign и Kyverno

Почему это важно: доверие к образам в Kubernetes

Kubernetes многое делает сам — планирует нагрузку, настраивает сеть, масштабирует сервисы. Но за доверие к образам он не отвечает. Если кто-то отправит образ в реестр под именем вашего проекта, кластер не задаст ни одного вопроса — просто скачает и запустит.

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

К счастью, есть простое решение: подписывание образов (image signing).

Подпись доказывает, кто собрал образ, и подтверждает, что с момента сборки он не изменился. Cosign (часть проекта Sigstore) берёт на себя подписывание и проверку подписей, давая каждому контейнерному образу верифицируемую идентичность. Kyverno, в свою очередь, применяет эту границу доверия внутри кластера — он умеет блокировать любую нагрузку, чей образ не подписан доверенным ключом.

В этой статье мы:

  • вручную подпишем и проверим контейнерный образ с помощью Cosign;

  • создадим политику Kyverno, которая отклоняет неподписанные рабочие нагрузки;

  • добавим небольшой GitHub Action, чтобы каждый новый сборочный артефакт автоматически подписывался перед деплоем.

Никакой долгой настройки PKI — только практичное, проверяемое доверие, которое можно подключить к любому кластеру Kubernetes прямо сегодня.

Зачем вообще подписывать образы

Рассмотрим простой пример, который объясняет, почему это важно.

Допустим, у вас чистый сборочный пайплайн. Команда пушит в ghcr.io/company/backend:latest, Kubernetes тянет образ прямо в прод. Все доверяют этому тегу.

Но в один прекрасный день вы поднимаете форк, быстро тестируете изменение и пушите его под тем же тегом. Упс. Реестр принимает образ. Кластер автоматически передеплоивает. Теперь в кластере крутится нечто загадочное.

Ничего злонамеренного не произошло — ни эксплойта, ни скомпрометированных учётных данных. Просто чрезмерно доверенный тег и отсутствующая подпись.

Именно это и решает подписывание образов. Вместо того чтобы верить, что latest — это ваш образ, вы верите в то, что он подписан конкретным известным вам лицом. Cosign даёт это доказательство. Kyverno проверяет его до того, как что-либо запустится.

Прежде чем мы нырнём в детали, давайте посмотрим на реальный публичный пример. Chainguard поддерживает реестр cgr.dev, в котором хранятся подписанные минималистичные контейнерные образы. Каждый из них — например, cgr.dev/chainguard/nginx — можно проверить с помощью Cosign и журнала прозрачности Sigstore.

Быстрая проверка через OIDC (не беспокойтесь — о настройке мы поговорим чуть позже):

matt.brown@matt ~ % cosign verify \
--certificate-identity-regexp "https://github.com/chainguard-images/images/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
cgr.dev/chainguard/nginx
Verification for cgr.dev/chainguard/nginx:latest --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificates

Cosign проверит подпись и подтвердит, что образ был подписан в рамках рабочего процесса OIDC на GitHub. Именно к этому мы и стремимся: верифицируемое доверие к образам, которое доказывает происхождение ваших рабочих нагрузок.

Установка Cosign

Прежде чем что-то подписывать, нужно убедиться, что Cosign установлен и работает локально.

Установка Cosign на macOS

Проще всего поставить Cosign на macOS через Homebrew. Но есть нюанс: Homebrew сейчас поставляет Cosign 3.x, который перешёл от создания отдельных файлов .sig к хранению подписей в виде OCI-бандлов (OCI bundles).

Это хорошее изменение с прицелом на будущее, но сегодня оно ломает совместимость с Kyverno (и рядом других инструментов, которые всё ещё ожидают устаревшие теги .sig).

Если поискать в Homebrew, мы увидим только одну формулу:

brew search cosign
==> Formulae
cosign

Поэтому, чтобы сохранить совместимость с Kyverno, установим Cosign 2.6.1 вручную через Go.

Сначала установим Go и убедимся, что $PATH включает $HOME/go/bin (пропустите, если Go уже установлен):

brew install go
echo 'export PATH="$PATH:$HOME/go/bin"' >> ~/.zshrc
source ~/.zshrc

Затем устанавливаем Cosign 2.6.1:

go install github.com/sigstore/cosign/v2/cmd/cosign@v2.6.1

После установки проверяем версию:

cosign version

Ожидаемый вывод:

______ ______ _______. __ _______ .__ __.
/ | / __ \ / || | / _____|| \ | |
| ,----'| | | | | (----`| | | | __ | \| |
| | | | | | \ \ | | | | |_ | | . ` |
| `----.| `--' | .----) | | | | |__| | | |\ |
\______| \______/ |_______/ |__| \______| |__| \__|
cosign: A tool for Container Signing, Verification and Storage in an OCI registry.
GitVersion: v2.6.1
GitCommit: unknown
GitTreeState: unknown
BuildDate: unknown
GoVersion: go1.25.3
Compiler: gc
Platform: darwin/arm64

Генерация ключей подписи

Переходим к генерации ключей.

Ключи, созданные Cosign

Самый простой способ начать — воспользоваться встроенной возможностью генерации ключей в Cosign. Ключи создаются командой generate-key-pair. Она запросит пароль для закрытого ключа.

matt.brown@matt ~ % cosign generate-key-pair
Enter password for private key:
Enter password for private key again:
Private key written to cosign.key
Public key written to cosign.pub

В результате получаем:

matt.brown@matt ~ % ls | grep cosign
cosign.key
cosign.pub

Открытый ключ можно просто открыть в текстовом виде. Он понадобится нам позже.

matt.brown@matt ~ % cat cosign.pub
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

Если вы потеряете файл .pub, открытый ключ можно восстановить в любой момент командой:

cosign public-key --key cosign.key

Ключи, созданные вне Cosign

Можно также использовать ключи, сгенерированные сторонними инструментами — Cosign умеет их импортировать и приводить к нужному формату.

Вот команда для генерации нового ключа на основе эллиптической кривой (кривая P-256) с сохранением в файл private.pem:

openssl ecparam -name prime256v1 -genkey -noout -out private.pem

Затем импортируем его через Cosign:

matt.brown@matt ~ % cosign import-key-pair --key private.pem
Enter password for private key:
Enter password for private key again:
Private key written to import-cosign.key
Public key written to import-cosign.pub

Результат тот же — ключи готовы к подписи образов.

Публикация подписанных образов

Ключи готовы, значит, пора переходить к следующему шагу — непосредственно к подписи образа и его публикации в реестр.

Я предпочитаю GitHub Container Registry, но подойдёт и Docker Hub, и любой другой реестр.

Соберём образ. Можете использовать любой, но при желании возьмите мой (https://github.com/sf-matt/hello-flask-signed) — это простое Flask-приложение.

matt.brown@matt hello-flask-signed % docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/sf-matt/hello-flask-signed:v1 --push .
[+] Building 5.2s (12/12) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 257B 0.0s
=> [internal] load metadata for docker.io/library/python:3.12-slim
...

Теперь подпишем его:

matt.brown@matt cosign-generated-keys % cosign sign --key cosign.key sfmatt/hello-flask-signed:v1
WARNING: Image reference sfmatt/hello-flask-signed:v1 uses a tag, not a digest, to identify the image to sign.
This can lead you to sign a different image than the intended one. Please use a
digest (example.com/ubuntu@sha256:abc123...) rather than tag
(example.com/ubuntu:latest) for the input to cosign. The ability to refer to
images by tag will be removed in a future release.

Мне казалось умной идеей подписать образ по тегу — Cosign быстро меня поправил, хотя объяснил причину не очень внятно. А причина, в общем-то, проста.

Теги вроде :v1 изменяемы (mutable). Если тег позже будет указывать на другой образ, старая подпись всё равно будет выглядеть действительной для этого тега, хотя исходный образ уже изменился. Это полностью разрушает модель доверия. Дайджест (digest) однозначно идентифицирует конкретную сборку: после подписания никто не может изменить то, на что он указывает, не сделав подпись недействительной.

Cosign решает эту проблему, подписывая по дайджесту, а не по тегу. По всей видимости, в будущих версиях возможность подписывать по тегу будет полностью убрана. Если вы не сохранили дайджест, получим его с помощью Crane — лёгкого CLI от Google из проекта go-containerregistry, который позволяет инспектировать, копировать и управлять контейнерными образами прямо из терминала. Установите через Brew, если его ещё нет.

matt.brown@matt ~ % brew install crane
matt.brown@matt hello-flask-signed % crane digest ghcr.io/sf-matt/hello-flask-signed:v1
sha256:blahblah

Используем полученное значение для подписи:

matt.brown@matt cosign-generated-keys % cosign sign --key cosign.key ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah
Enter password for private key:
WARNING: "ghcr.io/sf-matt/hello-flask-signed" appears to be a private repository, please confirm uploading to the transparency log at "https://rekor.sigstore.dev"
Are you sure you would like to continue? [y/N] N

Интересно — что именно это означает? Когда вы подписываете приватный образ, Cosign предупреждает, что собирается загрузить метаданные в публичный журнал прозрачности Rekor (transparency log, tlog).

Что именно публикуется

Cosign никогда не загружает содержимое образа. По сути, он создаёт лишь небольшую запись, содержащую:

  • дайджест образа (хеш SHA256),

  • сертификат подписи (при использовании беcключевого режима, keyless),

  • криптографическое доказательство того, что запись существует в журнале.

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

Почему нам не нужен Rekor (моё мнение)

Если вы подписываете и проверяете образы, собранные для своего кластера, вы и так контролируете и реестр, и политику проверки. Публикация в публичный журнал прозрачности не даёт никаких преимуществ в плане безопасности — она просто делает дайджесты ваших внутренних образов публичными.

Поэтому пропустим tlog. Можно указать Cosign не публиковать данные в Rekor при подписании приватных образов. Также добавим флаг --recursive для поддержки мультиархитектурного образа.

cosign sign --tlog-upload=false --key cosign.key --recursive ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah

Подпись останется действительной и Kyverno сможет её проверить, но публичная запись в журнале аудита создаваться не будет. Вот и всё — у нас есть подписанный образ. Теперь закономерный вопрос: что именно произошло?

Проверка подписи

Пока что Cosign не дал нам никакого видимого подтверждения в терминале. Чтобы убедиться, что образ подписан, выполним верификацию по нашему открытому ключу:

matt.brown@matt cosign-generated-keys % cosign verify \
--key cosign.pub \
--insecure-ignore-tlog=true \
ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the signature.
Verification for ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah--
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
[{"critical":{"identity":{"docker-reference":"ghcr.io/sf-matt/hello-flask-signed"},"image":{"docker-manifest-digest":"sha256:blahblah"},"type":"cosign container image signature"},"optional":null}]

Подтверждение получено. Но что именно у нас есть? Найдём подпись с помощью команды triangulate:

matt.brown@matt cosign-generated-keys % cosign triangulate ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah
ghcr.io/sf-matt/hello-flask-signed:sha256-blahblah.sig

Посмотрим на артефакт в GHCR с помощью tree:

matt.brown@matt cosign-generated-keys % cosign tree ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah
📦 Supply Chain Security Related artifacts for an image: ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah
└── 🔐 Signatures for an image tag: ghcr.io/sf-matt/hello-flask-signed:sha256-blahblah.sig
└── 🍒 sha256:different-blahblah

Эту подпись можно увидеть и в интерфейсе GitHub.

Что такое образ .sig изнутри

Когда Cosign подписывает образ в версии 2.x и более ранних, он публикует артефакт подписи обратно в реестр. Этот артефакт появляется как тег, оканчивающийся на .sig — как мы видели: ghcr.io/sf-matt/hello-flask-signed:sha256-<digest>.sig. По сути это OCI-манифест, содержащий небольшой JSON-бандл с самой подписью, сертификатом (при беcключевом режиме) и опциональным доказательством из журнала прозрачности. Cosign и такие инструменты, как Kyverno, автоматически находят и проверяют этот артефакт при проверке образа, так что вам никогда не придётся работать с .sig напрямую.

OIDC и Sigstore

Для публичных образов можно полностью отказаться от локальных ключей и использовать беcключевой режим Sigstore (keyless mode). В этом удобном режиме аутентификация выполняется через вашу учётную запись GitHub (или ряд других провайдеров) по протоколу OpenID Connect (OIDC).

Поскольку в этом примере используется публичный образ, вы можете повторить шаги на моём: ghcr.io/sf-matt/hello-flask-oidc:v1.

Для подписи выполните ту же команду, что и раньше, но без ключей:

cosign sign ghcr.io/sf-matt/hello-flask-oidc@sha256:04147f2536d03c40a3ac595de6c1c87f06924b775dc92442e0b3b04e5ed5793e

Cosign откроет браузер и предложит войти через GitHub (или другой провайдер). После аутентификации будет выпущен кратковременный сертификат подписи от Fulcio, а подпись будет загружена и в реестр, и в журнал прозрачности Rekor.

Вы увидите сообщение, подтверждающее подпись и запись в журнале:

Retrieving signed certificate...
Successfully verified SCT...
tlog entry created with index: 672916270
Pushing signature to: ghcr.io/sf-matt/hello-flask-oidc

Для проверки подписи:

cosign verify --certificate-identity "sdmattbrown@gmail.com" --certificate-oidc-issuer "https://github.com/login/oauth" ghcr.io/sf-matt/hello-flask-oidc@sha256:<digest>

Cosign проверит сертификат, подтвердит запись в Rekor и покажет подписанные утверждения. Никаких локальных ключей, никаких запросов пароля — только подпись на основе OIDC, привязанная к вашей учётной записи GitHub.

Проверка образов с помощью Kyverno

Переходим к более интересной части — Kubernetes. Начнём с создания ImageValidatingPolicy. Вот пример для нашего подписанного образа.

apiVersion: policies.kyverno.io/v1alpha1
kind: ImageValidatingPolicy
metadata:
  name: ghcr-check-images
spec:
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE"]
      resources: ["pods"]
  evaluation:
    background:
      enabled: false
    validationActions: [Deny]
  failurePolicy: Ignore
  attestors:
  - name: cosign
    cosign:
      key:
        data: |
          -----BEGIN PUBLIC KEY-----
          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMitBUveNmKw57+UdJ3+mbGKlWp5B
          oWm+HWOBKap2V0Oa2whm/IHHoqReZUPdgj+fsAGyyBvSlbbfQV44zJhx5w==
          -----END PUBLIC KEY-----
  validations:
  - expression: >
      images.containers.all(i,
        (image(i).registry() == "ghcr.io" &&
         image(i).repository().startsWith("sf-matt/"))
        ? verifyImageSignatures(i, [attestors.cosign]) > 0
        : true
      )
    message: all images from ghcr.io/sf-matt must have a valid Cosign signature

Эта политика ImageValidatingPolicy делает следующее:

  • Область применения: распространяется на все операции CREATE для Pod.

  • Цель: проверяет только образы, извлекаемые из ghcr.io/sf-matt.

  • Attestor: использует встроенный открытый ключ Cosign для проверки подписей.

  • Логика: запускает verifyImageSignatures() для каждого образа контейнера.

  • Принуждение: отклоняет рабочую нагрузку, если хотя бы один образ из вашего пространства имён GHCR не подписан доверенным ключом.

  • Поведение: игнорирует другие реестры и отключает фоновое сканирование (проверка выполняется только при создании ресурса).

Если коротко: если образ пришёл из вашего пространства имён и не подписан криптографически — он не запустится.

Проблема возникает, если ваш GitHub Container Registry (GHCR) приватный, как в нашем примере. Kyverno нужны учётные данные для скачивания и проверки подписей. Их можно предоставить через стандартный секрет Kubernetes типа dockerconfigjson.

Сгенерируйте персональный токен доступа GitHub (классический или с ограниченными правами) с разрешением read:packages, затем создайте секрет в том же пространстве имён, где работает Kyverno (обычно kyverno). Сделать это в режиме imperative достаточно просто:

kubectl create secret docker-registry ghcr-creds --docker-server=ghcr.io --docker-username=<your-github-username> --docker-password=<your-personal-access-token> --docker-email=<your-email> -n kyverno

Проверяем создание:

kubectl get secret ghcr-creds -n kyverno

Затем добавляем ссылку на секрет в ImageValidatingPolicy через поле credentials. Это говорит Kyverno использовать ваши учётные данные GHCR при проверке подписей Cosign для приватных образов.

apiVersion: policies.kyverno.io/v1alpha1
kind: ImageValidatingPolicy
metadata:
  name: ghcr-check-images
spec:
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE"]
      resources: ["pods"]
  evaluation:
    background:
      enabled: false
    validationActions: [Deny]
  failurePolicy: Ignore
  credentials:
    providers:
    - "github"
    - "default"
    secrets:
    - "ghcr-creds"
  attestors:
  - name: cosign
    cosign:
      key:
        data: |
          -----BEGIN PUBLIC KEY-----
          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoYIRqyJPEOGk84mh9W3XWA42dOPm
          UE03IhLs2sLnRPegfWAO+6mSy8pbEO8R5orKIXqHWq2fz8s6UG9iTXbaRQ==
          -----END PUBLIC KEY-----
      ctlog:
        insecureIgnoreTlog: true
        url: "https://rekor.sigstore.dev"
  validations:
  - expression: >
      images.containers.all(i,
        (image(i).registry() == "ghcr.io" &&
         image(i).repository().startsWith("sf-matt/"))
        ? verifyImageSignatures(i, [attestors.cosign]) > 0
        : true
      )
    message: all images from ghcr.io/sf-matt must have a valid Cosign signature

Но здесь я столкнулся с проблемой. Похоже, политика ищет секреты как кластерные объекты. Если включить расширенное логирование, можно увидеть следующее:

2025-11-03T22:22:02Z -5 k8s.io/client-go@v0.33.3/transport/round_trippers.go:632 > Response logger=klog milliseconds=1 status="404 Not Found" url=https://10.96.0.1:443/api/v1/secrets/ghcr-creds v=6 verb=GET

Всё заработает, если переключить образ на публичный — но это противоречит цели нашего упражнения. Поэтому перейдём к проверенному ClusterPolicy с verifyImages.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: check-ghcr-image
spec:
  webhookConfiguration:
    failurePolicy: Fail
    timeoutSeconds: 30
  background: false
  rules:
  - name: check-ghcr-image
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "ghcr.io/sf-matt/hello-flask*"
      failureAction: Enforce
      attestors:
      - entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoYIRqyJPEOGk84mh9W3XWA42dOPm
              UE03IhLs2sLnRPegfWAO+6mSy8pbEO8R5orKIXqHWq2fz8s6UG9iTXbaRQ==
              -----END PUBLIC KEY-----
            rekor:
              ignoreTlog: true
              url: https://rekor.sigstore.dev
              pubkey: |-
                -----BEGIN PUBLIC KEY-----
                MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoYIRqyJPEOGk84mh9W3XWA42dOPm
                UE03IhLs2sLnRPegfWAO+6mSy8pbEO8R5orKIXqHWq2fz8s6UG9iTXbaRQ==
                -----END PUBLIC KEY-----
            ctlog:
              ignoreSCT: true
              pubkey: |-
                -----BEGIN PUBLIC KEY-----
                MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoYIRqyJPEOGk84mh9W3XWA42dOPm
                UE03IhLs2sLnRPegfWAO+6mSy8pbEO8R5orKIXqHWq2fz8s6UG9iTXbaRQ==
                -----END PUBLIC KEY-----

Теперь деплой должен проходить без проблем. А если у вас запущен Policy Reporter, вы увидите результат проверки.

Результат проверки политики Kyverno в Policy Reporter

Кстати, с ImageValidatingPolicy это отображение было бы недоступно — ещё один плюс в пользу ClusterPolicy.

Sigstore Policy Controller

Работа с Kyverno для проверки образов выглядит довольно громоздко. Попробуем другой подход.

В документации Sigstore я нашёл их Policy Controller — это просто admission controller. Давайте проверим, можно ли с помощью Sigstore policy-controller добиться того же, что мы сделали с Kyverno.

Возьмём пару ключей, сгенерированную ранее Cosign, или создадим новую. Можно использовать то же приложение или создать новое.

Установка Policy Controller

Установка policy-controller:

helm repo add sigstore https://sigstore.github.io/helm-charts
helm repo update
helm upgrade --install policy-controller sigstore/policy-controller -n cosign-system --create-namespace

Затем помечаем нужное пространство имён для тестирования.

Осторожно

Имейте в виду: после добавления метки, если для образа не задана политика, его деплой будет заблокирован.

kubectl label namespace default policy.sigstore.dev/include=true

Новый Admission Controller установлен. Создадим ClusterImagePolicy, которая требует наличия подписи, созданной вашей парой ключей. Вставим открытый ключ (PEM) непосредственно в манифест.

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: require-cosign-keypair
spec:
  mode: enforce
  images:
  - glob: ghcr.io/sf-matt/hello-flask*
  authorities:
  - key:
      data: |
        -----BEGIN PUBLIC KEY-----
        MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoYIRqyJPEOGk84mh9W3XWA42dOPm
        UE03IhLs2sLnRPegfWAO+6mSy8pbEO8R5orKIXqHWq2fz8s6UG9iTXbaRQ==
        -----END PUBLIC KEY-----

Применяем политику:

kubectl apply -f require-cosign-keypair.yaml

Теперь деплоим ту же рабочую нагрузку — она должна пройти без ошибок. Проверить результаты валидации можно в логах:

kubectl -n cosign-system logs deploy/policy-controller-webhook

Вы увидите примерно следующее:

{...Validated 1 policies for image ghcr.io/sf-matt/hello-flask-signed@sha256:blahblah...}

Всё работает — просто и без лишних усилий. Никаких проблем с image pull secrets и никаких сложных CEL-выражений (хотя от них мы отказались ещё при переходе на ClusterPolicy). Иметь ещё один Admission Controller не идеально, но по своей природе они хорошо сосуществуют и могут работать в стеке. Так что этот вариант определённо заслуживает внимания. Накладные расходы одного пода при этом весьма невелики:

Limits:
  cpu: 200m
  memory: 512Mi
Requests:
  cpu: 100m
  memory: 128Mi

Итоги

Kubernetes с удовольствием запустит всё, что ему дашь, — без лишних вопросов. Он не проверяет подписи, происхождение и то, кто на самом деле собрал образ. Кластер — это ultimate easy button.

Подписывание образов — это недостающий слой доверия, который большинство команд пропускает, хотя добавить его абсурдно просто.

С помощью Cosign вы можете дать каждой сборке верифицируемую идентичность — через собственную пару ключей или через беcключевое подписание, привязанное к OIDC-рабочему процессу GitHub. С помощью Kyverno можно чётко очертить границы того, что разрешено запускать в кластере. А Sigstore Policy Controller позволяет ещё туже затянуть эту петлю с более простыми и прямолинейными политиками.

Вместе они превращают Kubernetes API в настоящий контрольно-пропускной пункт цепочки поставок. Если что-то появится без подписи, с признаками подмены или собранное вне ваших пайплайнов — оно просто не запустится.

Лучшая часть? Это до смешного просто. Всё с открытым исходным кодом, всё проверяемо и построено на тех же основаниях, что лежат в основе современной безопасности цепочки поставок программного обеспечения.

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

© 2026 meganuke