Authorization with ACLs
Authentication tells Kafka who a client is; authorization decides what that client is allowed to do. Once you have SASL or mTLS in place, every connection carries a verified identity (a principal), and Kafka can enforce fine-grained access rules through Access Control Lists (ACLs). Getting this right is what stops a misconfigured consumer from reading payroll data or a rogue service from deleting a production topic. This page covers the authorizer, the ACL model, enabling the built-in StandardAuthorizer, and managing rules with kafka-acls.sh.
The authorizer
The authorizer is a pluggable component on the broker that intercepts every request and answers a single question: is this principal allowed to perform this operation on this resource? If no plugin is configured, Kafka runs with no authorization at all — any authenticated (or anonymous) client can do anything.
For KRaft clusters, the built-in implementation is org.apache.kafka.metadata.authorizer.StandardAuthorizer. It stores ACLs directly in the cluster metadata log managed by the controllers, so there is no ZooKeeper dependency. (The older AclAuthorizer is for ZooKeeper-based clusters and is being retired.)
Without an authorizer configured, Kafka is effectively wide open to any client that can connect. Authorization is opt-in — never assume it is on.
The ACL model
An ACL is a binding of five things:
| Element | Meaning | Examples |
|---|---|---|
| Principal | The authenticated identity | User:alice, User:orders-service |
| Operation | The action being attempted | Read, Write, Describe, Create, Delete, Alter, DescribeConfigs, All |
| Resource type | What is being accessed | Topic, Group, Cluster, TransactionalId |
| Resource name | The specific instance | orders, payments-*, * |
| Permission type | Allow or deny | Allow, Deny |
Resource names support a pattern type: LITERAL matches an exact name, while PREFIXED matches any name starting with the given string (e.g. a prefixed ACL on payments- covers payments-eu and payments-us).
The decision logic is straightforward but important to internalize:
1. If any matching DENY ACL exists -> denied
2. Else if any matching ALLOW ACL exists -> allowed
3. Else (no matching ACL) -> denied
Deny always wins over allow, and the default in the absence of any rule is to refuse. This makes ACLs fail-closed, which is what you want for security.
Enabling the StandardAuthorizer
Set the authorizer class in server.properties (or the controller/broker config) and add a super-user so you can still administer the cluster after locking it down. Without a super-user you can paint yourself into a corner where no one can manage ACLs.
# Enable the KRaft-native authorizer
authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
# Principals that bypass all ACL checks (semicolon-separated)
super.users=User:admin;User:kafka-controller
# Optional: allow access to a resource when NO ACL exists for it.
# Leave false (the default) for fail-closed security.
allow.everyone.if.no.acl.found=false
The principal name comes from how the client authenticated. With SASL the principal is the SASL username (e.g. User:alice); with mTLS it is derived from the certificate’s distinguished name unless you remap it with ssl.principal.mapping.rules.
Keep
allow.everyone.if.no.acl.found=false. Setting it totruemeans any resource without an explicit ACL is open to everyone — a common foot-gun during migrations.
Managing ACLs with kafka-acls.sh
The kafka-acls.sh CLI is the primary tool for creating, listing, and removing ACLs. It connects to the cluster with --bootstrap-server and (because adding ACLs is itself a privileged operation) must authenticate as a super-user via --command-config.
Granting a producer write access
A producer needs Write and Describe on the target topic. If it uses transactions or an idempotent producer, it also needs Write/Describe on the TransactionalId and IdempotentWrite on the cluster.
kafka-acls.sh --bootstrap-server broker:9093 \
--command-config admin.properties \
--add \
--allow-principal User:orders-service \
--operation Write --operation Describe \
--topic orders
Output:
Adding ACLs for resource `ResourcePattern(resourceType=TOPIC, name=orders, patternType=LITERAL)`:
(principal=User:orders-service, host=*, operation=WRITE, permissionType=ALLOW)
(principal=User:orders-service, host=*, operation=DESCRIBE, permissionType=ALLOW)
Current ACLs for resource `Topic:LITERAL:orders`:
(principal=User:orders-service, host=*, operation=DESCRIBE, permissionType=ALLOW)
(principal=User:orders-service, host=*, operation=WRITE, permissionType=ALLOW)
Granting a consumer read access
A consumer needs Read and Describe on the topic and Read on its consumer group (so it can commit offsets and join the group). You can express both in a single command:
kafka-acls.sh --bootstrap-server broker:9093 \
--command-config admin.properties \
--add \
--allow-principal User:reporting-service \
--operation Read --operation Describe \
--topic orders \
--group reporting-group
For teams that own a family of topics, a prefixed ACL avoids re-granting on every new topic:
kafka-acls.sh --bootstrap-server broker:9093 \
--command-config admin.properties \
--add \
--allow-principal User:payments-service \
--operation Write --operation Describe \
--resource-pattern-type prefixed \
--topic payments-
Listing and removing ACLs
# List every ACL for a given principal
kafka-acls.sh --bootstrap-server broker:9093 \
--command-config admin.properties \
--list --principal User:orders-service
# Remove the consumer's read access to the topic
kafka-acls.sh --bootstrap-server broker:9093 \
--command-config admin.properties \
--remove \
--allow-principal User:reporting-service \
--operation Read \
--topic orders
The --remove command prompts for confirmation unless you pass --force.
Common operations by client role
| Client role | Resource | Operations |
|---|---|---|
| Producer (plain) | Topic | Write, Describe |
| Producer (transactional) | Topic, TransactionalId, Cluster | Write, Describe, IdempotentWrite |
| Consumer | Topic, Group | Read, Describe |
| Admin / operator | Cluster, Topic | Create, Alter, Delete, DescribeConfigs |
| Connect/Streams app | Topic, Group, TransactionalId | Read, Write, Create, Describe |
Best Practices
- Run with
allow.everyone.if.no.acl.found=falseso the cluster is fail-closed; grant access explicitly and incrementally. - Use one principal per service rather than a shared account, so ACLs map cleanly to ownership and audits are meaningful.
- Prefer prefixed ACLs for topic families owned by a single team to cut down on per-topic churn, but keep prefixes specific.
- Grant the least privilege that works — producers rarely need
Read, consumers rarely needWrite, and almost nothing needsAll. - Reserve
DenyACLs for explicit carve-outs; remember deny always overrides allow and can silently block a service if scoped too broadly. - Keep
super.usersto a tiny, tightly controlled set (ideally automation/break-glass accounts) and never use them for everyday workloads. - Version-control your ACL definitions and apply them through automation so access is reviewable and reproducible across environments.