Kafka Security Hardening: SASL Authentication and ACL Access Control
Kafka clusters default to no auth — unauthorized access and data leak. Full SASL/SCRAM, SSL transport, and ACL config, with copy-paste commands and verification.
Scope
Apache Kafka is the default distributed message bus. Out of the box, Kafka security is off — any client that can hit 9092 can read/write any Topic. Use this when: production Kafka must stop unauthorized consumers; multi-team clusters need per-Topic R/W isolation; compliance/audit requires identity and access logging. After this, the cluster has auth, transport encryption, and fine-grained authorization.
Prerequisites
- Kafka 2.7+ (Kafka 3.6 here, path `/opt/kafka`)
- ZooKeeper or KRaft healthy, node clocks synced
- Linux root, inter-node reachability
- Domain or IP for certs (domain in production)
How It Works
Three independent layers: Authentication — SASL/SCRAM-SHA-256, username and salted password stored in ZooKeeper or KRaft metadata; Encryption — SSL/TLS on client↔Broker, stops MITM; Authorization — Kafka ACLs, Principal—operation (Read/Write/Describe)—resource (Topic/Group) triples. Stacked, even an exposed port yields no operations without creds.
Procedure
Step 1: Broker-side SASL
Edit `/opt/kafka/config/server.properties`:
# Listener and protocol mapping: 9092 uses SASL+SSL
listeners=SASL_SSL://0.0.0.0:9092
advertised.listeners=SASL_SSL://kafka1.example.com:9092
sasl.enabled.mechanisms=SCRAM-SHA-256
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-256
security.inter.broker.protocol=SASL_SSL
# SSL certificate configuration
ssl.keystore.location=/etc/kafka/ssl/server.keystore.jks
ssl.keystore.password=changeit
ssl.key.password=changeit
ssl.truststore.location=/etc/kafka/ssl/server.truststore.jks
ssl.truststore.password=changeit
ssl.client.auth=required
Step 2: Generate SSL certs and truststore
# Generate Broker keystore (production should use CA-issued certs; self-signed shown here)
keytool -keystore /etc/kafka/ssl/server.keystore.jks -alias kafka -validity 3650 -genkeypair -keyalg RSA -storepass changeit -dname "CN=kafka1.example.com" -ext SAN=DNS:kafka1.example.com
# Export and generate client truststore
keytool -keystore /etc/kafka/ssl/server.keystore.jks -exportcert -alias kafka -rfc -file /tmp/kafka.crt
keytool -keystore /etc/kafka/ssl/client.truststore.jks -alias kafka -import -file /tmp/kafka.crt -storepass changeit -noprompt
Step 3: Create SCRAM users
cd /opt/kafka
# Create application account app-user; admin permission group is used for management
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter --add-config 'SCRAM-SHA-256=[password=App@2026Secret],SCRAM-SHA-512=[password=App@2026Secret]' --entity-type users --entity-name app-user
# Create admin account (can perform cluster management operations)
bin/kafka-configs.sh --bootstrap-server localhost:9092 --alter --add-config 'SCRAM-SHA-256=[password=Admin@2026Secret]' --entity-type users --entity-name admin
Step 4: JAAS file
Create `/etc/kafka/kafka_server_jaas.conf`:
KafkaServer {
org.apache.kafka.common.security.scram.ScramLoginModule required
username="admin"
password="Admin@2026Secret";
};
Client {
org.apache.kafka.common.security.scram.ScramLoginModule required
username="admin"
password="Admin@2026Secret";
};
Load JAAS via KAFKA_OPTS in `bin/kafka-server-start.sh`:
export KAFKA_OPTS="-Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf"
Step 5: Enable ACL
Append to `server.properties`:
authorizer.class.name=kafka.security.authorizer.AclAuthorizer
super.users=User:admin
allow.everyone.if.no.acl.found=false
Restart the cluster, then grant app-user:
bin/kafka-acls.sh --bootstrap-server localhost:9092 --add --allow-principal User:app-user --operation Read,Write,Describe --topic orders --group app-group --command-config /etc/kafka/admin.properties
Step 6: Client connection config
Create `client.properties`:
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-256
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="app-user" password="App@2026Secret";
ssl.truststore.location=/etc/kafka/ssl/client.truststore.jks
ssl.truststore.password=changeit
Verify with the console consumer:
bin/kafka-console-consumer.sh --bootstrap-server kafka1.example.com:9092 --topic orders --group app-group --consumer.config client.properties
Validation
# 1. Connect without credentials, should be rejected
bin/kafka-topics.sh --bootstrap-server localhost:9092 --list
# Error: Unauthorized... or authentication failed
# 2. Authenticated admin user can list Topics
bin/kafka-topics.sh --bootstrap-server localhost:9092 --list --command-config /etc/kafka/admin.properties
# 3. Verify ACL is in effect: app-user attempts Describe on an unauthorized Topic
bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic secret-topic --command-config client.properties
# Error: Topic authorization failed
# 4. Verify TLS port with openssl
openssl s_client -connect kafka1.example.com:9092 -tls1_2 < /dev/null | head -20
FAQ
FAQ 1: All existing clients fail after ACL?
Expected — default deny. Keep `allow.everyone.if.no.acl.found=true` during transition, onboard clients in batches, then flip to `false`. Confirm `super.users` includes admins so you do not lock the cluster.
FAQ 2: TLS handshake fails because cert domain ≠ connect address?
Most common SSL fail: cert CN/SAN ≠ hostname in `advertised.listeners`. Cert must include the hostname clients actually connect to. Generate with `-ext SAN=DNS:<domain>`. Multi-broker: one cert per Broker with its own SAN.
Summary
Three steps: SASL/SCRAM for identity, SSL for the wire, ACL for Topic-level authz. Production: vault the secrets, rotate SCRAM passwords, put ACL changes in config management. After all three layers, verify reject with an unauthenticated client. Cluster goes from open to an auditable trusted data path.