
The first time I ran kubectl get nodes -o wide on a lab cluster I actually paused. In one line it told me the internal IP of the machine, the exact OS image, and the kubelet version. Handy for me. Also a gift for anyone who shouldn't be looking. None of it is a password, but if I were attacking that cluster, that's the first thing I'd want.
So this one is about the quiet stuff a node tells you. What a Kubernetes node actually records about itself, why that pile of "boring" detail is worth protecting, and how to make sure only the right people can read it. What, how, why, takeaway.
What node metadata actually is
Every worker machine in a cluster has a matching object in Kubernetes called a Node. That object is a description of the machine, kept and updated by the control plane. When people say "node metadata", they mean all the fields inside that description.
The kubelet, the agent running on each node, reports its state up to the API server, which stores it on the Node object. You read it back with kubectl get nodes or, for the full dump, kubectl describe node <name>. Think of it like the spec sheet stuck to the side of a server in a data centre: model, address, what's installed, whether it's healthy.
The scheduler leans on this data to decide where your pods run, so it has to be accurate. But the same sheet also describes the machine in enough detail to attack it. That double use, useful to you and useful to an attacker, is the whole tension in this post.
A quick tour of what's inside
Let me name the parts, because they come up again when we talk about risk. Nothing here is complicated, it's just a machine describing itself:
- Name and UID - the unique identity of the node.
- Labels - simple key/value tags used to group nodes, like
region=us-east-1ordisk=ssd. The scheduler reads these to place pods. - Taints - the opposite of a label: a "keep off unless you're allowed" marker. A taint like
NoSchedulestops normal pods landing on that node. - System info - the revealing bit: machine ID, system UUID, boot ID, kernel version, OS image, container runtime version, and the kubelet and kube-proxy versions.
- Addresses - the node's internal and external IP addresses.
- Conditions and capacity - is it Ready, is it under memory pressure, how much CPU and RAM it has.
- Cloud provider IDs - on EC2, GCE or Azure, the provider's own instance ID for the VM.
Here's the slice most people never look at twice, straight out of describe:
169.254.169.254 that hands VMs their instance data and credentials. Same phrase, totally different thing, and you protect them in totally different ways. I'll come back to that at the end so it doesn't muddy the water.Why this "boring" data is worth protecting
The mistake is thinking recon data doesn't count because none of it is a credential. But an attacker's hardest job early on is knowing what they're even looking at. Node metadata does that job for them. Four concrete ways it bites.
1. Version disclosure to targeted exploits
The problem: if someone can list your nodes, they get the kubelet version, the kernel version and the runtime version for free. That turns a blind attack into a precise one. Instead of spraying exploits and hoping, they look up the known bugs for that exact build and fire the one that fits.
The recon is a one-liner. This is the sort of thing I'd run in the first minute if I had read access I shouldn't:
2. A ready-made map of your network
List every node's addresses and you've drawn the internal network without touching a scanner. Which subnets exist, how many machines, where the control plane sits. From there it's easier to plan lateral movement, or just to point a flood of traffic at the right internal targets. It's the reconnaissance step done for you.
kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'
# 192.168.87.12 192.168.87.13 192.168.87.14
3. Tampered labels and taints send pods to the wrong node
This is the sneaky one, and it's about writing metadata, not reading it. Labels and taints decide where pods land. If an attacker can change them, they can change scheduling. Picture a node deliberately kept for sensitive workloads, protected by a taint so nothing else lands there. Strip that taint and ordinary pods start scheduling onto it. Now your sensitive stuff is sharing a machine with who-knows-what.
Removing a taint is a single command (that trailing - means "remove"):
kubectl taint nodes node-1 workload=sensitive:NoSchedule-
# node/node-1 untainted
So the labels and taints that feel like plumbing are actually a security boundary. Whoever can edit them can quietly redraw where your workloads run.
4. The compliance angle
Handing out kernel versions, OS builds and internal topology to anyone who asks is also the kind of thing that trips audits. If you're under something like GDPR or HIPAA, "unauthenticated users can enumerate our infrastructure" is not a line you want in a report. I'm no auditor, so take that as a nudge to check rather than gospel, but it lands in a lot of frameworks.
one list permission, no exploit yet
oldest kubelet / kernel on the board
version-specific, high hit rate
Who can actually read your nodes?
Good news first: on a sane, modern cluster this isn't wide open by default. Nodes are cluster-scoped (they don't live in a namespace), so reading them needs a ClusterRole binding, not a namespaced one. And anonymous access to the API is off in current setups. So a random pod or an unauthenticated request shouldn't be able to list nodes.
The real leak is almost always RBAC that's too generous. Someone binds the built-in view ClusterRole or, worse, cluster-admin to a service account "just to get it working", and now that account can enumerate every node. So the question isn't "is it locked?", it's "who did we accidentally hand the keys to?" Least privilege is the whole game here.
Hands-on: see the leak, then close it
Let's make this real with a tiny local cluster. You'll create a low-privilege account, watch it read node metadata it shouldn't, then take that away and confirm the door's shut. Everything below is on your own lab only.
Step 1: a throwaway cluster
I use kind (Kubernetes in Docker) for this because it's instant and disposable:
kind create cluster --name meta-lab
kubectl get nodes -o wide # this is the view we're about to restrict
Step 2: a service account with too much reach
Now the mistake, on purpose. We make a service account and bind it a ClusterRole that can list nodes. This stands in for the "just make it work" binding you find in real clusters:
kubectl create serviceaccount recon -n default
# a ClusterRole that can read nodes cluster-wide
kubectl create clusterrole node-reader --verb=get,list --resource=nodes
# bind it to our service account (this is the over-grant)
kubectl create clusterrolebinding recon-nodes \
--clusterrole=node-reader \
--serviceaccount=default:recon
Step 3: watch the metadata walk out
Impersonate that account with --as and pull the same recon an attacker would. The auth can-i check is the honest test of what an identity is allowed to do:
Step 4: take it back and prove it
Now remove the over-grant and re-run the exact same check. The switch from yes to no is your proof the fix worked:
| Check | Status | Result |
|---|---|---|
recon SA can list nodes (before) | FAIL | open |
recon SA can list nodes (after) | PASS | denied |
kubectl delete clusterrolebinding recon-nodes
# verify - should now say "no"
kubectl auth can-i list nodes --as=system:serviceaccount:default:recon
# no
kind delete cluster --name meta-lab # tidy up
The fix for reading node metadata isn't some clever setting, it's RBAC hygiene. Grant get/list on nodes to the handful of things that truly need it, and nothing else.
What to look for as a defender
Two cheap habits catch most of this. First, audit who can actually read and write nodes rather than trusting your memory of it. Second, watch for node metadata being changed, especially taints and labels, since that's the scheduling-integrity risk, not just information leak.
- Enumerate every subject that can touch nodes: comb your ClusterRoleBindings for the built-in
view,editandcluster-adminroles bound to service accounts. - Test specific identities directly:
kubectl auth can-i list nodes --as=...and--asyour service accounts. If something saysyesthat shouldn't, that's your finding. - In the API server audit log, flag
update/patchonnodesand onnodes/statusfrom anything that isn't the control plane or a known operator. A pod removing a taint is a red flag.
get/list on nodes to the few components that need it. Never bind view or cluster-admin to a service account for convenience. Keep anonymous auth off. Lock down who can patch nodes so no ordinary account can strip a taint or relabel a machine. Audit node reads and writes. And remember the kubelet exposes node data too, so harden that alongside the API.169.254.169.254, the provider's instance metadata service, which can hand out IAM credentials. That's a different beast from the Kubernetes Node object and needs its own controls (block the endpoint from pods, use IMDSv2, scope instance roles). I'll give that its own write-up rather than bolt it on here.Why this one slips through
The thing that stuck with me writing this: node metadata isn't a vulnerability, and that's exactly why it slips through. There's no scary CVE, no patch to apply, just a machine honestly describing itself to anyone allowed to ask. The fix isn't a feature, it's discipline, deciding who's allowed to ask, and meaning it. Reading nodes is an RBAC problem; editing labels and taints is a scheduling-integrity problem. I've done the read-side lab end to end, but I've not yet properly rigged the taint-tampering scenario with a live workload to watch a sensitive pod drift onto the wrong node, so that's my next evening. If you audit clusters, I'm curious: how often do you still find view or worse bound to a service account "temporarily"? I suspect the honest answer is "more than we'd admit".
If this made node metadata click, come say hi on LinkedIn or the contact page, and tell me what to pull apart next. More in the Kubernetes Journey.
Further reading
- Kubernetes docs: Nodes
- Kubernetes docs: Using RBAC authorisation
- Kubernetes docs: Taints and tolerations
FAQ
What is node metadata in Kubernetes?
It is everything the API server records about each worker machine in a Node object: its name and unique ID, labels and taints, the OS and kernel version, container runtime and kubelet version, internal and external IPs, resource capacity and cloud provider IDs. It describes the node and helps the scheduler place pods.
Why is exposing node metadata a security risk?
It hands an attacker a free targeting sheet. Kernel and kubelet versions point straight at version-specific exploits, internal IPs let them map your private network, and labels or taints reveal which nodes run sensitive workloads. None of it is secret, but together it turns blind guessing into precise attacks.
Can a normal user list Kubernetes nodes by default?
No. Nodes are cluster-scoped, so reading them needs a ClusterRole binding, and modern clusters disable anonymous access. The real risk is over-broad bindings: giving a service account or user a role with get and list on nodes, often by accident through a wide view or cluster-admin binding.
How do I stop workloads landing on the wrong node?
Treat the labels and taints that drive scheduling as security settings. Restrict who can patch nodes with RBAC so no ordinary account can remove a taint or relabel a node, and audit changes. If an attacker can strip a NoSchedule taint, sensitive pods can drift onto insecure nodes.
Is Kubernetes node metadata the same as the cloud metadata endpoint?
No, they are different things that share a name. This post is about the Kubernetes Node object read through the API. The cloud metadata endpoint at 169.254.169.254 is the provider service that hands out instance data and credentials to a VM. Both matter, but you protect them in completely different ways.
Related reading
- Topic 9: Authorization & RBAC (the least-privilege side of this)
- Topic 10: ClusterRoles & Bindings (why nodes need cluster-scoped access)
- Topic 11: Kubelet security (the agent that reports node metadata)
- Browse the whole Kubernetes Journey