Most Kubernetes clusters run with doors wide open. The defaults assume a trusted network and cooperative tenants, which is fine for local development but catastrophic in production. I've seen clusters compromised because a developer mounted the Docker socket, or because every pod could talk to every other pod, or because nobody bothered to restrict the default service account.
The good news? Eight configuration changes will close the gaps attackers actually exploit. These aren't theoretical CVEs—they're the patterns I see in post-incident reports and pentest findings.
1. Lock Down the Default Service Account
Every namespace gets a default service account, and every pod uses it unless you say otherwise. That service account can usually read secrets, list pods, and query the API server. An attacker who compromises a single container inherits those permissions immediately.
Disable token automounting for the default account in each namespace:
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: production
automountServiceAccountToken: false
Apply that to every namespace. Then create purpose-specific service accounts with minimal RBAC for pods that genuinely need API access. Most don't.
For pods that never touch the Kubernetes API, set automountServiceAccountToken: false directly in the pod spec as a second layer. Defense in depth matters here.
2. Enforce Pod Security Standards with Admission Control
Pod Security admission replaced PodSecurityPolicy in Kubernetes 1.25. It blocks privileged containers, host namespace access, and other dangerous configurations at creation time.
Enable it cluster-wide by adding this to your API server flags:
--admission-plugins=PodSecurity
Then label your namespaces according to risk. I use restricted for application workloads, baseline for system namespaces that need slightly more access, and privileged only for CNI and CSI pods that must touch the host.
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
The restricted profile blocks privileged escalation, requires dropping all capabilities, and forbids host path mounts. That stops the most common container escape vectors.
Audit mode will log violations without blocking them, which helps you discover what will break before you flip enforcement on.
3. Segment Traffic with Network Policies
By default, every pod can reach every other pod and every external IP. A compromised frontend container can exfiltrate data directly from your database, scrape secrets from internal services, or pivot to the kubelet API.
Network policies work like firewall rules for pods. Start by denying all ingress and egress in each namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Then add specific allow rules. For a web app that talks to PostgreSQL and nothing else:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-allow
namespace: production
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
The DNS exception is necessary because pods resolve service names through CoreDNS. Without it, nothing works.
This approach forces you to document which services talk to which, and it makes lateral movement harder for an attacker.
4. Restrict RBAC to Named Resources
Wildcard permissions (*) in RBAC rules show up constantly. A role that grants get, list, watch on all resources in all API groups hands an attacker a map of your entire cluster.
Bad example:
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list"]
Instead, name exactly what the service account needs:
rules:
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get"]
Audit existing roles with:
kubectl get clusterroles -o json | jq '.items[] | select(.rules[]?.resources[]? == "*") | .metadata.name'
Fix the ones that aren't system components. The principle is simple: if you can't justify why a workload needs a permission, it doesn't get it.
5. Disable Anonymous API Access
The API server accepts unauthenticated requests by default and maps them to the system:anonymous user. That user has no permissions in a default install, but misconfigurations happen. I've seen clusters where system:anonymous was accidentally bound to cluster-admin.
Turn it off:
--anonymous-auth=false
Add that flag to the kube-apiserver manifest. Legitimate clients all use certificates or tokens anyway.
If you run a managed Kubernetes service that requires anonymous health checks, leave it on but verify system:anonymous has zero RBAC bindings:
kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.name == "system:anonymous")'
That should return nothing.
6. Rotate and Scope Service Account Tokens
Older clusters issue service account tokens that never expire and work from anywhere on the network. A leaked token is a permanent backdoor.
Kubernetes 1.22 introduced bound tokens that expire after an hour and are scoped to a specific pod and volume. Enable the feature by setting:
--service-account-issuer=https://kubernetes.default.svc
--service-account-signing-key-file=/etc/kubernetes/pki/sa.key
Then verify new pods receive projected tokens by checking the mount:
kubectl exec -it <pod> -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d. -f2 | base64 -d
Look for an exp claim in the JWT. If it's missing, your cluster is still issuing legacy tokens.
For long-running batch jobs that legitimately need tokens lasting more than an hour, explicitly set serviceAccountToken in the pod spec with a longer expiration. Don't fall back to disabling the feature.
7. Enable Audit Logging for the API Server
You can't investigate an incident without logs. API server audit logs record who did what, when—every create, update, delete, and permission check.
Create an audit policy that captures authentication failures, secret access, and privilege escalation attempts:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: Metadata
verbs: ["get", "list", "watch"]
- level: RequestResponse
userGroups: ["system:unauthenticated"]
Point the API server at it:
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
--audit-log-path=/var/log/kubernetes/audit.log
--audit-log-maxage=30
Ship those logs to a SIEM or centralized logging system outside the cluster. If an attacker compromises the control plane, local logs disappear with it.
8. Harden Kubelet Configuration
The kubelet runs on every node and controls pod creation, volume mounts, and container execution. It also exposes an API on port 10250 that, if misconfigured, lets anyone run commands in any pod on that node.
Three flags prevent the most common kubelet attacks:
--anonymous-auth=false
--authorization-mode=Webhook
--read-only-port=0
The first blocks unauthenticated requests. The second forces the kubelet to check with the API server before honoring a command. The third disables the read-only port (10255) that exposes pod and node metadata without authentication.
Verify your settings on each node:
ps aux | grep kubelet | grep -E 'anonymous-auth|authorization-mode|read-only-port'
If you're running a managed service, check the provider's documentation. Most handle kubelet hardening automatically, but some let you override defaults in node pool configs.
What to check first
Start with the default service account and network policies. Those two changes block the majority of post-compromise lateral movement without breaking working applications.
Then tackle Pod Security admission and RBAC wildcards. Both require auditing what your workloads actually need, which takes time but pays off.
The rest—anonymous auth, token rotation, audit logs, kubelet hardening—are control-plane and node-level settings. If you manage your own cluster, knock them out in an afternoon. If you use a managed service, verify the provider's defaults and fill any gaps.
Kubernetes security hardening isn't a one-time checklist. New misconfigurations creep in with every deployment. Run regular audits with tools like kube-bench or Polaris, and gate deployments that violate policy with admission controllers. The work pays for itself the first time you don't end up in an incident channel at 2 AM.
FAQ
Do I need all eight fixes, or can I pick and choose?
Default service account restrictions and network policies give you the most bang for effort. The others depend on your threat model and who operates the cluster.
Will Pod Security admission break my existing workloads?
Probably, which is why you start in audit mode. Label a namespace with enforce=restricted and audit=restricted, then watch the audit logs for a week before blocking violations.
How do I know if my kubelet is already hardened?
SSH to a node and run ps aux | grep kubelet. Check for the three flags listed in fix eight. If they're missing, you have work to do.
What if my application legitimately needs host network access?
Give it a dedicated namespace labeled pod-security.kubernetes.io/enforce=privileged and lock down everything else. Don't let one special case weaken your baseline.
Can I use OPA Gatekeeper instead of Pod Security admission?
Yes. Gatekeeper offers more flexibility but requires writing Rego policies. Pod Security admission works out of the box for the most common cases.
Where automation helps
CI pipelines should validate manifests against security policy before they reach the cluster. Tools like Datree, Checkov, or Polaris catch privilege escalation, missing resource limits, and RBAC misconfigurations in pull requests.
Run kube-bench as a CronJob inside the cluster to continuously audit node and control-plane settings against CIS benchmarks. Alert on drift so you fix misconfigurations before they're exploited.
Finally, enforce Pod Security admission in warn mode everywhere and enforce mode in production. That way developers see violations locally and can fix them before deployment, instead of having their pods rejected at runtime.
