EN EnglishDE Deutsch

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

Eine Skyline mit Hochhäusern, über dunkles Wasser hinweg gesehen, unter schweren Gewitterwolken
Foto von Josh Hild / 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.

Enterprise-Authentifizierung

Unternehmen möchten Benutzerkonten und Berechtigungen nicht in jedem einzelnen System separat verwalten. Stattdessen binden sie einen Identity Provider (IdP) an, um Authentifizierung und Autorisierung zu zentralisieren.

In dieser Anleitung zeige ich, wie ich Keycloak als Identity Provider für einen Apache-Kafka-Cluster konfiguriert habe. Selbstverständlich können Sie bei Bedarf einen anderen IdP verwenden. Mit Keycloak lassen sich Authentifizierung und Autorisierung in Kafka effizient verwalten, und die Zugriffskontrolle bleibt sicher.

Ablaufdiagramm des OAuth 2.0 Client Credentials Flow: Der Client holt in den Schritten 1 und 2 ein Token vom OAuth-2.0-Autorisierungsserver (Keycloak), verbindet sich in Schritt 3 mit dem Kafka-Broker, der das Token in Schritt 4 validiert und in Schritt 5 über den Strimzi Keycloak Authorizer die Berechtigungen abruft.
Abbildung – OAuth 2.0 Client Credentials Flow

Wird Apache Kafka an einen Identity Provider (IdP) wie Keycloak angebunden, kommt für die OAuth-2.0-Authentifizierung typischerweise der Client Credentials Flow zum Einsatz. Der Ablauf der Authentifizierung:

  1. Client-Authentifizierung:
    Der Client, der sich mit Kafka verbinden will, authentifiziert sich zuerst mit seinen Client Credentials (Client-ID und Secret) beim OAuth-2.0-Server.
  2. Access Token abrufen:
    Nach erfolgreicher Authentifizierung stellt der OAuth-2.0-Server ein Access Token aus. Dieses Token enthält den Subject-Namen (sub) (z. B. "client-1") und eine Liste von Berechtigungen (Scopes oder Permissions).
  3. Verbindung zu Kafka:
    Mit dem Access Token baut der Client anschliessend die Verbindung zum Kafka-Broker auf.
  4. Token-Validierung durch den Kafka-Broker:
    Der Kafka-Broker validiert das Token, um seine Echtheit sicherzustellen. In diesem Fall geschieht das über die Prüfung der Token-Signatur. Dazu holt sich der Broker den öffentlichen Schlüssel vom JWKS-Endpunkt (JSON Web Key Set) des Autorisierungsservers. So kann Kafka bestätigen, dass das Token von einem vertrauenswürdigen Identity Provider ausgestellt wurde.
  5. Strimzi Keycloak Authorizer
    (Doku) In diesem Setup läuft auf den Brokern der Strimzi Keycloak Authorizer. Er fragt jede Minute beim OAuth-Server die aktuellen Berechtigungen ab.

Im Kern werden Authentifizierung und Autorisierung an einen externen Identity Provider (IdP) wie Keycloak delegiert, statt direkt im Kafka-Cluster verwaltet zu werden. Das zentralisiert das Identitäts- und Zugriffsmanagement: Administratoren steuern Authentifizierung, Autorisierung und Token-Richtlinien direkt in Keycloak.

Übersicht

Mit der Integration der Enterprise-Authentifizierung ist unser Kafka-Deployment gewachsen und etwas komplexer geworden. Die Broker betreiben zwei Ports:

  • SSL
    19093,29093,39093
    Verschlüsselung und SSL-(TLS-)Authentifizierung für die Kommunikation zwischen den Brokern im Cluster. Auch KafkaUI verbindet sich mit diesem internen Port.
  • SASL_SSL (Oauthbearer)
    19092,29092,39092
    Dieser Port ist für die Aussenwelt gedacht: Kafka-Clients verbinden sich darüber mit den Brokern und authentifizieren sich per OAuth 2.0.
Architekturdiagramm: Kafka-UI verbindet sich über die SSL-Ports 19093, 29093 und 39093 mit Client-Authentifizierung per TLS, Client-1 und Client-2 über die Ports 19092, 29092 und 39092 mit SASL Oauthbearer. Die drei Broker holen Berechtigungen bei Keycloak ab und sind an drei Zookeeper-Nodes mit SASL-Authentifizierung (Digest + MD5) angebunden.
Abbildung – Kafka-Deployment für Teil 4

Vorbereitungen

Bevor wir die Enterprise-Authentifizierung mit OAuth 2.0 in Kafka einrichten, bereiten wir unser «Mise en place» 🍽️ vor, damit alle nötigen Komponenten für eine reibungslose Konfiguration bereitstehen.

Eigenes Kafka-Docker-Image

Apache Kafka® bringt eine grundlegende OAuth2-Unterstützung in Form eines SASL-basierten Authentifizierungsmoduls mit, das den Abruf, den Austausch und die Validierung von Access Tokens als Zugangsdaten zwischen Client und Server übernimmt. Für den Praxiseinsatz braucht es Erweiterungen in Form von JAAS-Callback-Handlern, und genau die liefert Strimzi Kafka OAuth. Deshalb bauen wir uns jetzt ein eigenes Kafka-Image mit den nötigen Strimzi-OAuth-Bibliotheken.

FROM confluentinc/cp-kafka:latest

# Switch to root to install libraries
USER root

# Download and add Strimzi OAuth libraries
RUN curl -L -o /usr/share/java/kafka/strimzi-kafka-oauth-common-0.15.0.jar https://repo1.maven.org/maven2/io/strimzi/kafka-oauth-common/0.15.0/kafka-oauth-common-0.15.0.jar && \
 curl -L -o /usr/share/java/kafka/strimzi-kafka-oauth-server-0.15.0.jar https://repo1.maven.org/maven2/io/strimzi/kafka-oauth-server/0.15.0/kafka-oauth-server-0.15.0.jar && \
 curl -L -o /usr/share/java/kafka/strimzi-kafka-oauth-client-0.15.0.jar https://repo1.maven.org/maven2/io/strimzi/kafka-oauth-client/0.15.0/kafka-oauth-client-0.15.0.jar && \
 curl -L -o /usr/share/java/kafka/kafka-oauth-keycloak-authorizer-0.15.0.jar https://repo1.maven.org/maven2/io/strimzi/kafka-oauth-keycloak-authorizer/0.15.0/kafka-oauth-keycloak-authorizer-0.15.0.jar

# Download and add Nimbus JOSE+JWT library
RUN curl -L -o /usr/share/java/kafka/nimbus-jose-jwt-10.0.1.jar https://repo1.maven.org/maven2/com/nimbusds/nimbus-jose-jwt/10.0.1/nimbus-jose-jwt-10.0.1.jar

# troubleshooting tools install
RUN dnf install -y net-tools

# Switch back to appuser
USER appuser

Dockerfile – eigenes Kafka-Image mit den Strimzi-OAuth-Bibliotheken

Screenshot aus VS Code: oben das Dockerfile, das auf confluentinc/cp-kafka aufbaut und die Strimzi-OAuth- und Nimbus-JOSE-JWT-Bibliotheken herunterlädt, unten im Terminal die Ausgabe von docker build.
Abbildung – eigenes Kafka-Docker-Image bauen

Das Image wurde erfolgreich gebaut und in mein privates Docker-Repository hochgeladen.

Keycloak-Setup

Für diese Blogserie betreibe ich einen Keycloak-Server in einem Docker-Container. Sie können aber jeden anderen Identity Provider Ihrer Wahl verwenden. Keycloak selbst steht in dieser Serie nicht im Mittelpunkt; ich zeige einfach meine Keycloak-Konfiguration, die Authentifizierung und Autorisierung für Kafka übernimmt.

Wir bereiten bereits einige Clients und Berechtigungen vor, die wir später zum Testen verwenden:

  • client-1
    Einen «client-1» zum Testen angelegt. Aktivieren Sie client authentication und authorization. Kopieren Sie das client secret im Tab «Credentials» für später.
  • client-2
    Einen «client-2» zum Testen angelegt. Aktivieren Sie client authentication und authorization. Kopieren Sie das client secret im Tab «Credentials» für später. Gleiche Konfiguration wie client-1, nur der Name ist anders.
  • kafka-broker
    Einen Client «kafka-broker» angelegt, damit die Kafka-Broker die Berechtigungen vom Keycloak-Server abrufen können. Auch beim Broker-Client sollte authorization aktiviert sein. Notieren Sie sich ebenfalls das client secret. Im Tab «Authorization» habe ich die scopes gemäss der Anleitung des Strimzi Kafka Keycloak Authorizers konfiguriert (Doku). Danach habe ich einige resources angelegt, je eine policy vom Typ client für «client-1» und für «client-2» erstellt und schliesslich die permissions wie im Bild gezeigt angelegt.

Den Abruf eines Autorisierungstokens habe ich so getestet:

# get authorization token for client-1
curl -X POST \
  -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=client-1&client_secret=**********&audience=kafka-broker" \
  "https://my-keycloakserver.ch/auth/realms/master/protocol/openid-connect/token"

Bash – Autorisierungstoken für client-1 abrufen

Sie können das JWT inspizieren, indem Sie es auf jwt.io einfügen. Das Token sollte jetzt etwa die folgende Struktur haben. Achten Sie besonders auf das Attribut authorization.permissions, denn das verwendet später der Keycloak Authorizer.

In diesem Beispiel gewährt das Token die Berechtigungen Describe, Read, Write und Create für Topics nach dem Muster test*. Unsere Test-Client-Applikation arbeitet konkret mit dem Topic «test-topic».

Screenshot von jwt.io mit dem dekodierten Token von client-1: Im Payload stehen unter authorization.permissions die Scopes Describe, Read, Write und Create für das Topic-Muster test*.

Kafka-Deployment

  • KAFKA_ADVERTISED_LISTENERS:SASLSSL://kafka-1:19092,SSL://kafka-1:19093
    Dieses Deployment hat zwei Listener: SSL mit Mutual TLS für die interne Nutzung, also die Kommunikation zwischen den Brokern im Cluster und mit kafka-ui, und SASL SSL mit OAuth für die Kommunikation mit externen Clients.
  • KAFKA_INTER_BROKER_LISTENER_NAME:SSL
    Die Broker kommunizieren intern nur über SSL (TLS).
  • KAFKA_SASL_MECHANISM:OAUTHBEARER KAFKA_LISTENER_NAME_SASLSSL_SASL_ENABLED_MECHANISMS:OAUTHBEARER
    Für die Authentifizierung (und Autorisierung) der Clients verwende ich den SASL-Mechanismus OAUTHBEARER.
  • KAFKA_LISTENER_NAME_SASLSSL_OAUTHBEARER_SASL_SERVER_CALLBACK_HANDLER_CLASS:io.strimzi.kafka.oauth.server.JaasServerOauthValidatorCallbackHandler
    Legt den serverseitigen Callback-Handler für die OAuth-2.0-Authentifizierung fest. Mit dieser Klasse (von Strimzi) validiert Kafka die OAuth-Tokens der Clients.
  • KAFKA_LISTENER_NAME_SASLSSL_OAUTHBEARER_SASL_LOGIN_CALLBACK_HANDLER_CLASS:io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
    Legt den clientseitigen Callback-Handler für die OAuth-Authentifizierung fest. Kafka-Clients holen sich damit vor dem Verbindungsaufbau ein OAuth-Token.
  • KAFKA_STRIMZI_AUTHORIZATION_TOKEN_ENDPOINT_URI KAFKA_STRIMZI_AUTHORIZATION_CLIENT_ID
    Diese Einstellungen verwendet der Strimzi Keycloak Authorizer. Er verbindet sich jede Minute mit dem Token-Endpunkt, um eine aktuelle Liste (ein Token) der Berechtigungen für die aktiven Clients zu holen. Dafür nutzt er seine eigene Client-ID kafka-broker.

Testen

Auch diesmal hat die Test-App ein Update erhalten und kann sich jetzt per OAuth Bearer verbinden und autorisieren. Ich verwende client-1 und das zuvor in Keycloak erzeugte Secret. Die Test-App holt sich bei Keycloak ein Token mit den Berechtigungen, die wir oben im Abschnitt «Keycloak-Setup» definiert haben.

Screenshot der Test-App, verbunden mit kafka-1:19092 über OAUTHBEARER mit Token-Endpunkt, client-1 und Secret; eine Testnachricht wurde gesendet und empfangen.
Abbildung – Kafka-Test-App erfolgreich verbunden

Fazit

Et voilà: Kafka läuft jetzt mit Enterprise-OAuth-2.0 und Keycloak! Das Team von Confluent verdient ein High Five für seine eleganten (wenn auch teuren) Tools. Kafka-Cluster zu skalieren ist allerdings kein Spaziergang, und ich bin nicht sicher, ob wir die automatische Umverteilung der Daten so gut hinbekommen wie sie.
Als Nächstes: Kubernetes auf GKE. Aufsatteln für den Cluster-Ritt!