Зачем?
При работе в продакшене, как правило, необходимо использовать TLS везде, где это возможно. Например, если имеется Java-приложение, которое должно сходить в кафку и что-то записать\прочитать, нужно произвести некоторые настройки, чтобы данные были зашифрованы.
Как?
В Java есть Keystore – хранилище сертификатов. Для работы с таким хранилищем необходима утилита keytool, с помощью которой добавляются приватные ключи и сертификаты в хранилище. И Java в дальнейшем использует эти данные из keystore, который является бинарным файлом, внутри которого уже все серты и ключи.
Сам keystore “состоит” из следующих компонентов:
- truststore – содержит сертификаты. используется для того, чтобы приложение могло выполнять подключение к другим серверам. например, truststore содержит корневой сертификат СА, которым подписан сертификат стороннего сервера, к которому выполняется подключение со стороны исходного приложения, и приложение без проблем будет ему доверять
- keystore – содержит также нужные сертификаты + приватные ключи, чтобы к нему в свою очередь могли подключиться, когда приложение выступает в роли сервера
Spring boot & Kafka TLS
Особенностью работы с kafka является то, что для подключения с шифрованием трафика необходимо использовать mTLS (mutual TLS). Это история, когда не только клиент проверяет сертификат сервера, но и сервер проверяет сертификат клиента, т.к. взаимная валидация сертификатов друг друга.
В таком случае Java-приложение должно иметь в своём Truststore корневой сертификат, которым подписан сертификат брокера кафки, чтобы была возможность к этому брокеру подключиться. А в Keystore должен быть приватный ключ и сертификат, к которому будет подключаться уже брокер кафки. При этом у брокера в Truststore аналогично должен быть корневой сертификат, которым подписан сертификат клиента. Вот такая взаимная проверка.
Итого необходимы следующие сущности (как пример) и для клиента и для сервера:
- CA.pem (для truststore)
- client.pem + client.key (для keystore)
Теперь, когда все имеющиеся сертификаты и ключи получены, можно было бы сгенерировать keystore-файл с этими сертификатами. И при запуске java-приложения указать путь к этому keystore. И тогда всё будет работать.
Но если используется система оркестрации, необходимо запустить 10 экземпляров приложения или 10 контейнеров, а потом 20 или 50? В случае необходимости использования контейнеров понадобится зашивать keystore-файл внутрь образа. Это не всегда неудобно для управления, особенно когда сертификаты будут обновляться – всё придётся массово генерировать заново.
И тут на помощь приходит использование переменных окружения. Смысл в следующем:
- приложение на спринг полностью конфигурируется через переменные окружения
- вместо генерирования keystore-файла где-то отдельно, в приложение передаются переменные:
- SPRING_KAFKA_SSL_KEY_STORE_KEY – приватный ключ для keystore
- SPRING_KAFKA_SSL_KEY_STORE_CERTIFICATE_CHAIN – сертификат для keystore (или цепочка сертов)
- SPRING_KAFKA_SSL_TRUST_STORE_CERTIFICATES – сертификат СА, которым подписан сертификат брокера, чтобы можно было выполнить подключение к кафке по tls (truststore)
Исходные сертификаты могут располагаться в хранилище секретов (vault & etc) и будут передаваться в вышеописанные переменные окружения с помощью vault-secrets-webhook при запуске приложения в Kubernetes (ну, или передавать как необходимо в конкретном случае)
Пример как будет выглядеть application.yaml в таком случае:
kafka:
bootstrap-servers: ${SPRING_KAFKA_BOOTSTRAP_SERVERS}
listener:
type: batch
concurrency: 1
ssl:
key-store-key: ${SPRING_KAFKA_SSL_KEY_STORE_KEY}
key-store-type: PEM
key-store-certificate-chain: ${SPRING_KAFKA_SSL_KEY_STORE_CERTIFICATE_CHAIN}
trust-store-type: PEM
trust-store-certificates: ${SPRING_KAFKA_SSL_TRUST_STORE_CERTIFICATES}
security:
protocol: SSL
При старте приложения Java сама будет генерировать keystore файл на основе переданных ключей в переменных окружения и будет их использовать при подключении к Kafka.
Как это будет работать
- spring boot при запуске проверяет application.yaml и загружает оттуда явно заданные значения
- также он загружает переменные окружения, которые могут быть заданы при запуске приложения (в контейнере, например, с передачей env)
- при необходимости значения application.yaml переопределяются и берутся те, которые указы в переменных окружения
- библиотека для работы с кафкой также ожидает какие-то переменные, которые будет использовать:
Так, при создании переменных:
- SPRING_KAFKA_SSL_KEY_STORE_KEY:
- SPRING_KAFKA_SSL_TRUST_STORE_CERTIFICATES
- SPRING_KAFKA_SSL_KEY_STORE_CERTIFICATE_CHAIN
со значениями внутри них сертификатов приложение подхватит их значение и будет использовать при создании коннектора к kafka с помощью tls.
Заключение
В статье рассмотрен случай использования mTLS для работы с kafka и какие сущности для этого необходимы. Также описан пример для решения вопроса по части конфигурации приложения для дальнейшего его деплоя в множественных экземплярах. Такую схему легко растиражировать под любую CI\CD систему и используемый метод хранения секретов: будь то тупое хранение плейтекстом в гите, хранение в зашифрованном виде или же использование сторонних инструментов типа sre vault.