Skip to content

Metadata firewall: the unit becomes the proven single rule-writer, and doctor certifies only what it verifies#11

Merged
sweetcornna merged 1 commit into
mainfrom
firewall-unit-boot-proof
Jul 20, 2026
Merged

Metadata firewall: the unit becomes the proven single rule-writer, and doctor certifies only what it verifies#11
sweetcornna merged 1 commit into
mainfrom
firewall-unit-boot-proof

Conversation

@sweetcornna

Copy link
Copy Markdown
Collaborator

What & why

Fixes the three defects from #8 — the metadata-firewall unit could fail silently at exactly the moment it exists for, and doctor couldn't tell. Direction was agreed on the issue.

The unit becomes the proven single rule-writer. The inline iptables -I pre-insert loop is gone: the oneshot unit is now the only mechanism that inserts the DROP rules, at boot and at install time alike. That makes the install-time systemctl restart an actual proof — a fresh install exercises the real -C || -I insert path (rules absent), a re-run the -C path — where before, the pre-inserted rules guaranteed the unit's insert half was never exercised until the first real boot, which is precisely the "unproven promise" the restart was supposed to prevent. After the restart, install.sh asserts both rules with iptables -C and dies naming the target if the unit didn't deliver. Both ExecStart lines and the assertion loop are generated from one METADATA_TARGETS list — the endpoint set previously lived in three places, and a target added to one of them would have protected the live host but not reboots.

Every iptables call waits for the xtables lock (-w 10). After=docker.service schedules the unit at the moment dockerd is busiest programming iptables; on an iptables-legacy host a lost lock race made both -C and -I exit 4 immediately, failing the unit with no Restart= — and Before=dormice.service is ordering-only, so the daemon started anyway, sandboxes reachable to the metadata service. (systemctl enable also loses its 2>&1 gag: under set -e a failure there used to kill the install with no diagnostics.)

Doctor certifies only what it verifies. is-enabled exit 0 also covers enabled-runtime, static, alias, generated — states that do not start the unit at the next boot — and says nothing about whether the last run worked or the unit file still drops both targets. The metadata-persisted check now requires the word enabled, both targets present in the unit file, and a successful last run; each failure mode warns naming exactly what broke. Hosts persisted the pre-unit way via iptables-persistent still pass through the unchanged rules.v4 branch.

The three new doctor tests were each run against the previous check and fail there — every one catches a real false-pass (enabled-runtime, missing target, failed last run).

Real-machine acceptance (fresh install + reboot on the test host, proving the unit under a genuine boot) has not been run from this change yet — recommended before merge, same drill as #7.

Closes #8.

Checklist

  • pnpm build && pnpm typecheck && pnpm lint && pnpm test passes locally (build first — the e2e suite runs the built daemon)
  • Behavior changes come with tests that fail without the change (all three red-verified against the old check)
  • User-facing changes to @dormice/shared / sdk / cli have a changeset (pnpm changeset)
  • README updated if this changes documented behavior (README names no persistence mechanism; the website doctor page's wording still holds)

…d doctor certifies only what it verifies

At boot the dormice-metadata-firewall oneshot races dockerd — which is
busy building its own chains at exactly that moment — for the xtables
lock. iptables without -w exits immediately when the lock is held, the
unit fails, and the host runs with the metadata service reachable from
every sandbox. Silently: the rules were present after install because
a separate inline loop had inserted them, so nothing looked wrong
until the first reboot. Every iptables call in the unit and in
install.sh now carries -w 10, a bounded wait instead of a lost race.

The inline pre-insert loop had to go for a second reason: it meant the
unit's -I path was never exercised. install.sh inserted the rules
itself and only then restarted the unit, so at install time the unit
always took the -C branch — the insert path that boot depends on ran
for the first time on a real host at the first reboot, unobserved.
That is precisely the "unproven promise" the restart existed to
prevent. With the loop deleted the unit is the only rule-writer: on a
fresh install the restart takes the -C||-I path with the rules
genuinely absent, on a re-run the -C path, and install.sh then asserts
each rule with iptables -C, dying with the target's name if the unit
ran but a rule is absent.

The endpoint list also lived in three places — the inline loop, two
hand-written ExecStart lines, doctor's constants — inviting drift.
install.sh now generates the ExecStart lines from one METADATA_TARGETS
list; the generated unit text is byte-identical across runs, so the
[skip] content comparison keeps working.

doctor's metadata-persisted check certified persistence on `systemctl
is-enabled` exiting 0 alone. Exit 0 also covers enabled-runtime,
static, alias and generated — states that do not start this unit at
the next boot. The unit path now passes only when the state is the
literal word "enabled", the unit file still drops both metadata
targets, and is-active says the last run succeeded; each failure mode
warns in its own words. Any state other than "enabled" falls through
to the legacy rules.v4 branch unchanged, so pre-unit hosts persisted
via iptables-persistent still pass. All three new tests were run
against the old check first and all three went red — each one catches
a real false-pass.

`systemctl enable >/dev/null 2>&1` became `systemctl -q enable`: the
old form swallowed the error text, and under set -e the script died
with no diagnostics at all.

Closes #8.
@sweetcornna
sweetcornna merged commit 8c381c1 into main Jul 20, 2026
2 checks passed
@sweetcornna
sweetcornna deleted the firewall-unit-boot-proof branch July 20, 2026 02:21
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.

install.sh: the metadata-firewall unit can lose the xtables-lock race at boot — and doctor would still call the rules persisted

1 participant