SPIKE 2 complete: locked root closes the PVE web UI; before-network gives a measured zero window
Findings only — no script, profile or build file changed; no ISO built, nothing published.
documentation/audits/SPIKE-universal-iso-2-2026-07-31.md
Both Tier 0 boxes went offline mid-session (provider cable fault; four routes tried, no Tier 2
fallback used) and returned. All three scenarios then ran to completion on real PVE, each signalled
by reboot-mode='power-off' rather than a disk hash.
- A LOCKED ROOT CLOSES THE PVE WEB INTERFACE. Measured at the exact endpoint the UI uses
(POST /api2/json/access/ticket, root@pam) WITH A WORKING CONTROL: known-password install returns
HTTP 200 + ticket; locked install returns 401 for every password and none can exist.
passwd -S root = L, shadow = literal-asterisk, PVE uses the stock PAM stack.
- GRUB recovery mode is also closed ('the root account is locked') — but init=/bin/bash still gives
an unauthenticated root@(none):/#. A locked box is recoverable, operator-only, at the console.
The installed GRUB has NO password, so locking root is not a physical-security measure. R-152.
- before-network MEASURED (A/B, same image): the hook RUNS (marker, uptime 6.58s) with entropy 256,
writable /etc, all binaries and openssl_rand_len=32, while ip_global is EMPTY and
listen_22_8006 = 0. fully-up is the converse: sshd+pveproxy active, 3 listening. Zero window.
- R-148: answer.toml.tmpl:27 justifies fully-up with a pvesh/pct dependency the stub does not have
(grep rc=1) — it blocked the ordering now measured as the fix.
- R-149 three ordering values; R-150 Condition-guarded hooks skip silently; R-151 demo-felhom built
from an uncommitted profile.
Three probes failed and are recorded as failed: a container probe that ran as uid 0, a GRUB probe
that missed the 1-second menu timeout, and a kernel-line edit one line off (caught by a pre-typing
verification screendump). The interim 'Layer 1 teardown INCOMPLETE' is corrected — the fixture had
never landed, because the staging mkdir was in the SSH call that timed out.
This commit is contained in:
+27
-29
@@ -1,38 +1,36 @@
|
||||
## SPIKE 2 (no code change) — before-network gives a zero window by construction; locked root closes sulogin (2026-07-31)
|
||||
## SPIKE 2 (no code change) — locked root closes the PVE web UI; before-network gives a measured zero window (2026-07-31)
|
||||
|
||||
**No script, profile or build file was modified.** Findings:
|
||||
`documentation/audits/SPIKE-universal-iso-2-2026-07-31.md`. Opens R-148..R-151.
|
||||
`documentation/audits/SPIKE-universal-iso-2-2026-07-31.md`. Opens R-148..R-152.
|
||||
|
||||
**Both Tier 0 boxes went offline mid-session** (remote site, 12:28 CEST; four routes tried, our
|
||||
tailscale pod healthy). Q1/Q2/Q3 each retain a part that needs a nested VM and those parts are
|
||||
**BLOCKED, not answered**. The fixture is staged with Spike 1's two defects already corrected.
|
||||
Both Tier 0 boxes went offline mid-session (provider cable fault; four routes tried) and returned;
|
||||
all three scenarios then ran to completion on real PVE, each signalled by `reboot-mode="power-off"`.
|
||||
|
||||
Established without them:
|
||||
|
||||
- **STRUCTURAL — `ordering = "before-network"` maps to `proxmox-first-boot-network-pre.service`
|
||||
(`proxmox-first-boot` 9.2.5), which is `Before=network-pre.target` + `Type=oneshot`.** It therefore
|
||||
runs to completion before any interface is configured — a credential rotation there has a
|
||||
**zero-length window by construction**, not by being fast. Stronger than the timing number Spike 1
|
||||
failed twice to obtain.
|
||||
- **STRUCTURAL — the stub does not need `fully-up`.** `stub-first-boot.sh` contains no
|
||||
`pvesh`/`pct`/`pveum`/`qm` call (grep rc=1); that usage is in `felhom-bootstrap.sh`, which runs under
|
||||
its own `After=network-online.target ... pveproxy.service` unit. So `answer.toml.tmpl:27` justifies
|
||||
the current ordering with a dependency that does not exist. **R-148.**
|
||||
- **The ordering enum has THREE values** — `before-network`, `network-online`, `fully-up`. Spike 1
|
||||
recorded two; the middle one has never been evaluated. **R-149.**
|
||||
- **MECHANISM (container, not PVE) — a locked root closes `sulogin`:** `Cannot open access to console,
|
||||
the root account is locked.` for both `*` and `!`, with a working control (correct password ->
|
||||
SU_OK, wrong -> Authentication failure). So "discard" and "lock" are the SAME outcome for recovery,
|
||||
which makes the escrow decision binary.
|
||||
- **All four `proxmox-first-boot-*` units are `Condition*`-guarded, and a failed condition is a SKIP,
|
||||
not a failure** — a hook that never ran looks exactly like one that succeeded. **R-150.**
|
||||
- Q4: the single-entry menu is enforced by **four** gates in `iso-repack.sh` (`:153-155`, `:156`,
|
||||
`:160-164`, `:314-319`), so restoring an interactive entry is a gate change, not a template edit —
|
||||
but the banned-token gate would pass unchanged for a stock Graphical entry. `default`/`timeout` are
|
||||
- **A locked root CLOSES the PVE web interface.** Measured against the exact endpoint the UI logs in
|
||||
through (`POST /api2/json/access/ticket`, `root@pam`) **with a working control**: the known-password
|
||||
install returns **HTTP 200 + ticket**; the locked install returns 401 for every password, and no
|
||||
password exists. `passwd -S root` = `L`, shadow = `literal-asterisk`. PVE uses the stock PAM stack
|
||||
(`pve_pam_realm: 0`).
|
||||
- **GRUB recovery mode is ALSO closed**: `Cannot open access to console, the root account is locked.`
|
||||
— the container mechanism result confirmed verbatim on real PVE.
|
||||
- **But `init=/bin/bash` still works** — an unauthenticated `root@(none):/#`. So a locked-root box is
|
||||
recoverable, operator-only, at the physical console. The installed GRUB has **no password**, so
|
||||
locking root is NOT a physical-security measure. **R-152.**
|
||||
- **`before-network` measured, A/B, same image:** the hook runs (positive marker, uptime 6.58s) with
|
||||
entropy 256, writable /etc, all binaries and `openssl_rand_len=32`, while `ip_global` is EMPTY and
|
||||
`listen_22_8006` is **0**. The `fully-up` column is the converse proof: sshd + pveproxy already
|
||||
active, 3 listening. Zero credential window, measured.
|
||||
- **R-148:** `answer.toml.tmpl:27` justifies `fully-up` with a `pvesh`/`pct` dependency the stub does
|
||||
not have (grep rc=1) — it blocked the ordering that is now measured as the fix.
|
||||
- **R-149** three ordering values, not two · **R-150** Condition-guarded hooks skip silently ·
|
||||
**R-151** demo-felhom built from an uncommitted profile.
|
||||
- Q4 from source: four gates in `iso-repack.sh` enforce the single-entry menu; `default`/`timeout`
|
||||
already settable in `grub/grub.cfg.tmpl`.
|
||||
|
||||
The first mechanism probe was invalid (ran as uid 0, so `su` never invoked pam_unix; `sulogin` had no
|
||||
tty) and is recorded as failed, not massaged.
|
||||
Three probes failed and are recorded as failed, not massaged: a container probe that ran as uid 0
|
||||
(so `su` never invoked pam_unix), a GRUB probe that missed the **1-second** menu timeout and typed into
|
||||
the login prompt, and a kernel-line edit that landed one line off — caught by a pre-typing
|
||||
verification screendump.
|
||||
|
||||
## SPIKE (no code change) — a universal ISO needs a different disk strategy and a locked root (2026-07-31)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user