Skip to main content
Version: 1.2

Custom roles

note

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.

Suggested reading

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:

KindScopeBound withGrants
OrganizationRoleCluster-scoped (organization-wide)OrganizationRoleBindingHub API permissions across the organization, plus optional grants inside named realms
RealmRoleNamespaced in one realmRealmRoleBinding in that realmHub API permissions inside the realm, plus optional grants inside its control planes
ControlPlaneRoleNamespaced in one realmNot bound directlyA 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:

FieldRequiredMeaning
verbsYesOne or more of create, get, list, update, delete, admin, escalate, bind. At most 8.
apiGroupsYesHub API groups, such as fleet.hub.upbound.io. "" is the core group, whose only resource is the read-only namespaces view of realms.
resourcesYes<resource> or <resource>/<subresource>. A resource doesn't include its subresources, so controlplanes doesn't cover controlplanes/registrationtoken.
resourceNamesNoLimits 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 in apiGroups, resources, or resourceNames. 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 list or create realms, and admin is valid only on controlplanes. Hub rejects anything else and names the field. Put resources from different API groups in separate rules.
  • A rule with resourceNames can't allow list, or create on a resource itself, because neither request names an object. Creating through a subresource, such as controlplanes/registrationtoken, can be limited by name.
  • A rule inside a realm that names the realm itself (realms or namespaces) can't list resourceNames. It already applies only to that realm.

The special verbs:

  • admin on controlplanes allows changing a control plane's spec.identityProviders. update alone doesn't.
  • escalate on a role kind allows writing a role of that kind that grants more than the writer holds.
  • bind on organizationroles or realmroles allows 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.

FieldMeaning
spec.rulesHub 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.realmsRealm grants. Each grant has the rules and controlPlanes fields of a RealmRole, plus names, the realms it applies in. At most 64.
spec.realms[].namesRealm 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-.

FieldMeaning
spec.rulesHub API rules inside the realm. At most 128.
spec.controlPlanesControl-plane grants. Each grant has the rules and subroles fields of a ControlPlaneRole, plus names. At most 64.
spec.controlPlanes[].namesControl 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-.

FieldMeaning
spec.rulesKubernetes 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[].resourceNamespacesLimits the rule to these namespaces inside each control plane. Empty means every namespace.
spec.rules[].resourceNamesLimits the rule to objects with these names. Empty means every name.
spec.subrolesOther 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.

warning

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, or ControlPlaneRole) is allowed when you already hold everything the role grants, or hold escalate on roles of that kind. Hub weighs a ControlPlaneRole as 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 bind on 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-admin may write all three role kinds in any realm, and holds bind on organization and realm roles, so it can bind any role. Because it holds no permissions inside realms, an org-admin writing a role with realm or control-plane grants also needs a realm role (such as realm-admin) in the realms the role reaches.
  • realm-admin may write RealmRoles and ControlPlaneRoles in its realm, and bind them, as long as they grant only what realm-admin holds there.

A refused write or bind returns 403 Forbidden. The message, and details.causes, list up to 20 missing permissions:

Cause typeMeaning
PermissionNotHeldA permission the role grants that you don't hold. field says where it applies: organization, realms[<realm>], or realms[<realm>].controlPlanes[<control plane>].
TooLargeToCompareThe role lists more than 10,000 permission combinations to compare, so Hub counts it as not held.
RoleNotFoundThe 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.