A freshly installed Kubernetes cluster is optimized for developer velocity, not for containing a breach. Out of the box, pods run as root, any pod can reach any other pod on the network, secrets are stored base64-encoded rather than encrypted, and the default service account is mounted into every workload whether it needs API access or not. None of that is a bug — it is the price of a platform that has to run anyone's workload on day one. The problem is that most teams never revisit those defaults, so the cluster that shipped a hello-world demo is the same cluster now running customer data. Real incidents follow the same defaults: CVE-2018-1002105 let an unauthenticated user escalate to full cluster-admin through the API server's proxy handling, and the recurring pattern in cloud-native breaches is a single compromised container that had far more reach than it ever needed. Hardening Kubernetes is less about exotic controls and more about systematically walking back the trust the defaults hand out.
The 4C model: where do your controls actually live?
Kubernetes security is layered, and the community frames it as the "4Cs": Cloud, Cluster, Container, and Code. A control at one layer cannot fix a gap at another — an airtight pod security policy means nothing if the underlying node has a publicly exposed kubelet, and a perfectly locked-down cluster cannot save an application with a deserialization bug in its own code. The practices below map to the Cluster and Container layers, because those are the ones platform teams own directly and the ones attackers reach for first after a foothold.
1. Enforce Pod Security Admission on every namespace
PodSecurityPolicy was removed in Kubernetes v1.25, and Pod Security Admission (PSA) is its built-in replacement. PSA applies one of three profiles — privileged, baseline, or restricted — at the namespace level via labels. Set restricted everywhere you can; it forbids privilege escalation, requires non-root execution, and mandates a seccomp profile.
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
Your workloads then need a security context that satisfies the profile:
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
Setting enforce-version: latest matters: it opts the namespace into new restrictions as the profile tightens across releases instead of pinning you to an old, looser definition.
2. Make the network deny-by-default
By default, every pod in a cluster can open a connection to every other pod. That flat topology is why a single compromised web frontend can so often reach a database it should never touch. NetworkPolicies flip the model to deny-by-default the moment one selects a pod:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: payments
spec:
podSelector: {}
policyTypes: ["Ingress"]
With that baseline in place, you explicitly allow only the traffic each service needs — frontend to API, API to database — and everything else is dropped. Lateral movement, the step that turns one compromised pod into a cluster-wide incident, becomes dramatically harder.
3. Encrypt secrets at rest and stop over-mounting them
Kubernetes Secrets are base64-encoded, not encrypted — anyone with etcd read access or a broad RBAC grant can decode them trivially. Enable encryption at rest with an EncryptionConfiguration backed by a KMS provider so the bytes in etcd are actually protected. Just as important, stop mounting the default service account token into pods that never call the Kubernetes API:
apiVersion: v1
kind: Pod
metadata:
name: batch-job
spec:
automountServiceAccountToken: false
An unused token sitting in a filesystem is a credential waiting to be exfiltrated.
4. Lock down the control plane and nodes
Disable anonymous authentication on the API server, turn on audit logging, and set the kubelet to --anonymous-auth=false with webhook authorization so a compromised node cannot query or mutate other pods. Keep the cluster patched: container-escape and privilege-escalation CVEs in the runtime and kernel — from the runc breakout family to Linux kernel bugs like Dirty Pipe (CVE-2022-0847) — are exploitable precisely because they bridge the container-to-host boundary that everything else assumes is solid.
Hardening checklist
| Control | Default state | Hardened state |
|---|---|---|
| Pod security | Unrestricted | restricted PSA enforced per namespace |
| Container user | root | Non-root, no privilege escalation |
| Root filesystem | Writable | readOnlyRootFilesystem: true |
| Network | Flat, all-to-all | Deny-by-default NetworkPolicy |
| Secrets | base64 in etcd | Encrypted at rest via KMS |
| Service account token | Auto-mounted | Mounted only where needed |
| API server | Anonymous auth on | Anonymous off, audit logging on |
| Images | Any registry, any tag | Signed, pinned, policy-gated on admission |
How Safeguard helps
Enforcing these controls by hand across dozens of namespaces and hundreds of manifests is where drift creeps in — a namespace ships without the restricted label, a NetworkPolicy never lands, a workload quietly runs as root. Safeguard's infrastructure-as-code scanning reads your Kubernetes manifests and Helm charts in the pull request and flags the misconfigurations above before they reach a cluster, so the security context and PSA labels are checked the same way you check unit tests. For what runs on the nodes, Safeguard's container security scans the images your pods pull and gates admission on signature and vulnerability policy, and Griffin AI prioritizes findings by whether the vulnerable code path is actually reachable, so your team fixes the handful of exploitable issues instead of drowning in raw CVE counts. If you are weighing platforms, our comparison hub breaks down how this approach differs from posture-only tools.
Kubernetes will not harden itself, but you do not have to do it manually either. Create a free Safeguard account to scan your manifests and images today, or read the Safeguard documentation for step-by-step setup.