KubeGlanceDownload
What a kubeconfig actually grants, and how to scope one down

security

What a kubeconfig actually grants, and how to scope one down

A kubeconfig grants nothing by itself. It carries an identity, and the cluster decides what that identity can do. How to find out, and how to hand out less.

· 10 min read

A kubeconfig grants nothing. It is a file containing an address, a way to trust the server, and a credential that proves who you are. Everything you are allowed to do is decided by the cluster, from RBAC bound to that identity — which is why “can I give someone my kubeconfig” is the wrong question, and “what is this identity bound to” is the right one.

The practical consequence is that scoping down a kubeconfig is never done by editing the kubeconfig. It is done by issuing a different identity.

The fastest check

Who does this file claim to be:

kubectl auth whoami
ATTRIBUTE                                           VALUE
Username                                            kubernetes-admin
Groups                                              [kubeadm:cluster-admins system:authenticated]
Extra: authentication.kubernetes.io/credential-id   [X509SHA256=7e012c904b0423960669c1ce8897aeddbcc3f8c86f8ed2aa353df253c7044bea]

And what that identity may do:

kubectl auth can-i --list
Resources                                       Non-Resource URLs   Resource Names   Verbs
*.*                                             []                  []               [*]
                                                [*]                 []               [*]

*.* with verb * is cluster-admin. If that is what your everyday kubeconfig prints, every tool you point at it — every dashboard, every CI job, every half-finished script — is running as cluster-admin, whatever it claims about being read-only.

Note the two lines. The second, with an empty resource and [*] under non-resource URLs, is authority over non-resource paths like /healthz and /metrics. RBAC treats those separately from resources, and a role that grants everything on resources still grants nothing on those paths unless it says so.

The three parts, and which one carries the power

A context is a pointer to a cluster and a user. Only the user section carries a credential.
kubectl config view --minify --flatten

--minify reduces the output to the current context only, and --flatten inlines file references so you see what would actually be sent. On a certificate-based config the user block looks like this, with the data elided:

users:
- name: kind-glance-blog
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

That is the whole grant: a client certificate whose CN becomes the username and whose O values become groups. Three properties follow, and all three are the reason certificate-based kubeconfigs are a poor thing to hand out:

  • It cannot be scoped. The identity is fixed at issue time and RBAC is bound to it cluster-side. Nothing in the file limits it.
  • It cannot be revoked. Kubernetes has no certificate revocation path. Until it expires it is valid, and the only real remedy is rotating the cluster CA.
  • It usually does not expire soon. Installer-issued admin certificates are commonly valid for a year.

A bearer token in the same position is revocable by deleting the object behind it, and an exec credential plugin holds no secret at all — it shells out to your cloud CLI or OIDC helper on each call. Prefer either to a certificate for anything you did not issue for yourself.

Handing out less: a scoped identity in four commands

Do not copy your kubeconfig and delete things from it. Create an identity that never had the permissions in the first place.

kubectl -n prod create serviceaccount viewer
kubectl -n prod create rolebinding viewer-can-view \
  --clusterrole=view --serviceaccount=prod:viewer

view is a built-in ClusterRole; binding it with a RoleBinding rather than a ClusterRoleBinding confines it to that one namespace, which is the part people get wrong. Verify rather than assume — impersonation lets you ask the cluster directly without ever holding the credential:

kubectl auth can-i --list --as=system:serviceaccount:prod:viewer -n prod
Resources                                       Verbs
configmaps                                      [get list watch]
endpoints                                       [get list watch]
events                                          [get list watch]
...

and the negative cases, which are the ones worth checking:

kubectl auth can-i delete pods --as=system:serviceaccount:prod:viewer -n prod   # no
kubectl auth can-i get secrets --as=system:serviceaccount:prod:viewer -n prod   # no
kubectl auth can-i list pods   --as=system:serviceaccount:prod:viewer -n default # no

Those three no answers are the specification. view deliberately excludes Secrets, and the RoleBinding stops it at the namespace edge.

Then mint a token with a lifetime, rather than a permanent one:

kubectl -n prod create token viewer --duration=1h

This is the modern replacement for the legacy Secret-backed service account token. It is signed, time-bound, and it expires without anybody having to remember to clean it up. A token minted with --duration is the correct thing to paste into a tool you are trying out; a long-lived admin certificate is not.

Assembled into a kubeconfig, the user block becomes one line, and the file is now worth exactly one hour of namespace-scoped reads.

Credentials expire, and the errors do not say so

An exec plugin re-enters valid on its own. A static token and a certificate do not.

The failure mode worth planning for is that an expired credential rarely produces a message about expiry. What you get is a 401 rendered by whatever tool you were using, and tools in this category are notoriously bad at it — one popular terminal client reports a missing command rather than a lapsed token. When something that worked yesterday stops working today and the error is strange, check the credential before you check anything else:

kubectl auth whoami

An expired credential fails this immediately and unambiguously, which makes it a better first diagnostic than any request against a real resource. The related transport and trust errors — the ones that look similar and are not this — are worked through in kubectl connection and TLS errors.

Rules that hold up

  • Never hand out the installer’s admin kubeconfig. It is unscopable, unrevocable and long-lived. Issue a service account token instead, every time.
  • Bind with a RoleBinding unless you mean the whole cluster. A ClusterRoleBinding to view grants read of every namespace including whatever gets created next year.
  • Remember view excludes Secrets and edit does not. edit can read every Secret in its scope, which for most people is the moment “it is only edit” stops being reassuring.
  • Check what a tool asks for before pointing it at a cluster. Anything that needs write access to do a read is asking for the wrong thing.
  • Keep separate contexts for separate authority. A context whose credential is read-only cannot be turned destructive by a mistyped command, which is a stronger guarantee than any confirmation prompt. Which file wins when you have several is covered in kubeconfig merge rules.

For the copy-pasteable Role that a read-only dashboard actually needs — narrower than view, and with the reasoning for each verb — see a read-only RBAC role for a Kubernetes dashboard.

#kubeconfig #rbac #security #service accounts #tokens

The Kubernetes dashboard that fits in your pocket

KubeGlance is a native Kubernetes client for iPhone and iPad — the real dashboard, not a companion — with a full Mac app on the same core. Pods, workloads, logs and events, straight from your kubeconfig.

Download KubeGlance