# Enforce the restricted Pod Security Standard

> Turn on Pod Security Admission for a namespace without breaking its apps: dry-run the label, fix the Pods, then enforce restricted.

Level: Advanced · Topic: Security · About 15 minutes
Try it in the browser: https://k8wiz.com/playground/enforce-the-restricted-pod-security-standard

The playground is a simulated Kubernetes cluster that runs in the browser. kubectl output and errors follow the real ones.

## The situation

The `payments` namespace must enforce the `restricted` Pod Security Standard: no root, no privilege escalation, no extra capabilities, a seccomp profile.

Pod Security Admission checks a namespace's Pods against the level in its `pod-security.kubernetes.io/enforce` label. Pods that break it are rejected, and a Deployment can't replace its Pods.

`api` and `web` run there today. Find what would break before you enforce, fix both, and keep `web` answering on its Service. Their manifests are in the editor.

## Objectives

1. See which Pods would break, before enforcing (`kubectl label --dry-run=server` doesn't change anything)
2. api meets the restricted standard
3. web meets the restricted standard
4. payments enforces restricted
5. web still answers on its Service

## Hints

1. `kubectl label --dry-run=server --overwrite ns payments pod-security.kubernetes.io/enforce=restricted` lists what each Pod breaks: `allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile`.
2. Add to each Pod template: `securityContext: {runAsNonRoot: true, seccompProfile: {type: RuntimeDefault}}`, and to each container: `securityContext: {allowPrivilegeEscalation: false, capabilities: {drop: ["ALL"]}}`.
3. `api`'s image runs as a non-root user already. The official `nginx` image starts as root, so with `runAsNonRoot: true` it fails with `CreateContainerConfigError`. `nginxinc/nginx-unprivileged:1.29` runs as UID 101 and listens on 8080.
4. The Service targets the port named `http`. Rename nothing: change `containerPort` to 8080 and the Service follows.

## Solution

```bash
kubectl label --dry-run=server --overwrite ns payments pod-security.kubernetes.io/enforce=restricted
```
```bash
cat > api.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: payments
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: api
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        image: ghcr.io/k8wiz/hello:1.1
        ports:
        - name: http
          containerPort: 8080
EOF
```
```bash
cat > web.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: payments
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: web
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        image: nginxinc/nginx-unprivileged:1.29
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: payments
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: http
EOF
```
```bash
kubectl apply -f api.yaml
```
```bash
kubectl apply -f web.yaml
```
```bash
kubectl rollout status deployment api
```
```bash
kubectl rollout status deployment web
```
```bash
kubectl label ns payments pod-security.kubernetes.io/enforce=restricted pod-security.kubernetes.io/warn=restricted
```

## What it teaches

- Pod Security Admission is built in. A namespace label picks the level (`privileged`, `baseline`, `restricted`) and the mode: `enforce` rejects Pods, `warn` and `audit` only report.
- Enforcing never stops running Pods. It blocks new ones, so the next rollout or node failure is when things break. Dry-run the label first.
- Deployments are only warned about; their ReplicaSets then fail to create Pods (`FailedCreate`). Always look at the ReplicaSet's events when a rollout makes no Pods.
- `runAsNonRoot` is checked by the kubelet against the image's user. Prefer images built to run as non-root over forcing a UID onto one that expects root.
