Kubernetes hardening from an attacker's viewpoint
CIS Benchmarks tell you what to configure. They don't tell you what an attacker does first when they land a shell in your cluster. Here's the priority order a red teamer walks — pod compromise to cluster admin — and the specific hardening controls that break each step.
Kubernetes hardening guides read like tax code. Ninety percent of them are correct and nobody reads them. The reason: they’re written as configuration checklists, not as a threat model. The CIS Kubernetes Benchmark has 122 controls. The subset that actually matters when someone lands in your cluster is closer to fifteen. This post is the attacker’s version.
The frame: assume the attacker has a shell in one pod. That’s where the interesting question starts. What can they reach? What controls stop them? Which of the 122 CIS items matter for this step? For the cloud side of the same question, see AWS IAM attack paths — cluster compromise and cloud IAM compromise chain together in most real breaches.
The attacker’s step order
1. Recon the pod
First shell in a container, the attacker enumerates:
- What identity am I?
env | grep -i aws\|azure\|gcp,cat /var/run/secrets/kubernetes.io/serviceaccount/token,curl 169.254.169.254/latest/meta-data/. - What can I reach on the network?
nslookup kubernetes.default.svc, port-scan the pod CIDR, check for cluster-local services (etcd, kubelet, cloud metadata). - What runs in this container? Capabilities (
capsh --print), mounted paths (mount), running processes.
This is loud but almost never blocked in the wild. A pod that talks to 169.254.169.254 should be an alert. It usually isn’t.
Hardening that breaks this step:
- Egress network policies with default-deny to the metadata IP and to
kubernetes.default.svcfrom non-privileged pods. automountServiceAccountToken: falseunless the pod actually needs the API.- Cloud IMDSv2 enforcement (see AWS IAM post).
2. Read the service-account token
If the pod has a mounted service-account token — most do by default — the attacker uses it against the API server:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $CA -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/namespaces/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/pods
If that returns pod data — a very common default — the attacker now enumerates every workload’s environment variables, image, mounted volumes, and (crucially) the service accounts other pods use.
Hardening that breaks this step:
automountServiceAccountToken: falseat the pod spec, plus the ServiceAccount object.- Bind the service account to a Role with permissions scoped to what the workload actually uses. Not
get pods. Notlist configmaps. The minimum verb set. - Use
TokenRequestAPI with short-lived, audience-bound tokens instead of long-lived JWTs.
3. Escalate through RBAC
Even a “read-only” service account can be a stepping stone. The API server enforces exactly what RBAC says — no more, no less. The exploitable patterns:
get/liston Secrets in a namespace with cloud credentials → the attacker now has AWS/GCP/Azure keys.create podsin any namespace → the attacker can launch a privileged pod that mounts the host filesystem, then read/etc/kubernetes/pki/on the node. Every kubelet key. Every etcd cert.bindon ClusterRoles they don’t have — RBAC escalation via ClusterRoleBinding:kubectl create clusterrolebinding pwn --clusterrole=cluster-admin --serviceaccount=default:mine.impersonateverbs on users, groups, or service accounts.updateonpod/execorpod/attachin a namespace running privileged workloads.
Any one of these is game over for the namespace. Several are game over for the cluster.
Hardening that breaks this step:
- Regularly audit
ClusterRoleandRoledefinitions. Look for*verbs,*resources, andbind/impersonate/escalateverbs anywhere. - Set
--enable-admission-plugins=NodeRestriction,PodSecurityon the API server. EnforcePodSecurity: restrictedon all namespaces without a documented exemption. - Use OPA Gatekeeper or Kyverno to block ServiceAccounts from being bound to
cluster-adminoutside a small allow-list.
4. Escape to the node
The classic T1611 — Escape to Host. Now that the attacker can create pods, they launch:
apiVersion: v1
kind: Pod
metadata: {name: escape, namespace: default}
spec:
hostPID: true
hostNetwork: true
containers:
- name: escape
image: alpine
command: ["nsenter", "--target", "1", "--mount", "--uts", "--ipc", "--net", "--pid", "--", "sh"]
securityContext: {privileged: true}
volumeMounts: [{name: host, mountPath: /host}]
volumes: [{name: host, hostPath: {path: /}}]
That pod’s nsenter gives a root shell on the underlying node. From there: kubelet keys, etcd certs, cluster admin. Ten seconds if PodSecurity isn’t enforced.
Hardening that breaks this step:
PodSecurity: restrictedat the namespace level. Blocks privileged, hostPath, hostPID/Network, capabilities-add, runAsNonRoot enforcement.- Or Kyverno / OPA policies that enforce the same intent with better messaging.
- gVisor or Kata Containers as the runtime for untrusted workloads — turns node escape into a much harder problem.
Note: PodSecurityPolicies are gone since 1.25. If your cluster still relies on them, you have a bigger problem.
5. Pivot to cloud
The moment the attacker is on the node, they can reach:
- The node’s kubelet identity — a cloud IAM role or managed identity with permissions on the underlying VPC.
- The cluster’s control-plane cloud credentials, if the node has an over-privileged instance profile.
- The metadata service without the workload identity restrictions that the pod had.
This is where cluster compromise chains into cloud compromise. In every AWS EKS engagement we run, we test whether the node role has more permissions than the cluster needs. It usually does — because someone gave the CSI driver, autoscaler, or external-dns broad permissions at cluster-role level and never scoped them down.
Hardening that breaks this step:
- IRSA / Workload Identity / Azure Workload Identity. Bind IAM permissions to service accounts, not nodes. Then the node role has almost nothing.
- Constrain the node role to only what the kubelet needs (autoscaling, joining the cluster).
- Never give the node role broad
s3:*,ec2:*, oriam:*permissions.
6. Persistence
Once cluster-admin, persistence is trivial and often ignored:
- ClusterRoleBinding that grants a benign-looking name access to everything.
- MutatingWebhookConfiguration that injects the attacker’s payload into every new pod.
- CronJob in
kube-systemthat reissues credentials. - ServiceAccount token stored offline for reuse after remediation.
Remediation checklists that don’t rotate every certificate and every JWT after a cluster compromise leave the door open. Assume anything the attacker touched is still owned until proven otherwise.
The fifteen controls that actually matter
Distilled from the 122 CIS items, the ones that break the above chain at the earliest step:
automountServiceAccountToken: falseby defaultPodSecurity: restrictedon every namespace- Egress
NetworkPolicydefault-deny to metadata endpoints - Egress
NetworkPolicydefault-deny tokube-apiserverfrom non-privileged workloads - IRSA / Workload Identity for cloud permissions (never rely on node IAM)
- Constrained node role (kubelet-only)
--enable-admission-plugins=NodeRestrictionTokenRequestAPI with short-lived, audience-bound tokens- Regular RBAC audit — no
bind/impersonate/escalateverbs outside audited roles - OPA Gatekeeper / Kyverno on ClusterRoleBinding creation
- IMDSv2-required at cloud level
- Encrypted etcd + audit logging enabled + logs shipped
- Runtime detection (Falco, Tetragon) with alerts on
nsenter, hostPath mount from a container, kubectl exec into system pods AlwaysPullImagesadmission plugin so image tag reuse can’t inject- Regular privileged-workload inventory — everything with
privileged: true,hostPath,hostNetwork,hostPID
Fifteen is the number your defenders should be able to say yes to before you claim the cluster is hardened. If your last cloud assessment produced a report that didn’t map to this chain — the assessor was reading you the CIS Benchmark, not testing your cluster.
What a serious Kubernetes red team engagement looks like
Not a scanner. Not a CIS checklist read-back. An operator lands in a representative pod, walks steps 1–6, and produces a report that tells you exactly which of the fifteen controls above you have, which you’re missing, and — if they got to cluster admin — how they’d persist and pivot to cloud.
If your Kubernetes environment is production and hasn’t seen an assessment shaped like that, it hasn’t been assessed.
Ready to test this in your own environment?
Scope an engagement and we'll bring the same rigor to your stack.
Scope an engagement