Kafka ACL Permission Matrix Viewer

Turn a list of Kafka ACL bindings into a principal-by-resource matrix, with denials kept separate because a DENY beats every ALLOW. Catches the consumer with no GROUP grant and the IDEMPOTENT_WRITE requirement that changed in 3.0.

Only used for the IDEMPOTENT_WRITE rule, which WRITE implies from 3.0 onward and did not before. The rest of the analysis is version independent.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Build matrix. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A consumer with no group ACL

READ on the topic but nothing on the group, so the consumer fails to join

Current ACLs for resource `ResourcePattern(resourceType=TOPIC, name=orders, patternType=LITERAL)`:
	(principal=User:app, host=*, operation=READ, permissionType=ALLOW)

No host restriction

An ACL allowing any host, which is the default and rarely reconsidered

Current ACLs for resource `ResourcePattern(resourceType=TOPIC, name=events, patternType=PREFIXED)`:
	(principal=User:svc, host=*, operation=WRITE, permissionType=ALLOW)

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Granting topic READ without group READ

    A consumer needs READ on the topic AND READ on its consumer group. With only the first it fails to join, with an error about the group.

    Instead:Grant both.

  2. Leaving host as *

    The default allows the principal from anywhere. It is rarely reconsidered and is the easiest ACL tightening available.

    Instead:Restrict host where the client network is known.

  3. Expecting DENY to be a filter

    In Kafka a DENY always wins over an ALLOW for the same resource, exactly like IAM. It is not a precedence hint.

    Instead:Use DENY for guardrails, and scope ALLOW properly.

What the authorizer would actually allow

A list of bindings is not a permission model. The matrix is, and two Kafka rules make reading the list directly misleading.

A DENY beats every ALLOW, however specific

Kafka's authorizer checks denials first and returns on the first match. There is no specificity ordering, so a broad DENY plus a narrow ALLOW does not carve out an exception, it just denies. That makes a DENY binding much blunter than it looks, and it is why this page lists denials separately from grants rather than merging them into one cell. Superusers named in super.users bypass the authorizer entirely, so they will not appear here at all: an empty matrix does not mean nobody has access.

A consumer needs the group as well as the topic

READ on the topic gets you fetches. Joining a group, committing offsets and reading committed offsets are all authorized against the GROUP resource, so a consumer without a GROUP READ binding authenticates successfully and then fails at the first poll with GroupAuthorizationException. It reads as a topic permission problem and is not one. This is the most common incomplete ACL set and the page reports it directly.

IDEMPOTENT_WRITE changed in 3.0

Before Kafka 3.0, enable.idempotence=true needed a separate IDEMPOTENT_WRITE grant on the CLUSTER resource, and without it the producer failed with ClusterAuthorizationException. From 3.0 onward, WRITE on any topic implies it. Both directions matter: on an older cluster the grant is missing and nothing in current documentation tells you to add it, and on a newer one the grant is a cluster-scoped permission that no longer does anything. Set the version above and the advice follows it.

Transactions need a resource nobody grants in advance

A transactional producer needs WRITE and DESCRIBE on the TRANSACTIONAL_ID resource on top of its topic grants. Nothing fails until transactional.id is set, at which point initTransactions() throws TransactionalIdAuthorizationException, so this is almost always discovered during the change that enables exactly-once rather than before it.

PREFIXED is the pattern worth using

A literal resource name of * covers every resource of that type, including ones that do not exist yet, and combined with CREATE it means a client can invent a topic and then read and write it. A PREFIXED pattern over a namespace you own keeps future topics in scope without covering somebody else's, which is almost always what the wildcard was reaching for.

What this reads, and what it cannot see

Paste kafka-acls --list output unmodified, or one binding per line as User:app ALLOW READ TOPIC:orders with an optional PREFIXED. A line it cannot parse is reported and skipped rather than guessed at, because a binding read wrongly would put a grant in the matrix that does not exist. It does not know your super.users, your allow.everyone.if.no.acl.found setting, or your principal builder, all three of which change the real answer.