
Every command you run against a cluster goes through one door: the API server. So the first real question in Kubernetes security isn't "how do I stop attackers", it's simpler and sharper. Who are you, and what are you allowed to do? Topic 6 is about the first half of that, proving who you are, and the messy, genuinely-changed world of service accounts underneath it.
This is the groundwork the rest of cluster security stands on. If the front door is weak, nothing behind it matters.
The security primitives, from the outside in
Before the auth detail, it helps to zoom out. When I think about locking a cluster down I picture a few layers, each protecting the one inside it.
First, the hosts themselves. The nodes running your control plane and workloads are ordinary machines, and they deserve ordinary server hygiene: no root login, no password SSH, keys only, and whatever else you'd normally do to a box you care about. This sounds obvious, but it's the layer people skip because it feels like "not Kubernetes". If someone owns the host, they own everything scheduled on it. The cluster's security can't be better than the machine it runs on.
Then the API server, which is the part I care about most here, because it's the entry point for every human and every bot. Two gates sit in front of it: authentication (are you who you say you are) and authorisation (are you allowed to do this). Get past the first and you still have to clear the second.
Under the hood, the components talk to each other constantly. etcd, the controller manager, the scheduler, the API server, and out on the nodes the kubelet and kube-proxy. All of that chatter is wrapped in TLS, so it's encrypted and mutually authenticated with certificates. I'll come back to certificates properly in a later post, they deserve their own.
And finally, inside the cluster, pods can talk to each other freely by default. That "by default" is doing a lot of work. Network policies are how you carve that open network into something with walls, deciding which pods may reach which. I'll dig into those separately too.
Authentication asks "who are you?". Authorisation asks "what can you do?". Two different questions, two different gates, and you need both.
Hits the API server
Who are you?
Are you allowed?
Validated, then stored
Who actually logs in
Here's the bit that surprised me when it clicked: Kubernetes doesn't manage human users. There's no table of accounts you can list, no kubectl create user. For people, it leans entirely on something outside itself, a file of credentials, client certificates, or an identity provider like LDAP or Kerberos.
What it does manage natively is service accounts, the identities that apps and automation use to talk to the API. So the mental split is: humans (admins, developers) come from an external source, and machines (a monitoring agent, a CI job, a dashboard app) get first-class service accounts inside the cluster. Same API server checking both, different origins.
Admins and developers. Identity comes from OUTSIDE the cluster: a credentials file, client certificates, or an IdP (LDAP, OIDC, Kerberos). There is no kubectl create user.
Apps and automation. First-class identities Kubernetes manages ITSELF, and mounts into pods so code can talk to the API.
The API server supports a spread of ways to verify a human request: static credential files, tokens, certificates, and third-party identity protocols. Let's start with the two simplest, mostly because they're the clearest way to see how the mechanism works, not because you should run them.
Static password and token files (the training-wheels method)
The most basic option is a plain CSV of credentials that you hand to the API server. Each row is a password, a username and a user ID, with an optional group in a fourth column.
password123,user1,u0001,group1
password123,user2,u0002,group1
password123,user3,u0003,group2
You then point the API server at it with a flag:
kube-apiserver --basic-auth-file=user-details.csv
On a kubeadm cluster you don't edit a service unit, you edit the API server's static pod manifest (under /etc/kubernetes/manifests/) and add the flag to its command, and kubeadm restarts the API server for you:
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --authorization-mode=Node,RBAC
- --basic-auth-file=user-details.csv
# ...other flags...
The token version is the same idea, you just swap the password column for a long random token:
KpjCVbI7cFAHYPkByTIzRb7gulcUc4B,user10,u0010,group1
rJjncHmvtXHc6MlWQddhtvNyvhgTdXSC,user11,u0011,group1
kube-apiserver --token-auth-file=user-token-details.csv
and then you present that token as a bearer header on each request:
curl -v -k https://master-node-ip:6443/api/v1/pods \
--header "Authorization: Bearer KpjCVbI7cFAHYPkByTIzRb7gulcUc4B"
--basic-auth-file) was actually removed from Kubernetes back in 1.19, so on any modern cluster it isn't even an option. Treat this section as a mental model, then reach for client certificates or a real identity provider for anything you'd actually deploy.A quick word on authorisation
Clearing authentication only gets you through the first gate. What you can then do is decided by an authorisation mode, and Kubernetes has a few. RBAC (role-based access control) is the one you'll meet most, mapping users and groups to specific permissions. There's also ABAC (attribute-based), Node authorisation (which scopes what a kubelet can touch), and Webhook mode (which hands the decision to an external service). Most clusters run Node,RBAC, which you'll spot in the API server flags above. I'll give RBAC its own write-up, it's worth the space.
Service accounts: identity for machines
Now the part I find genuinely useful day to day. Say you build a small dashboard app that lists pods by hitting the Kubernetes API. It needs to authenticate somehow, and you don't want to bake a human's credentials into it. That's exactly what a service account is for.
Creating one is a one-liner:
kubectl create serviceaccount dashboard-sa
kubectl get serviceaccount
On older clusters, creating the service account also minted a token and stashed it in a Secret. You could describe the account, find its token secret, and pull the token out to use as a bearer credential, same header trick as before:
kubectl describe serviceaccount dashboard-sa
kubectl describe secret dashboard-sa-token-kbbdm
The convenient part is what happens when your app actually runs inside the cluster. Kubernetes mounts the service account token straight into the pod, so the code can read it off disk without you wiring anything up.
The default service account, and turning it off
Every namespace ships with a default service account, and if you don't name one, your pod gets it automatically. The token lands at a predictable path inside the container:
kubectl exec -it my-app -- ls /var/run/secrets/kubernetes.io/serviceaccount
# ca.crt namespace token
That auto-mount is convenient and also a little scary, because a pod that never needed API access is still carrying a credential an attacker would love to find. If your workload doesn't talk to the API, switch it off:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
automountServiceAccountToken: false
containers:
- name: my-app
image: my-app
And when you do want a specific identity, name it in the spec. Heads up: you can't swap the service account on a running pod, you delete and recreate it (a Deployment handles that with a rollout):
spec:
serviceAccountName: dashboard-sa
containers:
- name: my-app
image: my-app
The token story changed: 1.22 and 1.24
This is the bit I'd actually flag in a review, because the behaviour depends heavily on your cluster version, and the old default was quietly dangerous.
Before 1.22, every service account got a Secret holding a token that never expired. A non-expiring bearer credential, sitting in etcd, is exactly the kind of thing that leaks once and haunts you forever. So the project fixed it.
1.22 brought the TokenRequest API (KEP-1205) and "bound" tokens. Instead of a forever-token, the token a pod gets is audience-bound, time-bound and object-bound, tied to a specific use, a lifetime, and the pod itself. In a pod spec that shows up as a projected volume with an actual expiry on it:
volumes:
- name: kube-api-access
projected:
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
fieldPath: metadata.namespace
1.24 went further: creating a service account no longer auto-generates a Secret with a long-lived token. If you want a token now, you ask for one explicitly, and it comes with an expiry (an hour by default):
kubectl create token dashboard-sa
That gives you a signed JWT you can decode (JWT.io, or a bit of jq) to see its subject, audience and expiry. You can still force the old behaviour by hand-creating a Secret of type kubernetes.io/service-account-token with the right annotation, but unless you have a very specific reason, don't. A non-expiring token is a liability you're choosing to keep.
Non-expiring Secret in etcd
Audience, time and object-bound
No auto Secret; request with expiry
kubectl create token and short lifetimes. On anything you own, set automountServiceAccountToken: false for workloads that don't need the API, so there's simply no token to steal.What stuck after writing it
What stuck with me writing this up is how much of Kubernetes auth is really just "identity from somewhere else, checked at one door". Humans come from a file or an IdP, machines get service accounts, and both hit the same API server. The interesting security work is in the details: not leaving plaintext credentials around, not shipping pods with tokens they don't need, and knowing which version's token behaviour you're actually running. I'm still early on certificates and RBAC, so if I've oversimplified the bound-token stuff, tell me, I'd rather be corrected than confidently wrong.
Next in the series I want to take certificates apart properly, since that TLS mesh between components is the thing everything else trusts. If this helped, come say hi on LinkedIn or the contact page, and tell me whether you'd want RBAC or certificates broken down first.
Further reading
- Kubernetes docs: authenticating
- Kubernetes docs: configure service accounts
- Kubernetes Enhancement Proposals (KEPs)
FAQ
How does Kubernetes authenticate a request?
The API server checks who you are before it checks what you can do. It supports certificates, tokens and external identity, and if none prove your identity the request is rejected as anonymous.
What is a Kubernetes service account?
It is an identity for a pod, not a human. Pods use a service account token to talk to the API server. Treating those tokens as secrets matters, because a leaked one is a foothold for an attacker.
What changed with service account tokens in 1.22 and 1.24?
Tokens moved to short-lived, audience-bound tokens instead of long-lived secrets that never expired. It is a real security win, but it caught out anyone relying on the old always-mounted token behaviour.
Related reading
- Topic 7: TLS, certificates and PKI (the certificates behind it)
- Topic 8: Kubeconfig and API groups (kubeconfig and the API)
- Topic 9: Authorization and RBAC (authorisation with RBAC)
- Browse the whole Kubernetes Journey