Monedula
monedula-acl-rbac-converter docs menu

Convert ACLs in one command

Use convert to turn an ACL dump into Confluent RBAC role bindings as a shell script, without a run directory or MDS access.

Tutorials 2 of 2

4 min read

Updated 5 October 2026

Markdown

On this page

Turn a saved kafka-acls.sh --list output into a script of Confluent CLI role-binding commands with one convert call. No cluster or MDS access is needed: convert only writes the script.

What you need

1. Get the example files

git clone https://github.com/monedula-dev/monedula-acl-rbac-converter
cd monedula-acl-rbac-converter/examples/text-dump

The text-dump example holds the ACLs of a small microservices platform in acls.txt, and a scopes.yaml. Run the rest of the commands from this directory.

2. Look at the ACLs

acls.txt is in the format that kafka-acls.sh --list prints: the ACLs grouped by resource, one line per principal and operation.

Current ACLs for resource `ResourcePattern(resourceType=TOPIC, name=orders, patternType=LITERAL)`:
 	(principal=User:svc-orders-consumer, host=*, operation=READ, permissionType=ALLOW)
 	(principal=User:svc-orders-consumer, host=*, operation=DESCRIBE, permissionType=ALLOW)
 	(principal=User:svc-orders-owner, host=*, operation=ALL, permissionType=ALLOW)
 	(principal=User:svc-orders-relay, host=*, operation=READ, permissionType=ALLOW)
 	(principal=User:svc-orders-relay, host=*, operation=WRITE, permissionType=ALLOW)
 	(principal=User:svc-orders-relay, host=*, operation=DESCRIBE, permissionType=ALLOW)

Current ACLs for resource `ResourcePattern(resourceType=GROUP, name=orders-consumers, patternType=LITERAL)`:
 	(principal=User:svc-orders-consumer, host=*, operation=READ, permissionType=ALLOW)
 	(principal=User:svc-orders-consumer, host=*, operation=DESCRIBE, permissionType=ALLOW)

Current ACLs for resource `ResourcePattern(resourceType=TOPIC, name=payments, patternType=LITERAL)`:
 	(principal=User:svc-payments-producer, host=*, operation=WRITE, permissionType=ALLOW)
 	(principal=User:svc-payments-producer, host=*, operation=DESCRIBE, permissionType=ALLOW)

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

Current ACLs for resource `ResourcePattern(resourceType=TOPIC, name=pii-events, patternType=LITERAL)`:
 	(principal=User:legacy-importer, host=*, operation=READ, permissionType=DENY)

On your own cluster, save the same format with:

kafka-acls.sh --list --bootstrap-server $BROKER > acls.txt

3. Set the cluster ID

Every role binding names the Kafka cluster it applies to. The example’s scopes.yaml sets it:

kafka_cluster: lkc-demo-1

lkc-demo-1 is a placeholder, which is fine here because nothing is applied. For bindings you want to use, the value must be the ID that MDS knows your cluster by; Find the cluster IDs for scopes.yaml shows where to get it.

4. Convert

monedula-acl-rbac convert --from text --input acls.txt --scopes scopes.yaml > bindings.sh

convert plans the role bindings with the built-in mapping rules and prints them as a shell script. --from text names the input format, and no run directory is created.

5. Read the script

cat bindings.sh

The script starts with a header that counts the bindings, followed by one confluent iam rbac role-binding create command per binding. These are its first lines:

#!/usr/bin/env bash
set -euo pipefail

# Generated by monedula-acl-rbac emit --format script
# Bindings: 7 to create, 0 to skip (already exist).

confluent iam rbac role-binding create \
  --principal 'User:svc-payments-producer' \
  --role 'DeveloperWrite' \
  --kafka-cluster 'lkc-demo-1' \
  --resource 'Topic:payments'  # binding rb-3713a985833c (source_acl_ids=[9 10])

The trailing comment carries the binding’s ID and the source_acl_ids of the ACLs it replaces, numbered in the order they appear in acls.txt. The prefixed ACL becomes a prefixed binding, marked with --prefix:

confluent iam rbac role-binding create \
  --principal 'User:svc-analytics' \
  --role 'DeveloperRead' \
  --kafka-cluster 'lkc-demo-1' \
  --resource 'Topic:events.' --prefix  # binding rb-b6fac2ae9aa0 (source_acl_ids=[11 12])

The example’s expected/script.sh holds the full result; your bindings.sh has the same commands. It lacks only the # Plan: header line, because convert keeps the plan in memory instead of writing plan.json.

6. See how the ACLs mapped

Thirteen ACLs became seven bindings:

Principal ACLs Role binding
User:svc-orders-consumer Read and Describe on topic orders DeveloperRead on topic orders
User:svc-orders-consumer Read and Describe on group orders-consumers DeveloperRead on group orders-consumers
User:svc-payments-producer Write and Describe on topic payments DeveloperWrite on topic payments
User:svc-analytics Read and Describe on prefix events. DeveloperRead on prefix events.
User:svc-orders-owner All on topic orders ResourceOwner on topic orders
User:svc-orders-relay Read, Write and Describe on topic orders DeveloperRead and DeveloperWrite on topic orders
User:legacy-importer Deny Read on topic pii-events none

The relay gets two bindings and the DENY none; How ACLs map to RBAC roles explains both. convert prints no report, so the rejected DENY appears nowhere in its output. The example’s ./run.sh runs the step-by-step pipeline, extract, plan and emit, whose report.txt lists it.

7. Render the bindings for Kubernetes

The same plan can come out in another shape. Ask for Confluent for Kubernetes manifests instead of a script:

monedula-acl-rbac convert --from text --input acls.txt --scopes scopes.yaml --format cfk > bindings.yaml

bindings.yaml holds one ConfluentRolebinding resource per binding, named after the binding’s ID; --format mds-curl gives curl commands against the MDS REST API instead.

Next steps