Kubernetes Security Hardening Checklist
Kubernetes ships secure-by-default in far fewer places than people assume: by default, pods can run as root, containers can escalate privileges, and any pod on a cluster can talk to any other pod unless a NetworkPolicy says otherwise. Hardening a cluster means deliberately closing each of these gaps across several independent layers — pod-level security context, admission control, network segmentation, RBAC, and the control plane itself. This checklist walks through each layer with concrete manifests.
Pod Security Standards replace PodSecurityPolicy
PodSecurityPolicy (PSP) was deprecated in Kubernetes 1.21 and removed in 1.25. Its replacement, Pod Security Standards (PSS), defines three policy levels — Privileged, Baseline, and Restricted — enforced via the built-in Pod Security Admission (PSA) controller using namespace labels, rather than a separate cluster-wide resource.
The Restricted policy is the one to target for application workloads: it disallows privileged containers, host namespaces, host paths, requires a non-root user, and restricts capabilities. Applying it is a matter of labeling the namespace.
Enforce the Restricted Pod Security Standard on a namespace
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/audit: restricted
pod-security.kubernetes.io/warn: restrictedPod and container securityContext
Independent of PSA, every workload manifest should set an explicit securityContext rather than relying on image defaults. The most impactful settings are running as a non-root, non-privileged user, dropping all Linux capabilities and adding back only what's needed, and mounting the root filesystem read-only so a compromised process can't persist a payload to disk.
Hardened pod and container securityContext
apiVersion: v1
kind: Pod
metadata:
name: api-server
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: registry.internal/api-server:1.4.2
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}RBAC: scope roles to namespaces and specific verbs
The two most common RBAC mistakes are granting ClusterRole bindings where a namespaced Role would do, and using wildcard verbs or resources (`"*"`) instead of listing exactly what's needed. Both make it harder to reason about blast radius if a service account token is exfiltrated.
Service accounts for workloads should almost never have cluster-wide read access, and `automountServiceAccountToken` should be set to `false` on pods that don't call the Kubernetes API at all — most application workloads fall into this category and inherit a token they never use.
Namespaced Role scoped to specific verbs and resources
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: payments
name: configmap-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: payments
name: api-server-configmap-reader
subjects:
- kind: ServiceAccount
name: api-server
namespace: payments
roleRef:
kind: Role
name: configmap-reader
apiGroup: rbac.authorization.k8s.ioNetworkPolicies: default-deny, then allow explicitly
Without any NetworkPolicy resources, Kubernetes networking is flat — every pod can reach every other pod and, depending on the CNI, the node and metadata endpoints as well. The baseline hardening step is a default-deny NetworkPolicy per namespace, followed by explicit allow rules for the traffic each workload actually needs.
NetworkPolicies require a CNI plugin that implements them (Calico, Cilium, and most managed cluster CNIs do; the default kubenet does not), so verify enforcement is actually active before relying on it.
Default-deny ingress and egress, then allow specific traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: payments
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api-server
ports:
- protocol: TCP
port: 5432Admission control: OPA/Gatekeeper and Kyverno
Pod Security Admission covers a fixed set of pod-level checks, but organisations frequently need custom policy — requiring specific labels, blocking images from untrusted registries, enforcing resource limits, or requiring image digests instead of mutable tags. This is the role of a policy engine admission controller such as OPA/Gatekeeper or Kyverno, both of which intercept API requests via a ValidatingAdmissionWebhook (and, for Kyverno, can also mutate resources).
A common early policy is restricting image sources to an approved registry, which limits the impact of a compromised or typosquatted public image reference making it into a manifest.
Both projects are mature enough for production use and the choice often comes down to team preference: Gatekeeper's policies are written in Rego (the Open Policy Agent language), which is powerful and widely used beyond Kubernetes but has a steeper learning curve, while Kyverno policies are expressed as native Kubernetes YAML, which is generally faster for a platform team already comfortable with Kubernetes manifests to pick up. Whichever is chosen, deploy new policies in audit/dry-run mode first — enforcing a policy against live traffic without first checking what it would have blocked is a reliable way to cause an unplanned outage.
Kyverno policy: block images from outside the internal registry
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
validationFailureAction: Enforce
rules:
- name: allowed-registries
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Images must be pulled from registry.internal"
pattern:
spec:
containers:
- image: "registry.internal/*"Secrets, etcd encryption, and the control plane
Kubernetes Secrets are base64-encoded, not encrypted, in etcd by default — anyone with etcd read access or an etcd backup has the plaintext. Enabling encryption at rest for the Secrets resource via an EncryptionConfiguration on the API server closes this gap; on managed clusters (EKS, GKE, AKS), this is often available as a managed KMS-backed option rather than something you configure by hand on etcd itself.
Beyond encryption at rest, treat Secrets as a stopgap rather than a destination: many teams route application secrets through an external secrets manager (see the companion article on Vault vs AWS Secrets Manager vs Azure Key Vault) and sync only what's needed into the cluster, reducing what's exposed if the Kubernetes API itself is compromised.
- •Enable audit logging on the API server and ship logs to a system outside the cluster
- •Restrict access to the Kubernetes API server endpoint (private endpoint or IP allowlist on managed clusters)
- •Rotate and short-lived-token bootstrap credentials for node-to-control-plane authentication
- •Run kube-bench or a CIS Kubernetes Benchmark scan against the cluster to catch control-plane misconfigurations not covered by workload-level policy
Putting it together: a minimal hardening checklist
A cluster that addresses the following is meaningfully harder to pivot through than a default install:
- •Restricted Pod Security Standard enforced on all application namespaces
- •Explicit securityContext (non-root, dropped capabilities, read-only root filesystem) on every workload manifest
- •RBAC scoped to namespaced Roles with specific verbs; automountServiceAccountToken disabled where unused
- •Default-deny NetworkPolicy per namespace with explicit allow rules
- •Admission policy (OPA/Gatekeeper or Kyverno) enforcing image provenance and required labels
- •Secrets encryption at rest enabled on the control plane
- •Regular CIS Kubernetes Benchmark scans (kube-bench) and image scanning in the CI pipeline before deployment
References
Primary sources for the material above. Standards are cited by identifier so they stay findable as publishers reorganise their sites.
- Kubernetes Documentation — Pod Security Standards
- Kubernetes Documentation — Network Policies
- Kubernetes Documentation — Encrypting Confidential Data at Rest
- CIS Kubernetes Benchmark (Center for Internet Security)