Kubernetes RBAC for read-only debugging
Most debugging needs a small set of read permissions: list and get Pods, read their logs, list events, and look at Deployments and ConfigMaps. Give that to a dedicated identity, scoped to the namespaces it needs, and leave Secrets out. Then a leaked kubeconfig shows someone your Pod names, not your database passwords.
The built-in view ClusterRole is close, but it also covers resources you may not want to expose and cannot be trimmed. A small custom Role is easier to reason about.
Step 1: write the Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: debug-read
namespace: team-a
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "events"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list"]
Notes on what is and is not here:
pods/logis a subresource.get podsalone does not allow reading logs.- No
secrets. ConfigMaps are included because they are where non-secret configuration lives. Drop them if your team stores credentials in ConfigMaps. - No
watch. Add it if your tool streams updates; snapshot-style tools do not need it. - No
pods/exec,pods/portforwardorpods/attach. Those are effectively shell access.
Step 2: add cluster-scoped reads only if needed
Nodes and the namespace list are cluster-scoped, so a Role cannot grant them. If you want them, add a small ClusterRole bound cluster-wide:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: debug-read-cluster
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces"]
verbs: ["get", "list"]
Listing namespaces reveals their names across the cluster. Skip this if that matters to you; users can still type a namespace they have access to.
Step 3: create the identity and bind it
A ServiceAccount is the simplest identity for a tool:
apiVersion: v1
kind: ServiceAccount
metadata:
name: inspector
namespace: team-a
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: inspector-debug-read
namespace: team-a
subjects:
- kind: ServiceAccount
name: inspector
namespace: team-a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: debug-read
For a second namespace, create the same Role and a RoleBinding there. The subject can stay the ServiceAccount in team-a.
Step 4: issue a short-lived token and a kubeconfig
On Kubernetes 1.24 and later:
TOKEN=$(kubectl -n team-a create token inspector --duration=8h)
kubectl config set-credentials inspector --token="$TOKEN"
kubectl config set-context team-a-inspector --cluster=prod --user=inspector --namespace=team-a
The API server may cap the duration. The token expires on its own, so a lost laptop holds it for hours, not forever.
Step 5: verify with auth can-i
SA=system:serviceaccount:team-a:inspector
kubectl auth can-i get pods --subresource=log -n team-a --as=$SA # yes
kubectl auth can-i list events -n team-a --as=$SA # yes
kubectl auth can-i get secrets -n team-a --as=$SA # no
kubectl auth can-i update deployments -n team-a --as=$SA # no
kubectl auth can-i --list -n team-a --as=$SA
--list prints everything the identity can do in that namespace. Read it once. Anything you did not expect is worth tracing back to a binding.
Step 6: a separate Role for releases
Writes should come from a different identity than browsing. If you release from a desktop tool with server-side dry-run and a lock, that identity needs roughly:
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "update"]
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "create", "update"]
Keep it in its own kubeconfig context, so the identity you debug with cannot change anything.
Doing this in DevOpsPad
DevOpsPad reads with exactly these kinds of permissions and shows RBAC errors as they are, instead of empty lists. Resource lists need list on the resource, details need get, Events need list events, and Logs need get pods plus get pods/log. A missing events or logs permission only affects that section of the inspector. If listing namespaces is forbidden, you can still query the namespace saved in the connection settings. The kubeconfig from Step 4 works as-is, because token and client-certificate auth are supported. Exec-based auth plugins (cloud CLI helpers) are not supported yet.
The Infra console’s release, scale, restart and rollback actions use the Step 6 permissions: update on Deployments for the dry-run and the change, and get/create/update on Leases for the per-Deployment release lock. The RBAC permissions page has the full table. See also least-privilege accounts and releasing an image.