---
title: "Manage Confluent RBAC role bindings"
description: "Declare Confluent MDS role bindings with KafkaRoleBinding, or let topic access compile to role bindings on RBAC-backed clusters."
url: https://monedula.dev/flock/docs/gitops/how-to/manage-confluent-rbac/
---

## Before you start

- Confluent Platform with the Metadata Service (MDS). Role bindings do not work on Apache Kafka or Confluent Cloud, as [Supported platforms and versions](/flock/docs/gitops/concepts/supported-platforms/) explains.
- An MDS user that may manage role bindings at the scopes you bind, typically one with the `UserAdmin` or `SecurityAdmin` role, depending on your Confluent Platform version.
- The Kafka cluster ID, plus the Schema Registry, Connect or ksqlDB cluster ID if you bind roles on those scopes.

## Steps

### 1. Connect to MDS

Add an `authorization.mds` block to the `KafkaCluster`. This cluster config for the CLI connects to the brokers with SCRAM over TLS and to MDS with HTTP basic auth. Save it as `clusters/prod.yaml`:

```yaml
apiVersion: gitops.monedula.dev/v1alpha1
kind: KafkaCluster
metadata:
  name: prod
spec:
  bootstrapServers: broker1:9093
  tls:
    enabled: true
  auth:
    mechanism: SCRAM-SHA-512
    scram:
      username: { valueFrom: { env: KAFKA_USER } }
      password: { valueFrom: { env: KAFKA_PASSWORD } }
  authorization:
    mds:
      endpoint: "https://mds.example.com:8090"
      auth:
        type: basic
        username: { valueFrom: { env: MDS_USER } }
        password: { valueFrom: { env: MDS_PASSWORD } }
      clusters:
        kafkaCluster: "abc123"
```

`endpoint` and `clusters.kafkaCluster` (the Kafka cluster ID) are required. [Connect to a secured Kafka cluster](/flock/docs/gitops/how-to/connect-to-a-secured-cluster/) covers the other auth types and the cluster IDs for other scopes. With the operator, read the MDS credentials from a Secret, as in the `accessBackends` example below.

### 2. Declare the role binding

This binding grants `User:svc-checkout` the `DeveloperWrite` role on the topic `payments.orders`. Save it as `rbac/checkout-writer.yaml`:

```yaml
apiVersion: gitops.monedula.dev/v1alpha1
kind: KafkaRoleBinding
metadata:
  name: checkout-writer
spec:
  clusterRef:
    name: prod
  principal: User:svc-checkout
  role: DeveloperWrite
  scope:
    type: kafka
  resources:
    - type: Topic
      name: payments.orders
      patternType: literal
```

- `principal` takes the form `User:<name>` or `Group:<name>`.
- `role` is a Confluent role. Cluster-scoped roles, such as `SystemAdmin`, `ClusterAdmin`, `UserAdmin`, `Operator`, `SecurityAdmin` and `AuditAdmin`, bind to the whole scope and take no `resources`. Resource-scoped roles, `ResourceOwner`, `DeveloperRead`, `DeveloperWrite` and `DeveloperManage`, need at least one. An unknown role is accepted with a warning.
- `scope.type` is `kafka`, `schema-registry`, `connect` or `ksql`, and picks the matching cluster ID from `authorization.mds.clusters`.
- Each entry in `resources` has a `type`, a `name` and a `patternType`, `literal` by default or `prefixed`. The `kafka` scope takes `Topic`, `Group`, `Cluster` and `TransactionalId`; `schema-registry` takes `Subject` and `Cluster`; `connect` takes `Connector` and `Cluster`; `ksql` takes `KsqlCluster` and `Cluster`.

Use `patternType: prefixed` to bind a family of resources, such as every topic whose name starts with `payments.`. `clusterRef`, `principal`, `role` and `scope.type` cannot change after creation; `resources` can.

### 3. Preview and apply

<Tabs group="manage-confluent-rbac-mode" labels={['CLI', 'Operator']} label="Where you run it">

```bash
# validate
monedula-gitops validate -f ./rbac --cluster-config-file ./clusters/prod.yaml

# preview
monedula-gitops diff -f ./rbac --cluster-config-file ./clusters/prod.yaml

# apply (role binding removal also requires --prune)
monedula-gitops apply -f ./rbac --cluster-config-file ./clusters/prod.yaml
```

```bash
kubectl apply -f rbac/
```

</Tabs>

The plan shows an `AddRoleBinding` operation for the new binding. You can load `KafkaRoleBinding` manifests together with topics and access policies in the same `-f` run.

### 4. Remove a role binding

<Callout type="warning">
The prune scope for role bindings is keyed on principal, role and scope, not on the resource. Once your manifests declare `DeveloperWrite` for `User:svc-checkout` on the `kafka` scope, every live `DeveloperWrite` binding of that principal on that scope is in scope, including bindings someone made by hand on other resources, and pruning deletes the ones your manifests do not list.

With the operator, deleting a `KafkaRoleBinding` removes its bindings from MDS without an `allow-delete` annotation, except bindings another live resource still declares; set `deletionPolicy: Orphan` to keep them. [Safety gates](/flock/docs/gitops/concepts/safety-gates/) lists the deletion policy of every kind.
</Callout>

To remove a binding that is in scope, such as a resource you dropped from `resources`, apply with `--prune` (CLI) or set `spec.prune: true` on the `KafkaRoleBinding` (operator); until then it is `PruneDisabled` and stays in MDS. Bindings compiled from topic access share this scope. A binding whose principal, role and scope no manifest declares is out of scope: the CLI neither reports nor removes it, and with the operator you delete the `KafkaRoleBinding` resource.

## Compile topic access to role bindings

Instead of writing producer and consumer bindings by hand, you can let the topics' `access` blocks compile to them: add `accessBackends` next to `mds` in the cluster's `spec.authorization`. This block, from a `KafkaCluster` for the operator, reads the MDS credentials from a Secret and emits both ACLs and role bindings:

```yaml
authorization:
  mds:
    endpoint: https://kafka-mds.kafka.svc:8090
    auth:
      type: basic
      username:
        valueFrom:
          secretKeyRef:
            name: kafka-admin-credentials
            key: username
      password:
        valueFrom:
          secretKeyRef:
            name: kafka-admin-credentials
            key: password
    clusters:
      kafkaCluster: abc123
  accessBackends:
    - acl
    - rbac
```

`rbac` requires `authorization.mds`:

| `accessBackends` | ACLs | MDS role bindings |
| --- | --- | --- |
| unset or `[acl]` (default) | yes | no |
| `[rbac]` | no | yes |
| `[acl, rbac]` | yes | yes |

Producers become `DeveloperWrite` on the topic; consumers become `DeveloperRead` on the topic and on the group. RBAC roles are coarse and have no host, so an entry with a non-`*` host or a custom operation list still gets its binding, without that refinement: the topic gets the condition `RBACCoarsened=True` and the CLI warns. For host-scoped grants on an RBAC-backed cluster, use a [KafkaAccessPolicy](/flock/docs/gitops/how-to/write-advanced-acl-rules/).

With `[acl, rbac]`, ACLs and role bindings authorize independently, so a principal keeps access through whichever backend still grants it. Use the dual setting as a temporary step while you move a cluster from ACLs to RBAC or back. Before you count access as revoked, check that `diff` shows both an ACL removal and a role-binding removal.

Turning a backend off, for example from `[acl, rbac]` to `[acl]`, leaves that backend's existing grants live. Topic access stops declaring them, so unless another manifest keeps them in the managed scope, neither runtime reports or prunes them. To remove them, delete them outside monedula-gitops, with Kafka's `kafka-acls` tool for ACLs or the Confluent CLI for role bindings.

## Check it worked

<Tabs group="manage-confluent-rbac-check" labels={['CLI', 'Operator']} label="Where you run it">

```bash
monedula-gitops verify -f ./rbac --cluster-config-file ./clusters/prod.yaml
```

```bash
kubectl wait kafkarolebinding/checkout-writer --for=condition=RoleBindingSynced --timeout=120s
```

</Tabs>

`verify` reads the current bindings from MDS, exits 0 and prints `No changes.` when they match the manifests. With the operator, the `KafkaRoleBinding` reports the conditions `Ready`, `RoleBindingSynced` and `MDSReachable` with the status `True`. A `KafkaTopic` on a cluster with the `rbac` backend reports `RoleBindingSynced` and `MDSReachable` for its derived bindings.

## Related

- The [KafkaRoleBinding reference](/flock/docs/gitops/reference/resources/kafka-role-binding/) lists every field, and the [KafkaCluster reference](/flock/docs/gitops/reference/resources/kafka-cluster/) the MDS connection.
- [Topic-centric access](/flock/docs/gitops/concepts/topic-centric-access/) explains the `access` block.
- Runnable version: [scenarios/22-rolebinding](https://github.com/monedula-dev/monedula-gitops/tree/main/scenarios/22-rolebinding)