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.
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
AllACL is expanded into the operations it implies for its resource type, for exampleRead,Write,Create,Delete,Alter,Describe,DescribeConfigsandAlterConfigson a topic. - On Allow groups, operations that imply others are expanded:
Read,Write,DeleteandAlterimplyDescribe, andAlterConfigsimpliesDescribeConfigs. A loneWriteACL therefore behaves likeWriteandDescribe. Deny groups are not expanded, because denyingWritedoes not denyDescribe.
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 isCREATE, orSKIP_EXISTSwhen 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_MATCHmeans no rule covers the group’s operations, as for a loneCreateon the cluster.HOST_RESTRICTEDmeans the ACL is limited to a host.CONFLICTING_EXISTING_BINDINGmeans an existing binding gives the principal the same role on other resources.rejected: never converted. DENY ACLs are rejected withDENY_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.
Related
- The rules show command reference and the plan.v1.json format reference.
- Our article From Kafka ACLs to Confluent RBAC explains why we built the tool around a reviewable workflow.