From e18bd7bf0bc07d238dfb1a07a2397275015962d4 Mon Sep 17 00:00:00 2001 From: "avi@robusta.dev" Date: Thu, 9 Jul 2026 09:31:43 +0300 Subject: [PATCH 1/3] docs: restore namespace-scoped RBAC guide (holmes.roleBindingNamespaces) Re-adds the page removed just before #2110 merged, now with a 'How it works' section (ClusterRole vs binding scope, rendered RoleBinding manifest, cluster-scoped caveat, can-i nuance). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/setup-robusta/index.rst | 1 + docs/setup-robusta/rbac-namespace-scoping.rst | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 docs/setup-robusta/rbac-namespace-scoping.rst diff --git a/docs/setup-robusta/index.rst b/docs/setup-robusta/index.rst index 6c113d55a..7a5208ede 100644 --- a/docs/setup-robusta/index.rst +++ b/docs/setup-robusta/index.rst @@ -19,6 +19,7 @@ configuration-secrets openshift read-only-service-account + rbac-namespace-scoping node-selector proxies privacy-and-security diff --git a/docs/setup-robusta/rbac-namespace-scoping.rst b/docs/setup-robusta/rbac-namespace-scoping.rst new file mode 100644 index 000000000..dfa781c5e --- /dev/null +++ b/docs/setup-robusta/rbac-namespace-scoping.rst @@ -0,0 +1,119 @@ +.. _rbac-namespace-scoping: + +RBAC: Namespace-Scoped Deployments +======================================== + +By default, Robusta and HolmesGPT use cluster-wide RBAC: they are granted a ``ClusterRole`` bound with a +``ClusterRoleBinding``, so they can read (and, for the runner, act on) resources in every namespace. + +In restricted environments you may want to limit what HolmesGPT can see to a specific set of namespaces. +This guide explains how, how it works under the hood, and what the trade-offs are. + +Scoping HolmesGPT to Specific Namespaces +---------------------------------------- + +Set ``holmes.roleBindingNamespaces`` to the list of namespaces HolmesGPT is allowed to access. Instead of a +cluster-wide ``ClusterRoleBinding``, the chart then creates a namespaced ``RoleBinding`` in each listed +namespace (reusing the same ClusterRole for its rules): + +.. code-block:: yaml + + holmes: + roleBindingNamespaces: + - default + - monitoring + +When this list is empty (the default), HolmesGPT keeps its cluster-wide binding — existing installs are +unaffected. + +Apply it with a Helm upgrade (merge it into your existing values file, or pass it as an extra ``-f`` file): + +.. code-block:: bash + + helm upgrade --install robusta robusta/robusta \ + -f generated_values.yaml \ + -n + +You can combine this with a :ref:`read-only runner ` in the same values file to +get a fully restricted, audit-only deployment. + +How It Works +------------ + +In Kubernetes RBAC, a ``ClusterRole`` is only a set of permissions ("get/list/watch pods", etc.). On its +own it grants nothing — what determines **where** those permissions apply is the **binding type**, not the +role: + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Binding + - Effective scope + * - ``ClusterRoleBinding`` → ClusterRole + - Every namespace **plus** cluster-scoped resources (nodes, persistentvolumes, ...) + * - ``RoleBinding`` (in namespace ``X``) → ClusterRole + - Only namespaced resources **in namespace** ``X`` + +A ``RoleBinding`` is allowed to reference a ``ClusterRole``; when it does, Kubernetes applies that +ClusterRole's rules but confined to the RoleBinding's own namespace. HolmesGPT keeps one ClusterRole with +its read rules, and ``roleBindingNamespaces`` only changes how that role is *bound*. For +``roleBindingNamespaces: [default, monitoring]`` the chart renders one ``RoleBinding`` per namespace and no +``ClusterRoleBinding``: + +.. code-block:: yaml + + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: robusta-holmes-role-binding + namespace: "default" # a second identical binding is created in "monitoring" + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole # reuse the same rules, no duplication + name: robusta-holmes-cluster-role + subjects: + - kind: ServiceAccount + name: robusta-holmes-service-account + namespace: robusta # the ServiceAccount lives in the release namespace + +When HolmesGPT's ServiceAccount makes a request, the RBAC authorizer considers all ``ClusterRoleBinding`` +objects plus the ``RoleBinding`` objects in the request's namespace. A ``list pods`` in ``default`` matches +the RoleBinding there and is allowed; the same request in ``kube-system`` has no binding and is denied. That +is the entire scoping mechanism — the permission simply does not exist in namespaces where no binding was +created. + +.. important:: + + - The listed namespaces **must already exist**; the chart does not create them. + - Access is limited to **namespaced** resources in those 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= -n default`` may return ``yes`` even though real node + access is denied. That is a quirk of ``can-i``: passing ``-n default`` evaluates the check *inside* the + default namespace, where the RoleBinding does grant the ``nodes`` rule, and the authorizer matches + verb+resource without re-checking that ``nodes`` is cluster-scoped. A real ``kubectl get nodes`` request + uses an empty namespace, matches no binding, and is denied. + +Verifying the Scope +------------------- + +.. code-block:: bash + + SA=system:serviceaccount::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 + +Notes on the Runner +------------------- + +The Robusta runner remains cluster-wide. To reduce the runner's permissions, use +:ref:`a read-only ClusterRole ` via ``runner.overrideClusterRoles``. +Fully scoping the runner to a subset of namespaces is not supported through Helm values, because the +runner watches cluster-wide resources and events to function. From bdb40c23ae56f6c31b68fcc19fbb6bae944bb493 Mon Sep 17 00:00:00 2001 From: "avi@robusta.dev" Date: Thu, 9 Jul 2026 09:44:01 +0300 Subject: [PATCH 2/3] docs: rewrite namespace scoping as from-scratch RoleBindings (reuse chart SA + ClusterRole) Works with the released chart (no new Helm value): create RoleBindings that reuse the chart's ServiceAccount and ClusterRole, then remove the cluster-wide ClusterRoleBinding. Documents the helm-upgrade recreate caveat. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/setup-robusta/rbac-namespace-scoping.rst | 147 ++++++++++-------- 1 file changed, 81 insertions(+), 66 deletions(-) diff --git a/docs/setup-robusta/rbac-namespace-scoping.rst b/docs/setup-robusta/rbac-namespace-scoping.rst index dfa781c5e..5b8855a3d 100644 --- a/docs/setup-robusta/rbac-namespace-scoping.rst +++ b/docs/setup-robusta/rbac-namespace-scoping.rst @@ -1,48 +1,86 @@ .. _rbac-namespace-scoping: -RBAC: Namespace-Scoped Deployments +RBAC: Namespace-Scoped HolmesGPT ======================================== -By default, Robusta and HolmesGPT use cluster-wide RBAC: they are granted a ``ClusterRole`` bound with a -``ClusterRoleBinding``, so they can read (and, for the runner, act on) resources in every namespace. +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. -In restricted environments you may want to limit what HolmesGPT can see to a specific set of namespaces. -This guide explains how, how it works under the hood, and what the trade-offs are. +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. -Scoping HolmesGPT to Specific Namespaces ----------------------------------------- +What the chart already creates +------------------------------ -Set ``holmes.roleBindingNamespaces`` to the list of namespaces HolmesGPT is allowed to access. Instead of a -cluster-wide ``ClusterRoleBinding``, the chart then creates a namespaced ``RoleBinding`` in each listed -namespace (reusing the same ClusterRole for its rules): +With the default values, the Holmes chart creates these objects (```` is your Helm release name, +usually ``robusta``, in the release namespace): + +.. list-table:: + :header-rows: 1 + :widths: 30 30 40 + + * - Kind + - Name + - Purpose + * - ServiceAccount + - ``-holmes-service-account`` + - Identity the Holmes pod runs as + * - ClusterRole + - ``-holmes-cluster-role`` + - The read permissions Holmes needs (rules only) + * - ClusterRoleBinding + - ``-holmes-cluster-role-binding`` + - 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: .. code-block:: yaml - holmes: - roleBindingNamespaces: - - default - - monitoring + # 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: + +.. code-block:: bash -When this list is empty (the default), HolmesGPT keeps its cluster-wide binding — existing installs are -unaffected. + kubectl apply -f holmes-rolebindings.yaml -Apply it with a Helm upgrade (merge it into your existing values file, or pass it as an extra ``-f`` file): +Step 2 — Remove the cluster-wide binding, otherwise it still grants Holmes access to every namespace and the +RoleBindings above are redundant: .. code-block:: bash - helm upgrade --install robusta robusta/robusta \ - -f generated_values.yaml \ - -n + kubectl delete clusterrolebinding robusta-holmes-cluster-role-binding + +Holmes now has read access only in the namespaces where you created a RoleBinding. -You can combine this with a :ref:`read-only runner ` in the same values file to -get a fully restricted, audit-only deployment. +.. warning:: -How It Works + 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 ("get/list/watch pods", etc.). On its -own it grants nothing — what determines **where** those permissions apply is the **binding type**, not the -role: +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: .. list-table:: :header-rows: 1 @@ -55,51 +93,28 @@ role: * - ``RoleBinding`` (in namespace ``X``) → ClusterRole - Only namespaced resources **in namespace** ``X`` -A ``RoleBinding`` is allowed to reference a ``ClusterRole``; when it does, Kubernetes applies that -ClusterRole's rules but confined to the RoleBinding's own namespace. HolmesGPT keeps one ClusterRole with -its read rules, and ``roleBindingNamespaces`` only changes how that role is *bound*. For -``roleBindingNamespaces: [default, monitoring]`` the chart renders one ``RoleBinding`` per namespace and no -``ClusterRoleBinding``: - -.. code-block:: yaml - - apiVersion: rbac.authorization.k8s.io/v1 - kind: RoleBinding - metadata: - name: robusta-holmes-role-binding - namespace: "default" # a second identical binding is created in "monitoring" - roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole # reuse the same rules, no duplication - name: robusta-holmes-cluster-role - subjects: - - kind: ServiceAccount - name: robusta-holmes-service-account - namespace: robusta # the ServiceAccount lives in the release namespace - -When HolmesGPT's ServiceAccount makes a request, the RBAC authorizer considers all ``ClusterRoleBinding`` -objects plus the ``RoleBinding`` objects in the request's namespace. A ``list pods`` in ``default`` matches -the RoleBinding there and is allowed; the same request in ``kube-system`` has no binding and is denied. That -is the entire scoping mechanism — the permission simply does not exist in namespaces where no binding was -created. +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:: - - The listed namespaces **must already exist**; the chart does not create them. - - Access is limited to **namespaced** resources in those 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. + 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= -n default`` may return ``yes`` even though real node - access is denied. That is a quirk of ``can-i``: passing ``-n default`` evaluates the check *inside* the - default namespace, where the RoleBinding does grant the ``nodes`` rule, and the authorizer matches - verb+resource without re-checking that ``nodes`` is cluster-scoped. A real ``kubectl get nodes`` request - uses an empty namespace, matches no binding, and is denied. + 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 +Verifying the scope ------------------- .. code-block:: bash @@ -110,10 +125,10 @@ Verifying the Scope kubectl auth can-i list pods --as=$SA -n monitoring # -> yes kubectl auth can-i list pods --as=$SA -n kube-system # -> no -Notes on the Runner +Notes on the runner ------------------- The Robusta runner remains cluster-wide. To reduce the runner's permissions, use :ref:`a read-only ClusterRole ` via ``runner.overrideClusterRoles``. -Fully scoping the runner to a subset of namespaces is not supported through Helm values, because the -runner watches cluster-wide resources and events to function. +Fully scoping the runner to a subset of namespaces is not supported, because the runner watches +cluster-wide resources and events to function. From 4c11b7451549c71d7a2cdf55559fca79d3891a75 Mon Sep 17 00:00:00 2001 From: "avi@robusta.dev" Date: Thu, 9 Jul 2026 09:56:18 +0300 Subject: [PATCH 3/3] docs: tell Holmes its allowed namespaces via a global instruction Forbidden errors only name the denied namespace, never the allowed ones, so Holmes cannot infer its RBAC scope. Add a section + example global instruction listing the bound namespaces. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/setup-robusta/rbac-namespace-scoping.rst | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/setup-robusta/rbac-namespace-scoping.rst b/docs/setup-robusta/rbac-namespace-scoping.rst index 5b8855a3d..f2b73940f 100644 --- a/docs/setup-robusta/rbac-namespace-scoping.rst +++ b/docs/setup-robusta/rbac-namespace-scoping.rst @@ -125,6 +125,30 @@ Verifying the scope 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: + +.. code-block:: text + + 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 -------------------