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
2 changes: 1 addition & 1 deletion docs/platform-engineer-guide/authorization/conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ For a role mapping to permit a request, four things must all be 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.
Aggregation across bindings is unchanged. For a single entitlement value, access is **allowed** only if at least one matching binding has `effect: allow` and no matching binding has `effect: deny`; across entitlement values the results are unioned. 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).
Expand Down
30 changes: 24 additions & 6 deletions docs/platform-engineer-guide/authorization/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Two key properties:
- **Permissions cascade downward.** Granting `component:view` at the namespace scope allows viewing components in every project within that namespace.
- **Permissions do not cascade upward.** Even if a role includes actions for higher-level resources (e.g., `environment:view`), a binding scoped to a project will **not** grant access to namespace-level or cluster-level resources. If a user needs visibility into those, add supplementary role mappings at the appropriate scope — see [Scoping Roles Below Cluster Level](../authorization.md#scoping-roles-below-cluster-level).

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.
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 **to the same entitlement value** by an `allow` binding at the same or a higher scope. A `deny` bound to one entitlement value has no effect on access granted to another. See [How OpenChoreo RBAC determines access](#how-openchoreo-rbac-determines-access) for exactly how allow and deny bindings are combined.

### Conditions

Expand All @@ -124,14 +124,32 @@ When a request arrives, OpenChoreo evaluates it against every role binding the s
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:
Evaluation runs **once per entitlement value**, not once per user. A caller presenting several entitlement values — for example, membership of several groups — is evaluated separately against each one, and the results are then combined.

- **at least one** matching binding has `effect: allow`, **and**
- **no** matching binding has `effect: deny`.
For a single entitlement value, access is **allowed** only if:

A single matching `deny` is enough to block the request, even when multiple `allow` bindings would otherwise grant it. Deny applies across role kinds — a namespace-scoped `AuthzRoleBinding` with `effect: deny` can block access that a `ClusterAuthzRoleBinding` would otherwise allow.
- **at least one** binding matching that entitlement value has `effect: allow`, **and**
- **no** binding matching that entitlement value has `effect: deny`.

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.
Across entitlement values the results are **unioned**: the request is allowed if any one of the caller's entitlement values allows it. Effective permissions are the sum of what each entitlement value grants.

Within a single entitlement value, one `deny` is enough to block the request, even when several `allow` bindings would otherwise grant it. Deny also applies across role kinds — a namespace-scoped `AuthzRoleBinding` with `effect: deny` can block access that a `ClusterAuthzRoleBinding` would otherwise allow, provided both are bound to the same entitlement value.

Bindings default to `effect: allow`. Set `effect: deny` explicitly only when you need to create a targeted exception to a broader allow **on the same entitlement value** — for example, granting `acme-developers` the `developer` role across the `acme` namespace, then denying that same entitlement value on the `secret` project within it.

:::warning
`deny` is scoped to the entitlement value it is bound to. It is **not** a per-user kill switch.

Suppose `acme-developers` is granted the `developer` role across the `acme` namespace, and a user belongs to both `acme-developers` and `crm-developers`. Adding a `deny` on `crm-developers` for the `crm` project does **not** revoke that user's access to `crm` — the `acme-developers` grant already covers it and stands on its own. See [Revoking access](#revoking-access).
:::

### Revoking access

To revoke access to a resource, remove the role binding that grants it, or narrow that binding's scope so the resource falls outside it.

Adding a `deny` binding on a **different** entitlement value that the user also holds will not revoke anything, because each entitlement value is evaluated independently and any allowing entitlement value is sufficient on its own.

Which claim carries the entitlement is a deployment setting. On a deployment keyed on `groups`, the engine only ever sees the caller's group memberships — the individual user is not visible to the decision.

### Fail-Closed Evaluation

Expand Down
12 changes: 8 additions & 4 deletions docs/reference/api/platform/authzrolebinding.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,17 @@ spec:

## Allow and Deny

Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. When multiple bindings match a request, the system follows a **deny-overrides** strategy:
Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. Evaluation runs once per entitlement value the caller presents, and each entitlement value is resolved independently using a **deny-overrides** strategy:

- If **any** matching binding has effect `allow` **AND** **no** matching binding has effect `deny`: **ALLOW**
- If **any** matching binding has effect `deny`: **DENY** (deny always wins)
- If **any** binding matching that entitlement value has effect `allow` **AND** **no** binding matching that entitlement value has effect `deny`: **ALLOW**
- If **any** binding matching that entitlement value has effect `deny`: **DENY**
- If **no** bindings match: **DENY** (default deny)

A single `deny` binding can override any number of `allow` bindings, making it straightforward to revoke specific permissions without restructuring the entire role hierarchy.
The per-entitlement results are then **unioned**: the request is allowed if any one of the caller's entitlement values allows it.

A `deny` therefore overrides `allow` bindings carrying the **same** entitlement value, including across role kinds and down the resource hierarchy. It does **not** override access granted to a different entitlement value. If a user belongs to both `acme-developers` and `crm-developers`, a `deny` on `crm-developers` will not revoke access that `acme-developers` grants.

To revoke access, remove the binding that grants it or narrow its scope — see [Revoking access](../../../platform-engineer-guide/authorization/overview.md#revoking-access).

## Related Resources

Expand Down
12 changes: 8 additions & 4 deletions docs/reference/api/platform/clusterauthzrolebinding.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,17 @@ spec:

## Allow and Deny

Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. When multiple bindings match a request, the system follows a **deny-overrides** strategy:
Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. Evaluation runs once per entitlement value the caller presents, and each entitlement value is resolved independently using a **deny-overrides** strategy:

- If **any** matching binding has effect `allow` **AND** **no** matching binding has effect `deny`: **ALLOW**
- If **any** matching binding has effect `deny`: **DENY** (deny always wins)
- If **any** binding matching that entitlement value has effect `allow` **AND** **no** binding matching that entitlement value has effect `deny`: **ALLOW**
- If **any** binding matching that entitlement value has effect `deny`: **DENY**
- If **no** bindings match: **DENY** (default deny)

A single `deny` binding can override any number of `allow` bindings, making it straightforward to revoke specific permissions without restructuring the entire role hierarchy.
The per-entitlement results are then **unioned**: the request is allowed if any one of the caller's entitlement values allows it.

A `deny` therefore overrides `allow` bindings carrying the **same** entitlement value, including across role kinds and down the resource hierarchy. It does **not** override access granted to a different entitlement value. If a user belongs to both `acme-developers` and `crm-developers`, a `deny` on `crm-developers` will not revoke access that `acme-developers` grants.

To revoke access, remove the binding that grants it or narrow its scope — see [Revoking access](../../../platform-engineer-guide/authorization/overview.md#revoking-access).

## Related Resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Two key properties:
- **Permissions cascade downward.** Granting `component:view` at the namespace scope allows viewing components in every project within that namespace.
- **Permissions do not cascade upward.** Even if a role includes actions for higher-level resources (e.g., `environment:view`), a binding scoped to a project will **not** grant access to namespace-level or cluster-level resources. If a user needs visibility into those, add supplementary role mappings at the appropriate scope — see [Scoping Roles Below Cluster Level](../authorization.md#scoping-roles-below-cluster-level).

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.
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 **to the same entitlement value** by an `allow` binding at the same or a higher scope. A `deny` bound to one entitlement value has no effect on access granted to another. See [How OpenChoreo RBAC determines access](#how-openchoreo-rbac-determines-access) for exactly how allow and deny bindings are combined.

## How OpenChoreo RBAC determines access

Expand All @@ -108,14 +108,18 @@ When a request arrives, OpenChoreo evaluates it against every role binding the s
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:*`, `*`).

A request is **allowed** only if:
Evaluation runs **once per entitlement value**, not once per user. For a single entitlement value, access is **allowed** only if:

- **at least one** matching binding has `effect: allow`, **and**
- **no** matching binding has `effect: deny`.
- **at least one** binding matching that entitlement value has `effect: allow`, **and**
- **no** binding matching that entitlement value has `effect: deny`.

A single matching `deny` is enough to block the request, even when multiple `allow` bindings would otherwise grant it. Deny applies across role kinds — a namespace-scoped `AuthzRoleBinding` with `effect: deny` can block access that a `ClusterAuthzRoleBinding` would otherwise allow.
Across entitlement values the results are **unioned**: the request is allowed if any one of the caller's entitlement values allows it.

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.
Within a single entitlement value, one `deny` is enough to block the request, even when several `allow` bindings would otherwise grant it. Deny also applies across role kinds — a namespace-scoped `AuthzRoleBinding` with `effect: deny` can block access that a `ClusterAuthzRoleBinding` would otherwise allow, provided both are bound to the same entitlement value.

A `deny` on one entitlement value does **not** cancel access granted to another, so it is not a per-user kill switch. To revoke access, remove the binding that grants it or narrow its scope.

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

## Authorization CRDs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,17 @@ spec:

## Allow and Deny

Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. When multiple bindings match a request, the system follows a **deny-overrides** strategy:
Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. Evaluation runs once per entitlement value the caller presents, and each entitlement value is resolved independently using a **deny-overrides** strategy:

- If **any** matching binding has effect `allow` **AND** **no** matching binding has effect `deny`: **ALLOW**
- If **any** matching binding has effect `deny`: **DENY** (deny always wins)
- If **any** binding matching that entitlement value has effect `allow` **AND** **no** binding matching that entitlement value has effect `deny`: **ALLOW**
- If **any** binding matching that entitlement value has effect `deny`: **DENY**
- If **no** bindings match: **DENY** (default deny)

A single `deny` binding can override any number of `allow` bindings, making it straightforward to revoke specific permissions without restructuring the entire role hierarchy.
The per-entitlement results are then **unioned**: the request is allowed if any one of the caller's entitlement values allows it.

A `deny` therefore overrides `allow` bindings carrying the **same** entitlement value, including across role kinds and down the resource hierarchy. It does **not** override access granted to a different entitlement value. If a user belongs to both `acme-developers` and `crm-developers`, a `deny` on `crm-developers` will not revoke access that `acme-developers` grants.

To revoke access, remove the binding that grants it or narrow its scope.

## Related Resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,17 @@ In this example, `acme-admins` gets full `admin` access scoped to the `acme` nam

## Allow and Deny

Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. When multiple bindings match a request, the system follows a **deny-overrides** strategy:
Both `ClusterAuthzRoleBinding` and `AuthzRoleBinding` carry an **effect** field: either `allow` or `deny`. Evaluation runs once per entitlement value the caller presents, and each entitlement value is resolved independently using a **deny-overrides** strategy:

- If **any** matching binding has effect `allow` **AND** **no** matching binding has effect `deny`: **ALLOW**
- If **any** matching binding has effect `deny`: **DENY** (deny always wins)
- If **any** binding matching that entitlement value has effect `allow` **AND** **no** binding matching that entitlement value has effect `deny`: **ALLOW**
- If **any** binding matching that entitlement value has effect `deny`: **DENY**
- If **no** bindings match: **DENY** (default deny)

A single `deny` binding can override any number of `allow` bindings, making it straightforward to revoke specific permissions without restructuring the entire role hierarchy.
The per-entitlement results are then **unioned**: the request is allowed if any one of the caller's entitlement values allows it.

A `deny` therefore overrides `allow` bindings carrying the **same** entitlement value, including across role kinds and down the resource hierarchy. It does **not** override access granted to a different entitlement value. If a user belongs to both `acme-developers` and `crm-developers`, a `deny` on `crm-developers` will not revoke access that `acme-developers` grants.

To revoke access, remove the binding that grants it or narrow its scope.

## Related Resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ For a role mapping to permit a request, four things must all be 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.
Aggregation across bindings is unchanged. For a single entitlement value, access is **allowed** only if at least one matching binding has `effect: allow` and no matching binding has `effect: deny`; across entitlement values the results are unioned. 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).
Expand Down
Loading