ADG Оставить заявку
Блог DevOps 4 мин чтения

Harbor 2.12, Cosign и OPA Gatekeeper: как мы остановили первый несанкционированный образ

Перевели несколько K8s-кластеров на подписание образов через Cosign и Harbor 2.12 с нативным SBOM. Рассказываем про интеграцию с OPA Gatekeeper и первый реальный блок.

Контекст момента

Harbor 2.12 с нативной поддержкой SBOM и встроенной верификацией Sigstore подписей - ноябрь 2025

В октябре мы разбирали supply chain-атаки и рассказывали, как внедрили Sigstore для верификации образов у одного клиента. Тогда схема была через Kyverno. За последние пару недель аналогичное внедрение прошло ещё на нескольких кластерах - уже с Harbor 2.12 и OPA Gatekeeper, и за этот период случился один показательный инцидент, о котором стоит рассказать отдельно.

Почему Harbor 2.12 стал точкой старта

Harbor 2.12 вышел в ноябре с двумя изменениями, которые давно ждали: нативная поддержка SBOM в интерфейсе реестра и встроенная верификация Sigstore-подписей без сторонних плагинов. До этого SBOM хранился как обычный OCI-артефакт рядом с образом, но Harbor ничего о нём не знал - это был просто blob. Теперь реестр умеет отображать состав образа в UI, привязывать SBOM к конкретному дайджесту и не давать удалить образ, если к нему прикреплён SBOM и включён retention policy.

Верификация подписей тоже перестала быть внешним делом: Harbor 2.12 умеет блокировать push неподписанных образов на уровне проекта. Это первая линия обороны - до кластера. Мы её включили, но основной enforcement всё равно делается в кластере через admission controller: реестр можно обойти прямым API-вызовом, а вот admission controller - нет.

Схема, которую мы развернули

Пайплайн выглядит так: сборка в GitLab CI, затем syft генерирует SBOM в формате CycloneDX и аттестует его через cosign attest с OIDC-токеном GitLab-раннера. Образ и SBOM-аттестация уходят в Harbor. При деплое в Kubernetes OPA Gatekeeper с Constraint проверяет наличие и валидность cosign-подписи у каждого образа перед тем, как kubelet его запустит.

Схема верификации через Gatekeeper выглядит так:

flowchart LR
    CI["GitLab CI\ncosign sign"] --> Harbor["Harbor 2.12\n(project policy)"]
    Harbor --> K8s["kubectl apply"]
    K8s --> GK["OPA Gatekeeper\nConstraint: ImageSignature"]
    GK -->|pass| Kubelet
    GK -->|block| Error["AdmissionWebhook\n403"]

Constraint написан на Rego и проверяет две вещи: подпись присутствует и сделана ключом, соответствующим нашему Fulcio-сертификату с identity нашего GitLab-раннера. Посторонний ключ или подпись от другого CI-провайдера - блок.

Отдельный момент - работа с keyless-подходом Sigstore в изолированных средах. У части клиентов нет прямого доступа к публичному Rekor и Fulcio. Для таких случаев мы подняли локальный экземпляр sigstore/scaffolding - это набор компонентов (Trillian, Rekor, Fulcio, CT-Log) в одном helm-чарте. Настройка потребовала дня работы, но зато подпись и верификация работают без выхода наружу, что для КИИ-сред важно.

Первый заблокированный образ

Примерно через неделю после включения enforcement в одном из кластеров Gatekeeper поймал образ, который не прошёл верификацию. Это был не злоумышленник - инженер попытался задеплоить образ, который он собрал локально на своём ноутбуке и запушил напрямую в Harbor в обход CI-пайплайна. Причина была понятна: срочный патч, CI-прогон занял бы 15 минут, «просто проверить на стейдже».

AdmissionWebhook вернул 403 с сообщением про отсутствие валидной подписи. Деплой не прошёл. Инженер пришёл к нам с вопросом «что сломалось», получил объяснение - и теперь у нас есть живой пример того, зачем всё это нужно. Не теоретический, а конкретный: человек с хорошими намерениями мог задеплоить образ, собранный вне контролируемой среды, без SAST, без проверки зависимостей, без SBOM.

Важная деталь: Harbor с включённой политикой тоже мог бы этот push не принять. Но инженер нашёл project, где политика не была включена (мы ещё не успели прокатить настройки на все проекты), и запушил туда. Enforcement в кластере поймал то, что реестр пропустил. Это хорошая иллюстрация принципа defence in depth: рассчитывать только на реестр - недостаточно.

Что пока не решено

SBOM-сканирование в CI - Grype и Trivy интегрированы, но пока как информационный шаг без блокирующего gate'а. Настраиваем политику на CVE-severity: блокировать только critical без фиксов, остальное - предупреждение. Порог выбрать сложнее, чем кажется: слишком строгий - CI встанет на первом же популярном базовом образе.

Rotation ключей в keyless-схеме через Fulcio происходит автоматически - каждая подпись привязана к short-lived certificate. Но если Rekor или Fulcio недоступны в момент верификации, Gatekeeper падает в fail-closed режим. Нужен graceful degradation или кешированные transparency log entries - пока работаем над этим.

Охват не полный: несколько legacy-кластеров ещё без Gatekeeper, там пока только Harbor-политика. Перевести их на enforcement - ближайшие недели.

Для управляемой инфраструктуры такой стек - Cosign + Harbor + OPA Gatekeeper - выглядит как разумный выбор на сегодня: зрелые компоненты, понятная модель доверия, и первый реальный блок уже есть. Это не означает «задача закрыта» - означает, что инструмент работает.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.