Skip to main content
Kafka · lesson 9 of 12
Kafka intermediate Lesson 9 of 12

Also part of: Security Fundamentals

Kafka Security: SASL Authentication and a Real ACL Denial

A default Kafka cluster trusts every connection completely. Turn on SASL/PLAIN and an ACL authorizer, and watch an unauthorized client fail with the exact exception Kafka throws — for a topic, then for a consumer group.

By SkillByExample · Published

kafka security sasl acl authorization

A default Kafka broker authenticates nobody and authorizes everything — any TCP connection can produce, consume, or delete any topic. SASL adds authentication; an authorizer adds per-resource permission checks on top of it.

Starting a broker with SASL and an authorizer

This needs three broker settings beyond a plain cluster: a JAAS login config naming valid users, SASL_PLAINTEXT as the client listener’s protocol, and an authorizer class:

# server.properties (relevant lines)
listeners=SASL_PLAINTEXT://:9092,CONTROLLER://:9093
sasl.enabled.mechanisms=PLAIN
sasl.mechanism.inter.broker.protocol=PLAIN
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
super.users=User:admin
# jaas.conf — passed via KAFKA_OPTS=-Djava.security.auth.login.config=...
KafkaServer {
  org.apache.kafka.common.security.plain.PlainLoginModule required
  username="admin"
  password="admin-secret"
  user_admin="admin-secret"
  user_alice="alice-secret";
};

Two users exist: admin (a designated super user, exempt from every ACL check) and alice (an ordinary authenticated principal with no permissions until granted some).

admin bypasses every check

bin/kafka-topics.sh --bootstrap-server localhost:9092 \
  --command-config admin.properties \
  --create --topic secure-orders --partitions 1 --replication-factor 1
Created topic secure-orders.

No ACL exists yet for this topic at all — admin doesn’t need one, because super.users exempts it from authorization entirely.

alice is denied — a real TopicAuthorizationException

echo "hello from alice" | bin/kafka-console-producer.sh --bootstrap-server localhost:9092 \
  --producer.config alice.properties --topic secure-orders
[...] ERROR [Producer clientId=console-producer] Topic authorization failed for topics [secure-orders] (org.apache.kafka.clients.Metadata)
org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [secure-orders]

alice authenticated successfully — SASL isn’t the failure here. She’s a known, valid user with zero grants on secure-orders, and the authorizer refuses the produce outright.

Granting a Write ACL

bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config admin.properties \
  --add --allow-principal User:alice --operation Write --operation Describe \
  --topic secure-orders
Adding ACLs for resource `ResourcePattern(resourceType=TOPIC, name=secure-orders, patternType=LITERAL)`:
 	(principal=User:alice, host=*, operation=DESCRIBE, permissionType=ALLOW)
	(principal=User:alice, host=*, operation=WRITE, permissionType=ALLOW)

Describe is needed alongside Write — a producer fetches topic metadata (partition count, leader) before it can send, and that’s a separate checked operation from the write itself.

echo "hello from alice" | bin/kafka-console-producer.sh --bootstrap-server localhost:9092 \
  --producer.config alice.properties --topic secure-orders

No error — the produce succeeds silently. Confirmed by having admin (super user, unaffected by any of alice’s grants) read it back:

$ bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --consumer.config admin.properties --topic secure-orders --from-beginning --timeout-ms 6000 --group admin-verify-group
hello from alice
Processed a total of 1 messages

Consuming needs a second, independent ACL check

bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --consumer.config alice.properties --topic secure-orders --from-beginning \
  --timeout-ms 6000 --group alice-group
ERROR Error processing message, terminating consumer process:  (org.apache.kafka.tools.consumer.ConsoleConsumer)
org.apache.kafka.common.errors.GroupAuthorizationException: Not authorized to access group: alice-group
Processed a total of 0 messages

Different exception, different resource: alice has Write/Describe on the topic, but consuming also requires Read on the consumer group she’s joining — a completely separate ACL that doesn’t exist yet, so Kafka names precisely which check failed.

bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config admin.properties \
  --add --allow-principal User:alice --operation Read --topic secure-orders
bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config admin.properties \
  --add --allow-principal User:alice --operation Read --group alice-group
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
  --consumer.config alice.properties --topic secure-orders --from-beginning \
  --timeout-ms 8000 --group alice-group
hello from alice
[...] org.apache.kafka.common.errors.TimeoutException
Processed a total of 1 messages

Both the topic-Read and group-Read grants in place, and the same message finally reaches alice’s own consumer. The trailing TimeoutException is the console consumer’s normal exit after its idle timeout, not a failure — Processed a total of 1 messages confirms it worked.

Listing what’s actually granted

bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config admin.properties \
  --list --topic secure-orders
Current ACLs for resource `ResourcePattern(resourceType=TOPIC, name=secure-orders, patternType=LITERAL)`:
 	(principal=User:alice, host=*, operation=WRITE, permissionType=ALLOW)
	(principal=User:alice, host=*, operation=DESCRIBE, permissionType=ALLOW)
	(principal=User:alice, host=*, operation=READ, permissionType=ALLOW)

Three separate grants, accumulated one --add at a time — kafka-acls.sh has no bundled “read-write” shortcut; every operation is its own explicit rule.

Practice

1. Try creating a topic as alice, who has no Create ACL on the cluster.
bin/kafka-topics.sh --bootstrap-server localhost:9092 --command-config alice.properties \
  --create --topic alice-topic --partitions 1 --replication-factor 1
Error while executing topic command : Authorization failed.
[...] ERROR org.apache.kafka.common.errors.TopicAuthorizationException: Authorization failed.

Creating a topic checks a Create permission alice was never granted — same exception class as the produce failure, different underlying operation.

2. Remove alice's Write ACL and try producing again.
bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config admin.properties \
  --remove --allow-principal User:alice --operation Write --topic secure-orders --force
echo "second try" | bin/kafka-console-producer.sh --bootstrap-server localhost:9092 \
  --producer.config alice.properties --topic secure-orders
[...] ERROR [Producer clientId=console-producer] Aborting producer batches due to fatal error (org.apache.kafka.clients.producer.internals.Sender)
org.apache.kafka.common.errors.ClusterAuthorizationException: Cluster authorization failed.

Not TopicAuthorizationException this time — ClusterAuthorizationException. The modern console producer defaults to idempotent delivery, which needs a cluster-level permission before it even gets to the per-topic Write check; losing Write trips that broader cluster-level gate first. alice still keeps Read/Describe on the topic — only the produce path is affected.

3. What happens to a client with no SASL config at all, pointed at the SASL_PLAINTEXT listener?
bin/kafka-topics.sh --bootstrap-server localhost:9092 --list
Error while executing topic command : Timed out waiting for a node assignment. Call: listTopics
[...] ERROR org.apache.kafka.common.errors.TimeoutException: Timed out waiting for a node assignment. Call: listTopics

No authorization exception at all — the client can’t even complete the SASL handshake the broker expects, so it just hangs until its own request timeout gives up. There’s no anonymous fallback identity on a SASL_PLAINTEXT listener the way there is on plain PLAINTEXT; a missing SASL config looks like the cluster is unreachable, not like a permissions error.

4. Grant alice a prefixed ACL instead of a literal one — --resource-pattern-type prefixed --topic secure-.
bin/kafka-acls.sh --bootstrap-server localhost:9092 --command-config admin.properties \
  --add --allow-principal User:alice --operation Read --operation Write \
  --topic secure- --resource-pattern-type prefixed
Adding ACLs for resource `ResourcePattern(resourceType=TOPIC, name=secure-, patternType=PREFIXED)`:
 	(principal=User:alice, host=*, operation=READ, permissionType=ALLOW)
	(principal=User:alice, host=*, operation=WRITE, permissionType=ALLOW)

patternType=PREFIXED instead of LITERAL — this single grant matches every topic name starting with secure- (secure-orders, secure-payments, any future topic in that namespace) without a new grant per topic. Every ACL earlier in this lesson was LITERAL, matching one exact name only.

Next: how records survive broker restarts and disk failures — replication, ISR, and what retention actually deletes.

Frequently Asked Questions

What's the difference between SASL and ACLs?
SASL answers 'who are you' — a client authenticates with a mechanism like PLAIN, SCRAM, or Kerberos and the broker establishes a principal (a User: identity). ACLs answer 'what can that identity do' — a separate authorization layer that checks the authenticated principal against a list of allowed operations per resource. You can run SASL without ACLs (everyone authenticates but has full access) or ACLs without SASL (everyone gets the same default principal), but production clusters use both together.
Why did alice get a GroupAuthorizationException instead of a TopicAuthorizationException when consuming?
A consumer needs Read permission on two separate resource types: the topic it reads from, and the consumer group it joins (for offset tracking). alice already had topic-level Read; the error was specifically about the group resource, which had no ACL at all yet. Kafka checks and reports these independently, so the error names exactly which resource check failed.
Do I need to grant an ACL for every single operation, or is there a shortcut?
kafka-acls.sh accepts multiple --operation flags in one command, and some operations imply others in practice (Write implies the ability to produce, which requires Describe to fetch topic metadata first). But there's no wildcard 'give alice full access to this topic' beyond granting every operation you actually want — the additive-then-subtractive pattern (+@all then remove specific ones) that Redis ACLs use isn't how kafka-acls.sh works; each operation is granted individually.
Is PLAIN mechanism safe to use in production?
Only over an encrypted channel — PLAIN sends the password in cleartext within the SASL handshake, so production deployments pair SASL_PLAINTEXT's SASL/PLAIN with SASL_SSL instead (TLS-encrypted transport), or use SCRAM, which never sends the password itself. This lesson uses SASL_PLAINTEXT with no TLS specifically to keep every request inspectable — never do this over a real network.
What does User:ANONYMOUS being a super user mean for this setup?
In KRaft mode, the controller's internal self-registration traffic authenticates as ANONYMOUS over the (unauthenticated) CONTROLLER listener, separate from the client-facing SASL_PLAINTEXT listener alice and admin use. Granting ANONYMOUS super-user status here only affects that internal control-plane channel, not client access — every actual client request still goes through SASL authentication and ACL checks.