Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions docs/platform-engineer-guide/authorization/conditions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
title: Conditions
description: Restrict role grants by request attributes using CEL expressions on AuthzRoleBindings
sidebar_position: 4
---

# Conditions

A role binding answers _who_, _what_, and _where_. **Conditions** add a fourth constraint — _under what circumstances_. For example, you can grant a developer permission to manage release bindings in the `crm` project, but only when the target environment is `dev` or `staging` — keeping production off-limits.

Conditions are optional. Omit them and the role mapping behaves like any other RBAC grant — every action the role grants applies within the binding's scope.

## Condition Structure

A condition has two parts: a list of **actions** it applies to, and an **expression** that decides whether those actions are permitted. You can attach conditions to a role mapping either in YAML (the `conditions` field on `AuthzRoleBinding` / `ClusterAuthzRoleBinding`) or through the Access Control UI in Backstage.

| Field | Type | Required | Description |
| ------------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `actions` | string[] | Yes | Action patterns this condition applies to — the entry's `expression` is attached to each listed action. Supports exact matches and wildcards. |
| `expression` | string | Yes | A CEL expression that must evaluate to `true` for the action to be permitted by this role mapping. |

Action patterns follow the same wildcard rules used elsewhere in OpenChoreo RBAC:

- `releasebinding:create` — a single concrete action
- `releasebinding:*` — every action on the `releasebinding` resource
- `*` — every action in the system

Only entries whose `actions` match the request contribute to the decision. If a mapping has conditions but none target the requested action, the condition check is skipped for that action.

## Available Attributes

CEL expressions reference a predefined set of attributes. Each attribute is registered against the specific actions where it is meaningful; a binding that references an attribute on an action that does not support it will be rejected at creation time.

Currently the following attributes are available — more will be added in future releases:

| Attribute | Type | Available on | Description |
| ----------------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `resource.environment`<br/>[(dual-scoped)](#resource-identifiers-dual-scoped) | string | `releasebinding:create`, `releasebinding:view`, `releasebinding:update`, `releasebinding:delete`, `logs:view`, `metrics:view`, `traces:view` | Environment associated with the resource (e.g., `acme/prod`). |

When a condition lists multiple actions — whether explicitly (`["releasebinding:create", "logs:view"]`) or via a wildcard pattern (`releasebinding:*`) — the expression may only reference attributes registered for **every** action the entry covers. An attribute supported by only some of those actions is not usable in the condition.

### Resource Identifiers (Dual-Scoped)

Some resource kinds in OpenChoreo come in two variants — one namespace-scoped, one cluster-scoped. In conditions, both variants share a single logical name (such as `environment`). Conditions don't pick the variant by kind; they pick it by the **shape of the identifier**.

Attributes that identify such a resource (such as `resource.environment`) carry one of two forms:

- For the namespace-scoped variant: `{namespace}/{name}` — for example, `acme/prod`.
- For the cluster-scoped variant: just `{name}` — for example, `prod`.

Match the same form in your CEL expression: `resource.environment == "acme/prod"` targets a namespace-scoped environment named `prod` in `acme`, while `resource.environment == "prod"` targets the cluster-scoped one.

For resources that exist in only one scope, the resource identifiers simply carry the resource name.

## How Conditions Affect the Authorization Decision

For a role mapping to permit a request, four things must all be true:

1. The subject matches the binding's entitlement.
2. The target resource is within the binding's scope.
3. The role lists the requested action (exactly or via a wildcard).
4. The condition (if any) evaluates to `true`.

A mapping with no `conditions` skips step four. A mapping with conditions that don't target the request action also skips step four — steps one through three still apply.

Aggregation across bindings is unchanged. A request is **allowed** only if at least one matching binding has `effect: allow` and no matching binding has `effect: deny`. See [How OpenChoreo RBAC determines access](./overview.md#how-openchoreo-rbac-determines-access) for the full algorithm.

:::note
If a condition expression cannot be evaluated cleanly at runtime, OpenChoreo treats it as failing closed — see [Fail-Closed Evaluation](./overview.md#fail-closed-evaluation).
:::

### Multiple Entries on the Same Mapping

A single role mapping can carry multiple `conditions` entries. Among the entries whose `actions` match the request action, the expressions are combined with **OR** — at least one entry must evaluate to `true` for the role mapping to permit the action:

```yaml
conditions:
- actions: ["releasebinding:view"]
expression: 'resource.environment == "dev"'
- actions: ["releasebinding:view"]
expression: 'resource.environment == "staging"'
```

This binding permits the `releasebinding:view` actions when the target environment is either `dev` or `staging`. Combining alternatives in one entry with CEL's `in` operator (`resource.environment in ["dev", "staging"]`) is equivalent and usually clearer.

## Examples

A platform engineer needs to give the `backend-team` group `developer` access — but with two safety rails: release-binding mutations must stay out of production, and log access should be limited to `dev` and `staging`. A single role mapping can carry both rules, one condition per action group:

```yaml
apiVersion: openchoreo.dev/v1alpha1
kind: AuthzRoleBinding
metadata:
name: backend-team-binding
namespace: acme
spec:
entitlement:
claim: groups
value: backend-team
roleMappings:
- roleRef:
kind: AuthzRole
name: developer
conditions:
- actions:
- releasebinding:create
- releasebinding:update
- releasebinding:delete
expression: 'resource.environment != "acme/prod"'
- actions:
- logs:view
expression: 'resource.environment in ["acme/dev", "acme/staging"]'
effect: allow
```

Read-only actions on `releasebinding` (e.g., `releasebinding:view`) and every other action in the `developer` role remain unrestricted — only the listed actions are gated.

## Related Reading

- [Authorization Overview](./overview.md) — Subjects, scopes, actions, and the full evaluation model
- [Custom Roles and Bindings](./custom-roles.mdx) — Walkthrough of role and binding management in Backstage
- [AuthzRoleBinding API Reference](../../reference/api/platform/authzrolebinding.md) — Field reference for namespace-scoped role bindings
- [ClusterAuthzRoleBinding API Reference](../../reference/api/platform/clusterauthzrolebinding.md) — Field reference for cluster-scoped role bindings
36 changes: 31 additions & 5 deletions docs/platform-engineer-guide/authorization/custom-roles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ The Roles section has a dropdown to switch between **Cluster Roles** and **Names

## Creating a Role Binding

Role bindings connect a subject to one or more roles, each with its own scope. The UI uses a step-by-step wizard to guide you through the process.
Role bindings connect a subject to one or more roles, each with its own scope. The UI uses a step-by-step wizard on a dedicated page to guide you through the process — use the **Next** and **Back** buttons in the header to move between steps.

1. Go to **Settings → Access Control → Role Bindings**
2. Use the dropdown to select **Cluster Role Bindings** or **Namespace Role Bindings**
- For namespace bindings, select the target namespace from the dropdown first
3. Click **New Cluster Role Binding** or **New Namespace Role Binding**
3. Click **New Cluster Role Binding** or **New Namespace Role Binding** to open the wizard page

<img
src={require("./images/role-binding-tab.png").default}
Expand All @@ -75,7 +75,7 @@ The subject types shown in the wizard are dynamic and reflect what is configured

### Step 2: Configure Role Mappings

Add one or more role-scope pairs. Each role mapping combines a role (defining _what_ actions are permitted) with a scope (defining _where_ those actions apply).
Add one or more role-scope pairs. Each role mapping combines a role (defining _what_ actions are permitted), a scope (defining _where_ those actions apply), and optional conditions (defining _under what circumstances_ they apply).

For each mapping:

Expand All @@ -84,15 +84,16 @@ For each mapping:
- For cluster bindings: optionally select a namespace, then a project, then a component
- For namespace bindings: optionally select a project, then a component
- Omitting scope means the role applies at the widest level (cluster-wide or namespace-wide)
3. **Confirm** the mapping by clicking the checkmark
3. **Add conditions** (optional) to gate specific actions on request attributes — see [Conditions (Optional)](#conditions-optional) below.
4. **Confirm** the mapping by clicking the checkmark

You can add multiple mappings to a single binding. For example, you might grant `developer` access scoped to a specific project alongside `cluster-reader` access with no scope — all in one binding.

:::tip
The scope you choose determines the effective permissions of the mapping — a user can only perform an action if the role grants that action **and** the target resource falls within the scope. For more details, see [Scope](./overview.md#scope) and [Effective Permissions](./overview.md#effective-permissions).
:::

Select a role and configure its scope inline. Use the namespace, project, and component dropdowns to narrow the scope, then click the checkmark to confirm the mapping.
Select a role and configure its scope inline. The Conditions section below the role and scope fields lets you attach optional CEL-based restrictions (covered next). Click the checkmark to confirm the mapping.

<img
src={require("./images/role-binding-creation-role-mapping.png").default}
Expand All @@ -108,6 +109,31 @@ Once confirmed, each mapping appears as a row in the table. You can add more map
width="100%"
/>

#### Conditions (Optional)

Each role mapping can carry one or more **conditions** that gate specific actions on attributes of the request — for example, allowing release-binding mutations only outside production. Conditions are configured inline while editing a role mapping, before you confirm it.

1. With a role mapping open for editing, locate the **Conditions** section under the role and scope fields.
2. Click **Add condition** to create a new entry. The button is disabled until a role is selected and the role grants at least one action that supports conditions.
3. In the condition card:
- **Actions** — select one or more action patterns this condition applies to. Only actions granted by the role's action list are selectable.
- **Expression** — write a CEL expression that must evaluate to `true` for the listed actions to be permitted.
- **Available attributes** — chips below the Expression field list every attribute valid for your action selection. Click a chip to insert it at the cursor.

<img
src={require("./images/role-binding-creation-conditions-editing.png").default}
alt="Role binding wizard — condition card in edit mode"
width="100%"
/>

4. Click the **checkmark** to confirm the condition, or **X** to discard it.

Multiple conditions on the same mapping are combined with **OR** — at least one matching condition must evaluate to `true` for the action to be permitted. A mapping with no conditions, or whose conditions don't target the requested action, behaves as an unrestricted grant for that action.

:::tip
Available attributes are filtered to those supported by **every** action you've selected. After the first action is picked, the picker hides actions with no overlap, and if the resulting selection has no shared attribute the **Expression** field is disabled until the conflict is removed. For the full attribute model and YAML reference, see [Conditions](./conditions.md).
:::

### Step 3: Choose Effect and Name

Select the effect:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions docs/platform-engineer-guide/authorization/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,20 @@ Two key properties:

Each role binding also carries an `effect` field — either `allow` or `deny` (default: `allow`). A `deny` binding is an explicit exception: it revokes access that would otherwise be granted by an `allow` binding at the same or a higher scope. See [How OpenChoreo RBAC determines access](#how-openchoreo-rbac-determines-access) for exactly how allow and deny bindings are combined.

### Conditions

A role mapping can optionally carry **conditions** that further narrow when the mapping applies — for example, granting a developer permission to manage release bindings in the `crm` project, but only when the target environment is `dev` or `staging`, keeping production off-limits.

If `conditions` is omitted, the role mapping behaves as a plain RBAC grant. For the full attribute model, evaluation semantics, and examples, see [Conditions on Role Bindings](./conditions.md).

## How OpenChoreo RBAC determines access

When a request arrives, OpenChoreo evaluates it against every role binding the subject matches. For each binding, all of the following must hold for the binding to apply:

1. **The subject matches.** One of the caller's entitlement values (e.g., `groups:platformEngineer`) equals the binding's subject.
2. **The resource is within scope.** The target resource lies at or below the binding's scope in the resource hierarchy. A binding at `namespace: acme` applies to everything inside `acme`; a `ClusterAuthzRoleBinding` with no scope applies cluster-wide.
3. **The role grants the action.** The role referenced by the binding lists the requested action, either exactly (`component:create`) or via a wildcard (`component:*`, `*`).
4. **Conditions are satisfied.** If the matching role mapping defines `conditions`, at least one entry whose `actions` cover the request action must evaluate to `true`. Mappings without conditions, or mappings whose conditions do not target the request action, satisfy this step automatically.

A request is **allowed** only if:

Expand All @@ -117,6 +124,10 @@ A single matching `deny` is enough to block the request, even when multiple `all

Bindings default to `effect: allow`. Set `effect: deny` explicitly only when you need to create a targeted exception to a broader allow — for example, granting `developer` access across the `acme` namespace but denying it on the `secret` project within it.

### Fail-Closed Evaluation

OpenChoreo evaluates authorization **fail-closed**: if any part of a binding cannot be evaluated cleanly — including malformed condition expressions or other corrupted policy state — `allow` bindings do not grant access and `deny` bindings still deny. A misconfiguration can never silently widen access; the conservative outcome wins. CRDs are validated by admission webhooks at create/update time, so most issues are caught before they ever reach evaluation.

## Authorization CRDs

OpenChoreo uses four CRDs to manage authorization. **Roles** define what actions are permitted, and **role bindings** connect subjects to those roles with a specific scope and effect.
Expand Down
Loading