Skip to content
Cloud Security

Kubernetes Security Hardening Checklist

10 min read·cyber.encse.com Knowledge Base·Last reviewed 12 Aug 2026

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: restricted

Pod 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.io

NetworkPolicies: 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: 5432

Admission 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.

  1. Kubernetes Documentation — Pod Security Standards
  2. Kubernetes Documentation — Network Policies
  3. Kubernetes Documentation — Encrypting Confidential Data at Rest
  4. CIS Kubernetes Benchmark (Center for Internet Security)