# Container Security

## 1. What a Container Does and Doesn't Isolate

A container is a normal Linux process with **namespaces** (its own view of PIDs, mounts, network, hostname, users) and **cgroups** (resource limits) — `01-docker_fundamentals/`. All containers share the **host kernel**. A kernel exploit, a misconfiguration that hands the container host resources, or a runtime bug can let a process escape to the host. So container security is layers of defense:

```
supply chain (what's in the image) → build (how it's made) → configuration (how it runs)
→ runtime (what it's allowed to do) → host & orchestrator (what's around it)
```

## 2. The Image: Smaller, Pinned, Scanned

- **Minimal bases**: `-slim`, Alpine, **distroless**, or `scratch` (multi-stage builds, `06-multi_stage_builds/`). Fewer packages = fewer CVEs and no shell for an attacker.
- **Pin versions** — and ideally **digests**: `FROM node:22.11.0-bookworm-slim@sha256:…`, so a tag can't silently change underneath you.
- **Scan** images in CI (Trivy, Grype, Docker Scout) and fail on fixable critical/high CVEs; rescan regularly — new CVEs appear for old images.
- **Rebuild often** to pick up base-image patches.
- **No secrets in images** — not in `ENV`, `ARG`, files or layers. Inject at runtime (orchestrator secrets, mounted files, a secrets manager).
- **SBOM and signing**: generate a software bill of materials (Syft), sign images (cosign/Sigstore) and verify signatures at deploy time.

## 3. Run as Non-Root

By default a container process is **root (uid 0)**. Without user namespaces, that's the same uid 0 as on the host — if it escapes (or a host path is mounted), it's root on the host.

```dockerfile
RUN groupadd -r app && useradd -r -g app -u 10001 app
USER 10001
```

In Kubernetes: `securityContext: { runAsNonRoot: true, runAsUser: 10001 }`.

## 4. Drop Privileges at Runtime

```bash
docker run \
  --user 10001:10001 \
  --read-only --tmpfs /tmp \              # immutable root filesystem
  --cap-drop ALL --cap-add NET_BIND_SERVICE \   # only the capabilities you need
  --security-opt no-new-privileges \      # setuid binaries can't escalate
  --pids-limit 200 --memory 512m --cpus 1 \     # resource limits (DoS containment)
  --network app-net \
  myapp:1.4.2
```

| Setting | Protects against |
|---|---|
| non-root user | escalation to host root on escape |
| `--cap-drop ALL` | abuse of kernel capabilities (e.g. `NET_RAW`, `SYS_ADMIN`) |
| read-only root fs | attackers writing tools/backdoors into the container |
| `no-new-privileges` | escalation via setuid/setgid binaries |
| seccomp / AppArmor / SELinux profiles | dangerous syscalls (Docker's default seccomp blocks ~44) |
| resource limits | fork bombs, memory exhaustion of the host |

## 5. Never Do These

- `--privileged` — disables nearly all isolation; effectively root on the host.
- Mount the Docker socket (`-v /var/run/docker.sock:/var/run/docker.sock`) — anyone in the container controls the Docker daemon, i.e. the host.
- Mount sensitive host paths (`/`, `/etc`, `/proc`, `/var/run`).
- `--network host` / `--pid host` without a very good reason.
- Expose the Docker daemon over TCP without TLS.

## 6. Orchestrator Layer (Kubernetes)

- **Pod Security Standards** (`restricted` profile) enforce non-root, no privilege escalation, dropped capabilities, seccomp `RuntimeDefault`.
- **NetworkPolicies**: default-deny, then allow only the flows each service needs.
- **RBAC** for service accounts; don't automount tokens that aren't needed.
- **Admission control** (Kyverno, OPA Gatekeeper) to reject unsigned or unscanned images and insecure specs.
- **Runtime detection** (Falco): alert on shells spawned in containers, unexpected outbound connections, writes to sensitive paths.

## Under the Hood: Capabilities, Seccomp and User Namespaces

Linux split root's powers into ~40 **capabilities** (`CAP_NET_ADMIN`, `CAP_SYS_ADMIN`, `CAP_CHOWN`, …). Docker starts containers with a reduced default set (14 or so); `--cap-drop ALL` removes the rest, so even uid 0 inside can't, say, change network config or load kernel modules. `CAP_SYS_ADMIN` is the famous "new root": it allows mounts and many escape techniques — granting it nearly equals `--privileged`.

**Seccomp** filters system calls with a BPF program evaluated by the kernel on every syscall; Docker's default profile denies calls like `keyctl`, `add_key`, `unshare` (in many cases), `mount`, `kexec_load` and `ptrace` on older kernels — shrinking the kernel attack surface a container can reach.

**User namespaces** (`userns-remap`, rootless Docker/Podman) map uid 0 *inside* the container to an unprivileged uid range on the host (e.g. 100000–165535). Then even a process that is "root" in the container and escapes is just an ordinary user on the host. Rootless mode goes further and runs the whole daemon without root privileges.

![Defense in depth for containers: minimal pinned and scanned images, non-root users, dropped capabilities, read-only filesystems, seccomp and resource limits, and orchestrator policies, around a shared host kernel](diagrams/01-defense-layers.svg)

## Common Pitfalls

1. **Running as root** because it's the default.
2. **`latest` tags** and unpinned bases — unreproducible, unreviewed changes.
3. **Secrets baked into images** (`ENV API_KEY=...`).
4. **`--privileged` or the Docker socket** mounted "to make it work".
5. **Scanning once** at build time and never again.
6. **No resource limits** — one container can starve the node.

## Quick Reference

```
image:   slim/distroless · pinned (digest) · scanned in CI · SBOM + signed · no secrets
build:   multi-stage · USER non-root · .dockerignore
run:     --read-only --cap-drop ALL --security-opt no-new-privileges · limits · seccomp default
never:   --privileged · docker.sock mount · host namespaces
k8s:     restricted PSS · NetworkPolicy default-deny · RBAC · admission policies · runtime detection
