Главная

8 мин

Подпись контейнеров Cosign: политика допуска и CI/CD

Подпись контейнеров Cosign: как остановить подмену образов, настроить проверку в CI/CD и admission в Kubernetes, а исключения оформить и аудировать.

Подпись контейнеров Cosign: политика допуска и CI/CD

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

Контейнерный образ легко подменить так, чтобы снаружи он выглядел «тем же самым». Тег вроде app:1.2.3 сегодня может указывать на один набор слоев, а завтра - на другой. Внутри при этом может оказаться вредоносный код или незаметная настройка, которая открывает доступ. Самое неприятное в подмене то, что она часто не ломает сборку и не выглядит как атака.

Сканирование уязвимостей полезно, но оно отвечает на другой вопрос: «есть ли известные дыры в составе?». Оно не доказывает, что образ действительно собрала ваша команда и что его не заменили по пути. Вредоносный образ может быть «чистым» по CVE, но собранным злоумышленником. Поэтому подпись образов (Cosign и похожие механизмы) нужна как проверка происхождения и целостности.

Доверие чаще всего рвется в трех местах: в реестре (кто может перезаписать тег или протолкнуть образ), в CI (кто имеет доступ к секретам и шагам публикации), и в правах на кластере (кто может деплоить что угодно, обходя процесс).

Подпись позволяет ввести простой контроль: «этот образ выпустили мы, по нашим правилам». Практический смысл такой:

  • защититься от подмены даже при компрометации тега;
  • решить, где проверять подпись: в CI/CD, в Kubernetes или в обоих местах;
  • настроить понятные исключения, чтобы не тормозить работу и не терять аудит;
  • договориться, кто имеет право выпускать релизы и как это подтверждается.

Если вы работаете в среде с повышенными требованиями (госсектор, финансы, медицина), подпись образов часто становится не «опцией безопасности», а способом доказать контроль цепочки поставки при проверках и инцидентах.

Простыми словами: подпись, digest и аттестации

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

Базовые термины без лишней теории

  • Образ (image): набор слоев с приложением и зависимостями, который вы запускаете в контейнере.
  • Манифест: «оглавление» образа - какие слои входят, какие платформы поддерживаются, какие метаданные.
  • Digest: хэш (например, sha256) от содержимого манифеста. Если изменится хоть байт, digest станет другим.
  • Тег (tag): удобное имя вроде app:1.2 или app:latest. Тег можно переназначить на другой образ, и это нормальная функция реестра.

Ключевая мысль: digest почти всегда неизменяем, а тег изменяем. Поэтому политики безопасности обычно опираются на digest, а не на тег.

Подпись vs хэш: что защищает, а что нет

Digest отвечает на вопрос: «тот ли самый набор байтов?». Но он не отвечает на вопрос: «кто это выпустил и можно ли этому доверять?». Если злоумышленник получит доступ к реестру и запушит новый образ под тем же тегом, digest изменится, но без проверки на стороне кластера это легко пропустить.

Подпись добавляет вторую часть: кто подписал и по каким правилам вы это принимаете. Подпись не заменяет digest. Она привязывается к конкретному содержимому (обычно к digest) и подтверждает источник.

Аттестации: что можно подтвердить, кроме факта подписи

Помимо «образ подписан», можно проверять дополнительные доказательства (аттестации). Например:

  • образ собран в вашем CI, а не вручную на ноутбуке;
  • пройдено сканирование уязвимостей с приемлемым результатом;
  • использована конкретная базовая ОС или базовый образ;
  • применены нужные параметры сборки (например, запрет привилегированных инструкций).

Связь с политикой допуска простая: admission control в Kubernetes становится «охранником на входе». Он решает, можно ли запускать Pod, и проверяет подпись и аттестации. Если проверка не проходит, запуск блокируется еще до того, как образ начнет выполняться.

Cosign и Notary v2: чем отличаются и когда что уместно

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

Cosign вырос вокруг практичной идеи: подписывать конкретный образ по его digest и хранить подпись рядом с ним в реестре. Часто туда же добавляют аттестации (чем образ собран, какие проверки прошел). Поэтому Cosign обычно хорошо ложится на реальный CI/CD: собрали, проверили, подписали, дальше в деплой пускаем только подписанное.

Notary v2 задуман как стандартный слой доверия для реестров: публикация артефактов (в том числе подписи и метаданных) в формате OCI, чтобы разные инструменты понимали это одинаково. На практике это чаще история про унификацию и поддержку на стороне реестра и экосистемы, а не про один конкретный инструмент.

Ориентир выбора:

  • Берите Cosign, если нужно быстро поставить работающие ворота в CI/CD и привязать подпись к конкретному digest.
  • Смотрите в сторону Notary v2, если важна стандартизация и вы хотите опираться на поддержку со стороны реестра и платформенных компонентов.
  • Если у вас уже есть Kubernetes, проще начать с Cosign: его обычно легче связать с admission control и правилами допуска.

Главное - не смешивать подходы без плана. Частая ошибка: часть команд подписывает через Cosign, часть публикует метаданные в стиле Notary, а проверка в кластере настроена только на один вариант.

Чтобы не запутаться, заранее зафиксируйте:

  • один источник истины: какие подписи считаются валидными и где они лежат;
  • один набор правил проверки для CI и для Kubernetes;
  • кто владеет ключами или идентичностями подписания;
  • как вы мигрируете, если позже решите перейти на другой стандарт.

Модель доверия: кто подписывает и кто проверяет

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

Доверенная сторона, которая подписывает, обычно не разработчик на ноутбуке. Самый надежный вариант - CI, который собирает образ из репозитория, прогоняет тесты и сканирование, а затем подписывает итоговый артефакт. Так вы снижаете риск того, что кто-то подпишет «случайную» сборку или образ, скачанный неизвестно откуда. Если вы внедряете Cosign, заранее решите, кто является «владельцем релиза»: команда платформы, релиз-инженер или CI как сервисная учетная запись.

Ключи можно хранить двумя путями: классические ключи (желательно в HSM или KMS, а не в переменных окружения) или keyless-подход, где подпись привязана к удостоверенной идентичности (например, к учетной записи CI). Второй вариант уменьшает риск утечки долгоживущих ключей, но требует аккуратных прав и нормального журналирования.

Проверяющая сторона - это минимум две точки. Первая - CI/CD, когда вы разворачиваете в тест или стейдж и хотите остановить «левый» образ до Kubernetes. Вторая - кластер через admission control, чтобы в прод не попадало ничего, что не проходит политику.

Важно договориться, что считать «продом». Обычно это конкретные реестры и репозитории, отдельные проекты в registry, и четкое правило: в прод допускаются только образы по digest, а не по тегу.

Подписывать стоит не «папку с Dockerfile», а конкретный результат:

  • образ по digest (то, что реально запускается);
  • аттестации сборки (кто и как собрал);
  • SBOM или хотя бы базовую метку о составе (если вы это используете);
  • разрешенный источник (какой registry и какой репозиторий).

Пример: у интегратора, который ведет проекты для госсектора, удобно разделить доверие так: CI подписывает только сборки из основной ветки, реестр хранит только подписанные digests, а кластер в проде принимает образы только из «продового» репозитория и только с валидной подписью и аттестациями.

Пошагово: как внедрить подпись образов в команде

Начните с простого правила: подпись должна быть частью обычной сборки, а проверка - частью обычного деплоя. Тогда это не «дополнительная безопасность», а стандарт качества.

Сначала договоритесь, чем именно вы подписываете и что хотите доказать подписью. Для многих команд Cosign удобен тем, что хорошо ложится на GitHub Actions, GitLab CI и другие пайплайны. Notary v2 чаще выбирают, когда уже есть сильный фокус на реестр и единые политики для разных типов артефактов.

Рабочая последовательность, которая обычно не ломает процессы:

  • Определите формат: только подпись образа или подпись плюс аттестации (например, SBOM, кто собирал, какие тесты прошли).
  • Выберите модель ключей: классические ключи (и где они хранятся) или подпись через идентичность (OIDC) без долгоживущих секретов.
  • Зафиксируйте правило: подписываем только по digest, а не по тегу. Тег удобен людям, но digest фиксирует точный байт-в-байт образ.
  • Добавьте аттестации как отдельный обязательный артефакт: «собрано в CI из такого-то коммита», «прошло сканирование», «использовался такой-то базовый образ».
  • Составьте матрицу обязательности: какие репозитории и окружения требуют подпись всегда (prod), где допустимы исключения (dev), и кто их утверждает.

Небольшой пример: команда выпускает сервис для банка или госзаказчика, где важен контроль поставки. Для prod вы требуете подпись и аттестацию «собрано в корпоративном CI», а для песочницы разрешаете неподписанные образы, но только из отдельного реестра и с коротким сроком жизни.

На этом шаге не усложняйте. Сначала сделайте подпись обязательной для 1-2 критичных сервисов, обкатайте хранение ключей и выпуск по digest, и только потом расширяйте правило на весь каталог образов.

Проверка в CI/CD: где поставить ворота и что логировать

Подпись должна появляться тогда, когда артефакт уже заслуживает доверия. Обычно это момент после сборки и тестов, но до того, как образ «уйдет» дальше по цепочке. Так вы подписываете не «идею образа», а конкретный результат: один digest, один набор входных данных, одна версия.

Удобная схема выглядит так:

  • Сборка образа и прогон тестов.
  • Сканирование уязвимостей и базовые проверки (например, запрет root, минимальные базовые образы).
  • Создание аттестаций (что именно проверили и чем).
  • Подпись образа и публикация в реестр.
  • Отдельный шаг перед деплоем: проверка подписи и аттестаций как обязательные «ворота».

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

Чтобы обход был невозможен, закройте «дырки» в процессе. Типичный сценарий: кто-то может напрямую запушить образ в прод-репозиторий и обойти пайплайн. По смыслу решение простое: запрет прямых push, публикация только через CI-сервисный аккаунт, и отдельные репозитории для dev и prod с разными правами.

Логи и артефакты нужны не для красоты, а для разборов. Сохраняйте:

  • digest образа, тег и точное время публикации;
  • идентификатор сборки (pipeline run), коммит и автора изменений;
  • результат проверки подписи и список проверенных аттестаций;
  • версию ключей или идентификатор доверенного подписанта;
  • причину отказа в одном читаемом сообщении.

При провале проверки делайте статус понятным: «не найдена подпись», «подписант не из доверенного списка», «нет аттестации тестов». Дальше есть два безопасных пути: остановить деплой и оставить предыдущую версию, либо выполнить автоматический откат на последний успешно проверенный digest. Главное - чтобы «красный» шаг в пайплайне был финальным и не превращался в предупреждение, которое можно игнорировать.

Политика допуска в Kubernetes: блокировать неподписанное

Admission в Kubernetes - это проверка запроса на создание или изменение ресурсов до того, как они попадут в кластер. По сути, это последний рубеж: даже если кто-то смог протолкнуть манифест в репозиторий или обойти CI, кластер все равно может сказать "нет" и не запустить подмененный образ.

Практичный минимум для продакшена: разрешать запуск только тех образов, которые (1) подписаны и (2) закреплены по digest, а не по тегу. Теги вроде latest или release можно незаметно перезаписать, а digest всегда указывает на конкретный набор данных. Поэтому политика часто сводится к двум правилам: «теги запрещены» и «подпись обязательна». Если вы используете Cosign, это обычно превращается в проверку подписи по публичному ключу или по идентичности подписанта.

Чтобы не сломать разработку, правила стоит разделить по средам:

  • dev: режим аудита (разрешаем, но логируем неподписанное и использование тегов)
  • stage: блокируем теги, требуем подпись для критичных namespace
  • prod: блокируем все неподписанное и все, что не закреплено по digest

Отдельный вопрос - базовые образы. Часто команда тянет их из публичных реестров, и подписи там могут быть разными или отсутствовать. Рабочая стратегия: завести список доверенных базовых образов и зеркалировать их во внутренний реестр, где вы контролируете digest и можете подписать «как одобрено» после сканирования и проверки лицензий.

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

  • кто пытался деплоить (пользователь, сервисный аккаунт);
  • какой образ и какой digest;
  • почему отказано (нет подписи, неверная подпись, запрещен тег);
  • куда деплоили (кластер, namespace);
  • номер изменения или пайплайна, если он прокидывается в метаданные.

Так admission превращается из «охранника» в удобный аудит: видно не только факт блокировки, но и причину, и дальше это проще исправить без ручных разбирательств.

Частые ошибки и ловушки при внедрении подписи

Самая частая проблема - команда вроде бы включила подпись (например, через Cosign), но оставила лазейки, через которые в прод попадает другой образ. Подпись тогда превращается в формальность.

Один из типичных обходов - деплой по тегу. Тег можно перезаписать, и вы получите новый бинарник под старым названием. Если политика допуска не требует digest, злоумышленнику (или просто неосторожному инженеру) достаточно «подменить» тег в реестре. Правильный ориентир для проверок - digest, а подпись должна относиться именно к нему.

Еще одна ловушка - ручные деплои и «временные» отключения проверок. Когда срочно, люди идут короткой дорогой: kubectl apply с ноутбука, образ из личного репозитория, а проверка подписи остается только в CI. Если на стороне кластера нет admission control, эта короткая дорога становится постоянной.

Обычно систему ломает сочетание таких ошибок:

  • общий доступ к ключам подписи или хранение их в чатах и общих папках;
  • слишком широкие исключения (по namespace или по образу целиком) без срока действия;
  • нет разделения ролей: тот же человек и собирает, и «выпускает» в прод;
  • проверка сделана только в CI, а кластер принимает все подряд;
  • слабые права в реестре: много кто может пушить в «продовый» репозиторий.

Отдельно про исключения. Если у них нет владельца, причины и даты окончания, они копятся. Через полгода у вас уже «все временно», и политика допуска становится дырявой. Введите правило: каждое исключение имеет срок, тикет и ответственного, иначе оно не применяется.

Небольшой реальный сценарий: разработчик исправляет баг, пушит образ с тем же тегом, что и вчера, чтобы «не менять манифесты». CI проверил подпись на старую сборку, а кластер подтянул новую по тегу. В итоге в прод уехало то, что никто не утверждал. Обычно это лечится тремя мерами: требованием digest, контролем прав публикации и проверкой на стороне Kubernetes, а не только в пайплайне.

Как документировать исключения и не потерять контроль

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

Чтобы исключение не ломало Cosign (или Notary v2), оформляйте его как маленький контракт. В записи должно быть ясно, что именно разрешили и почему.

Минимальный шаблон, который обычно проходит аудит и реально помогает команде:

  • Причина: что случилось и почему нельзя быстро исправить по правилам.
  • Владелец: конкретный человек или команда, кто отвечает за закрытие исключения.
  • Срок действия: дата окончания и условия продления.
  • Оценка риска: что может пойти не так, если образ подменят или уязвимость всплывет.
  • Компенсирующие меры: что вы делаете вместо подписи прямо сейчас.

Дальше ограничьте радиус. Не делайте исключение «для репозитория» или «для проекта». Делайте его узким: разрешить только конкретный digest, только в одном namespace, только для одного service account. Тогда даже при ошибке или подмене ущерб будет меньше.

Утверждение и пересмотр должны быть простым ритуалом, иначе их начнут пропускать. Хорошая практика: еженедельные 15 минут у релиз-менеджера или SRE, где список исключений просматривают и закрывают просроченные.

Храните записи так, чтобы их можно было проверить: тикет с обсуждением, коммит в репозитории с политикой допуска, и логи admission controller (кто, когда и какой образ был допущен по исключению). Пример: в пятницу разрешили запуск неподписанного образа внешнего агента мониторинга, но только по digest и только в namespace observability, сроком на 7 дней, с обязательным сканом и запретом на доступ к секретам.

Небольшой сценарий из жизни: как ловится подмена образа

Команда выпускает обновление сервиса и выкатывает образ с привычным тегом payments:release. В реестре в этот же день происходит обновление базы образов: кто-то пересобрал образ и перезаписал тег, чтобы «быстрее поправить мелочь». Формально все выглядит нормально: тег тот же, пайплайн зеленый, деплой проходит.

Проблема проявляется позже. В проде внезапно появляются лишние сетевые обращения и странные ошибки. Разбор показывает, что под тегом payments:release теперь другой digest, а значит это другой образ. Это может быть обычная ошибка процесса (перетерли тег), а может быть подмена, если доступ к реестру оказался шире, чем думали.

Что пошло не так: команда доверяла тегам и не требовала проверку подписи. Пайплайн собирал и пушил образ, но не фиксировал digest как «единственную правду». А Kubernetes принимал все, что указано в манифесте.

Как бы это остановили: сборка в CI подписывает именно digest (например, через Cosign), а дальше есть два «ворота». Первое в CI: деплой разрешен только если подпись найдена и валидна. Второе в проде: admission control блокирует запуск, если образ не подписан доверенной идентичностью или не входит в разрешенный список.

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

  • разрешать не тег, а конкретный digest и только на короткий срок;
  • требовать заявку с причиной, владельцем и временем истечения;
  • включать усиленное логирование и уведомления на время исключения;
  • проверять образ дополнительным сканом и ручным подтверждением релиза.

После такого случая в политику обычно добавляют простые правила: теги считаются удобными ярлыками, но не гарантией; деплой делается по digest; подпись обязательна для прода; любое исключение документируется и автоматически «сгорает». Это стоит закрепить и в обучении команды: один перезаписанный тег не должен решать судьбу продакшена.

Короткий чеклист и следующие шаги

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

Минимальные правила на старт

  • Публикуем в реестр только образы, у которых есть подпись и зафиксированный digest.
  • Перед релизом проверяем: источник сборки (какой репозиторий и пайплайн), права на подпись (кто может подписывать), и соответствие тегов digest.
  • Ключи и права храним отдельно от кода: доступ по ролям, минимум людей с правом подписи, плановая ротация.
  • В CI/CD делаем «ворота»: если подпись или проверка digest не прошли, релиз не продолжается; в логах сохраняем кто, что и чем подписал.
  • В Kubernetes включаем admission control, который блокирует неподписанные образы (или переводим кластеры на режим audit и затем на enforce по плану).

После этого важно не потерять контроль на исключениях и «временных» обходах.

Регулярный контроль (раз в неделю)

  • Просматриваем исключения: какие просрочены, какие можно закрыть, кто владелец и почему они были нужны.
  • Отслеживаем новые репозитории и реестры: все ли они попадают под те же проверки, не появился ли «теневой» путь доставки образов.

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

Если нужна помощь с практической стороной (политика допуска, интеграция проверок в CI/CD и Kubernetes, работа с реестром и сопровождение), это можно делать вместе с GSE.kz (gse.kz) как с системным интегратором: так правила быстрее превращаются в работающий процесс, а не в документ на полке.

Частые вопросы

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

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

В чем разница между тегом и digest, и почему это важно для безопасности?

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

Если у образа есть digest, зачем еще подпись?

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

Где лучше проверять подпись: в CI/CD или в Kubernetes?

Базовый вариант — проверять и в CI/CD, и в Kubernetes: CI отсекает проблемы до деплоя, а кластер становится последним рубежом на случай обхода процесса. Если выбирать одно место, то для продакшена обычно важнее проверка в Kubernetes, потому что она не зависит от дисциплины людей и настроек пайплайнов.

Что выбрать для подписания: обычные ключи или keyless (через OIDC)?

Классические ключи проще понять и подходят, если вы умеете надежно хранить их в KMS/HSM и ограничивать доступ. Keyless уменьшает риск утечки долгоживущих секретов, но требует аккуратной настройки идентичностей CI и понятного аудита того, какая учетная запись подписала релиз.

Как внедрять подпись образов постепенно, чтобы не остановить релизы?

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

Что делать с базовыми образами из публичных реестров, которые не подписаны?

Проще всего зеркалировать базовые образы во внутренний реестр и закреплять их по digest, чтобы исключить неожиданные изменения. Дальше вы можете подписывать их как «одобрено» после ваших проверок, чтобы в кластере работало единое правило допуска.

Как правильно оформлять исключения, если без них никак?

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

Что важно логировать при подписании и проверке образов в CI/CD?

Логируйте то, что потом поможет восстановить цепочку: какой digest подписали, какой пайплайн и коммит это сделал, кем считается подписант, и почему проверка прошла или не прошла. При отказе делайте сообщение коротким и конкретным, чтобы инженер мог исправить проблему без ручных расследований.

Cosign или Notary v2: что выбрать и как не запутаться?

Cosign обычно выбирают, когда нужно быстро привязать подпись к конкретному digest и встроить проверку в практичный CI/CD и admission. Notary v2 уместнее, когда вам важна стандартизация на уровне OCI-артефактов и поддержка со стороны реестра и платформенных компонентов, но и там нужно заранее зафиксировать единые правила доверия.

Запрос

Пришлите спецификацию. Остальное сделаем мы

Одна позиция или объект под ключ, любое направление. Вашу поставку от первого звонка до акта ввода ведёт один менеджер.

Расчёт бесплатноДокументы для госзакупок и тендеровОдин договор и одна гарантия