RBAC: Namespace-Scoped HolmesGPT¶
By default, HolmesGPT uses cluster-wide RBAC: the chart creates a ClusterRole and binds it with a
ClusterRoleBinding, so Holmes can read resources in every namespace.
To restrict Holmes to a specific set of namespaces you can create your own RoleBinding objects that
reuse the ServiceAccount and ClusterRole the chart already creates, and remove the cluster-wide binding.
You do not need to create a ServiceAccount or ClusterRole yourself.
What the chart already creates¶
With the default values, the Holmes chart creates these objects (<release> is your Helm release name,
usually robusta, in the release namespace):
Kind |
Name |
Purpose |
|---|---|---|
ServiceAccount |
|
Identity the Holmes pod runs as |
ClusterRole |
|
The read permissions Holmes needs (rules only) |
ClusterRoleBinding |
|
Grants the ClusterRole cluster-wide — this is what makes Holmes see every namespace |
Reuse the ServiceAccount and ClusterRole; replace the binding¶
Step 1 — Create a RoleBinding in each namespace Holmes should access. It binds the existing
ServiceAccount to the existing ClusterRole, but a RoleBinding only grants those rules inside its own
namespace:
# holmes-rolebindings.yaml (one RoleBinding per target namespace)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: holmes-namespace-scoped
namespace: default # repeat this file for "monitoring", etc.
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole # reuse the chart's ClusterRole (rules only)
name: robusta-holmes-cluster-role
subjects:
- kind: ServiceAccount
name: robusta-holmes-service-account # reuse the chart's ServiceAccount
namespace: <release-namespace>
kubectl apply -f holmes-rolebindings.yaml
Step 2 — Remove the cluster-wide binding, otherwise it still grants Holmes access to every namespace and the RoleBindings above are redundant:
kubectl delete clusterrolebinding robusta-holmes-cluster-role-binding
Holmes now has read access only in the namespaces where you created a RoleBinding.
Warning
A helm upgrade re-creates robusta-holmes-cluster-role-binding (the chart always renders it),
which restores cluster-wide access. After each upgrade, re-run the kubectl delete above, or manage
the deletion/RoleBindings through your GitOps/post-render tooling.
How it works¶
In Kubernetes RBAC a ClusterRole is only a set of permissions. On its own it grants nothing — the
binding type decides where those permissions apply:
Binding |
Effective scope |
|---|---|
|
Every namespace plus cluster-scoped resources (nodes, persistentvolumes, ...) |
|
Only namespaced resources in namespace |
A RoleBinding may reference a ClusterRole; when it does, Kubernetes applies that ClusterRole's rules
but confined to the RoleBinding's own namespace. That is why we can reuse the chart's ClusterRole without
duplicating its rules — one rule set, one thin binding per namespace. When Holmes' ServiceAccount makes a
request, the authorizer checks all ClusterRoleBindings plus the RoleBindings in the request's namespace: a
list pods in default matches the RoleBinding there and is allowed; the same request in
kube-system matches nothing and is denied.
Important
Access is limited to namespaced resources in the bound namespaces. Cluster-scoped resources
(for example nodes, persistentvolumes, cluster-level events) are no longer granted, because a
RoleBinding structurally cannot grant them — only a ClusterRoleBinding can. Tools that rely on
them (node health, cluster-wide resource views) will not work in scoped mode.
Note
kubectl auth can-i list nodes --as=<holmes-sa> -n default may return yes even though real node
access is denied. Passing -n default evaluates the check inside the default namespace, where the
RoleBinding grants the nodes rule, and the authorizer matches verb+resource without re-checking that
nodes is cluster-scoped. A real kubectl get nodes (empty namespace) matches no binding and is denied.
Verifying the scope¶
SA=system:serviceaccount:<release-namespace>:robusta-holmes-service-account
kubectl auth can-i list pods --as=$SA -n default # -> yes
kubectl auth can-i list pods --as=$SA -n monitoring # -> yes
kubectl auth can-i list pods --as=$SA -n kube-system # -> no
Tell Holmes which namespaces it can access¶
Holmes has no way to discover its own RBAC scope. A denial only ever names the resource and namespace that
were rejected ("...cannot list resource pods ... in the namespace kube-system"); it never lists the
namespaces that are allowed. Without guidance, Holmes may assume it has cluster-wide access, repeatedly
retry cluster-wide queries (kubectl get pods -A, get nodes), or mistake the Forbidden errors for
a broken cluster.
After scoping, add a global instruction telling Holmes exactly which namespaces it may use. Global instructions are account-level and are injected into Holmes' system prompt for every investigation; set them in the Robusta UI (HolmesGPT settings → global instructions). Keep the list in sync with the namespaces you bound above.
Example global instruction:
This HolmesGPT instance has namespace-scoped RBAC. You can only read Kubernetes resources in the
"default" and "monitoring" namespaces. Always scope kubectl queries with `-n default` or `-n monitoring`.
Do not run cluster-wide queries such as `kubectl get pods -A`, `kubectl get nodes`, or
`kubectl get namespaces` — they will be denied. If something you need is in another namespace, report
that it is outside your permitted scope instead of retrying.
Notes on the runner¶
The Robusta runner remains cluster-wide. To reduce the runner's permissions, use
a read-only ClusterRole via runner.overrideClusterRoles.
Fully scoping the runner to a subset of namespaces is not supported, because the runner watches
cluster-wide resources and events to function.