Kafka ACL Generator
The kafka-acls.sh commands an activity really
needs, with the reason for each grant above it. The grants people miss are on
resource types they were not thinking about.
grant-acls.sh
updates as you type Common mistakes
These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
Granting on the topic and forgetting the group
A consumer needs READ on the topic AND READ on its consumer group. With only the first it fails to join, and the error names the group.
Instead:Grant both. A producer with transactions also needs WRITE on the transactional id.
Using LITERAL where PREFIXED was meant
A literal ACL covers exactly one name. A service writing to a topic per tenant needs a prefixed pattern or an ACL per topic forever.
Instead:Use PREFIXED for anything with a naming convention.
Leaving host as *
The default permits the principal from anywhere on the network. It is the cheapest tightening available and is almost never applied.
Instead:Restrict host where the client network is known.
Why a Kafka ACL is almost never one grant
Every activity touches more resource types than it sounds like, and the authorization error names the resource the client tried rather than the permission it was missing.
A producer needs DESCRIBE, not just WRITE
The producer fetches topic metadata before it can send anything, so WRITE alone fails at startup with an authorization error naming the topic. That reads as though the topic does not exist, which sends people to check the topic list rather than the ACL. Both operations, always.
A consumer needs the group, and the group is a separate resource type
READ on the topic is not enough: the consumer group is its own resource, and a consumer without READ on it fails when joining the group. This is the single most common Kafka ACL mistake. The error mentions the group id in a way that looks like a configuration typo, so the fix people try first is changing group.id, which creates a new group with the same problem.
A transactional producer needs a third resource type
TransactionalId is neither a topic nor a group, and it needs WRITE and DESCRIBE. Without it initTransactions blocks and then fails, after the producer has already connected successfully, which makes it look like a broker or a network problem rather than a permission. Nothing in a topic-shaped mental model of Kafka authorization includes this.
Streams needs a prefix on its own application.id
A Streams application creates changelog and repartition topics at runtime, named after its application.id, so it needs CREATE, WRITE, READ and DELETE on that prefix rather than on a topic that exists. That makes application.id a security boundary as well as an identity: a generic one grants access to a prefix another team may already be using, and a specific one does not.
Two things that look like security and are not
Restricting by --allow-host is weaker than it appears: a request arriving through a load balancer, a proxy or a VPC endpoint presents that hop's address, so the rule either blocks legitimate traffic or matches something unintended. And --deny-principal wins over every allow, which makes an accidental deny very hard to diagnose, because the principal has an allow ACL that visibly exists and does not work. Grant narrowly rather than denying broadly. Neither is used by this generator.
What this cannot see
It does not know your cluster's authorizer, whether allow.everyone.if.no.acl.found is set, or what the principal already has. It cannot tell you whether your mTLS principal mapping shortens the distinguished name the way your ACL assumes, which is where mTLS authorization usually goes wrong. Run the --list command at the end of the output to see what was actually granted, and treat that rather than this page as the truth.