Java Spring Boot: подключение в Kafka через TLS

Зачем?

При работе в продакшене, как правило, необходимо использовать 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.

Полезные ссылки

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: