kube-state-metrics only reads Kubernetes objects — it never writes, patches, or deletes anything — so its ClusterRole is entirely list and watch verbs. This guide explains every rule in that ClusterRole, when to use a namespaced Role instead, how to scope permissions down with --resources, and how to read and fix the Forbidden errors that show up after upgrades.

The Annotated ClusterRole

Each rule below grants read access to exactly the object types a specific set of KSM collectors needs. If you disable a collector with --resources, you can remove the matching rule.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kube-state-metrics
rules:
  # Core API group ("") — covers the pod, node, service and
  # config collectors. This is the largest rule and the one
  # most often hit by "Forbidden" errors after an upgrade.
  - apiGroups: [""]
    resources:
      - configmaps
      - secrets
      - nodes
      - pods
      - services
      - serviceaccounts
      - resourcequotas
      - replicationcontrollers
      - limitranges
      - persistentvolumeclaims
      - persistentvolumes
      - namespaces
      - endpoints
    verbs: ["list", "watch"]

  # Workload controllers — Deployments, ReplicaSets, DaemonSets,
  # StatefulSets. Required for kube_deployment_* / kube_statefulset_*.
  - apiGroups: ["apps"]
    resources: [statefulsets, daemonsets, deployments, replicasets]
    verbs: ["list", "watch"]

  # Batch workloads — needed for kube_job_* / kube_cronjob_*.
  - apiGroups: ["batch"]
    resources: [cronjobs, jobs]
    verbs: ["list", "watch"]

  # HPA collector — kube_horizontalpodautoscaler_*.
  - apiGroups: ["autoscaling"]
    resources: [horizontalpodautoscalers]
    verbs: ["list", "watch"]

  # Ingress/NetworkPolicy collectors.
  - apiGroups: ["networking.k8s.io"]
    resources: [ingresses, networkpolicies]
    verbs: ["list", "watch"]

  # StorageClass/VolumeAttachment collectors.
  - apiGroups: ["storage.k8s.io"]
    resources: [storageclasses, volumeattachments]
    verbs: ["list", "watch"]

  # PodDisruptionBudget collector — commonly forgotten when
  # people hand-write a ClusterRole instead of using the Helm chart.
  - apiGroups: ["policy"]
    resources: [poddisruptionbudgets]
    verbs: ["list", "watch"]

  # CertificateSigningRequest collector.
  - apiGroups: ["certificates.k8s.io"]
    resources: [certificatesigningrequests]
    verbs: ["list", "watch"]
💡
The Helm chart generates and maintains this ClusterRole for you, including new rules added for new collectors in each release. Hand-maintaining your own copy is the single biggest cause of RBAC drift after upgrades — prefer helm upgrade over manually edited YAML.

Role vs ClusterRole: Namespaced Installs

If you run KSM with --namespaces=team-a,team-b to restrict it to specific namespaces (common in multi-tenant clusters), you don't need a cluster-scoped ClusterRole + ClusterRoleBinding at all — a namespaced Role + RoleBinding per target namespace is more secure and satisfies most compliance requirements:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: kube-state-metrics
  namespace: team-a
rules:
  - apiGroups: [""]
    resources: [pods, services, configmaps, resourcequotas, persistentvolumeclaims]
    verbs: ["list", "watch"]
  - apiGroups: ["apps"]
    resources: [deployments, replicasets, statefulsets, daemonsets]
    verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kube-state-metrics
  namespace: team-a
subjects:
  - kind: ServiceAccount
    name: kube-state-metrics
    namespace: monitoring
roleRef:
  kind: Role
  name: kube-state-metrics
  apiGroup: rbac.authorization.k8s.io

Note the cluster-scoped resources — nodes, namespaces, persistentvolumes, storageclasses — cannot be granted through a namespaced Role. If your allowlisted metrics include any of those collectors, you still need a (much smaller) ClusterRole covering just those cluster-scoped types alongside the per-namespace Roles.

Scoping Down With --resources

Every rule you never use is unnecessary attack surface. If you only need Pod and Deployment metrics, disable the rest and delete the matching ClusterRole rules:

--resources=pods,deployments
--resources valueClusterRole rules still required
podsCore API group only (pods)
pods,deployments,replicasetsCore + apps
pods,horizontalpodautoscalersCore + autoscaling
certificatesigningrequestscertificates.k8s.io only

Diagnosing Forbidden Errors

The most common RBAC failure shows up in the pod logs as a Forbidden error, usually right after a Kubernetes upgrade that introduced a new API group KSM's ClusterRole hasn't been updated to cover:

E0101 12:00:00.000000  1 reflector.go:138: pkg/mod/k8s.io/client-go/tools/cache/reflector.go:167:
  Failed to watch *v1.PodDisruptionBudget: failed to list *v1.PodDisruptionBudget:
  poddisruptionbudgets.policy is forbidden: User
  "system:serviceaccount:monitoring:kube-state-metrics" cannot list resource
  "poddisruptionbudgets" in API group "policy" at the cluster scope
Error fragmentMeaningFix
cannot list resource "X" in API group "Y"ClusterRole is missing a rule for that resource/grouphelm upgrade to regenerate the ClusterRole, or add the rule manually
at the cluster scopeKSM is using a ClusterRole but the object it needs is cluster-scoped (e.g. nodes)A namespaced Role can't grant this — you need a ClusterRole for that specific rule
User "system:serviceaccount:...:kube-state-metrics" cannot...ServiceAccount exists but has no binding, or the binding references the wrong role nameCheck kubectl get clusterrolebinding kube-state-metrics -o yaml matches the ServiceAccount name/namespace
No RBAC error, but metric silently missingCollector disabled via --resources, not an RBAC issue at allCheck the deployment's args for --resources
⚠️
A Forbidden error doesn't crash kube-state-metrics — it logs the error and continues without that resource type. If a whole category of metrics (e.g. all kube_poddisruptionbudget_*) is silently missing from Prometheus, check the pod logs for Forbidden before assuming the collector is broken.

For general deployment failures beyond RBAC — CrashLoopBackOff, OOMKilled, high CPU — see the full troubleshooting guide. For the base install and quickstart, see the docs RBAC reference.