How to Harden a Kubernetes Cluster: RBAC, Pod Security, and Network Policies

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Kubernetes default configurations optimize for ease of deployment, not security. Default service accounts often have broad RBAC permissions, containers run as root by default, all pods can communicate with all other pods, and audit logging may be disabled. An attacker who compromises one container can use these defaults to escalate to cluster-admin and access all secrets, all pods, and the underlying nodes.
The hardening controls in this guide apply the principle of least privilege at every layer: RBAC restricts what API calls each service account can make, Pod Security Standards prevent containers from escaping into host namespace, network policies limit lateral movement between pods, and audit logging detects when these controls are tested or bypassed.
RBAC Least Privilege Configuration
Audit current RBAC bindings for excessive permissions:
# Find all ClusterRoleBindings that grant cluster-admin:
kubectl get clusterrolebindings -o json | jq '
.items[] | select(.roleRef.name == "cluster-admin") |
{name: .metadata.name, subjects: .subjects}'
# Find all service accounts with wildcard permissions:
kubectl get clusterroles -o json | jq '
.items[] | select(
.rules[]?.verbs[]? == "*" or
.rules[]?.resources[]? == "*"
) | .metadata.name'
# Check what permissions a specific service account has:
kubectl auth can-i --list --as=system:serviceaccount:default:my-service-account
# Find pods using the default service account (often has excessive permissions):
kubectl get pods --all-namespaces -o json | jq '
.items[] | select(.spec.serviceAccountName == "default" or
.spec.serviceAccountName == null) |
{namespace: .metadata.namespace, name: .metadata.name}'
Remove automount of service account tokens:
# For workloads that do not need to call the Kubernetes API:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: production
automountServiceAccountToken: false # No token mounted in pods
---
# Or disable at pod level:
apiVersion: v1
kind: Pod
spec:
automountServiceAccountToken: false
serviceAccountName: my-app-sa
Create minimal RBAC roles (least privilege example):
# Role: read-only access to ConfigMaps in one namespace only
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: configmap-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"] # Minimal verbs: never use "*"
resourceNames: ["app-config"] # Specific resource names if possible
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-configmaps
namespace: production
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: production
roleRef:
kind: Role
name: configmap-reader
apiGroup: rbac.authorization.k8s.io
Pod Security Standards Enforcement
Pod Security Standards (PSS) replaced PodSecurityPolicy in Kubernetes 1.25. Apply them per namespace using labels.
# Enable Restricted profile for a namespace:
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
# Verify a pod would pass the Restricted profile (dry run):
kubectl apply --dry-run=server -n production -f my-pod.yaml
# If it fails Restricted, the error message shows which fields violate policy
# Check which namespaces have PSS labels:
kubectl get namespaces -L pod-security.kubernetes.io/enforce
Compliant pod spec (passes Restricted profile):
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
securityContext:
runAsNonRoot: true # Must not run as root
runAsUser: 1000 # Specific non-root UID
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault # Required in Restricted profile
containers:
- name: app
image: myapp:v1.2.3
securityContext:
allowPrivilegeEscalation: false # Required
readOnlyRootFilesystem: true # Required in Restricted
capabilities:
drop:
- ALL # Drop all Linux capabilities
resources:
limits:
cpu: "200m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
volumeMounts:
- name: tmp-volume
mountPath: /tmp # Writable tmp via emptyDir (not root filesystem)
volumes:
- name: tmp-volume
emptyDir: {}
hostNetwork: false # Default; explicitly set for clarity
hostPID: false
hostIPC: false
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Network Policies for Pod Microsegmentation
By default, all pods can communicate with all other pods in a Kubernetes cluster. Network policies implement microsegmentation at the pod level.
Default deny all ingress and egress (baseline for each namespace):
# Apply this to every namespace as a starting point:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Applies to ALL pods in the namespace
policyTypes:
- Ingress
- Egress
# No ingress or egress rules = deny all by default
Allow only specific pod-to-pod communication:
# Allow: frontend pods can reach backend pods on port 8080 only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-allow-from-frontend
namespace: production
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 8080
---
# Allow: backend pods can reach the database on port 5432 only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-allow-from-backend
namespace: production
spec:
podSelector:
matchLabels:
role: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: backend
ports:
- protocol: TCP
port: 5432
---
# Allow DNS egress (required for all pods):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Audit Logging Configuration and Detection
Enable Kubernetes API server audit logging:
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all calls to sensitive resources at RequestResponse level:
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "serviceaccounts/token"]
- group: "rbac.authorization.k8s.io"
resources: ["clusterrolebindings", "rolebindings", "clusterroles"]
# Log all exec and port-forward requests (potential container escape):
- level: Request
resources:
- group: ""
resources: ["pods/exec", "pods/portforward", "pods/attach"]
# Log authentication failures:
- level: Request
userGroups: ["system:unauthenticated"]
# Minimal logging for everything else:
- level: Metadata
# Enable in kube-apiserver (kubeadm cluster):
# In /etc/kubernetes/manifests/kube-apiserver.yaml:
# - --audit-log-path=/var/log/kubernetes/audit/audit.log
# - --audit-policy-file=/etc/kubernetes/audit-policy.yaml
# - --audit-log-maxage=30
# - --audit-log-maxbackup=10
# - --audit-log-maxsize=100
Detection queries for Kubernetes audit logs:
// In Sentinel with Kubernetes audit logs forwarded:
// Detect: exec into a pod (potential container escape or lateral movement)
KubeAuditEvent
| where TimeGenerated > ago(24h)
| where ObjectRef.Resource == "pods" and ObjectRef.Subresource == "exec"
| where Stage == "ResponseComplete"
| project TimeGenerated, User.username, ObjectRef.Namespace,
ObjectRef.Name, ResponseStatus.code
// Detect: secret access by service accounts (potential credential theft)
KubeAuditEvent
| where TimeGenerated > ago(24h)
| where ObjectRef.Resource == "secrets"
| where Verb in ("get", "list", "watch")
// Exclude expected system accounts:
| where User.username !startswith "system:serviceaccount:kube-system"
| project TimeGenerated, User.username, ObjectRef.Namespace,
ObjectRef.Name, Verb
// Detect: RBAC privilege escalation attempts:
KubeAuditEvent
| where TimeGenerated > ago(24h)
| where ObjectRef.Resource in ("clusterrolebindings", "rolebindings")
| where Verb in ("create", "update", "patch")
| project TimeGenerated, User.username, ObjectRef.Name,
RequestObject, ResponseStatus.code
The bottom line
Kubernetes hardening applies least privilege at four layers: (1) RBAC: audit ClusterRoleBindings granting cluster-admin, remove wildcard permissions, set automountServiceAccountToken: false on service accounts that do not need API access; (2) Pod Security Standards: label namespaces with pod-security.kubernetes.io/enforce=restricted to block privileged containers, host namespace access, and root execution; (3) Network Policies: deploy a default-deny-all NetworkPolicy to every namespace, then explicitly allow only required pod-to-pod communication; (4) API server audit logging: capture RequestResponse level logs for secrets, RBAC changes, and pod exec calls; forward to SIEM and alert on exec-into-pod and secret access events.
Frequently asked questions
What Kubernetes RBAC permissions are most commonly abused by attackers?
The highest-risk RBAC permissions are: `cluster-admin` ClusterRoleBinding for any service account (full cluster control); wildcard resource or verb permissions (`resources: ["*"]` or `verbs: ["*"]`); `get`/`list` on Secrets (read all passwords and tokens in the cluster); `create` on pods or deployments (can deploy a privileged container to escape to the node); and `bind` or `escalate` verbs on RBAC roles (can grant themselves additional permissions). Audit these with `kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name == "cluster-admin")'`.
What are Kubernetes Pod Security Standards and how do they replace PodSecurityPolicy?
Pod Security Standards (PSS) are three built-in security profiles: Privileged (no restrictions), Baseline (minimum restrictions), and Restricted (full hardening): enforced by the built-in Pod Security Admission controller in Kubernetes 1.23+. They replaced PodSecurityPolicy (deprecated in 1.21, removed in 1.25) which required custom policies per cluster. PSS are applied per namespace via labels: `kubectl label namespace production pod-security.kubernetes.io/enforce=restricted`. The Restricted profile prevents privileged containers, root execution, host namespace access, and requires seccomp profiles.
How do I audit Kubernetes RBAC permissions to find over-privileged service accounts?
Audit Kubernetes RBAC in three steps: (1) List all ClusterRoleBindings with cluster-admin: `kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name == "cluster-admin") | .subjects'`. (2) Find service accounts with wildcard permissions: `kubectl get roles,clusterroles -o json | jq '.items[] | select(.rules[].resources[] == "*" or .rules[].verbs[] == "*")'`. (3) Use rbac-police (open-source tool) or kubectl-who-can to enumerate which service accounts can perform specific actions: `kubectl-who-can get secrets`. Also check for default service account tokens auto-mounted in pods: `kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.automountServiceAccountToken != false)'`.
What is a Kubernetes container breakout and how do I prevent it?
A container breakout is when an attacker escapes container isolation and gains access to the underlying host node. Common breakout techniques: mounting the host filesystem (hostPath volume to /, or /etc), running a privileged container (gives full host root access), exploiting kernel vulnerabilities from within the container namespace. Prevention: enforce Restricted Pod Security Standard (prevents privileged containers, hostPath mounts, hostPID/hostNetwork); use seccomp profiles to restrict syscalls; use AppArmor or SELinux profiles for mandatory access control; run containers as non-root (runAsNonRoot: true in securityContext). Runtime security tools (Falco, Sysdig Secure) detect breakout attempts via syscall monitoring — alerting on unusual filesystem access patterns from container processes.
How does Kubernetes network policy restrict east-west traffic between pods?
Kubernetes NetworkPolicy objects define which pods can communicate with which other pods and external endpoints. Without a NetworkPolicy, all pods in a cluster can reach all other pods (default allow-all). To implement least-privilege pod communication: first apply a default-deny policy in each namespace (`podSelector: {}` with no `from` rules blocks all ingress), then add explicit allow rules for necessary traffic. Example: allow the frontend pod to reach the backend pod on port 8080 only. NetworkPolicy requires a CNI plugin that supports it (Calico, Cilium, Weave Net) — the default CNI (kubenet) does not enforce NetworkPolicy. Verify your CNI supports NetworkPolicy before relying on it for security enforcement.
How do you detect and respond to a container escape in a Kubernetes cluster?
Container escape detection relies on runtime security tooling and audit log correlation. Deploy Falco (CNCF runtime security engine) with its default ruleset: Falco monitors Linux system calls and fires alerts for patterns characteristic of escape attempts such as opening a file under `/proc/[pid]/root` from within a container, writing to host filesystem paths like `/etc/cron.d`, spawning a shell as root inside a container, or loading a kernel module. Forward Falco alerts to your SIEM and create a P1 alert for any rule in the `container_escape` tag category. For response: use your EDR or `kubectl cordon` to prevent new pods from scheduling on the affected node, then isolate the node from the cluster (`kubectl drain [nodename] --ignore-daemonsets --force`). Preserve node memory and disk for forensics before terminating the instance: capture the pod spec with `kubectl get pod [podname] -o yaml`, the container's running process list, and active network connections before deleting the pod. In managed Kubernetes (EKS, GKE, AKS), review cloud audit logs for any IAM or service account actions taken after the estimated escape time to determine if the attacker pivoted to cloud credentials via the node's instance metadata service.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
