> For the complete documentation index, see [llms.txt](https://docs.pipekit.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pipekit.io/reference/cli/rbac.md).

# RBAC Policies

Publish, read, and roll back your organization's RBAC policy with the pipekit rbac commands.

{% hint style="warning" %}
This feature is in alpha. Behavior may change before general availability.
{% endhint %}

Your organization's access control is one YAML document. The `pipekit rbac` commands publish it, read it back, and list every version that has been published. For what the policy expresses, see [Concepts > Access Control](/concepts/access-control.md).

Publishing requires the `orgAdmin` role.

## Commands

| Command                                               | What it does                                          |
| ----------------------------------------------------- | ----------------------------------------------------- |
| `pipekit rbac publish -f policy.yaml`                 | Validate the document, then apply it as a new version |
| `pipekit rbac publish --dry-run -f policy.yaml`       | Validate and print the plan without changing anything |
| `pipekit rbac publish -f policy.yaml --git-sha <sha>` | Publish, recording the commit the document came from  |
| `pipekit rbac get`                                    | Print the active policy document to stdout            |
| `pipekit rbac get --version 3`                        | Print version 3's document to stdout                  |
| `pipekit rbac list`                                   | List every policy version, newest first               |

## Policy shape

Keys are camelCase. `schemaVersion` is required and must be `1`.

```yaml
schemaVersion: 1

orgBindings:
  - subject: idp:team:platform
    role: orgAdmin
  - subject: bot:rbac-ci
    role: orgAdmin

workspaces:
  - name: team-data
    targets:
      - cluster: cluster-dev
        namespace: data-dev
      - cluster: cluster-prod
        namespace: data-prod
    bindings:
      - subject: idp:team:data-engineers
        role: editor
      - subject: idp:team:data-leads
        role: workspaceAdmin

  - name: team-ml
    targets:
      - cluster: cluster-dev
        namespace: ml-dev
    bindings:
      - subject: user:ml-lead@example.com
        role: workspaceAdmin
```

A `subject` names who the grant applies to:

* `user:<email>` for a single Pipekit user
* `idp:team:<name>` for an identity provider group, matching the group name your IdP sends
* `bot:<name>` for an automation account

Whitespace around an `idp:team:` name is trimmed and Unicode is normalized, on both the name in your policy and the name your IdP sends, so the two cannot drift apart. A name that is empty after trimming is rejected, as is one too long to store: roughly 213 characters, fewer if the name contains characters that need escaping.

A `role` is one of `viewer`, `editor`, `workspaceAdmin`, or `orgAdmin`. `orgAdmin` is valid only under `orgBindings`, because it manages org-level entities that do not exist at workspace level.

Each `{cluster, namespace}` pair can be claimed by one workspace only, and namespaces must be written out in full. Wildcards are rejected.

## Publishing

```bash
pipekit rbac publish -f policy.yaml
```

The document is validated against your organization's live state, then applied. The command prints the checks it ran, the plan it applied, and the new version:

```
Validating...
✓ Schema, roles, and subjects valid
✓ No cross-workspace conflicts
✓ All clusters registered
✓ All clusters active
✓ All IdP groups resolvable
✓ Policy grants orgAdmin
✓ At least one orgAdmin can authenticate

Publishing...
  organization
    + grant idp:team:platform orgAdmin
+ workspace team-data  (2 targets, 2 grants)
    + target cluster-dev/data-dev
    + target cluster-prod/data-prod
    + grant idp:team:data-engineers editor
    + grant idp:team:data-leads workspaceAdmin
✓ Published v4 · 1 workspace added · 2 targets added · 3 grants added
Enforcement active as of 2026-07-29T12:30:00Z
Previous version: v3
Checksum: sha256:a3f9c12b4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8
```

Every change is listed, including deletions. Read the `-` lines before you merge: a workspace that disappears from the document is destroyed, and the grants under it are revoked.

### Recording the commit

Pass `--git-sha` to store the commit a version was published from:

```bash
pipekit rbac publish -f policy.yaml --git-sha "$GIT_SHA"
```

The value must be a commit hash, abbreviated to at least 7 characters or written in full. Anything else is rejected before the document is uploaded, so a CI job that passes an unset variable fails instead of publishing without provenance. The recorded commit is echoed after the checksum and shown by `pipekit rbac list`:

```
Checksum: sha256:a3f9c12b4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8
Git SHA: 4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70
```

`--git-sha` is ignored with `--dry-run`, which stores nothing. Republishing a document that is byte-identical to the newest version returns that version and keeps the commit it was first published with.

### Errors and warnings

Errors reject the whole document. Nothing is stored, the active policy keeps being enforced, and the command exits non-zero. The report goes to stderr, so redirecting stdout does not hide it:

```
Validating...
✗ Publish rejected — 2 errors:

[workspace: team-data]
  workspaces[0].targets[0]: namespace "data-*" is a wildcard; explicit namespaces are required

[cross-workspace conflict]
  workspaces[1].targets[0]: namespace "cluster-prod/data-prod" is already claimed by workspace "team-data"

Active config unchanged (v3 still enforced).
```

Warnings are advisory and never block a publish. A binding to an IdP group that does not exist yet is a warning: the policy is applied and the binding takes effect once the group appears.

```
⚠ [workspace: team-ml] idp group "team-ml-leads" not found — the binding will have no effect
```

Group names are matched exactly, so a name that differs from a real group only in case is still a warning. Case is significant: `Engineering` and `engineering` are two groups, and the one you named may be a group your IdP has not synced yet.

```
⚠ [workspace: team-ml] idp group "Platform-Admins" was not found; "platform-admins" exists but idp group names are case-sensitive
```

A name that differs only in spacing is an error, because that difference is invisible and nothing else can explain it:

```
[workspace: team-ml]
  workspaces[1].bindings[0]: idp group "Platform  Team" was not found; "Platform Team" exists and differs from it only in whitespace (policy: Platform%20%20Team, group: Platform%20Team)
```

The two names are repeated in escaped form because a doubled space collapses when it is displayed. Read the escaped pair to see what actually differs.

A check reported as `(skipped)` could not be evaluated, usually because the data it needs was unavailable.

## Reading and rolling back

`pipekit rbac get` writes the document to stdout and nothing else, so you can redirect it to a file:

```bash
pipekit rbac get > policy.yaml
pipekit rbac get --version 3 > policy-v3.yaml
```

`--version` takes a bare integer. Roll back by republishing an old version:

```bash
pipekit rbac get --version 3 | pipekit rbac publish -f -
```

`-f -` reads the document from stdin. A rollback is a new version, not a rewind: republishing v3 creates v5 with v3's content, and the history stays intact.

To see what has been published:

```bash
pipekit rbac list
```

```
VERSION   STATUS       CREATED                ACTIVATED              PUBLISHED BY        SOURCE   CHECKSUM       GIT SHA
v4        active       2026-07-29T12:30:00Z   2026-07-29T12:30:04Z   alice@example.com   cli      a3f9c12b4d5e   4d5e6f708192
v3        superseded   2026-07-28T09:14:00Z   2026-07-28T09:14:03Z   bob@example.com     cli      b1c2d3e4f506   8192a3b4c5d6
v2        superseded   2026-07-21T16:02:00Z   2026-07-21T16:02:02Z   bob@example.com     cli      4d5e6f708192   -
```

`PUBLISHED BY` is the email of whoever uploaded the version, and `ACTIVATED` is when it started being enforced. `SOURCE` is how the version was published. `cli` means `pipekit rbac publish`. A version that was uploaded but never activated shows `-` in `ACTIVATED`. `PUBLISHED BY`, `ACTIVATED`, and `SOURCE` show `-` for versions published before these were recorded.

`GIT SHA` appears only when some version was published with `--git-sha`, abbreviated to 12 characters. A `FAILURE REASON` column appears when a version failed to apply.

## GitOps

Keep the policy in Git and let CI publish it. Validate on every pull request, publish on merge to your default branch.

Validate:

```bash
pipekit rbac publish --dry-run -f policy.yaml
```

The command exits non-zero when the policy is rejected, so a failing check blocks the pull request. Warnings exit zero.

Publish:

```bash
pipekit rbac publish -f policy.yaml --git-sha "$GIT_SHA"
```

Passing the commit ties each published version back to the change that produced it, which is what `pipekit rbac list` shows later.

{% hint style="warning" %}
Once a policy is active, publishing requires `orgAdmin` **in the policy itself**. Grant `orgAdmin` to the account your automation uses, or that account loses the ability to publish, including the ability to publish a fix. Every document you publish must keep that grant.
{% endhint %}

{% hint style="warning" %}
CI authenticates with `PIPEKIT_USERNAME` and `PIPEKIT_PASSWORD` against a dedicated org-admin account, as described in [Advanced > Non-interactive login](/reference/cli/advanced.md#non-interactive-login).
{% endhint %}
