Всё больше компаний стремятся построить доверенную инфраструктуру вокруг Kubernetes. Одного контроля доступа недостаточно: в современных кластерах есть множество потенциальных векторов атак — от подмены контейнеров и эксплуатации уязвимостей рантайма до выполнения неподписанного кода внутри пода. Чтобы действительно знать, что именно запускается в кластере, и быть уверенным в целостности всех компонентов, нужны гораздо более строгие механизмы контроля.
При подготовке Deckhouse Kubernetes Platform к сертификации ФСТЭК России по 118-му приказу мы столкнулись с реальными ограничениями существующих решений. Многие инструменты решают лишь отдельные задачи: одни фокусируются на образах, другие — на манифестах, третьи — на стадии запуска. Сквозного контроля «от сборки до рантайма» не предлагает никто.
Меня зовут Максим Набоких, я руковожу разработкой Kubernetes-платформы Deckhouse. В этой статье я расскажу, как мы обеспечили контроль целостности трёх «К» и построили сквозную систему верификации без потери гибкости и совместимости. Будет полезно командам, которым важно управлять безопасностью Kubernetes в условиях реальных угроз, а также тем, кто стремится к соответствию требованиям регуляторов и хочет внедрить практики доверенного исполнения в своих кластерах.

Слабое звено в цепочке: пример контроля целостности из жизни
Начнём с примера из реального мира. Недавно мне понадобились новые наушники. Я заказал их на маркетплейсе, потому что времени на поход в офлайн-магазин не было. Продавец отправил посылку, курьер принёс её к моей двери. Однако наушников в коробке не оказалось.

И мне, и магазину было важно, чтобы в посылке был заказанный товар. В итоге я сильно расстроился, а продавец понёс убытки. Я доверял маркетплейсу в целом и конкретному магазину. Но кто-то в цепочке поставок оказался ненадёжным: слабым звеном был то ли склад, то ли курьер, понять это невозможно. И, что самое важное, у меня не было возможности проверить товар в коробке на каждом из этапов доставки. Это и есть проблема контроля целостности.
С доставкой кода в кластер Kubernetes та же история. Между написанием новой логики и запуском кода на узле есть достаточно длинная цепочка, каждое звено которой может оказаться скомпрометированным. Давайте разберёмся, как она устроена, где в ней слабые места и что можно с ними сделать.
Цепочка поставки кода в Kubernetes и статистика атак
Цепочка поставки нового кода в конкретный контур устроена следующим образом. Сначала команды разработки пишут код. На сборочном конвейере он превращается в артефакт — контейнерный образ — и отправляется в хранилище образов контейнеров (container registry). Параллельно инженеры или те же разработчики пишут манифесты для Kubernetes и доставляют их в нужное окружение: testing, development или production. После этого образы скачиваются из хранилища и запускаются.
В нашем случае на конце цепочки не окружения одной компании, а кластеры разных клиентов. Но суть не меняется: путь от кода до запущенного контейнера тот же.
Схематично процесс выглядит так:

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

Где в таком случае нас могут атаковать злоумышленники? Есть два основных вектора:
Злоумышленник встраивается в хранилище образов контейнеров или в транспорт между ним и кластером и доставляет вредоносный код.
Злоумышленник попадает непосредственно в кластер Kubernetes и подменяет что-то изнутри либо запускает вредоносные нагрузки.
Эти векторы не теория. Вот два публичных инцидента, по одному на каждый вектор.
Kong Ingress Controller (2024). Злоумышленник, воспользовавшись уязвимостью в CI/CD-пайплайне проекта, опубликовал в Docker Hub неавторизованную сборку Kong Ingress Controller 3.4.0 с криптомайнером. Инцидент обнаружили не по алертам безопасности, а по аномально высокой нагрузке на CPU. Проверка подписи образа при скачивании не дала бы этому образу попасть на узел. Детали инцидента — в официальном посте Kong.
Tesla (2018). Через незащищённую административную консоль Kubernetes были раскрыты облачные учётные данные, что позволило развернуть в одном из кластеров облачной инфраструктуры компании криптомайнер. Даже при таком уровне компрометации верификация контейнерных образов перед запуском не позволила бы выполнить неподписанный вредоносный контейнер и стала бы последним барьером на пути атакующих.Почитать подробности об инциденте можно в Ars Technica.
Эти кейсы — часть более широкой тенденции. По данным отчёта State of the Software Supply Chain к 2024 году было зафиксировано более 704 000 вредоносных пакетов в открытых репозиториях (накопительно с 2019 года), при этом в последние годы наблюдается резкий рост числа таких пакетов. Эти случаи рассматриваются как часть атак на цепочку поставок ПО.

Ну и моя любимая рубрика — «по данным Gartner». В целом Gartner говорит, что в защите цепочки поставок кода нет «серебряной пули». Рынок решений фрагментирован, и для покрытия всей цепочки нужен набор инструментов, каждый из которых закрывает свой участок.
Также у них есть прогноз, что к 2028 году 85 % команд разработки в крупных компаниях внедрят такие инструменты — они станут стандартной частью поставки ПО. Market Guide 2025 отдельно подчёркивает, что важной частью подхода является контроль целостности: проверка артефактов на всём пути доставки, включая подпись и верификацию, чтобы снизить риск их подмены.
Gartner — это хорошо, но что говорят российские регуляторы и реалии? В России контроль целостности ПО и компонентов инфраструктуры — это уже не рекомендация, а нормативное требование по защите информации для государственных информационных систем, субъектов КИИ и других регулируемых контуров.
Требования закреплены в нескольких документах:
Приказ ФСТЭК России № 118 «Требования по безопасности информации к средствам контейнеризации», где в числе прочего описываются требования к контролю целостности.
Приказ ФСТЭК России № 187 «Требования по безопасности информации к средствам виртуализации» — аналогично, с требованиями к защите и контролю целостности среды.
Методический документ ФСТЭК России «Меры защиты информации в государственных информационных системах», где есть мера ОЦЛ.1 — контроль целостности ПО.
Указ Президента № 166 «О мерах по обеспечению технологической независимости и безопасности КИИ РФ», который в целом задаёт направление на использование доверенных и сертифицированных решений с контролем целостности.
И кажется, что даже если компания формально не подпадает под эти требования, то контроль целостности имеет смысл рассматривать как базовую практику безопасности. Скомпрометированный код в продакшене может привести к отказу сервисов, утечке данных, ущербу для репутации, да и к финансовым потерям.
Из всей статистики, прогнозов и требований можно сделать краткий вывод:
В современном мире контроль целостности от сборки до запуска ПО — это скорее базовый минимум, чем роскошный максимум. Нужно его имплементировать.
Что требует контроля целостности в Kubernetes и почему всё сводится к контейнерам
Если мы хотим внедрить контроль целостности в Kubernetes, то он должен распространяться на:
Контейнеры — сюда относятся контейнерные образы и их выполнение. Компоненты control plane, аддоны и полезные нагрузки, если они запускаются в контейнерах.
Конфигурации — сюда относятся ресурсы Kubernetes, такие как Deployments, StatefulSets, Services и другие объекты, заданные через манифесты. Эти манифесты применяются к кластеру через Helm, GitOps или что-то ещё.
Компоненты операционной системы — сюда относится всё, что обеспечивает работу Kubernetes на уровне узла. Например, containerd, kubelet, агенты и системные службы.
Для простоты можно говорить про контроль целостности трёх «К».

Посмотрим, откуда каждое из трёх «К» доставляется в кластер. С контейнерами всё просто — они загружаются из хранилища образов контейнеров (container registry). Конфигурации, например Helm-чарты, можно хранить в отдельном ChartMuseum, но на практике их тоже можно упаковать в контейнер и положить в то же хранилище образов. Например, в случае Deckhouse Kubernetes Platform конфигурацию развёртывает оператор deckhouse-controller, который сам является контейнером. Компоненты ОС можно хранить в репозитории пакетов, но также их можно упаковать в OCI-образы и сложить в общее хранилище.
Если сделать всё вышеупомянутое, то получится, что вся наша поставка — это контейнеры:

Чем однороднее поставка, тем проще контролировать её целостность. Если всё хранится в виде контейнеров в одном registry, то задача контроля целостности сводится к тому, как контролировать целостность образов контейнеров.
Вспомним, как устроен образ контейнера согласно спецификации OCI. Когда среда запуска контейнеров загружает образ, он первым делом обращается к индексу (image index). Но сам по себе индекс данных образа не содержит. Он работает как оглавление и указывает на доступные манифесты. В свою очередь каждый манифест содержит ссылки на:
конфиг образа — его entrypoint, переменные окружения и всё, что нужно для запуска контейнера;
файловую систему, которая состоит из набора слоёв (layer 1, …, layer n).

Как всё это хранится на диске? Согласно OCI Image Layout, образ — это набор файлов, связанных между собой через SHA-256-хеши. Индекс представляет собой JSON-файл с перечислением манифестов. Манифест — это тоже JSON-файл, который содержит хеш конфига и хеши слоёв. Сами конфиги и слои лежат в хранилище в виде отдельных файлов (blobs), каждый из которых идентифицируется по своему SHA-256-хешу.

Манифест однозначно определяет образ контейнера, поскольку связывает воедино конфиг и все слои файловой системы через их SHA-256-хеши. Если изменить любой компонент образа, его хеш перестанет совпадать с тем, что указан в манифесте.
Но остаётся вопрос — как убедиться в целостности самого манифеста? Для этого его нужно подписывать: цифровая подпись подтверждает, что перед нами именно тот манифест, который мы собрали.
Как можно реализовать цифровую подпись манифеста
Я расскажу, как мы решаем задачу подписи манифестов в Deckhouse, а потом предложу альтернативные Open Source-варианты, которые вы можете использовать как основу для своей реализации.
В нашей Kubernetes-платформе есть инструмент сборки, который использует доработанный buildah и называется Deckhouse Delivery Kit. Он собирает контейнеры и работает внутри CI-системы. Вместе с ней они образуют CI/CD-конвейер.
Когда контейнеры собраны, на сцену выходит Deckhouse Stronghold — наше решение для безопасного управления жизненным циклом секретов. В данном случае мы используем его для подписей. Сначала Deckhouse Delivery Kit отправляет чек-сумму манифеста по Vault-совместимому API в Deckhouse Stronghold и получает на выходе цифровую подпись. Подписанные манифесты контейнеров складываются в хранилище образов.

Вместе с подписью к манифесту добавляются четыре аннотации (смотрите на annotations):
“config”: { “digest”: “ff82488acfcddd213198…”, ... }, “layers”: [ { “digest”: “8acfcddd2131ff82488a…”, ... }, ], “annotations”: { "io.deckhouse.delivery-kit.build-timestamp": "1750791050", "io.deckhouse.delivery-kit.cert": "LS0tLS1 ...", "io.deckhouse.delivery-kit.chain": "LS0tLS1 ...", "io.deckhouse.delivery-kit.signature": "MEQCIEV..." }
Первая аннотация показывает, когда была сделана подпись. Вторая указывает на сертификат, который использовался для подписания, в нашем случае это сертификат «Фланта». На третьем месте идёт цепочка сертификатов. Она нужна, если манифест подписан не корневым сертификатом, а промежуточным и представляет собой несколько X509-х. Последняя аннотация — это подпись, которая фиксирует чек-сумму манифеста.
Важный момент: подпись — это ещё не защита, а основа для проверки доверия. Подпись просто фиксирует, кто именно и что именно создал на каждом этапе.
Альтернативные решения
В качестве альтернативы можно использовать Open Source-решения. Для создания подписи вместо Deckhouse Stronghold можно взять HashiCorp Vault или OpenBao. Но и в Deckhouse Kubernetes Platform Community Edition доступны возможности CE-редакции Stronghold, их можно использовать, чтобы повторить оригинальную схему.
В качестве клиентов для подписания использовать Cosign или Notary. Но дальше в тексте будут причины, почему Deckhouse Delivery Kit лучше.
Уровни качества контроля целостности
Перед тем как разбирать нашу реализацию контроля целостности поставки кода в Kubernetes, посмотрим на существующие уровни качества этого контроля. Их три, в списке я расположил их от лучшего к худшему:
Неизменяемость — контроль устроен так, что внутри цепочки поставки ничего нельзя изменить.
Компенсация — автоматическое применение компенсирующих мер. Например, если злоумышленник что-то изменил, то система эти изменения откатила.
Сигнал тревоги — когда злоумышленник что-то меняет, система отправляет события безопасности вида «здесь что-то происходит, обратите внимание».

Контроль целостности контейнеров
Итак, контейнеры подписаны, но как контролировать их целостность? Мы задались вопросом, умеют ли уже существующие container-runtime-интерфейсы оказывать контроль целостности первого или второго уровня. Оказалось, что нет. Поэтому мы приступили к своей реализации для Deckhouse Kubernetes Platform.
За основу взяли containerd 2.0. На момент начала наших работ это был самый свежий релиз. Нужно было сделать:
скачивание из доверенных источников;
запуск только того, что присутствует в доверенном источнике, то есть запуск только того, что мы подписали;
периодическую проверку того, что всё работает.
Сontainerd 2.0 позволяет использовать enhanced read-only file system (EROFS) для контейнеров. Например, у нас есть образ контейнера — манифест, конфиг и слои файловой системы. А в containerd есть внутреннее хранилище снимков (snapshot storage) на каждом узле. При pull (он же apply) на узел containerd сохраняет манифест в это хранилище, а слои преобразует в формат EROFS. Это нужно знать, чтобы понять наши доработки.

Доработка 1: проверка подписи на стороне containerd
На моменте pull манифеста мы проверяем его зашитым в containerd сертификатом. В результате наш containerd загружает только контейнеры из доверенных источников. Дальше из всех слоев EROFS собирается одна большая read-only файловая система для контейнера.

В качестве альтернативы для проверки подписей можно было бы использовать плагин ImageVerifier. Но мы решили зашить сертификат прямо в containerd, потому что надёжнее жить без дополнительного бинарника.
Доработка два: интеграция dm-verity для защиты EROFS-слоёв
По умолчанию в containerd нет защиты от подмены одного из слоёв EROFS. Злоумышленник может заменить его прямо во внутреннем хранилище снимков, и система об этом ничего не узнает.
Мы решили закрыть эту уязвимость с помощью dm-verity, механизма безопасности ядра Linux для read-only файловых систем. Он строит дерево хешей (Merkle Tree) для блоков диска и проверяет целостность каждого слоя при чтении. Если данные были изменены, чтение блокируется.
Работает это так: создаётся mapper device, который через dm-verity ссылается на два других устройства — одно с данными, второе с хешами. При чтении dm-verity сверяет данные с хешами и возвращает ошибку, если они не совпадают. Это даёт нам контроль целостности при чтении.

Но откуда берутся хеши EROFS-слоёв для dm-verity? Вернёмся к примеру подписанного манифеста. На самом деле, помимо уже упомянутых четырёх аннотаций, в нём есть блоки для каждого слоя со своими аннотациями. Именно в них лежит посчитанный хеш для dm-verity:
"config": { "digest": "ff82488acfcddd213198…", ... }, "layers": [ { "digest": "8acfcddd2131ff82488a…", "annotations": { "io.deckhouse.delivery-kit.build-timestamp": "1750791050", "io.deckhouse.delivery-kit.dm-verity-root-hash": "1e9827da1457bc21b..." } ... }, ], "annotations": { "io.deckhouse.delivery-kit.build-timestamp": "1750791050", "io.deckhouse.delivery-kit.cert": "LS0tLS1 ...", "io.deckhouse.delivery-kit.chain": "LS0tLS1 ...", "io.deckhouse.delivery-kit.signature": "MEQCIEV..." }
Deckhouse Delivery Kit считает хеш для каждого EROFS-слоя на этапе сборки. Хеши доставляются вместе с подписанным манифестом, который сохраняется во внутреннем хранилище containerd с предварительной проверкой подписи, и дальше берутся напрямую из манифеста.
Получается, что даже если злоумышленник сможет как-то подменить один из слоёв EROFS, то контейнер не увидит этого до перезапуска. А при перезапуске подключится наша следующая доработка: если образ не пройдёт проверку целостности при перезапуске, он будет помечен как повреждённый, и мы заново загрузим его из хранилища. Злоумышленник останется не у дел.

Добработка три: read-only файловая система для контейнеров
На этом проблемы containerd не заканчиваются. Дело в том, что поверх read-only файловой системы containerd монтирует ещё и OverlayFS. Она нужна для того, чтобы можно было что-то записать в контейнеры, но это может использовать и злоумышленник.

Мы отказались от OverlayFS. Контейнеры в Deckhouse Kubernetes Platform всегда запускаются в read-only-режиме. При этом папки для маунтов должны быть предсозданы в контейнерах — иначе ничего не будет работать. Для временных файлов, если очень нужно, используется emptyDir, но в целом всё доступно только для чтения. Злоумышленник не сможет ничего записать в уже запущенный контейнер.

Результаты доработок для контроля целостности контейнеров
Подведём промежуточный итог по доработкам нашего containerd для контроля целостности контейнеров:
При скачивании контейнера его подпись проверяется зашитым внутрь containerd сертификатом.
Для гарантии иммутабельности в контейнере используется read-only файловая система.
При старте контейнера все снимки ставятся под dm-verity. Их нельзя поменять даже на хосте, а если вдруг получится, то контейнер не увидит этого до рестарта.
При нарушении целостности при старте контейнера образ целиком перекачивается из доверенного хранилища образов контейнеров.
OverlayFS не используется, добавить что-то в уже запущенном контейнере нельзя.
Периодическая проверка смотрит только на наличие dm-verity для всех слоёв EROFS.
Получился честный первый уровень качества контроля целостности для контейнеров. Переходим ко второй «К».
Контроль целостности конфигураций
Конфигурация — это всё, что описывает желаемое состояние кластера и приложений. Она определяет, что должно быть запущено, с какими параметрами и где. В Kubernetes конфигурация хранится в etcd и управляется через control plane.
Центральный компонент control plane — это kube-apiserver. Через него проходят все запросы к кластеру: и от пользователей (через CLI или графический интерфейс), и от внутренних компонентов вроде scheduler и controller-manager. API-сервер хорошо защищён: RBAC, admission controllers и другие механизмы контролируют, кто и что может делать. Все данные о состоянии кластера API-сервер хранит в etcd, и именно хранилище — это самая слабая часть схемы:

Доступ в etcd на самом деле то же самое, что режим бога в кластере. Если у вас есть доступ в хранилище, вы можете изменить секреты, подменить конфигурацию или запустить какую-то вредоносную нагрузку в обход всей системы проверок прав доступа в API-сервере.
Подпись данных перед сохранением в etcd
Основной вопрос по контролю целостности состоит в том, как мы можем убедиться, что всё записанное в etcd записано именно через kube-apiserver и никак не изменено. И наше решение здесь прежнее — цифровые подписи. Да, опять. В теории можно было бы зашифровать всю базу, но тогда данные не получится восстановить при потере ключа, плюс будет гораздо сложнее обнаружить в них подмену. Цифровые подписи дают чуть больше гибкости:
Свойство |
Шифрование at rest |
Цифровая подпись |
Предотвращает утечку |
✅ |
❌ |
Обнаруживает подмену |
❌ |
✅ |
Можно восстановить данные при потере ключа |
❌ |
✅ |
Мы решили добавить патч для kube-apiserver'а, который позволяет подписать данные.
Стандартная трансформация данных, которые kube-apiserver складывает в etcd, происходит следующим образом. У нас есть пользовательские объекты (Deployment, StatefulSet и так далее). Сначала они кодируются в JSON либо в Protobuf, далее шифруются, например с помощью KMS, и следом укладываются в etcd.
Мы добавили в эту цепочку ещё одно звено. Перед тем как складывать данные в etcd, мы их подписываем:

Для цифровой подписи данных используем JOSE — JSON Object Signing and Encryption. Это популярный стандарт, который совместим с HSM, HashiCorp Vault и просто ключами в файлах, что позволяет использовать разные ключи для проверки и для подписи. Также JOSE поддерживает эллиптические кривые, включая распространённый алгоритм ES256.
Куда мы добавили свою логику
Наша логика встроена в стандартную цепочку трансформации данных kube-apiserver. Для этого мы реализовали структуру SigningTransformer:
type SigningTransformer struct { transformer value.Transformer publicKeys *jose.JSONWebKeySet signer jose.Signer }
Здесь transformer — это стандартная цепочка трансформации, publicKeys — публичные ключи для проверки подписи при чтении, а signer — объект, который выполняет подпись при записи.
Метод TransformToStorage отвечает за запись в хранилище. При сохранении данных в etcd мы сначала прогоняем объект через цепочку трансформации, затем подписываем результат и сериализуем в наш формат:
func (t *SigningTransformer) TransformToStorage( data []byte, ctx context.Context, ) ([]byte, error) { transformedData, err := t.transformer.TransformToStorage(data) if err != nil {…} sig, err := t.signer.Sign(transformedData) if err != nil {…} return []byte(sig.FullSerialize()), nil }
В итоге в etcd попадает JSON в формате Flattened JWS JSON Serialization с определённым набором полей:
{ "payload": "eyJpc3MiOiJqb2UiLA0KI…", "protected": "eyJhbGciOiJFUzI1NiJ9", "header": { "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d" }, "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-X…" }
Здесь:
payload— зашифрованные данные;protectedиheader— метаданные ключа, которым подписаны данные;signature— подпись самих данных.
За чтение из хранилища отвечает метод TransformFromStorage. При извлечении данных из etcd мы проверяем подпись с помощью нескольких публичных ключей — здесь мы сразу заложили возможность ротации. Если подпись валидна, то прогоняем данные через стандартную цепочку трансформации Kubernetes:
func (t *SignatureTransformer) TransformFromStorage( ctx context.Context, data []byte, dataCtx value.Context, ) ([]byte, bool, error) { sig, err := jose.ParseSigned(string(data)) if err != nil {…} payload, err := sig.Verify(t.publicKeys) if err != nil {…} return transformer.TransformFromStorage(payload) }
Ошибка проверки подписи при чтении
Мы рассмотрели, что происходит, когда подпись верная. Но что делать, если при её проверке возникает ошибка? В Deckhouse Kubernetes Platform есть два ответа на этот вопрос:
Вариант «на каждый день». Если подпись неверна, мы отказываем в выдаче объекта, забрать его из etcd нельзя. Это стандартный механизм работы.
Вариант для миграции. Мы возвращаем объект, даже если у него нет верной подписи, но добавляем в audit log специальную аннотацию и инкрементную метрику. Этот вариант подходит в случае, если в etcd лежат не мигрированные данные.
Вариант «на каждый день» обеспечивает первый уровень качества контроля целостности, а вариант для миграции — третий уровень качества.

Результаты доработок для контроля целостности конфигураций
В итоге доработок мы получили:
надёжный путь записи данных в etcd;
обнаружение подмены;
аудируемую запись;
возможность ротации ключей;
совместимость цифровых подписей со слоём шифрования. То есть при желании данные можно одновременно и шифровать, и подписывать.
Переходим к последней «К» — компонентам операционной системы.
Контроль целостности компонентов операционной системы
Компоненты операционной системы — это kubelet, containerd, наш d8-клиент для Kubernetes, nc, resize2fs и множество других. Все они — бинарные ELF-файлы, и именно это свойство позволяет защитить их единым механизмом.
Подпись ELF-заголовков бинарных файлов
ELF-файлы позволяют добавлять подпись в их заголовок. Когда Deckhouse Delivery Kit собрал контейнер, он может посмотреть его финальную файловую систему и найти там бинарные файлы. Остаётся сходить в Deckhouse Stronghold, получить подпись для каждого бинаря и сложить её в ELF-хедеры.

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

Альтернативные решения
Если честно, возможной альтернативы я не знаю и именно поэтому в начале статьи говорил о важности Deckhouse Delivery Kit в нашем решении по контролю целостности. Наверное, можно придумать что-то на Bash + objcopy. Можно попробовать взять delivery-kit-sdk — нашу открытую библиотеку — и придумать решение для подписи бинарных файлов на её основе.
Если у вас есть другие идеи или вы знаете готовые решения, делитесь в комментариях.
Проверка подписи компонента операционной системы
С механизмом подписи разобрались, но где именно она хранится? Посмотрим с помощью readelf:
$ readelf --hex-dump=.note.delivery-kit.signature /opt/deckhouse/bin/containerd
Подпись выглядит примерно так:
Hex dump of section '.note.delivery-kit.signature': 0x00000000 17000000 2f0f0000 26594131 64656c69 ..../...&YA1deli 0x00000010 76657279 2d6b6974 2e736967 6e617475 very-kit.signatu 0x00000020 72650000 7b22696f 2e646563 6b686f75 re..{"io.deckhou 0x00000030 73652e64 656c6976 6572792d 6b69742e se.delivery-kit. 0x00000040 7369676e 61747572 65223a22 4d455143 signature":"MEQC 0x00000050 49477773 746e6150 75344168 556f7650 IGwstnaPu4AhUovP 0x00000060 6f6b5179 68394c75 6c415565 5a664761 okQyh9LulAUeZfGa 0x00000070 652f4744 35576739 416c5a64 41694263 e/GD5Wg9AlZdAiBc 0x00000080 70724750 77754a6b 4b465a54 374d7177 prGPwuJkKFZT7Mqw 0x00000090 4c44646a 432f4b67 4a436c6f 624e7267 LDdjC/KgJClobNrg 0x000000a0 4d595973 2b584274 75773d3d 222c2269 MYYs+XBtuw==","i 0x000000b0 6f2e6465 636b686f 7573652e 64656c69 o.deckhouse.deli 0x000000c0 76657279 2d6b6974 2e636572 74223a22 very-kit.cert":" 0x000000d0 4c533074 4c533143 5255644a 54694244 LS0tLS1CRUdJTiBD 0x000000e0 52564a55 53555a4a 51304655 52533074 RVJUSUZJQ0FURS0t
Если внимательно посмотреть на правую колонку, то в ней живёт вот такой JSON:
Hex dump of section '.note.delivery-kit.signature': { "io.deckhouse.delivery-kit.build-timestamp": "1750791050", "io.deckhouse.delivery-kit.cert": "LS0tLS1 ...", "io.deckhouse.delivery-kit.chain": "LS0tLS1 ...", "io.deckhouse.delivery-kit.signature": "MEQCIEV..." }
Если он вам что-то напоминает, то это не случайно — мы уже видели такую же подпись у контейнеров. То есть формат одинаковый что у подписей контейнеров, что у подписей бинарных файлов.
Что касается проверки подписей компонентов операционной системы, то первый уровень качества контроля целостности достижим только средствами самой ОС. Ваше ядро Linux должно уметь проверять подпись при каждом вызове бинарника. Есть дистрибутивы, которые это могут.
Если ваш дистрибутив не проверяет подписи, можно использовать наш инструмент d8-bcheck — небольшую утилиту на Go, которая с помощью crontab и fsnotify следит за ELF-файлами. Если кто-то изменил файл, d8-bcheck вызывает его повторный деплой и откатывает в исходное состояние. Это второй уровень контроля целостности с автоматическим принятием компенсирующих мер.
Результаты доработок для контроля целостности компонентов операционной системы
Краткое резюме по компонентам ОС:
Подпись превращает бинарный файл в контролируемый объект.
Удобно иметь единый формат подписи для компонентов ОС и контейнеров.
Поскольку компоненты ОС доставляются в контейнерах, дополнительно проверяется и подпись контейнера, что повышает безопасность.
Целостность контроля целостности
Мы разобрали контроль целостности контейнеров, конфигурации и компонентов операционной системы. Теперь важно пояснить, почему все три части должны работать только вместе и никак иначе.
Контроль целостности контейнеров выполняет доработанный containerd, конфигураций — пропатченный kube-apiserver, компонентов ОС — d8-bcheck. При этом они защищают друг друга по цепочке: d8-bcheck гарантирует, что запущен правильный containerd, containerd — что запущен правильный kube-apiserver, а kube-apiserver — что везде работает правильный d8-bcheck. Получается хорошо защищённая замкнутая система — злоумышленник не может подменить ни одно звено, не нарушив проверку в другом.

Но что произойдёт, если мы перестанем контролировать целостность хотя бы одного звена в цепочке? Представим, что злоумышленник может заменить конфигурацию в kube-apiserver. Тем самым он отключит d8-bcheck и задеплоит свой containerd на узел. В общем, сделает всё, что захочет, и получит полный доступ к системе.

Отсюда есть важный вывод.
Если нарушен хотя бы один элемент, вся система контроля целостности становится недостоверной.
Резюме
Контроль целостности — это не опция, это основа доверия.
Целостность в Kubernetes — системная задача, которую нужно обеспечивать на уровне контейнеров, конфигураций и компонентов операционной системы.
Open Source-решения дают базу, но не готовую систему. Её нужно собрать самостоятельно.
Целостность должна быть сквозной. Её нужно контролировать с момента сборки до запуска контейнеров. Любой непокрытый участок цепочки — потенциальный вектор атаки.
Контроль целостности контроля целостности критичен. Все меры нужно имплементировать одновременно.
Уровни качества контроля целостности важны. Лучше имплементировать максимально качественный контроль.
И, конечно, описанные в статье решения уже встроены в сертифицированную ФСТЭК России редакцию Deckhouse Kubernetes Platform — Certified Security Edition. Присмотритесь, если ищете Kubernetes с работающим контролем целостности.
Полезные ссылки
Если вы хотите построить собственную систему для контроля целостности в Kubernetes, заглядывайте ко мне на GitHub. Я собрал все полезные ссылки на инструменты, чтобы у вас была стартовая точка.
Granulex
dm-verity, JOSE, ELF-подписи – всё это контролирует, что было запущено. А то, что происходит после старта в памяти процесса – за пределами этой архитектуры. ФСТЭК доволен, атакующий тоже.
Hatsmith
Так в статье рассказывается как раз только об угрозе целостности. Для защиты рантайма в декхаусе есть другой инструмент. И для него нужна отдельная статья.