How to Create Custom Platform Role
TOC
1. Overview2. Core Concepts3. Technical Changes4. Choosing a Method5. Configuration5.1 Method A: FunctionResource5.2 Method B: ClusterRole aggregation (independent custom role)5.3 Method C: customRules5.4 Field Reference5.5 Verify a configuration actually takes effectCheck the RoleTemplate statusConfirm a user actually gets the permissionCheck whether an aggregation label is usable before writingaggregationRules5.6 Update and delete6. Copy & Trim (Examples)6.1 Trim a system RoleTemplate YAML (Method A)6.2 Build a multi-scope auditor role (Method B)1. Overview
This document explains the core concepts of custom roles and how to configure a RoleTemplate. There are two main ways to build a working custom role — pick either and follow it end to end:
- FunctionResource method (5.1): select permissions from the product's built-in permission catalog. Simplest option, no RBAC authoring required.
- ClusterRole aggregation method (5.2): author your own native
ClusterRoleand pull it into a RoleTemplate via label matching. More setup, but works independently of what any product module has migrated, and is the platform's long-term direction.
customRules (5.3) is a third, lower-level option for one-off native RBAC rules.
Not sure which to pick? Start with Method A (5.1) — it's the simplest and covers almost every need. Choose Method B (5.2) only when you specifically need a role built from your own ClusterRole, or one that must span multiple clusters.
Once a RoleTemplate exists, grant it to users via UserBinding — see Manage Roles for the console steps.
2. Core Concepts
- RoleTemplate: Custom role template. It defines role semantics and permission sets, and is converted by the controller into ClusterRoles.
- FunctionResource: An abstraction of K8s resources used by product features; referenced by
functionResourceRefin RoleTemplate. - ClusterRole: RBAC rule set. A hand-written ClusterRole can be aggregated into a RoleTemplate (or into a built-in system role) via labels.
- UserBinding: Binding between users and roles/scopes; the controller generates RoleBinding/ClusterRoleBinding based on UserBinding for final authorization.
- The value of
functionResourceRefcomes from FunctionResourcemetadata.name. - Display names are typically in
metadata.annotations, which you can use to map to UI modules.
3. Technical Changes
Since ACP v4.3, feature permissions are gradually migrating from FunctionResource to native K8s ClusterRole management, aggregated into system roles via ClusterRole labels; FunctionResource-based management will be retired in v4.5. You don't need this background to create a role — skip to section 5 for the steps.
The migration is still in progress: most product modules haven't published aggregation-ready ClusterRoles yet, so the built-in system labels mentioned in 5.4 (aggregate-to-namespace-developer, etc.) resolve to nothing today. Don't build a custom role by pointing aggregationRules at one and hoping — 5.2 shows the pattern that works, and 5.5 lets you verify. (You may also see hidden legacy-* RoleTemplates in kubectl output; these are compatibility bridges for the built-in roles and can be ignored.)
4. Choosing a Method
All three methods produce a working RoleTemplate; they differ in what you author and what they can express.
5. Configuration
Always set rbac.cpaas.io/version: "v2" and auth.cpaas.io/roletemplate.frozen: "false" on a custom RoleTemplate. For custom roles the controller auto-fills a missing version with v1 (which ignores aggregationRules) and a missing frozen with true (which stops later spec edits from updating the ClusterRoles), handling the two independently — so setting only one still leaves the other at the legacy default. The examples below also set auth.cpaas.io/roletemplate.official: "false" (optional, but never set it to "true" on a custom role — that flips it to a built-in system role) and auth.cpaas.io/roletemplate.level, which decides where the role is granted — pick the smallest that fits: namespace (per namespace), project (all namespaces in a project), cluster (a whole cluster), or platform (the whole platform).
5.1 Method A: FunctionResource
Use this when the permissions you need are already exposed as FunctionResources — this is true for almost all current product modules.
Step 1 — find the FunctionResources you need
metadata.name is what you'll put in functionResourceRef; the display-name annotation tells you which UI feature it maps to.
Step 2 — write the RoleTemplate
Step 3 — apply and verify with 5.5.
acp-namespace-resource-manage is not allowed in custom roles.
5.2 Method B: ClusterRole aggregation (independent custom role)
Unlike the FunctionResource method, aggregationRules doesn't define permissions by itself — it only pulls rules in from existing ClusterRoles that carry matching labels. To build a genuinely independent custom role this way, you provide both halves yourself: the ClusterRole with real rules, and the label that connects it to your RoleTemplate.
Step 1 — write a native ClusterRole with the real permissions, and label it with your own aggregation label plus a scope label. Don't reuse the built-in system-role labels (5.4) unless you deliberately want to grant these permissions to everyone who already holds that built-in role — for an independent role, invent your own label name.
Step 2 — if this role needs to work in business clusters too, and the ClusterRole above was only created in the global cluster, add rbac.cpaas.io/sync.scope: "platform" so the platform replicates it to every cluster. Without this label the ClusterRole only exists wherever you created it, and the RoleTemplate below will resolve to empty permissions on any cluster that doesn't have it.
Step 3 — create the RoleTemplate, referencing the same labels via aggregationRules:
To cover more than one scope (e.g. also cluster or system-ns), add one ClusterRole per scope with the matching scope label, and one more entry under aggregationRules per scope — see 6.2 for a worked multi-scope example.
Step 4 — apply and verify with 5.5. An empty result means either the labels on the ClusterRole and in clusterRoleSelectors don't actually match, or the ClusterRole hasn't synced to the cluster you're checking (step 2).
Don't point aggregationRules at a system/product label you hope exists (e.g. rbac.cpaas.io/aggregate-to-namespace-auditor) — most modules haven't published theirs yet, so it resolves to nothing and the role silently grants no permissions. Your own ClusterRole plus your own label, as above, always works.
5.3 Method C: customRules
For a one-off native RBAC rule that doesn't warrant a separate ClusterRole object, inline it directly in the RoleTemplate:
customRules are written only into the role's base ClusterRole — they can't be targeted at a specific scope like cluster or system-ns (there is no scope field on customRules). If you need cross-scope rules, use Method A or Method B.
5.4 Field Reference
The first table covers metadata and the labels every custom role needs; the second covers the permission fields, grouped by the method they belong to (5.1–5.3).
Metadata and labels (on every custom RoleTemplate)
Permission fields (use the set for your method)
- Use either direct rules (
spec.rules/spec.customRules) orspec.aggregationRulesfor a given scope, not both — where they overlap on the same generated ClusterRole, the aggregation rule wins and the direct rules are dropped. auth.cpaas.io/roletemplate.level(where the role is granted —platform/cluster/project/namespace) andspec.aggregationRules[].scope(which generated ClusterRole each aggregated rule lands in) are different things. The controller enforces one constraint between them:platform- andcluster-level roles may only usescope: cluster;project- andnamespace-level roles may use any of the five scopes (includingcluster). Breaking this rule puts the RoleTemplate into aFailedstate.
Labels for matchLabels
For an independent custom role, invent your own aggregation label — any rbac.cpaas.io/aggregate-to-<name> string works (5.2). Pair it with one scope label, which controls where the rules apply:
rbac.cpaas.io/aggregate-to-scope-cluster: "true"rbac.cpaas.io/aggregate-to-scope-project-ns: "true"rbac.cpaas.io/aggregate-to-scope-business-ns: "true"rbac.cpaas.io/aggregate-to-scope-system-ns: "true"rbac.cpaas.io/aggregate-to-scope-kube-public: "true"
To instead add permissions to a built-in system role (advanced — this grants them to everyone who already holds that role), label your ClusterRole with rbac.cpaas.io/aggregate-to-<built-in-role>, e.g. aggregate-to-namespace-developer or aggregate-to-platform-admin.
Multi-cluster distribution (5.2 step 2): add rbac.cpaas.io/sync.scope: "platform" to a ClusterRole you create by hand to replicate it from the global cluster to all clusters. It has no effect on a RoleTemplate or on the ClusterRoles a RoleTemplate generates.
5.5 Verify a configuration actually takes effect
Check the RoleTemplate status
The RoleTemplate reports what it generated and whether it synced — start here:
status.phase should be Ready; status.clusterRoles lists the ClusterRoles it produced; status.conditions carries the reason/message when something fails. If the phase isn't Ready, status.clusterRoles is empty, or the ClusterRoles it names have empty rules (kubectl get clusterrole <name> -o yaml), the role isn't granting what you expect. Common causes, in rough order of frequency:
- The RoleTemplate is missing
rbac.cpaas.io/version: "v2", or was auto-frozen — see the required-labels caution in section 5. This is the most frequent reasonaggregationRulesappears to do nothing. - A
functionResourceRefname is misspelled — the controller skips unknown references silently and produces an empty ClusterRole with no error. - Your
aggregationRuleslabels don't match any ClusterRole, or the source ClusterRole hasn't synced to this cluster.
Confirm a user actually gets the permission
Generating ClusterRoles isn't the end goal — a user having access is. After granting the role via UserBinding (see Manage Roles), confirm the real outcome with an impersonated check:
Check whether an aggregation label is usable before writing aggregationRules
Before referencing an rbac.cpaas.io/aggregate-to-* label in clusterRoleSelectors, confirm it actually resolves to a ClusterRole:
- No output → no ClusterRole carries this label. If you're using your own custom label (5.2), double-check the label was actually applied to your ClusterRole and that it synced to this cluster. If you're using a system label (5.4) hoping a product module published it, it likely hasn't — see 3.
- Output shows a
legacy-*value forauth.cpaas.io/roletemplate→ this ClusterRole comes from the FunctionResource-based compatibility bridge described in 3, not from a module that has actually migrated. The permissions still originate from FunctionResource, and this bridge is expected to disappear in v4.5.
5.6 Update and delete
- Update: edit the RoleTemplate
spec. Changes only take effect whileauth.cpaas.io/roletemplate.frozenis"false"(see the caution in section 5). - Delete: delete the RoleTemplate. The controller garbage-collects the ClusterRoles it generated across all clusters — you don't clean those up by hand. (A ClusterRole you hand-authored for Method B is yours to delete.)
6. Copy & Trim (Examples)
Two worked examples: trimming a built-in template (Method A, 6.1), and building a multi-scope role from your own ClusterRoles (Method B, 6.2). For a FunctionResource role, copying a built-in system template and trimming it is the quickest way to avoid missing module permissions.
6.1 Trim a system RoleTemplate YAML (Method A)
Steps:
- Keep the required FunctionResources.
- Reduce verbs to read-only (get/list/watch).
- Remove unused modules.
Role example: developer auditor without secret permissions
- In custom roles, do not configure Namespace Resource Management (FunctionResource:
acp-namespace-resource-manage). - Remove User Secret Dictionary (FunctionResource:
acp-user-secret). - Set all verbs to get/list/watch.
Example YAML:
6.2 Build a multi-scope auditor role (Method B)
This is the aggregation-method equivalent of 6.1: a namespace-level read-only role that spans both the business and project namespaces, built from your own ClusterRoles and your own label — following the pattern from 5.2.
ClusterRoles — one per scope you want to cover, all sharing the same custom label, each with its own scope label:
RoleTemplate — one aggregationRules entry per scope, reusing the same custom label:
Add system-ns the same way if you need it, and remember rbac.cpaas.io/sync.scope: "platform" on each ClusterRole (5.2 step 2) if it's only created in the global cluster but the role must work in business clusters too.