---
title: "Write advanced ACL rules"
description: "Use KafkaAccessPolicy for prefixed patterns, shared consumer groups, host restrictions and rules that do not belong to a single topic."
url: https://monedula.dev/flock/docs/gitops/how-to/write-advanced-acl-rules/
---
A `KafkaAccessPolicy` holds raw Kafka ACL rules for access that a topic's `access` block cannot express: prefixed patterns, shared consumer groups, transactional IDs, the cluster resource, `Deny` rules and host restrictions.

## Before you start

- A connecting principal that may manage ACLs, which takes `Describe` and `Alter` on the `Cluster` resource, or a super-user.
- On Confluent Cloud, write principals with the service account's numeric ID, such as `User:9207877` ([Supported platforms](/flock/docs/gitops/concepts/supported-platforms/)).

## Steps

### 1. Write the policy

This policy lets `User:svc-billing` write with the transactional ID `billing-tx` and read with the consumer group `billing-cg`. Save it as `manifests/policy.yaml`:

```yaml
apiVersion: gitops.monedula.dev/v1alpha1
kind: KafkaAccessPolicy
metadata:
  name: billing-access
spec:
  clusterRef:
    name: shared
  rules:
    - principal: "User:svc-billing"
      permission: Allow
      host: "*"
      resource:
        type: transactionalId
        name: billing-tx
        patternType: literal
      operations:
        - Write
        - Describe
    - principal: "User:svc-billing"
      permission: Allow
      host: "*"
      resource:
        type: group
        name: billing-cg
        patternType: literal
      operations:
        - Read
```

Each rule grants its `operations` on one resource pattern to one `principal`. `resource.type` is `topic`, `group`, `cluster`, `transactionalId` or `delegationToken`; `resource.patternType` is `literal` (default) or `prefixed`; `permission` is `Allow` (default) or `Deny`; and `host` (default `*`) is matched against the client IP as the broker sees it. `operations` lists at least one of `Read`, `Write`, `Create`, `Delete`, `Alter`, `Describe`, `ClusterAction`, `DescribeConfigs`, `AlterConfigs`, `IdempotentWrite` and `All`.

Grant shared consumer groups in a policy, as this one does for `billing-cg`, instead of in a topic's `access` block.

### 2. Add a prefixed, host-restricted rule

A prefixed rule covers every resource whose name starts with the prefix. Add this rule to `spec.rules` to let `User:svc-billing` read, write and describe every topic whose name starts with `billing.`, but only from the host `10.0.0.15`:

```yaml
- principal: User:svc-billing
  host: "10.0.0.15"
  resource:
    type: topic
    name: billing.
    patternType: prefixed
  operations: [Read, Write, Describe]
```

A `Deny` rule must not contradict an `Allow`. The same principal, host, resource and operation granted `Allow` by one resource, a topic's `access` block included, and `Deny` by another is a conflict. The CLI rejects the manifests when it loads them and exits 2, naming the conflicting rule. With the operator, the admission webhook, when enabled, rejects the conflicting policy, and the reconciler always marks every resource party to the conflict with the `ACLConflict` condition and leaves the contested rule out of what it applies.

### 3. Apply the policy

<Tabs group="write-advanced-acl-rules-mode" labels={['CLI', 'Operator']} label="Where you run it">

```bash
monedula-gitops apply -f ./manifests --cluster-config-file ./cluster.yaml
```

```bash
kubectl apply -f manifests/
```

</Tabs>

Add `--dry-run` to the CLI command to preview the ACLs it would create. With the operator, a namespace that a [tenancy policy](/flock/docs/gitops/how-to/enforce-tenancy/) restricts to topic prefixes may only declare `topic` and `group` rules that match them.

### 4. Change or remove rules

When you narrow a rule that stays declared, for example drop an operation, the ACLs it no longer needs become `PruneDisabled` candidates until you [opt in to pruning](/flock/docs/gitops/concepts/safety-gates/) with `apply --prune` or `spec.prune: true` on the policy. When you remove a rule and no other manifest declares that principal on that resource pattern, its ACLs leave the managed scope and stay in Kafka, unreported. Changing a rule's `resource.name` or `patternType` leaves the old pattern behind the same way. To revoke that access, delete the leftover ACLs outside monedula-gitops, for example with Kafka's `kafka-acls` tool.

<Callout type="warning">
With the operator, a `KafkaAccessPolicy` defaults to `deletionPolicy: Delete`: with the annotation `gitops.monedula.dev/allow-delete: "true"`, deleting the resource deletes the ACLs it manages, except those 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>

## Check it worked

<Tabs group="write-advanced-acl-rules-check" labels={['CLI', 'Operator']} label="Where you run it">

```bash
monedula-gitops verify -f ./manifests --cluster-config-file ./cluster.yaml
```

```bash
kubectl get kafkaaccesspolicy billing-access -o jsonpath='{.status.conditions}'
```

</Tabs>

`verify` exits 0 when the live ACLs match the policy. With the operator, the `Ready` and `AccessPolicySynced` conditions have the status `True`. For the policy from step 1, the broker now has three ACLs for `User:svc-billing`: `Write` and `Describe` on the transactional ID `billing-tx`, and `Read` on the group `billing-cg`.

## Related

- The [KafkaAccessPolicy reference](/flock/docs/gitops/reference/resources/kafka-access-policy/) lists every field.
- [Topic-centric access](/flock/docs/gitops/concepts/topic-centric-access/) explains when to use a policy instead of a topic's `access` block.
- Runnable version: [scenarios/06-access-policy](https://github.com/monedula-dev/monedula-gitops/tree/main/scenarios/06-access-policy)