Pod Security Standards and Gatekeeper

Admission is the last chance to reject a bad pod before it is stored. Kubernetes has built-in plugins for that. Pod Security Standards cover the common cases. Gatekeeper covers the rest.

The request path is:

API request → authn → authz → mutating admission → validating admission → etcd

Mutating runs first. Validating sees the object after mutation. If you are debugging a deny, look at both.

Built-in plugins worth having

On the API server:

--enable-admission-plugins=NodeRestriction,ResourceQuota,LimitRanger,ServiceAccount,DefaultStorageClass,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,PodSecurity

NodeRestriction limits what a kubelet identity can write. PodSecurity enforces the three levels below. Webhook plugins are how Gatekeeper attaches.

Pod Security Standards

Three levels, least to most tight:

  • privileged — no extra restrictions
  • baseline — blocks known privilege-escalation patterns
  • restricted — hardened defaults

The practical way to apply them is namespace labels:

kubectl label namespace secure-apps \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

enforce rejects. warn and audit tell you what would have been rejected.

Cluster-wide defaults live in admission config:

# /etc/kubernetes/admission-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
  configuration:
    apiVersion: pod-security.admission.config.k8s.io/v1beta1
    kind: PodSecurityConfiguration
    defaults:
      enforce: "baseline"
      enforce-version: "latest"
      audit: "restricted"
      audit-version: "latest"
      warn: "restricted"
      warn-version: "latest"
    exemptions:
      namespaces: ["kube-system", "kube-public"]

Baseline blocks privileged: true, host namespaces, hostPath, and the loud capabilities (SYS_ADMIN, NET_ADMIN, and friends). Restricted also wants non-root, allowPrivilegeEscalation: false, drop: ["ALL"], and seccompProfile.type: RuntimeDefault.

A restricted-shaped pod:

apiVersion: v1
kind: Pod
metadata:
  name: restricted-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: nginx
    securityContext:
      allowPrivilegeEscalation: false
      runAsNonRoot: true
      capabilities:
        drop: ["ALL"]
      seccompProfile:
        type: RuntimeDefault
      readOnlyRootFilesystem: true

Test before you apply:

kubectl --dry-run=server apply -f pod.yaml
kubectl get namespace default --show-labels
kubectl get events --field-selector reason=PodSecurityViolation

Gatekeeper

PSS is a fixed menu. Gatekeeper is policy as code: a ConstraintTemplate (Rego) plus a Constraint (where it applies).

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.14/deploy/gatekeeper.yaml
kubectl get pods -n gatekeeper-system

A template that rejects privileged containers:

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sblockprivileged
spec:
  crd:
    spec:
      names:
        kind: K8sBlockPrivileged
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sblockprivileged

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          container.securityContext.privileged == true
          msg := "Privileged containers are not allowed"
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockPrivileged
metadata:
  name: block-privileged-containers
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]

Other policies that come up constantly: require CPU and memory limits, block UID 0, refuse :latest and untagged images.

kubectl get constrainttemplates
kubectl get constraints
kubectl describe constraint block-privileged-containers
kubectl logs -n gatekeeper-system -l control-plane=controller-manager

PSS for the baseline. Gatekeeper for the house rules. Do not put kube-system under restricted unless you like broken addons.