Monedula
monedula-acl-rbac-converter docs menu

How ACLs map to RBAC roles

Why Kafka ACLs and Confluent RBAC do not map one-to-one, how the default mapping rules work, and what ends up unmapped or rejected.

Concepts 2 of 4

5 min read

Updated 5 October 2026

Markdown

On this page

A Confluent RBAC role binding grants a predefined role, a bundle of operations, so translating ACLs means choosing roles. monedula-acl-rbac chooses them with explicit rules and records every choice in the plan.

What does not translate

Three properties of ACLs have no RBAC equivalent. A role always grants a set of operations, so an ACL for one operation needs a role that grants at least that operation, and possibly more. RBAC has no deny, so a DENY ACL cannot become a binding and its intent cannot be carried over. RBAC has no host-based scoping, so an ACL limited to one host could only be expressed by widening it to every host.

From ACL rows to groups

The planner first groups the ACL rows that share everything except the operation: the same principal, host, resource type, resource name, pattern type and permission. A consumer’s Read and Describe ACLs on one topic become one group with two operations. Each group remembers the IDs of its source ACLs, which is how every binding in the plan names the source_acl_ids it replaces.

Two expansions follow, both mirroring what the Kafka authorizer itself allows:

  • An All ACL is expanded into the operations it implies for its resource type, for example Read, Write, Create, Delete, Alter, Describe, DescribeConfigs and AlterConfigs on a topic.
  • On Allow groups, operations that imply others are expanded: Read, Write, Delete and Alter imply Describe, and AlterConfigs implies DescribeConfigs. A lone Write ACL therefore behaves like Write and Describe. Deny groups are not expanded, because denying Write does not deny Describe.

Rules choose the roles

A rule says which role a group gets. Its when clause matches a group by operations, resource type and permission; its then clause names the role:

rules:
  - when:
      operations: [Read, Describe]
      operations_mode: all   # default; alternatives: any
      resource_type: Topic
      permission_type: Allow
    then:
      role: DeveloperRead
      scope_template: kafka-cluster

With operations_mode: all, the default, the group must hold every listed operation; with any, one is enough. The rules are tried in order, and the planner selects them greedily: it takes the first rule that matches and covers at least one operation still uncovered, marks the operations that rule accounts for, its listed operations and the ones they imply, as covered, and continues while operations remain. Each distinct role it selects becomes one binding.

This is why a principal that both reads and writes a topic gets two bindings. No single Developer role grants both, so DeveloperRead covers Read and Describe, and DeveloperWrite then covers Write. RBAC bindings are additive, so the two together reproduce the original access. An All group stops after one rule: the broad All rules come first in the default list and cover every operation the group holds, so the group becomes a single ResourceOwner or SystemAdmin binding.

The default rules

The tool ships these rules, in this order. monedula-acl-rbac rules show prints them as YAML.

Source ACLs Mode Role
All on Cluster any SystemAdmin
All on Topic, Group or TransactionalId any ResourceOwner
Read and Describe on Topic all DeveloperRead
Write and Describe on Topic all DeveloperWrite
Read and Describe on Group all DeveloperRead
Write and Describe on TransactionalId all DeveloperWrite
DescribeConfigs or AlterConfigs on Topic any ResourceOwner
IdempotentWrite on Cluster any DeveloperWrite

Every default rule matches Allow ACLs only. Topic configuration access maps to ResourceOwner because RBAC cannot separate configuration rights. A superuser from CFK manifests arrives as All on the cluster and becomes SystemAdmin.

Overriding the rules

A rules.yaml passed to plan --rules is merged into the defaults key by key. The key is the shape of the when clause: its operations, mode, resource type and permission. A rule with the same shape as a default replaces it in place, keeping its position. A rule with a new shape is added after the defaults, so it applies only where no default rule already covered the operations. monedula-acl-rbac rules show > rules.default.yaml gives you a starting point, and init writes a rules.yaml with a commented example that maps All on the cluster to ClusterAdmin instead of SystemAdmin.

Rules are checked when they are loaded: an unknown operation, resource type, mode or permission is an error, so a typo such as read for Read fails loudly instead of leaving ACLs unmapped.

Scope is never widened

A binding keeps the resource pattern of its source ACLs exactly. A PREFIXED ACL on Topic:foo produces a binding on the prefix foo, never a cluster-wide grant, and a LITERAL ACL a literal binding. The cluster scope comes from scopes.yaml and the resource type: bindings on Kafka resources are scoped to kafka_cluster, and bindings on Schema Registry subjects to schema_registry_cluster.

Principals

A principal passes through unchanged unless principals.yaml maps it, for example to the name MDS knows for a TLS distinguished name or an LDAP group. Config files describes the mapping, the fail fallback and the MTLS_DN_PASS_THROUGH warning.

Where every ACL ends up

Every input ACL lands in exactly one list of the plan, so none is silently dropped:

  • bindings: covered by a rule. The action is CREATE, or SKIP_EXISTS when an existing binding found during extraction, for example in CFK manifests, already grants it.
  • unmapped: not convertible as it stands, with a reason. NO_RULE_MATCH means no rule covers the group’s operations, as for a lone Create on the cluster. HOST_RESTRICTED means the ACL is limited to a host. CONFLICTING_EXISTING_BINDING means an existing binding gives the principal the same role on other resources.
  • rejected: never converted. DENY ACLs are rejected with DENY_PERMISSION.

When the selected roles still leave an operation ungranted, for example a Delete that no rule covers next to Read and Describe, the plan records a PARTIAL_RULE_COVERAGE warning: that grant is not converted, and the warning says so. plan exits 3 while anything is unmapped or rejected; Plan and review role bindings shows how to resolve or accept it.

For each DENY, the plan also records a DENY analysis, SAFE_TO_REMOVE, WOULD_GRANT_ACCESS or UNKNOWN, which decides what Remove DENY ACLs may remove later.