Kafka Security: SSL, SASL & ACL Authorization

Out of the box, Kafka transmits data in plaintext and accepts connections from anyone. In production you need three layers: encryption (SSL/TLS) to protect data in transit, authentication (SASL) to verify client identity, and authorization (ACLs) to control which clients can produce or consume which topics.

Share

Contents

SSL/TLS Overview

SSL provides two things: encryption (all bytes on the wire are ciphertext) and optional mutual authentication (both broker and client present certificates). You need a keystore on each broker and a truststore on each client.

Java
/*
  Key concepts:
  ─────────────────────────────────────────────────────
  Keystore   — contains a private key + certificate
               (used by the holder to prove identity)

  Truststore — contains trusted CA certificates
               (used to verify the other party's cert)

  One-way SSL — only the broker has a certificate;
                clients trust the broker CA.

  Mutual TLS (mTLS) — both broker and client present
                      certificates to each other.
  ─────────────────────────────────────────────────────
*/

Broker SSL Configuration

Generate a broker keystore with keytool and configure the broker to listen on a separate SSL port (9093 by convention).

Shell
# 1. Generate a CA key and certificate
openssl req -new -x509 -keyout ca-key -out ca-cert -days 365 -subj "/CN=kafka-ca"

# 2. Create broker keystore
keytool -keystore kafka.broker.keystore.jks -alias broker -validity 365 \
  -genkey -keyalg RSA -dname "CN=kafka-broker"

# 3. Sign the broker certificate with the CA
keytool -keystore kafka.broker.keystore.jks -alias broker -certreq -file broker-cert-request
openssl x509 -req -CA ca-cert -CAkey ca-key -in broker-cert-request \
  -out broker-cert-signed -days 365 -CAcreateserial

# 4. Import CA + signed cert into keystore
keytool -keystore kafka.broker.keystore.jks -alias CARoot  -import -file ca-cert
keytool -keystore kafka.broker.keystore.jks -alias broker  -import -file broker-cert-signed

# 5. Create truststore containing the CA
keytool -keystore kafka.broker.truststore.jks -alias CARoot -import -file ca-cert
Properties
# server.properties — broker config
listeners=PLAINTEXT://localhost:9092,SSL://localhost:9093
advertised.listeners=PLAINTEXT://localhost:9092,SSL://localhost:9093

ssl.keystore.location=/etc/kafka/ssl/kafka.broker.keystore.jks
ssl.keystore.password=keystore-password
ssl.key.password=key-password
ssl.truststore.location=/etc/kafka/ssl/kafka.broker.truststore.jks
ssl.truststore.password=truststore-password

# Require client certificates (mTLS)
ssl.client.auth=required

Client SSL Configuration

Clients (producers and consumers) need a truststore containing the CA that signed the broker certificate. For mTLS they also need their own keystore.

Java
Properties props = new Properties();
props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, "localhost:9093");
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SSL");

// Client truststore — trust the broker's CA
props.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, "/etc/kafka/ssl/client.truststore.jks");
props.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "truststore-password");

// Client keystore — for mTLS (present own certificate to broker)
props.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, "/etc/kafka/ssl/client.keystore.jks");
props.put(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG, "keystore-password");
props.put(SslConfigs.SSL_KEY_PASSWORD_CONFIG,      "key-password");

// Serializers …
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,   StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

KafkaProducer<String, String> producer = new KafkaProducer<>(props);

SASL/PLAIN Authentication

SASL/PLAIN sends username and password. Use it only over SSL, without encryption the credentials are transmitted in cleartext.

Properties
# server.properties — enable SASL_SSL listener
listeners=SASL_SSL://localhost:9094
advertised.listeners=SASL_SSL://localhost:9094
security.inter.broker.protocol=SASL_SSL
sasl.mechanism.inter.broker.protocol=PLAIN
sasl.enabled.mechanisms=PLAIN

# JAAS config file path (or inline with listener.name prefix)
listener.name.sasl_ssl.plain.sasl.jaas.config=\
  org.apache.kafka.common.security.plain.PlainLoginModule required \
  username="admin" password="admin-secret" \
  user_admin="admin-secret" \
  user_producer="producer-secret" \
  user_consumer="consumer-secret";
Java
// Client SASL/PLAIN config
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG,  "SASL_SSL");
props.put(SaslConfigs.SASL_MECHANISM,                    "PLAIN");
props.put(SaslConfigs.SASL_JAAS_CONFIG,
    "org.apache.kafka.common.security.plain.PlainLoginModule required " +
    "username=\"producer\" password=\"producer-secret\";");
// SSL truststore config (same as above) …
SASL/PLAIN stores passwords in plaintext in the JAAS configuration. For production, prefer SASL/SCRAM which stores salted password hashes in ZooKeeper/KRaft metadata.

SASL/SCRAM Authentication

SCRAM stores credential hashes in Kafka metadata. Create users with the kafka-configs.sh tool; no broker restart required to add new users.

Shell
# Create a SCRAM-SHA-512 credential for user 'alice'
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --alter --add-config 'SCRAM-SHA-512=[iterations=8192,password=alice-password]' \
  --entity-type users --entity-name alice

# List all users
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --describe --entity-type users
Properties
# server.properties
sasl.enabled.mechanisms=SCRAM-SHA-512
listener.name.sasl_ssl.scram-sha-512.sasl.jaas.config=\
  org.apache.kafka.common.security.scram.ScramLoginModule required;
Java
// Client SCRAM config
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
props.put(SaslConfigs.SASL_MECHANISM, "SCRAM-SHA-512");
props.put(SaslConfigs.SASL_JAAS_CONFIG,
    "org.apache.kafka.common.security.scram.ScramLoginModule required " +
    "username=\"alice\" password=\"alice-password\";");

ACL Authorization

Kafka's built-in ACL authorizer controls which principals (users) can perform which operations on which resources (topics, consumer groups, cluster).

Shell
# Enable ACL authorizer in server.properties
# authorizer.class.name=kafka.security.authorizer.AclAuthorizer
# super.users=User:admin

# Grant alice WRITE on topic 'orders'
bin/kafka-acls.sh --bootstrap-server localhost:9092 \
  --add --allow-principal User:alice \
  --operation Write --topic orders

# Grant bob READ on topic 'orders' and on consumer group 'bob-group'
bin/kafka-acls.sh --bootstrap-server localhost:9092 \
  --add --allow-principal User:bob \
  --operation Read --topic orders
bin/kafka-acls.sh --bootstrap-server localhost:9092 \
  --add --allow-principal User:bob \
  --operation Read --group bob-group

# List all ACLs
bin/kafka-acls.sh --bootstrap-server localhost:9092 --list
A consumer needs READ ACL on both the topic and its consumer group to successfully consume. Forgetting the group ACL is a common mistake that results in an authorization exception at poll time.

Spring Boot Security Config

Configure SSL and SASL in application.properties for a Spring Boot Kafka application without any Java configuration code.

Properties
spring.kafka.bootstrap-servers=localhost:9094
spring.kafka.security.protocol=SASL_SSL

# SASL
spring.kafka.properties.sasl.mechanism=SCRAM-SHA-512
spring.kafka.properties.sasl.jaas.config=\
  org.apache.kafka.common.security.scram.ScramLoginModule required \
  username="alice" password="alice-password";

# SSL truststore
spring.kafka.ssl.trust-store-location=classpath:client.truststore.jks
spring.kafka.ssl.trust-store-password=truststore-password

# Optional: client certificate for mTLS
spring.kafka.ssl.key-store-location=classpath:client.keystore.jks
spring.kafka.ssl.key-store-password=keystore-password
spring.kafka.ssl.key-password=key-password