
After Topic 7 I could talk to the API server with raw certificates, and it was miserable. Every single command meant typing out three file paths. There had to be a better way, and there is: the kubeconfig file. Then, once you can actually reach the API, the next question is how the thing is laid out, which is where API groups come in. Two topics today, and they're a natural pair, because both are really about the same thing: how you talk to a cluster, and how an attacker who steals your access does too.
I'll keep this simple. For each idea: what it is, how it works, why it matters, and the one thing to take away.
The pain kubeconfig removes
Here's what talking to the API by hand looks like. You point curl at the API server and hand it your client key, client certificate and the CA certificate so both sides trust each other:
curl https://my-kube-playground:6443/api/v1/pods \
--key admin.key \
--cert admin.crt \
--cacert ca.crt
The server checks those and answers. An empty cluster just gives you back an empty list:
{
"kind": "PodList",
"apiVersion": "v1",
"items": []
}
kubectl can take the same details as flags, which is a little nicer but still a mouthful:
kubectl get pods \
--server my-kube-playground:6443 \
--client-key admin.key \
--client-certificate admin.crt \
--certificate-authority ca.crt
Typing that before every command is the kind of thing that makes you quietly give up on security and hardcode something dumb. So Kubernetes gives you a file that remembers it all: the kubeconfig. Drop it at the default path $HOME/.kube/config and kubectl reads it automatically, no flags, no objects to create. That's the whole pitch. Now let's open it up.
The three pieces inside every kubeconfig
A kubeconfig is just YAML with three lists in it, plus a pointer to say which one you're using right now. Once these three click, the whole file stops looking scary.
Clusters answer "where do I connect?". Each entry is an API server URL plus the CA certificate you trust for it. You might have dev, staging and prod all listed here.
Users answer "who am I?". Each entry holds authentication material: a client certificate and key, a bearer token, or an exec plugin that fetches a cloud token. This is the sensitive half.
Contexts answer "which user talks to which cluster?". A context is nothing more than a named pairing of one cluster and one user, optionally with a default namespace. The context admin@production means "log into the production cluster as admin".
current-context is the single line that says which context is live right now. It's the first thing I check on any box, because it tells me who I'm about to act as. Here's a trimmed real file so you can see the shape:
apiVersion: v1
kind: Config
current-context: dev-user@google
clusters:
- name: production
cluster:
server: https://prod-api.example.com:6443
certificate-authority: prod-ca.crt
users:
- name: dev-user
user:
client-certificate: dev-user.crt
client-key: dev-user.key
contexts:
- name: dev-user@google
context:
cluster: google
user: dev-user
namespace: development
Clusters and users are the raw ingredients, a context is the recipe that combines them, and current-context is the plate in front of you.
Driving kubeconfig from the command line
You rarely hand-edit this file. kubectl has a whole config sub-command for it. To see what you've got loaded:
kubectl config view
# or point at a specific file
kubectl config view --kubeconfig=my-custom-config
To find out who you currently are before you touch anything:
kubectl config current-context
To switch identity, you change the active context. This one line can move you from a harmless dev cluster to production, so read it twice:
kubectl config use-context prod-user@production
~/.kube/config tells kubectl (and any client) which cluster to talk to, who you are, and therefore what you're allowed to do. Anyone who can read it can potentially become you.A context can also carry a default namespace, which saves you typing --namespace on every command. Add one field and kubectl quietly scopes everything to, say, finance:
contexts:
- name: admin@production
context:
cluster: production
user: admin
namespace: finance
If you keep a separate kubeconfig and don't want to pass --kubeconfig every time, point the KUBECONFIG environment variable at it and make it stick across shells by adding it to your shell profile:
# in ~/.bashrc
export KUBECONFIG=$HOME/my-kube-config
# reload the current shell
source ~/.bashrc
Certificates: by path or baked in
In the cluster and user blocks you can reference certificates two ways. Either a file path, which is portable-ish but assumes the file exists on that machine, or the certificate content itself, base64-encoded, using the -data variants (certificate-authority-data, client-certificate-data, client-key-data). You use one or the other for a given entry, not both:
# path
certificate-authority: /etc/kubernetes/pki/ca.crt
# OR embedded (base64 of the PEM)
certificate-authority-data: LS0tLS1CRUdJTiBDRVJU...
If you ever pull a kubeconfig off a box and see that base64 blob, remember it decodes straight back to the PEM. Embedded creds mean the whole file is self-contained, which is convenient for you and just as convenient for anyone who walks off with it.
Why a kubeconfig is an attacker's dream
This is the part I care about most as a pentester. A kubeconfig is not config, it's a credential. Treat it like an SSH private key. And unlike an SSH key, it often carries several identities and several clusters in one tidy file.
Grab it from a dev laptop, CI runner or jump host, often world-readable.
Check current-context and the contexts list to see who and where you are.
Multiple contexts mean one file can unlock several clusters.
So when I land on a machine, the kubeconfig is one of the first things I look at. What I check, roughly in order:
- File permissions on
~/.kube/config. Is it readable by more than its owner? It usually is. - Does a non-admin box somehow have an
adminorprodcontext sitting in it? - Long-lived client certificates or tokens that never expire.
- Over-privileged users, which really means loose RBAC bindings behind those user entries.
- CI/CD kubeconfigs stored as plaintext secrets in a pipeline.
- Whether you can switch context and act with zero re-auth or MFA (you almost always can).
~/.kube/config to 600, keep prod contexts off developer and CI machines, prefer short-lived tokens over long-lived client certs, and remember that "we use RBAC" means nothing if the bindings behind these users are generous.Now the map: Kubernetes API groups
Kubeconfig gets you to the API. API groups are how the API itself is organised, and you need that map before authorisation (RBAC) makes any sense, because RBAC rules are written in terms of groups, resources and verbs.
Everything in Kubernetes is the API. kubectl is just a friendly wrapper around REST calls. You can prove it by asking the server its version directly:
curl https://kube-master:6443/version
{
"major": "1",
"minor": "13",
"gitVersion": "v1.13.0",
...
}
The API is split into groups by function. A handful of top-level paths give you the lay of the land: /version for the build, /healthz and /metrics for health, /logs for logging, and the two that actually hold resources: /api and /apis.
Core group vs named groups
Those two paths are the split that trips everyone up at first, so here it is plainly.
The old, essential resources. No group name in the path. pods, namespaces, nodes, services, endpoints, events, configmaps, secrets, persistentvolumes.
Everything added later, sorted into named buckets: apps (deployments, replicasets, statefulsets), networking.k8s.io (networkpolicies), storage.k8s.io, authentication.k8s.io, certificates.k8s.io (CSRs).
Inside any group, a resource supports a set of actions called verbs: get, list, create, delete, update and watch. That trio, group plus resource plus verb, is the exact language RBAC uses to allow or deny you. Hold that thought for the next post.
The full shape is worth memorising, because it's how you read any RBAC rule or API path:
# named groups
/apis → group → version → resource
e.g. /apis/apps/v1/deployments
# core group (no group segment)
/api → version → resource
e.g. /api/v1/pods
Talking to the API directly
You can browse the groups yourself. Hit the server with no path and it lists what's there:
Passing certs on every curl is tedious (sound familiar?), so kubectl gives you a shortcut: kubectl proxy. It opens a local HTTP proxy that reuses your kubeconfig credentials, so you can curl plain http://localhost:8001 and it authenticates for you:
kubectl proxy
# Starting to serve on 127.0.0.1:8001
curl http://localhost:8001/apis
If you'd rather not use the proxy, you authenticate the request yourself with a bearer token instead of certs:
curl -X GET $APISERVER/api --header "Authorization: Bearer $TOKEN" --insecure
where a service account's token, for example, comes from its secret and is base64 the whole way, so you decode it before use.
The pentest angle on API groups
Once I have any identity on a cluster, API groups become an enumeration map. The workflow is simple and it's mostly about finding the gap between what you are and what you can do.
- List the groups.
curl https://<api-server>/apisto see what's exposed. - Find what you can touch.
kubectl auth can-i --listshows your effective permissions, andkubectl auth can-i create podsanswers a single question fast. - Hunt high-impact resources. Not all resources are equal. Some are a straight line to owning the cluster.
| Resource | Group | Why it's dangerous |
|---|---|---|
| deployments / pods | apps, core | code execution run a container, get a shell |
| secrets | core | credential access read tokens and keys |
| networkpolicies | networking.k8s.io | network bypass open blocked paths |
| certificatesigningrequests | certificates.k8s.io | privilege escalation mint a trusted client cert |
API groups aren't trivia. They're the coordinate system for every RBAC rule, and the shortlist above is where a "read-only" account quietly turns into cluster-admin when the bindings are sloppy.
Two things that clicked
Two things clicked writing this. First, a kubeconfig is a credential wearing a config file's clothes, and I'll never look at ~/.kube/config as harmless again. Second, API groups finally gave me the map I was missing: /api for the core stuff, /apis/group/version/resource for everything else, and verbs on top. I'm still fuzzy on exec-plugin auth in kubeconfig (the cloud IAM path), so if you've abused one of those, tell me. Next post is the obvious one: RBAC, where these groups, resources and verbs finally turn into allow-or-deny.
If this helped, come say hi on LinkedIn or the contact page, and tell me if you want RBAC broken down the same way.
Further reading
- Kubernetes docs: organising cluster access with kubeconfig
- Kubernetes docs: API concepts and groups
- Kubernetes docs: access the API with kubectl proxy
FAQ
What is a kubeconfig file?
It is the file kubectl reads to know which cluster to talk to and how to prove who you are. It bundles clusters, users and contexts, a context being a pairing of a cluster and a user.
What are Kubernetes API groups?
The API is split into groups of related resources (core, apps, rbac and more), each with resources and verbs. Understanding groups, resources and verbs is what makes RBAC rules make sense.
Where is my kubeconfig stored?
By default at ~/.kube/config. Because it can hold credentials to the whole cluster it is a high-value file, so treat it like a secret and never commit it to a repository.
Related reading
- Topic 7: TLS, certificates and PKI (the certs a kubeconfig holds)
- Topic 9: Authorization and RBAC (RBAC on those API groups)
- Topic 6: Security primitives and authentication (service account identities)
- Browse the whole Kubernetes Journey