Pod Security Standards on EKS

Pod Security Standards (PSS) define three levels of pod hardening. Pod Security Admission (PSA) is the built-in admission controller that enforces them per namespace. Both are standard Kubernetes and enabled on every EKS cluster; there is nothing to install. The definitions are covered in Pod Security Standards (concepts) — this page is about rolling them out on EKS without breaking it.

The three levels

LevelBlocksUse for
privilegedNothingkube-system, CNI, CSI, security agents
baselinePrivileged containers, host namespaces, hostPath, host ports, added capabilitiesThe minimum for any application namespace
restrictedAlso requires non-root, dropped capabilities, seccompProfile: RuntimeDefault, no escalationThe target for application namespaces

Each namespace sets a level for three modes: enforce rejects the pod, audit records a violation in the audit log, warn returns a warning to the client.

apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: v1.33
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Pin enforce-version. With latest, a control plane upgrade can tighten the rules and start rejecting pods that were fine the day before.

The EKS default is “allow everything”

A namespace without labels falls back to the cluster-wide default, and on EKS that default is privileged for all three modes. You cannot change it, because EKS does not expose the API server’s admission configuration file. In practice:

  • every namespace needs labels — nothing is protected until you add them;
  • new namespaces start unprotected, so label them at creation through your namespace template, GitOps repo or a policy engine that mutates or rejects unlabelled namespaces.

What must stay privileged

EKS system components genuinely need host access, so kube-system stays privileged:

ComponentWhy it cannot meet baseline
aws-node (VPC CNI)Host network, privileged init, manipulates ENIs and iptables
kube-proxyHost network, writes iptables/nftables rules
EBS / EFS CSI node pluginsPrivileged, mount host paths to attach and mount volumes
Pod Identity agentHost network
GuardDuty agent, Fluent Bit, node exportersRead host paths and processes

Put your own infrastructure add-ons in dedicated namespaces labelled privileged, and tightly control who can deploy there with RBAC. A privileged namespace anyone can deploy into defeats the whole scheme.

Rollout that does not cause an outage

  1. See what would break. A server-side dry run evaluates existing pods against a level without changing anything:

    kubectl label --dry-run=server --overwrite ns --all \
      pod-security.kubernetes.io/enforce=baseline
  2. Start with warn and audit at restricted on application namespaces. Developers see warnings on kubectl apply; you see violations in the audit log (query them in control plane logs by the pod-security.kubernetes.io/audit-violations annotation).

  3. Enforce baseline. Most well-behaved applications pass unchanged.

  4. Fix manifests, then enforce restricted. The usual changes are in the pod’s security context:

    securityContext:
      runAsNonRoot: true
      seccompProfile: { type: RuntimeDefault }
    containers:
      - name: app
        securityContext:
          allowPrivilegeEscalation: false
          capabilities: { drop: ["ALL"] }

Note that PSA evaluates pods. A Deployment that violates the policy is accepted with a warning and then silently fails to create pods; look at the ReplicaSet’s events when replicas stay at zero.

Where PSA stops

PSA is deliberately simple. It cannot:

  • exempt a single workload inside a namespace (exemptions are by namespace, user or runtime class, set in the API server config that EKS does not expose);
  • mutate pods to add missing defaults;
  • express anything outside the three profiles: allowed registries, required labels, resource limits, image signatures.

For those, add a policy engine — see policy management on EKS. A common pattern is PSA baseline everywhere as a safety net that cannot be bypassed by a webhook outage, plus Kyverno or a ValidatingAdmissionPolicy for the fine-grained rules.

History: PodSecurityPolicy

Clusters created before Kubernetes 1.25 used PodSecurityPolicy with a permissive default named eks.privileged. PSP was removed in 1.25. Anything still referencing it is dead configuration and can be deleted.

Across the wiki