Как стать автором
Обновить

Я разработал свой keyless продукт для удостоверения Docker образов — DIA

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров2.3K

Идея подписи и проверки образов Docker крутилась у меня давно. Часто я не понимал на сколько это снижает риски и повышает реальную безопасность приложений. Но для себя таки понял одно - защищает она от одного но важного вектора - атака на ваш реестр образов (registry) и внедрение вредоносного кода в образы. От всего остального нужно защитится другими средствами. Если у вас все запускается по чексуммам, можете не париться.... Но если ваши образы в приложениях k8s описаны в виде тэгов, а не чексумм, kubernetes запускает образ из источника вслепую. А еще если установлен imagePullPolicy: always, тогда в случае компрометации реестра ваш кластер запустит вредоносное ПО уже при следующем рестарте.
 

containers:
 - image: registry.local/my-awesome-app:alpine
imagePullPolicy: always

Неоднократно исследовались различные решения от Notary до Cosign. Так или иначе все эти продукты были сложны для использования, требовали отдельного хранилища для криптографической информации + на клиентах также нужно было где то хранить кучу закрытых ключей. Cosign более прост в использовании но для него также требовалось где-то хранить закрытые ключи, менять их и все прелести этого. Мне же хотелось чтобы подпись работала по принципу обычных x509 сертификатов но без валидации срока действия. Подписал образ доверенным УЦ (выдал серт) — все, образ валиден. Эту мысль я и стал развивать далее и все оказалось очевидно но, не без нюансов…

Идея заключалась в следующем: взять чексумму образа, выдать сертификат с commonName эквивалентным чексумме (или чтобы чексумма входила в его часть). НО, сертификат нужно было куда то положить. Если положить его в образ тогда, чексумма изменится и сертификат станет уже неактуален. Потом я понял что можно положить сертификат например в LABEL образа затем, при верификации убирать LABEL и отсчитывать обратную чексумму. Все это слишком сложно… Затем я увидел как cosign хранит подпись образа и решил сделать также (да, я это позаимствовал). Cosign создает дополнительный тэг sig-<sha256sum> в котором и хранит публичную часть ключа. Я сделал аналогично, только префикс изменил на dia-<sha256sum> где sha256sum это чексумма образа в реестре. Пазл сложился, но опять, не все так просто….

В силу некоторого своего невежства я упустил что In the common name field of the DN of a X 509 certificate, as defined in ASN. 1 notation for OID "2.5. 4.3", the limit is up to 64 characters. Решил я эту проблему, невежливо) я его обрезал. Т.е. валидация может быть выполнена по части символов из чексуммы, этот параметр я назвал digestSlice.

openssl x509 -noout subject -in image.crt  
subject=C = RU, ST = Moscow, L = Moscow, O = company, OU = local, CN = dia-0addcc1de26ee0f660d21b01c1afdff9f59efb

Оставалось разработать вебхук для валидации в k8s ValidationWebhook и предоставить удобный инструмент для запроса сертификатов после сборки образа в CI. Архитектура получилась следующая:

Вебхук для k8s написал как полагается на go, также helm чарт для его установки: https://github.com/spanarek/dia/tree/master/chart. Чтобы в неймспейсе запускались только подписанные образы необходимо NS пометить так: diwah=enabled. Принцип работы заключается в следующем: чарт имеет параметр attestor_ca. В эту переменную необходимо положить сертификат УЦ (base64 строкой разумеется) который выдает образам сертификаты и будет доверенным. Во время создания pod вебхук берет ссылку до образа в registry, затем ищет слой с сертификатом (dia-<sha256sum>), проверяет CN, пренадлежность к доверенному УЦ (attestor_ca) и выдает заключение. Образы которые объявлены в pod по чексумме, а не по тэгу, валидации не подлежат и пропускаются (это не имеет смысла кмк).

В качестве инструмента для подписи я написал скрипт для ручного добавления сертификата образа, а также манифест для выпуска сертификата в gitalb-ci с помощью hashicorp vault. Поскольку уже был опыт и паттерны использования, vault в gitlab, hashicorp полностью решает вопрос автоматизированного выпуска сертификатов для ваших приложений. Итого для gitlab разработчикам нужно просто добавлять одну строку в script джобы сборки образа:

dia-sign.sh ${DOCKER_REG_IMAGE}

Job целиком в моем случае(с vault) выглядит так:

make-test-image:
  image:
    name: ghcr.io/spanarek/dia/dia-dind:hashicorp0.1.0
  stage: build
  variables:
    VAULT_AUTH_ROLE: any
    VAULT_AUTH_PATH: auth/jwt/gitlab
    VAULT_ADDR: https://vault.local:8200
    VAULT_CAPATH: vault.pem
    VAULT_PKI_PATH: pki/issue/by-gitlab-id
    DOCKER_REG_IMAGE: registry.local/test-app
  script:
    - docker login -u "{REGISTRY_PASSWORD}" "${REGISTRY_URL}"
    - docker build -t ${DOCKER_REG_IMAGE}:latest .
    - docker push ${DOCKER_REG_IMAGE}:latest
    - dia-sign.sh ${DOCKER_REG_IMAGE}

Что имеем в итоге:

  • подпись стандартом x509 и независимость от решения, я использовал pki vault для получения сертификатов, но это в силу специфики инфраструктуры компании, вы вольны использовать любую PKI

  • простота подписывания для разработчиков и devops (один дополнительный шаг, для записи сертификата в отдельный тэг)

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

    Очевидные и приемлемые на мой взгляд минусы:

  • не самая мощная криптографическая стойкость (следствие обрезанного хэша)

  • невозможность отозвать сертификат у образа (пока я это не реализовал, но возможно сделаю)

А что означает DIA вы прочтете здесь :-)

Теги:
Хабы:
Всего голосов 7: ↑7 и ↓0+7
Комментарии5

Публикации

Истории

Работа

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань