Running workloads in Kubernetes shifts your security perimeter from static servers to ephemeral containers, namespaces, and service accounts. A misconfigured pod can escape its sandbox, escalate to cluster-admin, or exfiltrate secrets from the entire cluster. I've watched post-mortems where a single forgotten privileged: true flag opened the door to root access on every node.
The seven policies below form the minimum viable defense for any production cluster. They cover pod security standards, role-based access control, network segmentation, and admission control—mechanisms built into Kubernetes that you activate with YAML and command-line flags.
1. Enforce Pod Security Standards with admission controllers
Pod Security Standards replaced PodSecurityPolicies in Kubernetes 1.25. Three profiles exist: Privileged (unrestricted), Baseline (minimal restrictions), and Restricted (hardened, defense-in-depth). Most production namespaces should run Restricted; only infrastructure namespaces (kube-system, monitoring) need Privileged.
Label your namespace to enforce a standard:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Restricted mode blocks privileged containers, host namespaces (PID, IPC, network), hostPath volumes, and the default root user. Every pod must define a non-root securityContext.
Start with warn mode to see violations without blocking deployments, then graduate to enforce after fixing manifests. Check violations with kubectl describe on denied pods.
2. Drop all Linux capabilities by default
Containers inherit a surprising number of Linux capabilities even when not running as root. CAP_NET_RAW lets a container packet-sniff the node network. CAP_SYS_ADMIN is nearly equivalent to root.
Drop every capability, then add back only what the application needs:
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # Only if binding to port <1024
Most web apps need zero capabilities. If your container legitimately needs CAP_SYS_ADMIN or CAP_SYS_PTRACE, isolate it in a dedicated namespace and apply extra monitoring.
3. Restrict service account tokens and RBAC bindings
Every pod mounts a service account token by default. That token carries RBAC permissions—often more than the pod needs. In clusters with overly broad roles, a compromised pod can list secrets, modify deployments, or exec into other pods.
Disable automatic token mounting for pods that don't call the Kubernetes API:
apiVersion: v1
kind: ServiceAccount
metadata:
name: webapp
automountServiceAccountToken: false
For pods that do need API access, create a dedicated ServiceAccount with a narrow Role:
apiVersion: v1
kind: ServiceAccount
metadata:
name: log-reader
namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: read-logs
namespace: production
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: log-reader-binding
namespace: production
subjects:
- kind: ServiceAccount
name: log-reader
roleRef:
kind: Role
name: read-logs
apiGroup: rbac.authorization.k8s.io
Never bind cluster-admin or wildcard permissions (*) to service accounts. Audit existing bindings with:
kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.kind=="ServiceAccount") | {name: .metadata.name, role: .roleRef.name, subjects: .subjects}'
Look for service accounts in kube-system or default with admin-level roles.
4. Isolate workloads with NetworkPolicies
Kubernetes allows all pod-to-pod traffic by default. A frontend pod can connect to the database, the CI runner, and every other service in the cluster. Container escapes become lateral movement opportunities.
NetworkPolicies define firewall rules at the pod level using label selectors. Start with a default-deny policy for each namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Then allow only necessary traffic. For example, let the frontend talk to the API but nothing else:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 8080
- to: # DNS
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
Remember to allow DNS (port 53 UDP to kube-dns or CoreDNS) or every egress policy will break name resolution.
5. Enable admission webhooks to enforce custom policies
Pod Security Standards catch common misconfigurations, but they can't enforce organization-specific rules like "no images from Docker Hub" or "all ingresses must use our TLS issuer." Admission controllers intercept create/update requests and validate or mutate them before they land in etcd.
Two popular policy engines are OPA Gatekeeper and Kyverno. Kyverno uses native Kubernetes YAML, making it easier to adopt:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-registry-prefix
spec:
validationFailureAction: enforce
rules:
- name: check-image-registry
match:
resources:
kinds:
- Pod
validate:
message: "Images must come from registry.company.com"
pattern:
spec:
containers:
- image: "registry.company.com/*"
Deploy Kyverno as a Helm chart, then create ClusterPolicies for your requirements. Start with validationFailureAction: audit to gather violations without blocking deploys.
Common policies to enforce:
- Require resource requests and limits on every container
- Block latest image tags
- Require signed images (via Cosign or Notary)
- Disallow hostPath, hostNetwork, and hostPID
6. Scan container images and block vulnerable workloads
A container image is a tarball of file layers plus metadata. Vulnerabilities hide in outdated base images, unpatched libraries, and abandoned dependencies. I've seen critical CVEs in images that shipped two years ago and never received updates.
Integrate image scanning into your CI pipeline and cluster admission. Trivy is open-source and scans both filesystem layers and language-specific dependencies:
trivy image --severity HIGH,CRITICAL registry.company.com/app:v1.2.3
For runtime enforcement, pair an admission webhook with a scanning service. For example, configure Trivy to reject images with HIGH or CRITICAL findings:
trivy image --exit-code 1 --severity CRITICAL registry.company.com/app:v1.2.3
If the scan fails (exit code 1), your CI rejects the build. Alternatively, deploy an admission controller that queries a vulnerability database before allowing the pod to schedule.
Scan regularly—new CVEs appear daily. Automate rescanning of running images with a CronJob and alert on new findings.
7. Harden the control plane and enable audit logging
The API server, etcd, and kubelet are high-value targets. An attacker who gains etcd access can read every secret in the cluster. Kubelet compromise yields root on the node.
API server hardening:
- Disable anonymous authentication: --anonymous-auth=false
- Enable RBAC: --authorization-mode=Node,RBAC
- Restrict kubelet API access: --kubelet-certificate-authority and client certs
- Rotate service account signing keys regularly
Etcd encryption:
Secrets are stored in etcd in plaintext by default. Enable encryption at rest with an EncryptionConfiguration:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
Pass --encryption-provider-config=/etc/kubernetes/enc.yaml to kube-apiserver.
Audit logging:
Configure the API server to log every request with --audit-log-path and --audit-policy-file. Ship logs to a SIEM or centralized log collector. Watch for privilege escalation attempts, secret reads, and failed authentication.
Example audit policy:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: Metadata
omitStages: ["RequestReceived"]
This logs full request/response bodies for secret modifications and metadata for everything else.
How do you audit your current cluster?
Before deploying new policies, assess what's already running. Use kubectl and open-source scanners to find misconfigurations.
Check for privileged pods:
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.containers[]?.securityContext?.privileged==true) | {name: .metadata.name, namespace: .metadata.namespace}'
Find pods running as root:
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.securityContext.runAsUser==0 or .spec.containers[]?.securityContext?.runAsUser==0) | {name: .metadata.name, namespace: .metadata.namespace}'
List service accounts with cluster-admin:
kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name=="cluster-admin") | {name: .metadata.name, subjects: .subjects}'
Scan for missing NetworkPolicies:
kubectl get networkpolicies --all-namespaces
If a namespace appears empty, all traffic is allowed.
For comprehensive auditing, run kubesec, kube-bench, or Polaris against your manifests and cluster state. Each tool outputs a scored report of misconfigurations.
What happens when a policy blocks a legitimate workload?
You'll see events in kubectl describe pod explaining the denial. Common messages:
violates PodSecurity "restricted:latest"— Pod Security Standard blocked the pod. ChecksecurityContextsettings.admission webhook denied the request— Your policy engine (Kyverno, OPA) rejected it. Review the policy name in the error.Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:pod-reader" cannot create resource "pods"— RBAC issue. The service account lacks the necessary Role or ClusterRole.
Start with warn or audit mode for new policies. Monitor logs for a week, fix manifests, then switch to enforce.
Start with the quick wins
Pod Security Standards and RBAC are configuration flags—no new components to install. Apply them namespace by namespace starting with your least critical workload. Drop Linux capabilities and disable token automounting in the same pass.
NetworkPolicies and admission webhooks require a CNI plugin and policy engine, respectively. If your cluster lacks them, schedule installation in the next sprint. Image scanning integrates into CI/CD and costs nothing but pipeline minutes.
The control plane hardening (etcd encryption, audit logs) touches the API server flags, so coordinate with your platform team if you're on a managed Kubernetes service. Most providers (GKE, EKS, AKS) enable some of these by default.
You don't need all seven on day one, but you do need a plan to deploy them before the next pentest or compliance audit. Pick two policies this week and schedule the rest.
