Chapter 22 — Hands-on: attack a cluster, then harden it
Brief
Take a deliberately-vulnerable Kubernetes cluster, walk a full attack path from "I can deploy one pod" (or "I have a low-priv SA token") to cluster-admin and the node, then apply the Part 4 controls one at a time, re-testing after each, until every path is cut. Finish with runtime detection proven against the attacks.
Setup
* A kind (or k3d) cluster with a CNI that enforces NetworkPolicy
(Cilium/Calico).
* Deploy KubernetesGoat (`madhuakula/kubernetes-goat`) AND the
BishopFox `badPods` manifests. Optionally the "Kubernetes CTF" / kctf.
* Tools: kubectl (+ krew: who-can, rakkess, access-matrix), KubeHound,
rbac-police, KubiScan, kube-bench, kubescape, peirates, Falco,
Tetragon, Kyverno.
Part 1 — Model (30 min)
1. kube-bench + kubescape (NSA + CIS frameworks) -> baseline score and
top findings.
2. KubeHound -> the attack-path graph. Note the paths from
`serviceaccount` nodes and from `container` nodes to
`node`/`cluster-admin`.
3. Pick two goal paths to walk end to end.
Part 2 — Attack (75 min) — document every command + output
[ ] RBAC escalation: from a low-priv SA with `create pods`, schedule a
pod with a powerful `serviceAccountName` (or hostPath/privileged)
-> read kube-system secrets -> steal a powerful token -> `auth
can-i --list` == `*`.
[ ] Node escape: run a badPod (privileged / hostPath `/` / hostPID) ->
get a node shell -> read `/var/lib/kubelet` secrets and the kubelet
cert -> talk to the API as `system:node:<name>`.
[ ] Metadata: from a pod, `curl 169.254.169.254` -> node role creds
(if the goat/cluster exposes it).
[ ] Persistence: create a MutatingWebhookConfiguration or a
ClusterRoleBinding or a static pod.
[ ] Lateral: reach another namespace's service with no NetworkPolicy.
Use `peirates` to automate some of this and compare.
Part 3 — Harden (90 min) — re-test after each
[ ] PSA `restricted` (warn+audit -> enforce) on all non-system
namespaces -> badPods now rejected at creation. Re-run each.
[ ] Kyverno: deny `serviceAccountName` outside an allowlist per
creator; deny hostPath except named system DaemonSets; require
signed images by digest; generate a default-deny NetworkPolicy per
namespace.
[ ] RBAC: remove the `create pods` -> `powerful SA` path (scope the
operator SA; `automountServiceAccountToken: false` default; deny
broad `secrets`). Re-run KubeHound -> path gone.
[ ] NetworkPolicy (default-deny + explicit allows) in every namespace;
block `169.254.169.254`; make etcd/kubelet unreachable from pods.
[ ] etcd encryption at rest (KMS/aesgcm provider); verify a raw etcd
read of a Secret is now ciphertext.
[ ] Node: IMDSv2 hop-limit-1 (or the cloud equiv); kubelet
`--anonymous-auth=false`, `--authorization-mode=Webhook`.
[ ] Alert on MutatingWebhookConfiguration / ClusterRoleBinding creates
(audit policy -> SIEM).
Part 4 — Detect (45 min)
[ ] Install Falco (eBPF) + a custom escape-attempt rule + "shell in
container" + "contact API server from unexpected pod".
[ ] Re-run the (now-blocked) attacks and any that still partially work;
confirm Falco fires; record fidelity + latency.
[ ] Add Tetragon with an ENFORCING TracingPolicy that kills
`nsenter -t 1` / release_agent writes; verify the process dies.
[ ] Wire Falco -> a webhook that cordons the node + scales the pod to
0 (a script is fine for the lab).
Deliverable
~/sec-lab/adv/reports/p4-k8s.md: the KubeHound graph before/after, every
attack command with output, every control with its passing re-test, the
kube-bench/kubescape score delta, and the Falco/Tetragon detections with
evidence. Plus: "the three controls that cut the most paths, ranked".
Definition of done
- Two full attack paths (RBAC->admin and pod->node) walked and documented.
- PSA
restrictedenforced; Kyverno policies inEnforce; the specific RBAC escalation edges removed. - NetworkPolicy default-deny everywhere; metadata blocked; etcd encrypted at rest.
- KubeHound shows no remaining path from a workload SA / container to node/cluster-admin.
- Falco detecting the residual attempts; one Tetragon enforcement policy proven to kill an escape.
- kube-bench/kubescape score materially improved, remaining findings explained.
Rubric (/100)
Attack paths walked + documented .............. 20
PSA + admission policy in Enforce ............ 20
RBAC escalation edges removed (KubeHound clean) 20
NetworkPolicy + etcd encryption + node hardening 20
Runtime detection + one enforcement, proven ... 15
Report ...................................... 5
Further reading
- Practice: KubernetesGoat (with its walkthrough),
badPods, kctf / "Kubernetes CTF", the "Kubernetes LAN Party" (k8slanparty.com), pwnedlabs.io k8s tracks. - Tools: KubeHound, rbac-police, KubiScan, peirates, kube-bench, kubescape, Falco, Tetragon.
- Guidance: NSA/CISA "Kubernetes Hardening Guidance" (work through it as a checklist); CIS Kubernetes Benchmark; the Kubernetes "Security Checklist" in the official docs.
- Book: Hacking Kubernetes (Martin & Hausenblas) — structured almost exactly as this Part; do its labs.
End of Part 4 — Milestone check
- I can list the container isolation primitives and the escape classes as concrete review items
- I can name the three components where cluster security is decided and the trust boundaries between pod, node, and cluster
- I have escalated a low-priv SA to cluster-admin and to a node shell in a lab, then cut every path (KubeHound clean)
- I can roll out Pod Security Admission
restrictedwithout an outage and add a signed-image admission gate - I can write default-deny NetworkPolicy + FQDN egress + mesh AuthorizationPolicy and explain what each layer adds
- I have runtime detection (Falco) and one enforcement policy (Tetragon) proven against real attack techniques
- I can choose a multi-tenancy isolation model from the trust level and justify it
- I completed the attack-then-harden cluster lab end to end