Custom roles
Custom roles are an alpha feature. The iam.hub.upbound.io/v1alpha1 API that
serves them may change in incompatible ways between releases. See the feature
lifecycle.
Start with the Access management overview for the tenancy model and the two binding resources, and the Roles reference for what the built-in roles grant.
The four built-in roles (org-admin, realm-admin, realm-editor,
realm-viewer) cover the common cases. When they grant too much or too
little, define your own roles. Hub serves three role kinds in the
iam.hub.upbound.io/v1alpha1 API group:
| Kind | Scope | Bound with | Grants |
|---|---|---|---|
OrganizationRole | Cluster-scoped (organization-wide) | OrganizationRoleBinding | Hub API permissions across the organization, plus optional grants inside named realms |
RealmRole | Namespaced in one realm | RealmRoleBinding in that realm | Hub API permissions inside the realm, plus optional grants inside its control planes |
ControlPlaneRole | Namespaced in one realm | Not bound directly | A reusable set of Kubernetes permissions over resources synced from control planes, included by name from a RealmRole or OrganizationRole grant |
The built-in roles are objects of the same kinds. org-admin is an
OrganizationRole, and every realm contains the three built-in
RealmRoles. You can list and read them like any other role, but you can't
change or delete them. Hub labels them iam.hub.upbound.io/predefined, and
only Hub may set that label.
kubectl --context hub get organizationroles
kubectl --context hub get realmroles -n prod-east
kubectl --context hub get controlplaneroles -n prod-east
Every rule in a role only grants. A request is allowed when any rule or grant in any role the caller holds allows it. No rule takes away what another gives.
Hub API rules
OrganizationRole and RealmRole share one rule shape for permissions on
Hub's own API, written the way a Kubernetes RBAC rule is:
| Field | Required | Meaning |
|---|---|---|
verbs | Yes | One or more of create, get, list, update, delete, admin, escalate, bind. At most 8. |
apiGroups | Yes | Hub API groups, such as fleet.hub.upbound.io. "" is the core group, whose only resource is the read-only namespaces view of realms. |
resources | Yes | <resource> or <resource>/<subresource>. A resource doesn't include its subresources, so controlplanes doesn't cover controlplanes/registrationtoken. |
resourceNames | No | Limits the rule to objects with these exact names. Empty means every name. |
Hub validates every rule so that a role never lists a permission that grants nothing:
"*"isn't accepted inapiGroups,resources, orresourceNames. List each one.- Each combination of API group, resource, and verb must be one that the
role's scope can grant. For example, a rule inside a realm can't
listorcreaterealms, andadminis valid only oncontrolplanes. Hub rejects anything else and names the field. Put resources from different API groups in separate rules. - A rule with
resourceNamescan't allowlist, orcreateon a resource itself, because neither request names an object. Creating through a subresource, such ascontrolplanes/registrationtoken, can be limited by name. - A rule inside a realm that names the realm itself (
realmsornamespaces) can't listresourceNames. It already applies only to that realm.
The special verbs:
adminoncontrolplanesallows changing a control plane'sspec.identityProviders.updatealone doesn't.escalateon a role kind allows writing a role of that kind that grants more than the writer holds.bindonorganizationrolesorrealmrolesallows binding any role of that kind, whatever it grants.
OrganizationRole
An OrganizationRole applies across the organization. Its name must start
with org-, and names starting with org-upbound- are reserved for Upbound.
| Field | Meaning |
|---|---|
spec.rules | Hub API rules across the organization. A rule that names a realm-scoped resource, such as realmrolebindings, allows it in every realm. At most 128. |
spec.realms | Realm grants. Each grant has the rules and controlPlanes fields of a RealmRole, plus names, the realms it applies in. At most 64. |
spec.realms[].names | Realm names, or "*" on its own for every realm, including realms created later. A "*" grant can't name ControlPlaneRole subroles, because each realm defines its own. |
Realm grants are the only way an OrganizationRole reaches into a realm's
control planes and the resources synced from them. This role lets an auditor
read realms and every role binding in the organization, and read control
planes and their Crossplane resources in two production realms:
apiVersion: iam.hub.upbound.io/v1alpha1
kind: OrganizationRole
metadata:
name: org-access-auditor
spec:
rules:
- apiGroups: [hub.upbound.io]
resources: [realms]
verbs: [get, list]
- apiGroups: [iam.hub.upbound.io]
resources: [organizationrolebindings, realmrolebindings]
verbs: [get, list]
realms:
- names: [prod-east, prod-west]
rules:
- apiGroups: [fleet.hub.upbound.io]
resources: [controlplanes]
verbs: [get, list]
controlPlanes:
- names: ["*"]
subroles:
- kind: ClusterRole
name: crossplane-view
RealmRole
A RealmRole lives in a realm's namespace and applies only in that realm.
Its name can't start with org- or upbound-.
| Field | Meaning |
|---|---|
spec.rules | Hub API rules inside the realm. At most 128. |
spec.controlPlanes | Control-plane grants. Each grant has the rules and subroles fields of a ControlPlaneRole, plus names. At most 64. |
spec.controlPlanes[].names | Control plane names in the realm, or "*" on its own for every control plane, including ones registered later. |
This role lets a team operate two control planes in prod-east: read the
realm, update control planes, mint a registration token for one of them, and
see synced resources through a ControlPlaneRole and Crossplane's view
ClusterRole:
apiVersion: iam.hub.upbound.io/v1alpha1
kind: RealmRole
metadata:
name: payments-operator
namespace: prod-east
spec:
rules:
- apiGroups: [hub.upbound.io]
resources: [realms]
verbs: [get]
- apiGroups: [""]
resources: [namespaces]
verbs: [get]
- apiGroups: [fleet.hub.upbound.io]
resources: [controlplanes]
verbs: [get, list, update]
- apiGroups: [fleet.hub.upbound.io]
resources: [controlplanes/registrationtoken]
verbs: [create]
resourceNames: [payments]
controlPlanes:
- names: [payments, billing]
subroles:
- kind: ControlPlaneRole
name: payments-reader
- kind: ClusterRole
name: crossplane-view
ControlPlaneRole
A ControlPlaneRole is a named set of Kubernetes permissions over the
resources the hub-connector syncs out of control planes. It lives in a
realm's namespace and takes effect only where a RealmRole in that realm,
or an OrganizationRole realm grant for that realm, names it as a subrole.
You can't bind it directly. Its name can't start with org- or upbound-.
| Field | Meaning |
|---|---|
spec.rules | Kubernetes RBAC rules naming the control plane's own API groups and resources, not Hub's. "*" is accepted in verbs, apiGroups, and resources. At most 128. |
spec.rules[].resourceNamespaces | Limits the rule to these namespaces inside each control plane. Empty means every namespace. |
spec.rules[].resourceNames | Limits the rule to objects with these names. Empty means every name. |
spec.subroles | Other roles whose permissions this role includes, each a kind and name. kind is ClusterRole (a ClusterRole as each control plane defines it, which grants nothing where it's missing) or ControlPlaneRole (another ControlPlaneRole in the same realm). At most 64. |
Hub applies these permissions to its own copies of the synced resources,
exactly as it applies the built-in roles' crossplane-view and
crossplane-edit projections. They don't change RBAC inside the control
planes. Hub follows nested ControlPlaneRole subroles up to eight levels
deep.
apiVersion: iam.hub.upbound.io/v1alpha1
kind: ControlPlaneRole
metadata:
name: payments-reader
namespace: prod-east
spec:
rules:
- apiGroups: [apiextensions.crossplane.io]
resources: [compositions, compositeresourcedefinitions]
verbs: [get, list]
- apiGroups: [rds.aws.m.upbound.io]
resources: [instances]
verbs: [get, list]
resourceNamespaces: [payments]
Bind a custom role
Bind a custom role with the same resources you use for the built-in roles.
spec.roleRef.name names the role:
apiVersion: iam.hub.upbound.io/v1beta1
kind: RealmRoleBinding
metadata:
name: payments-team
namespace: prod-east
spec:
roleRef:
name: payments-operator
subjects:
- kind: Group
name: "corp:payments"
---
apiVersion: iam.hub.upbound.io/v1beta1
kind: OrganizationRoleBinding
metadata:
name: auditors
spec:
roleRef:
name: org-access-auditor
subjects:
- kind: Group
name: "corp:auditors"
A RealmRoleBinding can name any RealmRole in its realm, and an
OrganizationRoleBinding any OrganizationRole. Hub rejects a binding whose
role doesn't exist with a not-found error on spec.roleRef.name. As with
built-in roles, spec.roleRef.name is immutable.
Hub projects only bindings to realm-admin, realm-editor, and
realm-viewer into Spaces as ObjectRoleBindings. A RealmRoleBinding to a
custom RealmRole grants access through Hub but never reaches a Space.
Who can write or bind a role
Hub follows the Kubernetes RBAC escalation rules, so no one can give anyone, themselves included, permissions they don't hold:
- Writing a role (creating or changing an
OrganizationRole,RealmRole, orControlPlaneRole) is allowed when you already hold everything the role grants, or holdescalateon roles of that kind. Hub weighs aControlPlaneRoleas granted in every control plane of its realm. - Binding a role (creating a binding, or changing its subjects) is
allowed when you hold everything the role grants, or hold
bindon roles of that kind.
Only what your own roles grant counts as held. The basic access every
signed-in user gets and a control plane's own RBAC don't count. A grant for
every realm ("*") is covered only by an organization rule or another
"*" realm grant. A grant for every control plane is covered only by another
"*" control-plane grant in the same realm or in every realm, and organization
rules never cover control-plane grants. A ControlPlaneRole subrole that
doesn't exist, or is nested too deeply to follow, counts as granting everything
when Hub checks a write or bind.
Among the built-in roles:
- No role holds
escalate. org-adminmay write all three role kinds in any realm, and holdsbindon organization and realm roles, so it can bind any role. Because it holds no permissions inside realms, anorg-adminwriting a role with realm or control-plane grants also needs a realm role (such asrealm-admin) in the realms the role reaches.realm-adminmay writeRealmRoles andControlPlaneRoles in its realm, and bind them, as long as they grant only whatrealm-adminholds there.
A refused write or bind returns 403 Forbidden. The message, and
details.causes, list up to 20 missing permissions:
Cause type | Meaning |
|---|---|
PermissionNotHeld | A permission the role grants that you don't hold. field says where it applies: organization, realms[<realm>], or realms[<realm>].controlPlanes[<control plane>]. |
TooLargeToCompare | The role lists more than 10,000 permission combinations to compare, so Hub counts it as not held. |
RoleNotFound | The binding's role was deleted. Changing who that binding names requires bind, because a role later created under that name grants whatever it then defines. |
Bootstrap
Roles aren't bootstrappable: hub-core's bootstrap directory accepts
OrganizationRoleBinding and RealmRoleBinding but not the role kinds.
Create custom roles through the API. A bootstrap binding that names a custom
role fails, and Hub retries it on each bootstrap pass, until that role
exists.
Related resources
- Access management overview
- Roles reference: what the built-in roles grant.
- Filtering and self-review: check what a role lets a caller
see with
SelfSubjectAccessReview.