Access Control
There are currently two modes of access control supported by kro, if you install through the Helm chart:
-
unrestricted -
aggregation
The mode is selected with a values property rbac.mode, and defaults to unrestricted.
unrestricted Access
In the unrestricted access mode, the chart includes a ClusterRole granting
kro full control to every resource type in your cluster. This can be
useful for experimenting in a test environment, where access control is not
necessary, but is not recommended in a production environment.
In this mode, anyone with access to create ResourceGraphDefinition resources,
effectively also has admin access to the cluster.
aggregation Access
In the aggregation access mode, the chart includes an aggregated ClusterRole
which dynamically includes all rules from all ClusterRoles that have the label
rbac.kro.run/aggregate-to-controller: "true".
There is a very minimal set of permissions provisioned by the chart itself, just
enough to let kro run at all: full permissions for ResourceGraphDefinitions
and its subresources, and full permissions for CustomResourceDefinitions as
kro will create them in response to the existence of an RGD.
However, this does not automatically set up permissions for kro to actually reconcile those generated CRDs! In other words, when using this mode, you will need to provision additional access for kro for every new resource type you define.
Example
If you want to create a ResourceGraphDefinition that specifies a new resource
type with kind: Foo, and where the graph includes an apps/v1/Deployment and
a v1/ConfigMap, you will need to create the following ClusterRole to ensure
kro has enough access to reconcile your resources. Include the /status
subresource of the generated kind: kro writes instance status through it, and a
grant on foos alone does not cover foos/status.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
rbac.kro.run/aggregate-to-controller: "true"
name: kro:controller:foos
rules:
- apiGroups:
- kro.run
resources:
- foos
- foos/status
verbs:
- "*"
- apiGroups:
- apps
resources:
- deployments
verbs:
- "*"
- apiGroups:
- ""
resources:
- configmaps
verbs:
- "*"
Graph and ServiceAccount impersonation
Granting a user permission to create or update Graph resources in a namespace
effectively lets them act as any ServiceAccount in that namespace that kro is
allowed to impersonate. Read this section before enabling Graph in a
multi-tenant or shared cluster.
This section applies when the GraphKind feature gate
is enabled. For what a Graph is and how to enable it, see the
Graph overview.
Unlike a cluster-scoped ResourceGraphDefinition, a Graph is a namespaced,
user-creatable kind that directly describes cluster resources. To keep that
power from running as kro's own (broad) controller identity, kro applies a
Graph's resources while impersonating a ServiceAccount:
- The identity is
system:serviceaccount:<graph-namespace>:<name>. The ServiceAccount is always resolved in the Graph's own namespace — a Graph cannot name a ServiceAccount in another namespace. spec.serviceAccountNameselects which ServiceAccount in that namespace to use. When it is unset, kro impersonates the namespace'sdefaultServiceAccount.- Every read and write the Graph performs on its resources is authorized against that ServiceAccount's RBAC. A Graph can therefore never do more than a ServiceAccount in its namespace is already granted.
- The kro controller itself also needs cluster-wide
listandwatchon the resource types a Graph manages, so it can watch them for changes. See What the kro controller itself needs.
This is the same trust model as create pod
This mirrors how Pods work in Kubernetes: anyone who can create a Pod (directly,
or via a Deployment/Job/etc.) can set spec.serviceAccountName to any
ServiceAccount in the same namespace and run as it. Being able to create or
update a Graph grants the equivalent capability.
So, concretely:
Permission to mutate
Graphin a namespace ⇒ permission to act as any ServiceAccount in that namespace that kro can impersonate.
The namespace is the trust boundary. Delete is included, since deleting a Graph tears its resources down under the same impersonated identity.
Restricting which ServiceAccounts kro may impersonate
Impersonation only works for ServiceAccounts kro itself is permitted to
impersonate. You control that with kro's own RBAC. In unrestricted mode
kro already holds every verb on every resource. In aggregation mode the chart
grants impersonate on all ServiceAccounts cluster-wide when GraphKind is
enabled; to narrow it, remove that grant and bind the verb per namespace
instead:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kro-impersonate-applier
namespace: team-payments
rules:
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["impersonate"]
# Only these ServiceAccounts in this namespace can back a Graph.
resourceNames: ["kro-applier"]
Bind that Role to the kro controller's ServiceAccount per namespace. Any
ServiceAccount kro is not granted impersonate on simply cannot be used by a
Graph, so a Graph naming it fails to apply rather than escalating. If kro has no
impersonate permission for a namespace at all, Graphs there cannot apply
anything.
What the kro controller itself needs
Impersonation covers the reads and writes of a Graph's resources, but not the
watches kro uses to notice when those resources change. Those run under the
controller's own identity and are cluster-wide, so besides impersonate the
controller needs list and watch on every resource type any Graph creates,
reads, or patches. In unrestricted mode it already has them. In aggregation
mode, add them to a ClusterRole labeled for aggregation:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
rbac.kro.run/aggregate-to-controller: "true"
name: kro:controller:graph-watches
rules:
- apiGroups: [""]
resources: ["namespaces", "services"]
verbs: ["list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies", "ingresses"]
verbs: ["list", "watch"]
Only list and watch are needed. The controller does not need create,
patch, or delete on the resource types a Graph manages. Those verbs belong
on the applier ServiceAccount, which also needs get, list, and watch on
the same resource types.
The controller's own identity is refused
The one identity namespace confinement does not naturally protect is kro's own
ServiceAccount. A Graph created in kro's namespace that resolves to the
controller's ServiceAccount (by name, or because its default is the controller
SA) would otherwise run under kro's broad identity. kro refuses such a Graph,
marking it Accepted=False (reason InvalidGraph) before it applies anything.
Any other privileged ServiceAccount reachable in a namespace remains yours to
scope via the impersonate RBAC above.
Granting users access to Graph
The chart does not add graphs to the built-in edit, admin, or view
ClusterRoles, precisely because of the trust model above. Grant it explicitly
to the users or groups who should author Graphs, scoped to a namespace where
possible:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: graph-author
namespace: team-payments
rules:
- apiGroups: ["kro.run"]
resources: ["graphs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Recommendations
- Treat
create/updateonGraphas a privileged grant, on par withcreate pod— especially in shared namespaces likekube-system. - Grant kro the
impersonateverb narrowly (per namespace, withresourceNames) rather than cluster-wide. - Keep the namespace
defaultServiceAccount minimally privileged; a Graph that does not setspec.serviceAccountNameruns as it.