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:
@@ -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-
|
||||
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.
|
||||
|
||||
## [ ] 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.
|
||||
|
||||
Reference in New Issue
Block a user