Skip to main content
When a Cloud user clicks into a cluster, the control plane injects their identity onto the request and the in-cluster Radar pod (running with --auth-mode=proxy) enforces it on the K8s API. Two paths, both anchored to your in-cluster RBAC:
  • Direct resource access is impersonated. Live reads (pods, nodes, etc.), writes, and exec/logs are all sent to the API server impersonated as the user - Kubernetes itself authorizes and enforces them against your in-cluster RBAC. Radar adds no authorization on this path.
  • Cached timeline/topology is SubjectAccessReview-authorized, then filtered. The surfaces Radar serves from its own cache (timeline, topology) can’t be impersonated per object, so for each cached object Radar runs a SubjectAccessReview against the user’s identity and filters the results to what that identity may read.
Either way, your in-cluster Kubernetes RBAC is the source of truth - not a custom permission system.

The header contract

For every request through the tunnel, the control plane:
  1. Strips inbound X-Forwarded-* to prevent spoofing.
  2. Injects:
    • X-Forwarded-User: <user_id> (e.g. user_01HF...)
    • X-Forwarded-Groups: a comma-separated list of the groups below.
The groups header can carry any of these, and you can bind K8s RBAC to any of them: The radar:idp:* and radar:email:* groups are the hooks the governance examples bind - they come straight from your IdP assertion, so they exist independent of this chart’s tier bindings. The same set is also emitted under the legacy cloud: prefix (deprecated, kept so pre-rename bindings keep working; write new bindings against radar:*). The in-cluster Radar (under RADAR_CLOUD_MODE=true) is forced to --auth-mode=proxy with these header names pinned. The chart enforces this; you can’t override it without un-cloud-mode-ing the in-cluster Radar.

The default RBAC

The chart ships three ClusterRoleBindings out of the box: K8s itself decides what each ClusterRole permits. view is read-only for namespaced resources. edit adds writes. admin adds RBAC operations.

Cluster-scoped resources need an extra grant

The built-in view / edit / admin roles are namespace roles - by Kubernetes design they grant nothing on most cluster-scoped resources: So a tier bound only to a built-in role can’t see Nodes, PVs, storage, or ClusterTrustBundles - core infrastructure and trust metadata used by an observability tool. Radar’s chart closes this with a cluster-read add-on, on by default per tier (cloud.defaultRbac.clusterScopedRead.{viewer,member,owner}, each true). It grants get/list/watch on the kinds above - no Secrets, no RBAC objects, and no PodCertificateRequests.

Pod certificate requests are opt-in

Kubernetes 1.37 serves namespaced PodCertificateRequest resources, and Radar can display them in the generic resource browser. The default Cloud tier bindings deliberately do not grant access: certificate-request metadata is more sensitive than ordinary workload inventory. A user sees these resources only where their own Kubernetes RBAC grants get/list access. To opt in, bind a Role or ClusterRole granting get/list/watch on certificates.k8s.io/podcertificaterequests to the intended radar:viewer, radar:member, or radar:owner group. Prefer a namespaced Role when users need access only within a specific workload boundary. Cluster-read is its own axis, separate from which namespaced ClusterRole a tier binds to:
  • Turn it off for a tier to remove this add-on’s binding. With the default built-in mappings that leaves the tier namespaced-only: clusterScopedRead.viewer: false leaves viewers on the stock view (namespaced read) with no cluster-scoped visibility - the usual choice for a low-trust viewer tier (see security-conscious setups). With a custom *ClusterRole, the tier’s cluster-scoped reach is whatever that role grants - the flag only controls our binding.
  • Swapping a tier’s namespaced role does not disable cluster-read. viewerClusterRole/memberClusterRole/ownerClusterRole and clusterScopedRead.* are independent bindings. If you want a custom namespaced role and no infra-read, set that tier’s clusterScopedRead to false too.
  • A tier you’ve disabled entirely (viewer: false) gets nothing here either - you own that tier’s RBAC.
The add-on covers the core cluster-scoped kinds above. Cluster-scoped CRDs that appear in topology (Karpenter NodePool/NodeClaim/*NodeClass, GatewayClass, Cilium cluster policies, …) are not included - grant those per the CRD’s group if your tiers need them. Owner node cordon/drain is a separate cluster-scoped write and is off by default - enable with cloud.defaultRbac.nodeOps: true.

Overriding the defaults

The chart’s cloud.defaultRbac.* block lets you swap each binding:
To disable a binding entirely (e.g. you want only owners to have access, no members or viewers):
Members and viewers will then hit 403s when they try to do anything in the cluster. The Cloud UI still shows the cluster in the list - it’s the K8s API that’s saying no.

Locking down for security-conscious orgs

The defaults optimize for “an observability tool should show infrastructure”: every tier gets cluster-scoped read out of the box, and writes still flow through each tier’s K8s role. If your security posture is tighter, these are the levers - each is a small values.yaml change, no custom roles required: Make the viewer tier namespaced-only (most common). Viewers keep read access to the namespaces their K8s identity allows, but cannot enumerate Nodes, PVs, or storage cluster-wide:
No cluster-scoped read for anyone - every tier is limited to its namespaced built-in role:
Non-admin owners. The default owner → admin reads Secrets and mutates RBAC in every namespace (namespace-scoped, not cluster-wide). Point owners at a tighter custom ClusterRole; cluster-read stays independent, so you keep infra visibility unless you also disable it:
Keep cluster-scoped write off. Node cordon/drain (nodeOps) is already false by default. Leave it off unless owners genuinely operate nodes from Radar - K8s RBAC cannot scope patch nodes to just the cordon field (it also permits relabeling/tainting nodes) and cannot scope pods/eviction to a single node’s pods (it permits evicting any pod in any namespace; PodDisruptionBudgets bound the impact only where they exist). What you cannot do through values.yaml is change which cluster-scoped kinds the add-on grants (it’s a fixed, Secret-free infra set). If you need a different set for a tier - say Nodes but not PVs - set that tier’s clusterScopedRead to false and bind your own ClusterRole to the radar:<tier> group (recipe below). The radar:* groups exist independent of this chart, so your binding composes with the built-in namespaced one.

Choosing a governance model

There are two coherent ways to combine the tier defaults with group-based bindings. Both are fully supported; they differ in where cluster access is governed, and you can move from the first to the second at any time. Mixed (the default): tiers for your operators, IdP groups for scoped access. Your platform team holds Owner/Member and rides the default tier bindings - cluster-wide access through the upstream-maintained admin/edit roles, zero binding authoring. External or namespace-scoped users (vendor teams, contractors) get their tier binding disabled (viewer: false) and access exclusively through radar:idp:<group> / radar:user:<id> bindings you author. Each population sits on the mechanism built for it. The tradeoff: two systems of record - operator access is governed by Radar Cloud membership roles (visible in the Cloud UI and audit log), scoped access by your IdP groups and RBAC manifests. Full-IdP: every human’s cluster access flows through your IdP groups. For organizations whose access reviews require a single source of truth, disable all three tier bindings and bind your own groups end to end:
Now every grant - operator and vendor alike - is a binding referencing one of your IdP groups: one review process, one place to look, and offboarding is group removal in your IdP. Two things to keep straight in full-IdP mode:
  • Cloud roles don’t disappear - they govern the product, not the cluster. Owner/Member/Viewer still control billing, member management, SSO, cluster lifecycle, and owner-only product surfaces. The mental model: Cloud role = control-plane permissions; Kubernetes bindings = cluster access. The tier bindings are merely the default cluster access, and they’re optional.
  • The add-on grants ride the tier bindings. Cluster-scoped read, nodeOps, and the Helm add-on roles bind to tier groups - with all tiers disabled, none of them render, and you own those grants in your own ClusterRoles (see the recipe above for the cluster-read set).

Per-user bindings

If you need finer control than role-level (e.g. specific user can edit deployments but only in apps/* namespaces), bind directly to the per-user group:
Find a user’s user_id in:
  • The audit log row metadata.
  • The user’s PAT page (their own ID is shown at the top).
  • Owner’s view of Settings → Organization → Members (hover the row).

Per-org bindings

Every request also carries radar:org:<org_id> in the groups header. This is mostly useful for clusters that move between orgs - typically you wouldn’t need to bind to it directly. But if you ever need “any user from this org can do X”, that’s the hook.

Auditing what the in-cluster Radar did

K8s itself logs impersonated requests in audit.k8s.io if you’ve enabled the audit policy on the apiserver. The user ID and groups are present in the audit log entry, so you can correlate “user X did Y” against the Cloud audit log (which shows control-plane actions) and the K8s audit log (which shows in-cluster actions).

I can’t see Nodes / PVs / StorageClasses

If a tier can’t see cluster-scoped resources, its bound role doesn’t grant them. Most likely:
  • That tier’s clusterScopedRead is off - set cloud.defaultRbac.clusterScopedRead.<tier>: true (the default).
  • You’re on an older chart that predates the cluster-read add-on - upgrade, or grant the reads directly (below).
To grant cluster-scoped read directly (works on any chart version), bind the tier group to a ClusterRole. Give it a distinct name - the chart already renders a ClusterRole named radar-cluster-read, so reusing that name would make kubectl apply overwrite the Helm-owned object. Use radar-cluster-read-custom:

Things to watch out for

  • Default admin is broad within namespaces, but not cluster-wide. It reads Secrets and mutates RBAC in every namespace, but as a namespace role it grants nothing cluster-scoped on its own - no Node read, no node drain (that’s the opt-in nodeOps add-on), no PV/StorageClass. If you have non-admin owners, swap radar:owner to a custom ClusterRole.
  • view doesn’t see Secrets. That’s K8s view definition, not ours. If you want viewers to see Secret names without values, use a custom ClusterRole that grants get/list on secrets (which lets them see Secret data too - K8s doesn’t have field-level RBAC). Since Secret data is redacted by Radar before display, this is usually safe but think it through.
  • Built-in roles are aggregation targets. view/edit/admin are auto-reconciled and accept aggregate-to-* labels, so third-party charts (metrics-server, kyverno, some operators) can widen what a tier effectively holds - the tier’s scope drifts with cluster contents, not just with this chart. Audit with kubectl auth can-i --list --as-group=radar:viewer --as=x if you need certainty.
  • Helm write needs more than edit. Helm install / upgrade / rollback / uninstall need broad write access, including Secrets (Helm releases are Secrets). The chart’s default member → edit is intentionally not enough for Helm writes; opt in via a custom binding if you want members to manage Helm.

See also