Kubernetes RBAC
How roles and assignments are written into each cluster's Kubernetes RBAC, and why a release can need a cluster administrator once.
Access control has two enforcement paths. Calls to the platform API are decided by the RBAC service. Calls straight to a cluster's Kubernetes API server are decided by Kubernetes' own RBAC, which the platform writes from the same roles and assignments — so that a person or service principal signing in to the API server with their platform identity gets rights that match their roles.
What a role becomes
Each action in the platform's catalogue can carry Kubernetes rules; kernel/k8sChannel/read, for
example, carries get, list and watch on the platform's own resources. A role's Kubernetes
rights are the rules of the control-plane actions it grants, minus those of its NotActions; data
actions are not written into Kubernetes. Built-in roles can add rules of their own on top: Owner,
Contributor, Reader and the three platform roles carry rules on pods, deployments, services, jobs
and similar core resources.
- Built-in roles become ClusterRoles, written by the platform.
- Custom roles become namespaced Roles, only in the namespaces where they are assigned.
Caution
Some of these rules give more in Kubernetes than the role gives in the portal:
- A shell in every pod the binding covers (
pods/exec): Owner, Contributor, Platform Owner, Platform Contributor, Apps Operator and Blueprints Operator. Apps Operator and Blueprints Operator carry it because their file browsing and terminals reach the pod through it.- Secret values (
secrets), which Kubernetes returns to anyone who maygeta Secret. Platform Owner and Platform Contributor may read and change every Secret in every namespace, the platform's own andkube-systemincluded. Any role whose actions includekernel/k8sChannel/readSecretMetadata— directly or through a wildcard such as*/*orkernel/*— mayget,listandwatchSecrets in the namespaces it is bound in; Owner and Contributor are such roles.Treat an assignment of these roles at the root, a boundary or a resource group as including those rights.
What an assignment becomes
| Scope of the assignment | Kubernetes binding |
|---|---|
/ |
A ClusterRoleBinding — the role applies in every namespace of every cluster |
| A boundary | A RoleBinding in each of the boundary's namespaces |
| A resource group | A RoleBinding in the resource group's namespace |
| A resource | None — resource scope exists only in the platform API |
An active just-in-time grant is written like an assignment and removed when it expires or is
revoked. Custom roles are never bound at /: a root binding applies in every namespace,
including the platform's own and kube-system, so it is limited to the built-in roles.
Users and service principals appear to Kubernetes as the user oidc: followed by their principal
ID, and groups as a Kubernetes group named with the group's ID. The prefix is the module setting
Rbac__Reconciler__OidcUsernamePrefix and must match the API server's username prefix. The
installer writes what an API server needs to accept platform sign-ins into the ConfigMap
stackship-kubeapi-oidc; putting it on the control-plane nodes is up to you:
kubectl -n stackship-system get configmap stackship-kubeapi-oidc -o yamlDeny assignments
Kubernetes RBAC cannot deny. A deny assignment can therefore only leave a role's binding out of a cluster, and it does so only when all of these hold:
- the deny names the principal the binding is for. Groups are not expanded: a deny on a user leaves the bindings the user has through a group in place;
- the deny applies at the binding's scope;
- the deny covers every action and every data action the role grants. A role's wildcard is
covered only by the same or a wider one, so Owner's
*/*needs*/*in both the deny's actions and its data actions.
Anything less is enforced by the platform API but not by Kubernetes: the principal keeps the Kubernetes rights of the role. Active just-in-time grants are written to Kubernetes whatever denies apply to them.
Caution
A deny created in the portal has control-plane actions only, so it never removes the binding of a role that has data actions — Owner, Contributor, Reader, Platform Owner, Platform Contributor, Apps Operator and Blueprints Operator among them. Someone denied everything in the portal still has these roles' full Kubernetes rights, including the shells and Secret reads above. To take them away in Kubernetes, remove the role assignment — or create the deny through the API or the CLI with data actions that cover the role's as well, naming the principal each binding is for.
The permission ceiling
The platform writes these objects as a dedicated identity, the ServiceAccount
stackship-iam-projector in stackship-system, bound to a ClusterRole of the same name —
its ceiling: every rule the catalogue can express. Kubernetes only lets an identity write a
role containing rules it holds itself, so the ceiling is what the platform can ever grant, and the
platform cannot widen its own ceiling.
When a release adds rules on a resource the ceiling does not cover yet, a cluster administrator has to widen it once:
- Open Lifecycle Manager and its After upgrading tab. A cluster in the state Needs a cluster administrator shows the rules it is missing and the commands to run.
- Run those commands once with a kubeconfig that holds cluster-admin.
- Choose Apply to all clusters again. It runs with your own credentials and is recorded against your account; applying again is safe.
View the manifest downloads the full set of objects a cluster needs, for review or to apply
to a new cluster by hand. Reading it needs rbac/projector/read and applying it
rbac/projector/apply, both at the root.