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

Kate

Administrator
Команда форума
Идея подписи и проверки образов 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. Архитектура получилась следующая:

2fc3e1a1833b78bd85450af13bd9c35d.png

Вебхук для 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 (один дополнительный шаг, для записи сертификата в отдельный тэг)
  • отсутствие издержек на хранение закрытых ключей и т. п., ключи не хранятся нигде, вообще, мы про них забываем сразу после получения сертификата
    Очевидные и приемлемые на мой взгляд минусы:
  • не самая мощная криптографическая стойкость (следствие обрезанного хэша)
  • невозможность отозвать сертификат у образа (пока я это не реализовал, но возможно сделаю)


 
Сверху