Most Kubernetes breaches start with a misconfigured pod or overly permissive service account. An attacker lands in one container, pivots to another workload via the internal network, and escalates privileges through the API server. I've seen clusters where every pod ran as root, network policies were non-existent, and service accounts had cluster-admin by default. That's asking for trouble.
The six controls below plug the most common gaps. They're stable features in modern Kubernetes, not experimental flags or third-party tools. Enable them in your cluster today.
1. Enforce Pod Security Standards
Pod Security Standards (PSS) replaced the older PodSecurityPolicy in Kubernetes 1.25. They define three levels—Privileged, Baseline, and Restricted—and you apply them at the namespace level. Restricted is the tightest: it blocks privileged containers, host namespaces, and most dangerous capabilities.
Start by auditing your namespaces. Which pods actually need to run as root? Which need hostNetwork or hostPID? The answer is usually very few. Your application workloads should run under the Restricted profile; only system components or CNI pods might need Baseline or Privileged.
Label your namespace to enforce Restricted mode:
apiVersion: v1
kind: Namespace
metadata:
name: production-apps
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
When a pod tries to violate the policy, it's rejected at admission time. No privileged escalation, no host mounts, no running as UID 0. The audit and warn labels let you see violations in logs and API responses without blocking them—useful during a rollout.
If you need to allow specific capabilities for a workload, create a separate namespace with a Baseline policy and document why. Don't weaken the default.
2. Apply Least-Privilege RBAC
Every pod gets a service account, and that account can talk to the Kubernetes API. By default, the default service account in each namespace has limited permissions, but many teams grant it broader access or create service accounts with cluster-admin for convenience. That's a privilege-escalation path.
Audit your ClusterRoleBindings and RoleBindings. Look for these anti-patterns:
- Service accounts bound to
cluster-admin - Bindings that grant
*verbs on*resources - Service accounts in application namespaces with permissions outside that namespace
Here's a common mistake:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: app-deployer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: app-sa
namespace: production-apps
That service account can now delete nodes, modify secrets cluster-wide, and impersonate any user. If an attacker compromises a pod using app-sa, they own the cluster.
Create narrow roles instead. If your app only needs to read ConfigMaps in its namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: configmap-reader
namespace: production-apps
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-sa-binding
namespace: production-apps
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: configmap-reader
subjects:
- kind: ServiceAccount
name: app-sa
namespace: production-apps
Scope everything to namespaces unless the workload genuinely needs cluster-wide access. Even then, limit the verbs and resources.
For pods that don't call the API at all, disable the service account token:
apiVersion: v1
kind: Pod
metadata:
name: web-server
spec:
automountServiceAccountToken: false
containers:
- name: nginx
image: nginx:1.25
No token, no API access, no lateral movement via the control plane.
3. Implement Default-Deny Network Policies
Without NetworkPolicies, every pod can reach every other pod and every service. An attacker in a compromised frontend container can connect directly to your database pods, exfiltrate data, or pivot to other workloads. I've responded to incidents where the blast radius was huge because nothing blocked east-west traffic.
Start with a default-deny policy in each namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production-apps
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
That policy selects all pods (podSelector: {}) and blocks all ingress and egress by default. Now you whitelist only the flows you need.
Allow your frontend pods to talk to the backend API:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-to-backend
namespace: production-apps
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Backend pods with app: backend now accept traffic from frontend pods on port 8080. Everything else is dropped.
For egress, allow DNS and specific external endpoints:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-and-api
namespace: production-apps
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
This lets backend pods query DNS in kube-system and connect to database pods on 5432. No random internet egress.
NetworkPolicies require a CNI plugin that enforces them—Calico, Cilium, and Weave support it; the default kubenet does not. Check your cluster's CNI before you rely on these rules.
4. Enable Audit Logging
Kubernetes audit logs capture every request to the API server: who made it, what resource they touched, and whether it succeeded. Without audit logs, you're blind when investigating a breach. You won't know which service account an attacker used, what secrets they read, or how they escalated.
Create an audit policy that logs metadata for most requests and full request/response bodies for sensitive resources:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: ""
resources: ["secrets", "configmaps"]
- group: "rbac.authorization.k8s.io"
resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
- level: Metadata
omitStages:
- RequestReceived
Pass the policy file to the API server with --audit-policy-file and configure a backend with --audit-log-path or --audit-webhook-config-file. Rotate and ship those logs to a central SIEM or log aggregator. Local log files fill up fast in busy clusters.
Search the logs for anomalies: repeated 403s from a service account, secret access from unexpected pods, or ClusterRoleBinding changes outside maintenance windows.
5. Restrict API Server Access
Your API server is the control plane. If an attacker reaches it from the internet and you have weak RBAC or no authentication, they can take over the cluster. Even with strong RBAC, limiting network access reduces the attack surface.
Don't expose the API server on a public IP unless you absolutely must. Put it behind a VPN or bastion host. If you're running managed Kubernetes (EKS, GKE, AKS), enable private cluster mode so the API endpoint is only reachable from your VPC.
For GKE:
gcloud container clusters create my-cluster \
--enable-private-nodes \
--enable-private-endpoint \
--master-ipv4-cidr 172.16.0.0/28
For EKS, enable private endpoint access in the cluster config and disable public access if your CI/CD and operators run inside the VPC.
If the API server must be public, restrict source IPs with firewall rules or security groups. Whitelist your office, CI/CD runners, and on-call engineer home IPs. Block the rest.
Enable OIDC or another external identity provider instead of static token files. Service account tokens should have bounded lifetimes; check that --service-account-issuer and --service-account-signing-key-file are set and that TokenRequest is in use.
6. Run Admission Controllers
Admission controllers intercept requests before they're persisted to etcd. You've already used one if you enabled Pod Security Standards—that's the PodSecurity admission plugin. Two more are essential for security: NodeRestriction and the validating/mutating webhook interface.
NodeRestriction prevents kubelets from modifying resources they shouldn't. A compromised node can't delete pods on other nodes or change labels that affect scheduling. Enable it with --enable-admission-plugins=NodeRestriction in the API server config. Most managed Kubernetes providers turn it on by default.
Validating and Mutating Webhooks let you enforce custom policies. Open Policy Agent (OPA) Gatekeeper and Kyverno are popular choices. You write rules like "all images must come from our internal registry" or "every namespace must have a cost-center label," and the webhook rejects or modifies requests that violate them.
Here's a simple Gatekeeper ConstraintTemplate that blocks images from Docker Hub:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: blockunapprovedregistry
spec:
crd:
spec:
names:
kind: BlockUnapprovedRegistry
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package blockunapprovedregistry
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not startswith(container.image, "myregistry.example.com/")
msg := sprintf("Image %v is not from approved registry", [container.image])
}
Apply the template, then create a Constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: BlockUnapprovedRegistry
metadata:
name: block-public-images
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
Now any pod using an image outside myregistry.example.com is rejected. Combine this with image scanning in your registry and you control the software supply chain.
Putting it together
You don't need to flip all six switches at once, but you should have a timeline. Start with Pod Security Standards and default-deny NetworkPolicies—they catch the most common misconfigurations. Add least-privilege RBAC and audit logging next. Finally, lock down API access and deploy admission webhooks for policy enforcement.
Test each change in a staging cluster first. Restrictive policies can break workloads that relied on loose permissions. Check your logs for PodSecurity warnings and NetworkPolicy drops before you enforce in production.
After you've hardened the cluster, review it quarterly. Teams add new namespaces, deploy new workloads, and permissions drift. A periodic audit keeps the controls effective.
What about runtime security and image scanning?
This guide focuses on Kubernetes-native controls—RBAC, NetworkPolicies, Pod Security Standards—that prevent breakouts and lateral movement. Runtime security tools like Falco detect suspicious behavior (unexpected processes, file access, network connections) inside running containers. Image scanning tools (Trivy, Clair, Anchore) catch vulnerabilities before deployment. Both are valuable layers but orthogonal to the hardening controls here.
Do I need a service mesh for security?
Service meshes like Istio add mTLS between pods, fine-grained authorization policies, and traffic telemetry. They're powerful but add complexity. NetworkPolicies handle most segmentation needs. If you're already running a mesh, great—use its policy features. If not, don't adopt one solely for security. Start with the six controls above.
How do I test if my RBAC is too permissive?
Run kubectl auth can-i --list --as=system:serviceaccount:namespace:service-account-name to see what a service account can do. Use kubectl-who-can (a kubectl plugin) to find which subjects can perform a given action. For deeper analysis, tools like rbac-lookup or krane audit RBAC across the cluster and highlight risky bindings.
Can I automate these checks?
Yes. Integrate policy-as-code tools (OPA Gatekeeper, Kyverno) into CI/CD. Scan manifests with kubesec or kube-bench before applying them. Run CIS Kubernetes Benchmark checks with kube-bench on running clusters. Schedule periodic scans and alert on drift.
Start with RBAC and network policies
The controls above aren't exotic or experimental. They're built into Kubernetes and widely deployed in production. RBAC, NetworkPolicies, and Pod Security Standards stop the most common attack paths—privilege escalation via the API, lateral movement between pods, and container breakouts via privileged capabilities.
Pick two controls this week. Enable Pod Security Standards in one namespace and write a default-deny NetworkPolicy. Measure the impact, adjust, and roll them out cluster-wide. Repeat with RBAC and audit logging. In a month, your cluster will be measurably harder to breach.
