> ## Documentation Index
> Fetch the complete documentation index at: https://docs.celum.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Delegate to a team

> Give a team the permissions to run their own project — and verify what they actually got, as them, before telling them it works.

By the end of this page a group of people can operate the project you built for them and nothing else, and you have confirmed that from their side rather than from yours.

This is the last station in this sequence, and it is what turns everything before it into someone else's to run.

## What this changes on your cluster

Nothing. IAM lives entirely in Celum's database — policies, groups, memberships. No cluster object is created, and revoking access is a database change, not a cluster change.

```mermaid theme={null}
flowchart LR
    U[Person signs in] -->|"OIDC claim"| G[IAM group]
    G --> P[Policy document]
    P -->|"action + KRN"| D{Allow or deny}
```

## Before you start

* **`DATABASE_URL` on the backend.** IAM is stored there, and without it there is nothing to store policies in.
* **`IAM_ENABLED=true`.** This is the one that catches people out: with it unset the middleware evaluates and logs but **allows everything**. A carefully scoped policy on a deployment with IAM disabled grants full access, silently.
* **Permissions** `iam:CreatePolicy`, `iam:CreateGroup`, `iam:AttachGroupPolicy`, `iam:AddGroupMember` — all scoped `iam:*`.

## The shape of it

**Pick or write a policy → put it on a group → map the group to a claim → verify as the user.** The last step is the only one that tells you the truth.

## Step 1 — Start from a managed policy if one fits

Six ship with the product and cover most of what people need:

| Policy | What it is for |
| - | - |
| `K8sGateAdmin` | everything |
| `K8sGateOperator` | read everything, manage clusters, Helm releases and operational tasks |
| `K8sGateViewer` | read-only, **no credential downloads** |
| `K8sGateSensitiveRead` | kubeconfig, talosconfig and SSH password downloads |
| `K8sGateDeleteApprover` | approve or reject other people's guest-cluster deletions |
| `K8sGateClusterDeleteBypass` | delete a guest cluster without a second person's approval |

Note the split between the third and fourth: **reading the platform and downloading credentials for it are deliberately different grants**, so "read-only access" does not quietly include handing out a working kubeconfig.

> [!NOTE]
>
> **Managed policies are immutable.** Update and delete both refuse to touch them. Copy one into a policy of your own if you need a variant — editing in place is not offered, on purpose.

> [tab] Dashboard
>
> IAM → **Policies**.

> [tab] API
>
> `GET /api/iam/policies`, `POST /api/iam/policies` to add your own.

> [tab] MCP
>
> No tools. IAM is dashboard and API only.

![The Policies page listing the six managed policies K8sGateAdmin, K8sGateClusterDeleteBypass, K8sGateDeleteApprover, K8sGateOperator, K8sGateSensitiveRead and K8sGateViewer, each marked Managed, with IAM enforcement shown as Enforcing](https://mintcdn.com/celum-e0119be0/A2ZTzkZ9GpO9lMML/images/get-started/iam-policies.png?fit=max\&auto=format\&n=A2ZTzkZ9GpO9lMML\&q=85\&s=3c32c8d0bbf48ccbefcdab8e7ba11db7)

IAM → Policies on a new installation: the six managed policies

## Step 2 — Write the resource, and count the segments

A statement pairs an action with one or more KRNs. The grammar has one rule that causes nearly every "my policy does nothing" report:

> [!WARNING]
>
> **A KRN matches on an exact segment count unless it ends in `*`.** `krn:celum:supervisor:prod:cluster:*` matches clusters on `prod` — it does **not** match `krn:celum:supervisor:prod:cluster:web:helm-release:x`, which has more segments. Matching compares segment by segment and a different count simply returns false, with no error anywhere.
>
> A policy written one segment short of the resource it meant to cover denies everyone and says nothing.

Useful shapes:

```text theme={null}
krn:celum:supervisor:*                                every supervisor
krn:celum:supervisor:prod                             one supervisor
krn:celum:supervisor:prod:cluster:*                   every cluster on it
krn:celum:supervisor:prod:project:team-a:vm:*         every VM in one project
```

Evaluation is AWS-shaped and worth stating once: **default deny → an explicit `Deny` wins immediately → otherwise an `Allow` grants → otherwise denied.** A Deny anywhere in any attached policy ends the question.

## Step 3 — Create the group and map it to a claim

A group is how people reach a policy. It carries either an OIDC claim value or an explicit member list.

> [tab] Dashboard
>
> IAM → **Groups** → attach policies, add members.

> [tab] API
>
> `POST /api/iam/groups`, then `POST /api/iam/groups/{id}/policies` and `POST /api/iam/groups/{id}/members`.

![The Groups page with one group, Administrators, mapped to the OIDC group celum-admins, with one policy attached and an edit and a delete action](https://mintcdn.com/celum-e0119be0/A2ZTzkZ9GpO9lMML/images/get-started/iam-groups.png?fit=max\&auto=format\&n=A2ZTzkZ9GpO9lMML\&q=85\&s=a3042a8581048fc594c3175222ec3c54)

IAM → Groups: each group shows the claim value it matches

The claim the group matches against depends on how the backend is configured — security-group ids by default, or App Role values when the deployment maps roles instead. Whichever it is, **the value in the group must match the token exactly**; there is no normalisation.

> [!WARNING]
>
> Attaching a policy, adding a member and updating the group are **three separate permissions**. An identity holding only one of them can complete part of a change and have the rest silently dropped — so grant the set, or expect partial results.

## Verify — as the user, not as yourself

This is the step that matters, and the product provides it because reading your own policy document proves nothing about how it evaluates.

> [tab] Dashboard
>
> IAM → **My Profile** shows the signed-in user their own effective permissions and groups.

> [tab] API
>
> `GET /api/iam/me/permissions` and `GET /api/iam/me/groups` — deliberately open to any signed-in user, so anyone can answer "what am I allowed to do" without holding an IAM grant.

![My Profile showing the signed-in user, the OIDC groups from the token (authentik Admins and celum-admins, the second highlighted as matched), and the matched IAM group Administrators via oidc](https://mintcdn.com/celum-e0119be0/A2ZTzkZ9GpO9lMML/images/get-started/iam-my-profile.png?fit=max\&auto=format\&n=A2ZTzkZ9GpO9lMML\&q=85\&s=3b1c2405fc59f5c05aef8d71ec9d89f1)

IAM → My Profile: the claim values in the token, and the IAM groups they matched

Have someone from the team sign in and read it. Two failures show up here and nowhere else: a claim value that does not match, and a KRN with the wrong segment count. Both look like a correct policy from the authoring side.

Then have them do the actual thing — create a VM in their project, publish it. A permission that evaluates correctly and still refuses the real request usually means the route is authorised against a resource shape the policy did not anticipate.

## If you need to undo it

Remove the member, detach the policy, or delete the group — `iam:RemoveGroupMember`, `iam:DetachGroupPolicy`, `iam:DeleteGroup`. Access ends at the next evaluation; nothing on any cluster changes.

Deleting a policy that groups still reference removes the grant from all of them at once. Detaching from one group is the narrower action, and usually the one you meant.

## What commonly goes wrong

| Symptom | Cause |
| - | - |
| A scoped policy grants everything | `IAM_ENABLED` is not `true`. The middleware logs and allows |
| The policy is right and the user has nothing | the group's claim value does not match the token. Compare them character for character |
| A policy matches nothing, with no error | a KRN segment count that does not match the resource. Exact unless it ends in `*` |
| An Allow is ignored | an explicit Deny somewhere else wins, immediately and without explanation |
| A managed policy will not save | they are immutable. Copy it and edit the copy |
| Half of a group change applied | attach, add-member and update are separate permissions |
| Read-only users can download kubeconfigs | that is `K8sGateSensitiveRead`, attached separately. Check what else the group carries |

## Next

> [card] [Permissions model](https://docs.celum.io/concepts/permissions-model.md)
>
> How a request becomes an action and a KRN, and why an unmapped route is denied.

> [card] [Permissions reference](https://docs.celum.io/reference/permissions.md)
>
> Every action Celum defines, with the KRN each is checked against.

> [card] [Authentication & IAM](https://docs.celum.io/get-started/authentication.md)
>
> The sign-in half — OIDC, sessions, and claim mapping.

> [card] [Projects](https://docs.celum.io/concepts/projects.md)
>
> The boundary most delegations are scoped to.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.