Skip to content
Apache Kafka kf security 5 min read

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:

ElementMeaningExamples
PrincipalThe authenticated identityUser:alice, User:orders-service
OperationThe action being attemptedRead, Write, Describe, Create, Delete, Alter, DescribeConfigs, All
Resource typeWhat is being accessedTopic, Group, Cluster, TransactionalId
Resource nameThe specific instanceorders, payments-*, *
Permission typeAllow or denyAllow, 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 to true means 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 roleResourceOperations
Producer (plain)TopicWrite, Describe
Producer (transactional)Topic, TransactionalId, ClusterWrite, Describe, IdempotentWrite
ConsumerTopic, GroupRead, Describe
Admin / operatorCluster, TopicCreate, Alter, Delete, DescribeConfigs
Connect/Streams appTopic, Group, TransactionalIdRead, Write, Create, Describe

Best Practices

  • Run with allow.everyone.if.no.acl.found=false so 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 need Write, and almost nothing needs All.
  • Reserve Deny ACLs for explicit carve-outs; remember deny always overrides allow and can silently block a service if scoped too broadly.
  • Keep super.users to 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.
Last updated June 1, 2026
Was this helpful?