Skip to content

Prove the audit bind guard mid-migration (#960) - #963

Merged
sehkone merged 1 commit into
mainfrom
sehkone/issue-960
Aug 29, 2026
Merged

Prove the audit bind guard mid-migration (#960)#963
sehkone merged 1 commit into
mainfrom
sehkone/issue-960

Conversation

@sehkone

@sehkone sehkone commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Summary

The audit bind is declared create_host_path: false so Docker refuses to manufacture the bind source when the reserve mount is not there. The migration window is where that guarantee matters most — the store is aside under .pre-mount, the reserve is mounted but still carries no openbao/, and a bring-up that created that directory would leave a bind source on the destination side of a copy that has not run.

bootroot infra up already refuses that state (src/commands/audit_store.rs), but that is a different assertion: it proves bootroot declines to start containers, not that Docker itself refuses the bind when something else brings the stack up. The migration E2E scenario never attempted a Compose bring-up inside the window, so the guarantee was asserted nowhere against the state the window actually produces.

Closes #960

Part of #926

Changes

All of it is in scripts/impl/run-registrar-internal-init-e2e.sh, inside the existing assert_the_rendered_steps_migrate_an_existing_store lifecycle path. No Rust changed.

  • assert_the_audit_bind_refuses_to_create_its_source_mid_migration — a focused sibling called between pass 2 (the activation) and pass 3 (the copy), which is exactly the window. It restates its preconditions rather than assuming them (.pre-mount present, the mount unit active, <audit_store_dir>/openbao absent), checks that the override the refusal pass rendered still names that store and still carries create_host_path: false, then runs the ordinary docker compose up -d --no-deps openbao over it.
  • The failure is read from the bring-up itself — its non-zero exit, the error naming the bind source, and the container not running — so a bring-up that returned success having quietly done nothing cannot pass on the absent source alone. Only then does it assert the destination was not manufactured: openbao/ still absent, and the mounted store's entry list identical to the one taken before the attempt.
  • Both writers are stopped for the window. Rendered step 1 stops them, and rendered_migration_commands drops those two lines — one would stop this scenario's whole Compose stack, the other names a unit no CI host has installed. The OpenBao writer is now stopped by service through the same Compose surface just before the aside rename, stays down across every pass, and is brought back only on the closing path at the end of the section. The registrar writer is asserted not running rather than stopped: stopping a unit this scenario did not install would be a host-changing step it has no way to undo.

The fixture starts from an existing audit store, so the aside rename and the full window are exercised rather than simulated. Nothing is created, removed, mounted or unmounted to stand in for Docker's result, and no rendered command list, outcome, or withholds_activation behaviour is touched.

Test plan

  • docker compose up is attempted while .pre-mount exists, the reserve mount is active, and <audit_store_dir>/openbao is absent.
  • The OpenBao container's failure is asserted by its own non-zero exit and by the error naming the bind source, not by the absence of a side effect alone, and no entry is created beneath the mounted store.
  • The scenario runs through the existing registrar-internal-init lifecycle harness rather than a new one, starting from an existing audit store and leaving both writers stopped until the normal closing path.
  • The existing infra up refusal coverage is kept and unchanged — cargo test --bin bootroot commands::audit_store:: passes 136 tests, including infra_up_refuses_the_bring_up_while_a_migration_is_open.
  • cargo clippy --all-targets -- -D warnings passes.
  • cargo fmt -- --config group_imports=StdExternalCrate --check passes.
  • ./scripts/check-docs.sh passes.
  • shellcheck -x and bash -n on the changed script are clean.
  • ./scripts/validate-e2e-run-scope.sh and ./scripts/validate-e2e-leftover-check.sh pass.
  • The Linux Docker E2E registrar-internal-init job passes — it ran green on this commit (job 99074231742), and its log shows the new assertions actually executing inside the reserve_activation_is_possible guard rather than being skipped: PASS both writers are stopped before the store moves aside, PASS the reserve is mounted for the mid-window bring-up, the mounted store holds [lost+found] before the mid-migration bring-up, PASS the mid-migration bring-up fails on the guarded audit bind source, PASS the refused bring-up added no entry beneath the mounted store, and PASS the writers come back only once the migration has closed. It could not run on the development host — macOS with no passwordless sudo, no systemd, no loop device and no mkfs.ext4 — so that CI job is the only arm that exercises it.

The audit bind is declared `create_host_path: false` so Docker refuses
to manufacture the bind source when the reserve mount is not there, and
the migration window is where that matters most: the store is aside
under `.pre-mount`, the reserve is mounted and empty, and a bring-up
that created `openbao/` there would leave a bind source on the
destination side of a copy that has not run.

`bootroot infra up` already refuses that state, but that is a different
assertion — it says bootroot declines to start containers, not that
Docker itself refuses the bind for whoever else brings the stack up.
The migration scenario never attempted a Compose bring-up inside the
window, so the guarantee was asserted nowhere against the state the
window actually produces.

The attempt is driven through the override the refusal pass rendered,
which is the surface an operator has on disk at that moment, and the
failure is read from the bring-up's own non-zero exit and from the
error naming the bind source, so a bring-up that returned success
having quietly done nothing cannot pass on the absent source alone.

The window is also made faithful at its other end. Rendered step 1
stops both writers, and the scenario drops those two lines because one
would stop its whole Compose stack and the other names a unit no CI
host has installed; the OpenBao writer is now stopped by service
instead, stays down across every pass, and comes back only once the
migration has closed.

Closes #960
@sehkone sehkone changed the title Prove the audit bind guard mid-migration Prove the audit bind guard mid-migration (#960) Aug 29, 2026
@sehkone

sehkone commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

[Reviewer Round 1]

Approved — no findings.

The new E2E helper correctly verifies the actual migration window: holding directory exists, reserve is active, and openbao/ is absent before Compose is invoked. It then requires Compose itself to fail on that source and confirms neither the bind source nor any top-level reserve entry was created. See run-registrar-internal-init-e2e.sh.

The surrounding lifecycle change also keeps OpenBao stopped from before the aside rename until after the migration closes, avoiding a writer race in the newly exercised window. PR linkage and test-plan hygiene are correct (Closes #960, permitted Part of #926); there are no review-thread issue creations.

@sehkone

sehkone commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

[Review Verdict Round 1: APPROVED]

@sehkone
sehkone merged commit 940c82b into main Aug 29, 2026
21 checks passed
@sehkone
sehkone deleted the sehkone/issue-960 branch August 29, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prove the audit bind refuses to create its source during the migration window

1 participant