Skip to main content

Roles and Access

DevsPortal controls who can do what, and where, through role-based access control. This page is the team admin's working guide to that model: how access is expressed, the common role tiers, how to scope them to a team, and how to add fine-grained conditions when you need them.

note

DevsPortal is built on the Control Plane Operator engine. The authorization resources — AuthzRole, AuthzRoleBinding, and their cluster-scoped siblings ClusterAuthzRole / ClusterAuthzRoleBinding — and the occ CLI are unchanged engine internals. Examples target the Fedshi instance.

Portal first, CLI second

View and grant roles from the portal's Settings → Access Control tab; the occ/YAML form below is for scripting, review, and GitOps. The sections below explain the model — see Managing access in the console for the click-path and Managing access with the CLI for the equivalent commands.

The model in four words

Every access decision answers four questions:

  • Subjectwho is asking.
  • Actionwhat they want to do.
  • Scopewhere in the hierarchy it applies.
  • Conditionunder what circumstances (optional).

You grant access by defining a role (a named set of actions) and creating a role binding (which attaches that role to a subject at a scope, with an optional condition and an allow/deny effect).

Subjects

A subject is an identity, matched by entitlements — claim/value pairs pulled from the caller's login token. The most useful is the group claim, because binding to a group means new team members inherit access automatically:

  • groups:checkout-team — anyone in the checkout team's group
  • email:alice@fedshi.com — one specific person
  • sub:user-abc-123 — one specific account

Always prefer group bindings for teams. Per-person bindings are for narrow exceptions.

Where the group claim comes from

The groups claim isn't defined in DevsPortal — it's minted by your identity provider (on the Fedshi instance, ThunderID) and carried in the login token. So a "team" is really two cooperating pieces:

  1. A group in the IdP — the membership list. Adding a person to the IdP group is what puts the matching value in their groups claim at next login.
  2. A role binding here — the entitlement above, which maps that claim value to a role and a scope.

Authentication ≠ authorization: a user who signs in but belongs to no group lands with no permissions until a binding matches one of their claims.

Two different "groups" — don't confuse them

The IdP group is what drives permissions. The portal also shows catalog Group entities under Catalog → Groups (e.g. the seeded openchoreo-users) — those are ownership/display metadata and grant no access on their own. They can be auto-mirrored from the IdP (see Identity & SSO), but a binding's entitlement always reads the token's claim, never the catalog. Always bind to the IdP group.

Creating a new team, end to end

  1. Create the group in the IdP and add members — on Fedshi, in the ThunderID console (Groups → New). Seeded groups already exist (admins, developers, platform-engineers, sres); name a new one for the team, e.g. checkout-team.
  2. Bind it to a role at a scope — create the AuthzRoleBinding (project-scoped) or ClusterAuthzRoleBinding (cluster-wide) whose entitlement matches claim: groups, value: checkout-team, as shown under Roles and bindings.
  3. Members re-login — the new token carries groups: checkout-team and the binding takes effect.

For the seeded tiers you can skip steps 1–2 entirely: just add the person to the existing developers / admins group, whose bindings ship with the platform.

Actions

Actions follow resource:verb, e.g. component:create, project:view, logs:view. They support wildcards — component:* (all component verbs) and * (everything). The full catalog of actions spans components, releases, bindings, workflows, observability (logs:view, metrics:view, traces:view), and the platform resources; the complete list is in the upstream authorization reference.

The scope hierarchy

Resources nest, and scope follows the nesting:

Cluster
└── Namespace
└── Project
└── Component

A binding's scope says how far down its permissions reach. Permissions cascade downward, never upward.

ScopeHow to set itReaches
Cluster-wideomit scope on a ClusterAuthzRoleBindingeverything
Namespacescope.namespace: fedshithe namespace and all its projects/components
Projectscope.namespace: fedshi, scope.project: checkoutone project and its components
Componentscope.component: cart-apione component

The effective permission is the intersection: the role must grant the action and the target must fall within the scope. A developer role scoped to checkout lets a user manage components only in checkout, even if the role lists actions for other resources.

Roles and bindings

A role lists actions. A binding ties a subject to one or more roles, each with its own scope (and optional conditions), plus an effect of allow (default) or deny.

apiVersion: openchoreo.dev/v1alpha1
kind: AuthzRole
metadata:
name: developer
namespace: fedshi
spec:
actions:
- project:view
- component:*
- workload:*
- componentrelease:view
- componentrelease:create
- releasebinding:*
- workflowrun:*
- logs:view
- metrics:view
- traces:view
- component:exec
---
apiVersion: openchoreo.dev/v1alpha1
kind: AuthzRoleBinding
metadata:
name: checkout-team-developer
namespace: fedshi
spec:
entitlement:
claim: groups
value: checkout-team
roleMappings:
- roleRef:
kind: AuthzRole
name: developer
scope:
project: checkout
effect: allow

Allow vs deny

A request is allowed only if at least one matching binding says allow and no matching binding says deny. A single deny wins — even over a broader allow, and even across kinds (a namespace deny overrides a cluster allow). Use deny sparingly, as a targeted exception: e.g. grant developer across the fedshi namespace but deny it on a sensitive payments-secrets project.

Authorization is fail-closed: if a binding can't be evaluated cleanly, allow won't grant and deny still denies. Misconfiguration never silently widens access.

Common role tiers

Three tiers cover most teams. Bind each to the team's group at the appropriate scope.

TierIntentTypical actionsTypical scope
ViewerRead-only situational awareness*:view, logs:view, metrics:view, traces:viewnamespace-wide (so they can see neighbors)
DeveloperBuild, deploy, operate own componentscomponent:*, workload:*, releasebinding:*, workflowrun:*, component:exec, observability viewsthe team's project(s)
AdminEverything a developer can do, plus manage the team's accessdeveloper actions + authzrole:*, authzrolebinding:*the team's project(s) / namespace

You can seed these from the Developer / Viewer / Admin quick-start templates in the Access Control UI and tweak from there.

Conditions: access under circumstances

Conditions add attribute-based control (ABAC) on top of RBAC, written in CEL. The classic use: let a developer manage release bindings except in production, and read logs only in non-prod.

apiVersion: openchoreo.dev/v1alpha1
kind: AuthzRoleBinding
metadata:
name: checkout-team-developer
namespace: fedshi
spec:
entitlement:
claim: groups
value: checkout-team
roleMappings:
- roleRef:
kind: AuthzRole
name: developer
scope:
project: checkout
conditions:
- actions:
- releasebinding:create
- releasebinding:update
- releasebinding:delete
expression: 'resource.environment != "fedshi/production"'
- actions:
- logs:view
expression: 'resource.environment in ["fedshi/development", "fedshi/staging"]'
effect: allow

Here, the team can deploy and promote freely up to staging but cannot mutate the production binding, and their log access is limited to non-prod — while every other action the role grants stays unrestricted. Multiple conditions on one action group combine with OR. Conditions can only reference attributes registered for the actions they gate (today, resource.environment on release-binding and observability actions).

This is the mechanism behind much of governance and guardrails — gates expressed as policy, not as manual approvals.

Managing access in the console

The portal is the primary way to view and grant roles — you don't have to write YAML. Open Settings from the left nav, then the Access Control tab (alongside General and Secrets):

The portal Settings page, showing the General / Access Control / Secrets tabs

  • Roles — create cluster or namespace roles; pick a quick-start template, name the role, and select actions with the grouped action picker (component:*, *, etc.).
  • Role Bindings — a step-by-step wizard: choose the subject (the group claim), add role-scope mappings, attach conditions inline (with a CEL editor and attribute chips), pick allow/deny, name it, and review.
  • Actions — browse every action to decide what a role should include.

Managing access with the CLI

occ authzrole list -n fedshi
occ authzrole get developer -n fedshi
occ authzrolebinding list -n fedshi
occ authzrolebinding get checkout-team-developer -n fedshi
occ apply -f developer-role.yaml # create/update from YAML

Cluster-scoped equivalents: occ clusterauthzrole … and occ clusterauthzrolebinding ….

Good practices

  • Bind to groups, scope to projects. New joiners get access by joining the group; the blast radius stays inside the team's project.
  • Start from the three tiers, refine later. You can edit a role without recreating bindings.
  • Reach for conditions before deny. Express "no prod mutations" as a condition on the developer role rather than a separate deny binding.
  • Keep production special. Gate prod release-binding mutations with a condition for most of the team; grant unconditioned access only to those who operate prod.

What's next