EN EnglishDE Deutsch

Kafka in Produktion: von null zum Profi (Teil 3)

Eine Enigma-Chiffriermaschine aus dem Zweiten Weltkrieg in einem Holzkasten, ausgestellt in einer gläsernen Museumsvitrine
Foto von Mauro Sbicego / Unsplash

Kafka in Produktion – wir bauen einen skalierbaren, sicheren und Enterprise-tauglichen Message Bus auf der Google Cloud Platform
opensight.ch - roman hüsler

Inhaltsverzeichnis

TL;DR

Ich empfehle zwar, die Blogposts zu lesen, aber ich weiss, dass manche direkt zur «Single Source of Truth» springen: zum Code. Das ist ungefähr so, wie an einem Freitag ungetestet zu deployen.
Bitte sehr:

Einführung

Apache Kafka ist eine leistungsfähige Event-Streaming-Plattform, doch der Betrieb in Produktion ist eine ganz andere Herausforderung. Wir beginnen mit den Grundlagen, aber das hier ist kein Spielplatz: Schritt für Schritt bauen wir einen Message Bus in Enterprise-Qualität, der Hochverfügbarkeit, Sicherheit und Skalierbarkeit für echte Workloads bietet.
Ich bin selbst kein Kafka-Spezialist, ich lerne unterwegs. Wie so oft bei den opensight-Blogs können Sie mich auf meinem Weg von null zum Profi begleiten.

In dieser Blogserie gehen wir mit Kafka über die Grundlagen hinaus. Wir entwerfen ein produktionsreifes Kafka-Cluster-Deployment, das folgende Eigenschaften hat:

  • Skalierbar – bewältigt wachsende Last ohne Performance-Engpässe.
  • Sicher – setzt Authentifizierung (Plain, SCRAM, OAUTH), Autorisierung (ACL, OAUTH) und Verschlüsselung (TLS) durch.
  • Sauber strukturiert – inklusive Schema Registry für eine ordentliche Data Governance.
  • Enterprise-tauglich – mit den Funktionen, die eine ereignisgesteuerte Architektur in einer grossen Umgebung braucht.

Kafka-Cluster mit TLS-Verschlüsselung, Authentifizierung und ACLs

In diesem Teil der Blogserie bauen wir einen Kafka-Cluster in einer Laborumgebung auf. Der Einfachheit halber läuft der ganze Cluster auf einem einzigen Docker-Host. In einem produktiven Setup sollten Sie die einzelnen Komponenten jedoch auf mehrere virtuelle Maschinen (VMs) verteilen, um Skalierbarkeit, Redundanz und Ausfallsicherheit zu gewährleisten. In einem der nächsten Blogs setze ich einen echten Kubernetes-Cluster auf GKE auf.

Die Broker-Ports wirken vielleicht ungewöhnlich: 19093, 29093 und 39093. Das ist aber Absicht. So können alle Broker ohne Portkonflikte auf einem einzigen Testhost laufen. Mit getrennten Portbereichen arbeitet jeder Broker unabhängig, ohne mit Standardports oder anderen Diensten auf dem System zu kollidieren. Natürlich ist dieses Setup nur zum Testen gedacht: In Produktion verwenden Sie mehrere VMs, auf denen der Kafka-Broker jeweils auf demselben Port läuft.

Architekturdiagramm: Kafka-UI, Client-1 und Client-2 authentifizieren sich per SSL (TLS) mit eigenen Zertifikaten an drei Kafka-Brokern auf den Ports 19093, 29093 und 39093. Die Broker verschlüsseln, authentifizieren und prüfen ACLs, kommunizieren untereinander und sind an drei Zookeeper-Nodes angebunden.
Abbildung – Kafka-Cluster-Setup für Teil 3

Authentifizierung mit Client-Zertifikaten (mTLS)

Für die Authentifizierung verwenden wir Mutual TLS (mTLS): Um eine Verbindung zu den Kafka-Brokern aufzubauen, braucht ein Client ein Client-Zertifikat, das von unserer Certificate Authority (CA) ausgestellt wurde. Anders als bei Kafkas SASL-Authentifizierungsschicht stützt sich dieser Ansatz ausschliesslich auf die Validierung von SSL-(TLS-)Zertifikaten. Stammt das Zertifikat eines Clients nicht von unserer CA, schlägt der TLS-Handshake fehl und der Client kann nicht mit den Brokern kommunizieren. SASL ist in diesem Setup nicht beteiligt.

Unsere Kafka-Broker-Konfiguration umfasst:

  • SSL-(TLS-)Verschlüsselung für eine sichere Kommunikation.
  • Mutual-TLS-(mTLS-)Authentifizierung, erzwungen über ssl.client.auth: required.
  • Kommunikation zwischen den Brokern über SSL, sodass der gesamte Verkehr zwischen den Kafka-Nodes verschlüsselt bleibt.

Späteres Upgrade: SASL_SSL mit OAuth
In einem späteren Deployment wechseln wir auf SASL_SSL mit OAuth. Damit kommt eine tokenbasierte Authentifizierung hinzu, während die SSL-Verschlüsselung erhalten bleibt. So lässt sich ein OAuth-Identity-Provider (z. B. Keycloak oder Okta) für Authentifizierung und Autorisierung anbinden, was das System in einer Produktionsumgebung besser skalierbar und einfacher verwaltbar macht.

Zertifikate erzeugen

Diagramm der Zertifikatshierarchie: ca.crt stellt die Zertifikate kafka.crt, kafka-ui.crt, client-1.crt und client-2.crt aus.
Abbildung – Kafka-Zertifikate

Zuerst erzeugen wir ein CA.crt mit privatem Schlüssel, mit dem wir alle weiteren Zertifikate ausstellen.

  • kafka.crt
    Zertifikat der Kafka-Broker, um die Kommunikation zu verschlüsseln und sich gegenseitig zu authentifizieren
  • kafka-ui.crt
    Zertifikat, mit dem sich KafkaUI mit einem Kafka-Broker verbindet
  • client-1.crt
    Zertifikat für einen beliebigen «client-1», der sich mit Kafka verbindet
  • client-2.crt
    Zertifikat für einen beliebigen «client-2», der sich mit Kafka verbindet
# generate all required certificates
./generate_certs.sh changeit  

Befehl – alle benötigten Zertifikate per Skript erzeugen

Alle Schlüssel erzeugt dieses Skript im Git-Repository automatisch. Java wird benötigt, weil wir mit keytool einen Java-Keystore erstellen. Nachdem alle Zertifikate erzeugt sind, sieht das so aus:

Terminal-Ausgabe von ls -l im Verzeichnis certs: CA-, Broker-, Kafka-UI- und Client-Zertifikate mit Schlüsseln, CSRs, Keystores, Truststore und der Datei credentials.
Abbildung – erzeugte TLS-Zertifikate für Kafka

Deployment

Um das ganze Setup zu starten, führen Sie Folgendes aus:

# start the kafka cluster on docker (and generate certs)
make run
Terminal-Ausgabe von docker ps mit den laufenden Containern: Test-App, Kafka-UI, drei Kafka-Broker und drei Zookeeper-Instanzen.
Abbildung – laufende Kafka-Container für das Setup aus Teil 3

Das sind die wichtigsten Konfigurationsparameter für dieses Setup:

  • advertised.listeners:SSL://kafka-1:19093 advertised.listeners:SSL://kafka-2:29093 advertised.listeners:SSL://kafka-3:39093
    (Doku) Die Broker laufen jetzt mit SSL-(TLS-)Verschlüsselung auf Port 9093.
  • security.inter.broker.protocol:SSL
    (Doku) Für die Kommunikation mit einem Kafka-Broker ist jetzt SSL-(TLS-)Verschlüsselung Pflicht.
  • ssl.keystore.filename:server.keystore.jks
    (Doku) Alternativ lässt sich ssl.keystore.location verwenden, dann muss aber meines Wissens der vollständige Dateipfad angegeben werden. Als Keystore dient der zuvor erzeugte Server-Keystore, der das ca.crt sowie den privaten Schlüssel und das Zertifikat (kafka.crt) für den Betrieb der Kafka-Broker enthält. Dieser Keystore ist zentral, um die Kommunikation zwischen den Kafka-Brokern per SSL/TLS abzusichern. Übrigens: ssl.keystore.credentials sind die Zugangsdaten für den Keystore; sie liegen in der Datei certs/credentials und werden in den Container gemountet.
  • ssl.client.auth:required
    (Doku) Clients müssen sich per SSL (TLS) mit einem Client-Zertifikat authentifizieren.
  • authorizer.class.name:kafka.security.authorizer.AclAuthorizer
    (Doku) In diesem Abschnitt schauen wir uns Access Control Lists (ACLs) in Apache Kafka an. Mit ihnen legen Sie fest, welche Clients auf welche Topics zugreifen dürfen. Über ACLs setzen Sie feingranulare Sicherheitsrichtlinien durch, sodass nur berechtigte Benutzer oder Applikationen aus Kafka-Topics lesen oder in sie schreiben können.
  • principal.builder.class:org.apache.kafka.common.security.authenticator.DefaultKafkaPrincipalBuilder
    (Doku) Diesen Principal Builder habe ich zuerst verwendet. Er konnte aber den eigentlichen Benutzernamen nicht aus dem Zertifikat (CN=kafka) extrahieren, sondern lieferte CN=kafka statt kafka. Deshalb habe ich schliesslich ssl.principal.mapping.rules : RULE:^CN=(.*?)$$/$$1/L ergänzt. Damit wird der Benutzername korrekt extrahiert.
  • super.users:your_super_users
    (Doku) Sie können im Setup auch Superuser definieren, die keine ACLs benötigen.

Zugriffskontrolle

Ein Blick in KafkaUI zeigt, dass es kein neues Topic im Cluster anlegen kann. Das ist zu erwarten: KafkaUI verbindet sich mit dem erzeugten kafka-ui.crt, aber der Benutzer kafka-ui (CN=kafka-ui) hat in den Access Control Lists (ACL) noch nicht die nötigen Berechtigungen.

Screenshot von UI for Apache Kafka beim Anlegen des Topics test-topic, mit der Fehlermeldung «400 Bad Request – Authorization failed».
Abbildung – Kafka UI hat keine Berechtigung, ein Topic anzulegen

Jetzt passen wir die Access Control List (ACL) an und geben Kafka-UI die nötigen Berechtigungen, um den Kafka-Cluster zu verwalten.

# Give Kafka-UI full access to all topics
docker exec -it kafka-1 kafka-acls --authorizer-properties zookeeper.connect=zookeeper-1:2181 \
  --add --allow-principal "User:kafka-ui" --operation All --topic '*'

# Allow Kafka-UI full access to the Kafka cluster
docker exec -it kafka-1 kafka-acls --authorizer-properties zookeeper.connect=zookeeper-1:2181 \
  --add --allow-principal "User:kafka-ui" --operation All --cluster

# restart kafka ui
docker restart kafka-ui

Befehl – dem Benutzer kafka-ui die nötigen Berechtigungen erteilen

Legen wir auch die nötigen Berechtigungen für die Test-App an. Sie verwendet das erzeugte client-1.crt mit dem Common Name CN=client-1.

# grant client-1 permissions to create test-topic
docker exec -it kafka-1 kafka-acls --authorizer-properties zookeeper.connect=zookeeper-1:2181 \
  --add --allow-principal "User:client-1" --operation Create --topic test-topic

# grant client-1 permissions to read/write test-topic
docker exec -it kafka-1 kafka-acls --authorizer-properties zookeeper.connect=zookeeper-1:2181 \
  --add --allow-principal "User:client-1" --operation Read --operation Write --topic test-topic

# grant client-1 permissions to read the test group
docker exec -it kafka-1 kafka-acls --authorizer-properties zookeeper.connect=zookeeper-1:2181 \
  --add --allow-principal "User:client-1" --operation Read --group test-group

# grant client-1 permissions to describe the test group
docker exec -it kafka-1 kafka-acls --authorizer-properties zookeeper.connect=zookeeper-1:2181 \
  --add --allow-principal "User:client-1" --operation Describe --group test-group

Befehl – client-1 die nötigen Berechtigungen erteilen

Testen

Das Deployment ist abgeschlossen, also Zeit für einige Tests mit der Test-Applikation. Sie finden die Test-App im GitHub-Repository.

Alles scheint korrekt zu funktionieren. Jetzt können Sie mit den Access Control Lists (ACLs) experimentieren und prüfen, dass Benutzer ohne die nötigen Berechtigungen nicht mehr auf den Kafka-Cluster zugreifen können. So stellen Sie sicher, dass die Zugriffsbeschränkungen tatsächlich greifen.

Screenshot der Test-App, verbunden mit kafka-1:19093 über SSL mit CA-Zertifikat, client-1.crt und client-1.key; eine Testnachricht wurde gesendet und empfangen.
Abbildung – Test-App mit Kafka verbunden, Nachrichten werden gesendet und empfangen

Fazit

Wir kommen gut voran! Jetzt haben wir einen Kafka-Cluster mit Verschlüsselung, Authentifizierung, Zugriffskontrolle und Skalierbarkeit.

Im kommenden Teil 4 dieser Serie konzentriere ich mich auf Funktionen für den Enterprise-Einsatz, konkret auf OAuth-Authentifizierung und OAuth-basierte Berechtigungen. Diese Erweiterungen stärken die Sicherheit weiter und vereinfachen die Verwaltung der Zugriffe von Benutzern und Applikationen. Bleiben Sie dran!