fivenines
39/40
Lesson 39 · Production

Hardening MiniDock

In one sentence: Production hardening matches one mitigation to each link of the attack chain — verify what you run (digest pinning, scanning, signing), constrain what it can do (user remap, read-only rootfs, no new privileges), and contain what it can reach (segmentation, secrets off disk).

You already know

  • From Lesson 6: capabilities, seccomp, LSM — the default sandwich around every container.
  • From Lesson 8: digests make content verifiable; from Lesson 28: absent edges are unreachability.

Before it runs: trust the bytes

Most compromises arrive in the image, not through the kernel. Three gates: pin digests — deploy image@sha256:…, not a mutable tag someone can quietly repoint (Lesson 17's mutable-pointer risk, weaponized); scan layers against vulnerability databases before promotion; verify signatures so only images signed by your build system are admitted. All three are pull-time policies bolted onto machinery you already designed in Parts 2–3.

While it runs: assume breach

Layer the runtime so a compromised app is a disappointing prize. User-namespace remap (Lesson 4): container-root maps to an unprivileged host UID, so an escape lands without privileges. Read-only rootfs: the overlay upper becomes immutable; the app writes only to declared volumes and tmpfs — malware can't persist itself. no-new-privileges: setuid binaries can't re-escalate. Network segmentation (Lesson 28) bounds lateral movement; tmpfs secrets (Lesson 25) keep credentials off every disk and out of every image layer.

Architecture — mitigations pinned to the attack chain
flowchart TB
  A1["attacker publishes
poisoned image tag"] -- "blocked by" --> D1["digest pinning +
signature verification"] A2["known CVE in a layer"] -- "blocked by" --> D2["pre-deploy scanning"] A3["app exploited at runtime"] -- "contained by" --> D3["read-only rootfs +
no-new-privileges"] A4["escape to host"] -- "lands unprivileged via" --> D4["user-ns remap +
L6 sandwich"] A5["lateral movement"] -- "bounded by" --> D5["network segmentation (L28)"] A6["credential theft from disk"] -- "prevented by" --> D6["tmpfs-only secrets (L25)"]
Sequence — a poisoned tag dies at admission
sequenceDiagram
  participant A as Attacker
  participant R as Registry
  participant E as Hardened engine
  A->>R: repoint tag web:stable at poisoned manifest
  E->>R: pull web@sha256:9f3c… (pinned digest, ignores the tag)
  R-->>E: the ORIGINAL manifest — tags can lie, digests can't
  E->>E: signature check: signed by our CI ✓
  note over E: even if forced to use tags, the unsigned
poisoned manifest fails admission

Anchor these

  • Verify bytes before run; constrain behavior during run; bound reach after breach.
  • Tags are attack surface — production deploys pin digests.
  • Every hardening knob reuses a mechanism from earlier lessons; security is configuration of good architecture.
Next step

See what actually stuck.

Take the practice scenarios now.