docs(10-03): record C-4 build-host evidence procedure (UNVERIFIED)
Task 3 of 10-03 is a blocking checkpoint: proving the shipped rootfs tar is identity-free needs a real ISO build host with podman/docker and disk for a full rootfs rebuild. This commits the prepared evidence document with the exact command sequence, marked UNVERIFIED, rather than claiming the check passed. The document states the inverted expectation explicitly. The audit's C-4 entry expected SSH host keys and the TLS key to be PRESENT — that described the broken state it was measuring. After the strip layer those must be ABSENT, so the audit's stated expectation is now the failure condition. A future reader comparing the two would otherwise conclude the check regressed. Also records two things the operator would otherwise get wrong: - RECIPE_HASH must be read from the stamp file, not computed from the repo file. build-debian-iso.sh rewrites the builder's relative paths into a temp copy before exec, and the hash covers "$0"; the hashed region has 35 such rewritten expressions plus an absolutised SCRIPT_DIR, so the value is specific to the build host and checkout path. - C-4 is a build-host check only. Two-node key divergence is C-3 and stays separately UNVERIFIED; the note explains why SSH host keys are the sharper signal there than TLS, given the installer's per-install TLS fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
408b328c39
commit
201ef474e7
@@ -0,0 +1,154 @@
|
||||
# KEY-02 — build-host evidence for the rootfs identity strip
|
||||
|
||||
**Audit item:** C-4 of `docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md` (§868), which
|
||||
belongs to finding **F-03** (fail-open, never-retried first-boot secret regeneration over
|
||||
a fleet-shared rootfs).
|
||||
|
||||
**Status: ⛔ UNVERIFIED — awaiting a run on a real ISO build host.**
|
||||
|
||||
The code change is committed and unit-tested; the tar listing that proves its effect on a
|
||||
real build has not been produced yet, because it requires a build host with podman/docker
|
||||
and enough disk for a full rootfs rebuild. Do not read anything below the "Result" heading
|
||||
as a passing check until it is filled in.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Builder commit (Task 1) | `21043096` — fail-closed first-boot regeneration |
|
||||
| Builder commit (Task 2) | `408b328c` — rootfs identity strip |
|
||||
| Builder file | `image-recipe/_archived/build-auto-installer-iso.sh` (LIVE; `image-recipe/build-debian-iso.sh` execs it) |
|
||||
| Build host | _to be recorded_ |
|
||||
| Date run | _to be recorded_ |
|
||||
| RECIPE_HASH observed | _to be recorded — read it from the stamp file, see the caveat below_ |
|
||||
|
||||
---
|
||||
|
||||
## The expectation is deliberately INVERTED relative to the audit
|
||||
|
||||
This is the single most important thing to understand when comparing this document with the
|
||||
audit, and the reason it is stated before the commands rather than after.
|
||||
|
||||
The audit's C-4 entry says:
|
||||
|
||||
> **Expected:** SSH host keys and the TLS key **present** (they are baked — see
|
||||
> `build-auto-installer-iso.sh:345`, `:463-469`), `random-seed` **absent**, `machine-id`
|
||||
> absent or zero-length. Anything else changes F-03's severity.
|
||||
|
||||
That expectation described the **broken** state the audit found, and recording it was how the
|
||||
audit measured the size of F-03. Phase 10 plan 10-03 Task 2 removed that material. So:
|
||||
|
||||
**After this change, the audit's stated expectation is the FAILURE condition.** If SSH host
|
||||
keys or the TLS private key still appear in the tar, the strip layer did not run — most
|
||||
likely because a cached `archipelago-rootfs.tar` was reused. That is not a regression in the
|
||||
check; it is the check working.
|
||||
|
||||
The two negative findings the audit recorded are unchanged and must still hold:
|
||||
`var/lib/systemd/random-seed` absent, `etc/machine-id` absent or zero-length.
|
||||
|
||||
---
|
||||
|
||||
## Commands to run
|
||||
|
||||
Run all of these **on the build host**, from the repo root, on a checkout that contains
|
||||
commits `21043096` and `408b328c`.
|
||||
|
||||
### 1. Force a full rebuild
|
||||
|
||||
The strip layer lives inside the `RECIPE_HASH` region (between the `# STEP 1: Build complete
|
||||
root filesystem` and `# STEP 2: Build minimal installer` markers), so the hash changes and the
|
||||
cached tar is invalidated automatically. `--rebuild` is passed anyway so that a stale tar
|
||||
cannot mask the result for any reason:
|
||||
|
||||
```bash
|
||||
UNBUNDLED=1 bash image-recipe/build-debian-iso.sh --rebuild
|
||||
```
|
||||
|
||||
`UNBUNDLED=1` is mandatory per `CLAUDE.md` and project memory — the default env silently
|
||||
builds the wrong full-bundle variant.
|
||||
|
||||
### 2. List the identity artefacts in the shipped tar
|
||||
|
||||
`WORK_DIR` is `image-recipe/build/auto-installer`, so:
|
||||
|
||||
```bash
|
||||
tar -tvf image-recipe/build/auto-installer/archipelago-rootfs.tar \
|
||||
| grep -E 'etc/ssh/ssh_host|etc/machine-id|var/lib/systemd/random-seed|archipelago/ssl/archipelago'
|
||||
```
|
||||
|
||||
### 3. Expected result after this plan
|
||||
|
||||
- **no** `etc/ssh/ssh_host_*` entries at all
|
||||
- **no** `etc/archipelago/ssl/archipelago.key` and **no** `archipelago.crt`
|
||||
(the `etc/archipelago/ssl/` **directory** must still be present — the first-boot staging
|
||||
swap needs somewhere to land)
|
||||
- **no** `var/lib/systemd/random-seed`
|
||||
- `etc/machine-id` present with size **0**, or absent. Either satisfies "not shared"; record
|
||||
which one was actually observed rather than generalising.
|
||||
|
||||
### 4. Confirm the provenance file rode along
|
||||
|
||||
```bash
|
||||
tar -tvf image-recipe/build/auto-installer/archipelago-rootfs.tar | grep rootfs-identity-stripped
|
||||
```
|
||||
|
||||
Expected: one entry, `opt/archipelago/rootfs-identity-stripped`. Its absence means the strip
|
||||
layer did not execute and the whole check is void.
|
||||
|
||||
### 5. Confirm the regeneration path is still shipped
|
||||
|
||||
This is the brick check, and it is not optional. A stripped rootfs whose first-boot
|
||||
regeneration script failed to ship would leave every flashed node with no SSH host key and
|
||||
nothing to create one.
|
||||
|
||||
```bash
|
||||
ls -l image-recipe/build/auto-installer/installer-iso/archipelago/scripts/first-boot-secrets.sh \
|
||||
image-recipe/build/auto-installer/installer-iso/archipelago/scripts/archipelago-first-boot-secrets.service
|
||||
```
|
||||
|
||||
Expected: both present, `first-boot-secrets.sh` executable.
|
||||
|
||||
### 6. Record the RECIPE_HASH the builder actually used
|
||||
|
||||
```bash
|
||||
cat image-recipe/build/auto-installer/archipelago-rootfs.recipe.sha256
|
||||
```
|
||||
|
||||
**Caveat — do not compute this hash from the repo file.** `image-recipe/build-debian-iso.sh`
|
||||
copies the archived builder to a temp path and rewrites its relative paths before exec'ing it,
|
||||
and `RECIPE_HASH` hashes `"$0"` — the rewritten copy. The hashed region contains 35 such
|
||||
rewritten path expressions, and `SCRIPT_DIR` is substituted with an absolute path, so the hash
|
||||
is specific to the build host and checkout location. For reference, hashing the region of the
|
||||
committed repo file directly gives `d2dc4df5427fe73d48227aab08cdf6debfe8dd554e6b18e3718f8d37ea9d675c`,
|
||||
which is **expected to differ** from the stamp above.
|
||||
|
||||
---
|
||||
|
||||
## Result
|
||||
|
||||
_Paste the raw output of steps 2, 4, 5 and 6 here, then set the status at the top of this
|
||||
document to VERIFIED with the date and build-host label._
|
||||
|
||||
```text
|
||||
(pending — not yet run on a build host)
|
||||
```
|
||||
|
||||
**Verdict:** _pending_
|
||||
|
||||
---
|
||||
|
||||
## What this does and does not prove
|
||||
|
||||
**Proves (once run):** the rootfs tar extracted verbatim onto every disk flashed from the ISO
|
||||
carries no SSH host key, no TLS private key and no populated machine-id — so a first-boot
|
||||
regeneration failure degrades to "no key, the service refuses to start" rather than
|
||||
"fleet-shared key, silently", which is the substance of F-03.
|
||||
|
||||
**Does not prove:** that two nodes flashed from the same ISO actually end up with different
|
||||
keys. That is audit item **C-3** (§779) and needs two physical machines; it remains
|
||||
separately UNVERIFIED. C-4 is a build-host check only.
|
||||
|
||||
**Also worth recording when C-3 is run:** the installer writes a per-install TLS keypair as a
|
||||
fallback (`build-auto-installer-iso.sh`, the "Ensure SSL cert exists for nginx HTTPS" block).
|
||||
That fallback fires on every install now that the rootfs ships without a keypair. It is
|
||||
generated separately on each target machine, so it is per node and does not reopen F-03 — but
|
||||
it does mean the two nodes' *TLS* certs will differ even if first-boot regeneration failed on
|
||||
both. SSH host keys have no such fallback, so they are the sharper signal for C-3.
|
||||
Reference in New Issue
Block a user