docs/TODO: add #15 — scope root overlay to per-subtree tmpfs (un-shadowable /usr + STIG mount options)

Captures the design + quantified STIG payoff (~21 mount/partition rules
notapplicable/waived -> pass, per reports/stig-arf.xml), the writable-set +
noexec/nosuid/nodev flag matrix, the kiosk-/tmp-noexec prerequisite, the
fstab-vs-runtime OVAL note, and that it's bench-gated after TPM enrolment (#11).
This commit is contained in:
Audiobuzz
2026-07-02 11:31:33 +00:00
parent 7f975bfa5f
commit 1e1265b5a0
+78
View File
@@ -1247,3 +1247,81 @@ desired-state pull (version pin, reboot/drain, kiosk URL) wired to the deploymen
manifest + SPDK flow. Build-time decisions (telemetry encoding, metrics/desired- manifest + SPDK flow. Build-time decisions (telemetry encoding, metrics/desired-
state store, per-host vs cohort, osquery/FleetDM eval) listed in the design note. state store, per-host vs cohort, osquery/FleetDM eval) listed in the design note.
Depends on the step-ca fleet CA (#8) for the mTLS identity. Depends on the step-ca fleet CA (#8) for the mTLS identity.
## [ ] 15. Scope the root overlay → per-subtree tmpfs/overlay (un-shadowable `/usr` + STIG mount options)
**DESIGN CAPTURED 2026-07-02.** Today the whole root is one `systemd.volatile=overlay`
tmpfs over `/` (see #5, invariant on the volatile overlay). Replace it with a
**read-only verity root + a handful of scoped writable mounts**, for two payoffs
that land from the same change:
1. **Security — un-shadowable `/usr`.** dm-verity seals the backing store, but the
whole-root overlay lets any root process write a tmpfs *copy* over a system
binary (e.g. `/usr/sbin/fleet-enrol`) that then executes in preference to the
sealed one until reboot. fapolicyd is the current compensating control (CLAUDE.md:
"the writable-overlay exec guard dm-verity can't provide"). Keeping `/usr` (and
`/`, `/opt`, `/boot`) strictly **read-only** makes the executable set both
verity-sealed *and* un-shadowable — defence in depth, shrinks fapolicyd reliance.
Also: writes outside the sanctioned set fail loudly (`EROFS`) instead of silently
landing in RAM and vanishing at reboot — enforces invariant #1 (no per-host state)
architecturally.
2. **STIG — converts the partition/mount family from `notapplicable`/waived to real
`pass`.** Quantified against the scan in `reports/stig-arf.xml` (NOT estimates):
the `mount_option_*` rules currently score **`notapplicable`** (no separate mount
to check — the "false-clean via notapplicable" pattern #2 warns about), and
`partition_for_*` are **`notselected`** (waived in `diskless-tailoring.xml`
lines 61-66). Scoped tmpfs mounts with `noexec,nosuid,nodev` + re-selecting the
partition rules convert **~21 rules to genuine `pass`**:
- 15 `mount_option_*` N/A→pass: `/tmp` (nodev,nosuid[,noexec*]), `/var/tmp`
(all 3), `/var/log` (all 3), `/var/log/audit` (all 3), `/var` (nodev), `/home`
(nodev,nosuid).
- 6 `partition_for_*` re-selects→pass: tmp, var, var_log, var_log_audit, var_tmp,
home (+1 easy: `partition_for_dev_shm` — already a separate tmpfs).
- Retires **deviation-register entry #1** (the narrative hand-wave over the whole
partition/mount family) — one fewer waiver to defend at ATO.
- `/dev/shm`'s three option rules **already pass** — no work there.
**Writable-set + flag matrix (the design):**
- `/` `/usr` `/opt` `/boot` → **read-only** (verity as-is, no overlay). `/usr` keeps
suid for legit setuid bins (sudo/su/passwd).
- `/etc` → overlay (lower=verity `/etc`, upper=tmpfs) — needs defaults + writability
(machine-id, regenerated ssh host keys, NM connections, resolv.conf, chrony).
- `/var` → overlay (NOT bare tmpfs — bare tmpfs hides `/var/lib/rpm`, selinux store);
`nodev`. Nest the noexec leaf mounts on top (order: `/var` first, then children).
- `/var/tmp` `/var/log` `/var/log/audit` → tmpfs `noexec,nosuid,nodev`.
- `/tmp` → tmpfs `nosuid,nodev` (+`noexec` only after the kiosk repoint, below).
- `/home` `/root` `/srv` → tmpfs `nodev,nosuid`.
- Each tmpfs needs the **correct SELinux context** (`tmp_t`, `var_log_t`,
`auditd_log_t`, …) via `context=` or restorecon — this is the login-deny hazard
class (#5); a mislabeled tree denies logins / stops auditd.
**Prerequisites / caveats that gate the count:**
- `mount_option_tmp_noexec` (1 of the 21) is **conditional**: register entry #1 notes
the Chromium kiosk uses `/tmp` for profile/cache and breaks under `noexec`. Must
**repoint the kiosk scratch** (Chromium `--user-data-dir`/`--disk-cache-dir`) to a
dedicated exec-capable tmpfs (e.g. `/run/kiosk`) first → ties to #13/#4. So ~20
land immediately, +1 after the repoint.
- **fstab-vs-runtime OVAL — mostly de-risked:** the rules return `notapplicable`
(evaluating the live mountpoint) not `fail` (missing fstab line), evidence they read
the runtime mount table — so systemd mount units / initramfs mounts should score
without fstab entries. Confirm per-rule on the bench; add fstab lines if any rule is
fstab-based (compatible with the empty-fstab invariant — root still comes from
netroot+verity).
- **Does NOT fix red findings** (this family is ~0 fail today) — the value is
evidenced compliance + one fewer deviation + kernel-enforced noexec, not clearing
fails. Framed honestly so it isn't oversold.
- **`partition_for_usr` stays waived** — `/usr` should be verity-sealed, not a
separate writable partition (a *stronger* control than the rule asks). Same for
`encrypt_partitions`, `auditd_..._large_partition`, boot/boot_efi (genuinely N/A).
**Mechanism:** `systemd.volatile=` has no per-dir knob (it's all-or-var-only), so this
means dropping the global volatile overlay for explicit early mount units (ordered
before `local-fs.target`) or initramfs mounts. Reproducibility unaffected (runtime
mounts, not image content → no verity-hash drift).
**Load-bearing / bench-gated:** touches the boot path (#5 territory) — full boot-chain
bench (`serving/dot1x-bench/`) + careful SELinux-label verification + a reconciliation
note in `design-v2-errata.md`. **Do AFTER TPM enrolment (#11) is bench-verified.**
Tailoring edits: re-select the 6 `partition_for_*` (+dev_shm), delete/gut register
entry #1, re-run `scripts/stig-deviations.sh` to confirm the new passes.