Monedula
monedula-acl-rbac-converter docs menu

Safety guarantees

The guarantees monedula-acl-rbac makes: no mutation without confirmation, script-only deletion, no dropped ACLs, no widened scope, stale plans refused.

Concepts 3 of 4

4 min read

Updated 5 October 2026

Markdown

On this page

A permission that disappears breaks production; one that grows too wide opens it. Each guarantee below is enforced by the tool and closes one way for either to happen silently.

Nothing changes without confirmation

A command that changes external state does nothing unless you pass --confirm or type yes at an interactive prompt. Outside a terminal, --confirm is the only way. apply --dry-run is the explicit preview: it reads MDS, records every call it would make and writes nothing. Every other command only reads, or writes local files such as the run directory’s artifacts.

Deletion happens only through scripts

The tool never runs a destructive Kafka call itself. delete-acls and delete-deny-acls write a script that you can read, edit or abandon, and the matching rollback script at the same time; to automate deletion, your pipeline runs the generated script after review. delete-acls also needs --i-understand-this-is-destructive before it writes anything.

apply only creates role bindings, and the tool never deletes or modifies them: you roll back apply with the Confluent CLI or the MDS API, as Recover from a failed run shows.

No ACL is silently dropped

Every input ACL appears in exactly one list of the plan, bindings, unmapped or rejected, with a reason for each one not converted (How ACLs map to RBAC roles). plan exits 3 until you resolve or accept the unconverted ones, and a role that does not grant every operation of its source ACLs raises a PARTIAL_RULE_COVERAGE warning.

Scope is never widened

A binding keeps the resource pattern of the ACLs it replaces, as the mapping rules describe. An ACL that could only be converted by widening it, such as one limited to a host, is left unmapped instead.

Stale plans are refused

Every artifact that later steps trust is fingerprinted:

  • plan writes plan.json.sha256 next to plan.json. apply, verify, delete-acls and delete-deny-acls check it and refuse a plan that changed since, with exit code 5.
  • The generated deletion scripts hash plan.json again when they run, and refuse to run on a mismatch. They fail closed: if plan.json cannot be hashed at all, because neither sha256sum nor shasum exists or the file is not readable there, they refuse too. The only escape hatch is MONEDULA_SKIP_PLAN_HASH_CHECK=1, for a script you run on purpose where its run directory is not present, such as inside a broker container.
  • plan stamps the SHA-256 of acls.json into the plan. The delete commands select the ACLs to remove from acls.json by id, so they refuse an acls.json that changed after planning.
  • verify stamps the plan’s SHA-256 into verify.json. The delete commands refuse a verify.json that checked a different plan, so an old verification cannot vouch for a regenerated plan: after a plan change, apply and verify again.

To apply a plan you edited by hand, plan --revalidate checks its structure and refreshes the checksum. It does not run the mapping or the DENY analysis again, so the new checksum vouches only for the plan you reviewed (Plan and review role bindings).

Effective access, verified before deletion

By default, verify asks MDS, for each source ACL, whether the original access is now granted, so a binding that exists but grants nothing shows up as EFFECTIVE_MISSING (Verify effective access).

delete-acls only deletes source ACLs whose result is EFFECTIVE_OK, or BINDING_EXISTS when verify.json comes from --mode bindings-exist, which only checks that the binding rows exist. Results that MDS could not answer, EFFECTIVE_UNKNOWN, block deletion too, unless you ran verify --accept-unknown-verify for an MDS that lacks the lookup endpoint. That override is opt-in, recorded in the detail field of every result it changed, and never weakens the block on EFFECTIVE_MISSING.

DENY ACLs get extra care

DENY ACLs are never converted. Removing one can grant access, so it takes a separate command, delete-deny-acls, which needs --i-understand-this-may-grant-access and a target principal, and refuses unless every verify result is confirmed. Its script checks each DENY against MDS again at the moment it runs, and skips a DENY whose removal would now grant access, with no override. DENY ACLs for a wildcard principal are never removable by the tool, and the script never puts credentials on a command line or in the script itself (Remove DENY ACLs).

One apply per run directory

apply takes a lock file in its run directory, so two apply runs against that directory cannot race; a stale lock left by a dead process must be cleared with --force-unlock. The lock covers only apply. It does not coordinate who runs the generated scripts, or two operators using different run directories against the same MDS: plan for one operator per cluster during a migration window.