Skip to content

feat(status): report where ZFW-IN sits in INPUT, and what runs ahead of it - #26

Open
chicohaager wants to merge 1 commit into
masterfrom
feat/input-chain-order
Open

feat(status): report where ZFW-IN sits in INPUT, and what runs ahead of it#26
chicohaager wants to merge 1 commit into
masterfrom
feat/input-chain-order

Conversation

@chicohaager

Copy link
Copy Markdown
Owner

Phase 0 of the blocklist plan — and useful on its own.

ZFW takes INPUT position 1 once and never re-asserts it. A tool installed later can end up ahead of it; a permissive rule there bypasses ZFW silently while the dashboard still says "active". Seen on a real host: ZFW-IN at rule #3 behind two foreign chains.

  • Status gains input_position / input_before (and the IPv6 pair). The -S INPUT dump was already fetched; it is now parsed instead of reduced to a boolean.
  • Audit finding M9 — open with the predecessors named, fixed only when ZFW-IN is first.
  • Firewall tab shows the position (green at Connections Tab Error #1, amber otherwise) and lists what runs ahead.
  • Deliberately no auto re-insertion: two tools fighting for slot 1 loop forever. Report, don't fight.

Tests sabotage-checked (three of four parser tests and the M9 test go red under the old semantics). Firewall tab rendered headless for both states.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GRUX9ZKesnvfMooL7UFPtX

…of it

ZFW hooks its chain with `-C INPUT -j ZFW-IN || -I INPUT 1 -j ZFW-IN`: it
takes position 1 once and never re-asserts it. Anything a later tool inserts
ahead of it — a blocklist module's own chain, Tailscale's ts-input, an
ACCEPT-all from a VPN installer — is evaluated before ZFW. A stricter chain
there is harmless; a permissive one bypasses ZFW without any tab saying so.
Seen on a real host: ZFW-IN at INPUT rule #3 behind two foreign chains,
while Status reported hooked=true and the dashboard read "active".

Status already fetched `iptables -S INPUT` and reduced it to a boolean
(`strings.Contains(out, "-j ZFW-IN")`). It now keeps the information:

- Status.InputPosition (1-based rule index, 0 = not hooked) and
  Status.InputBefore (the specs ahead of it, capped at 32), plus the same
  pair for ZFW-IN6 in the IPv6 INPUT chain. Parsing is a pure function
  (inputOrder) over the dump: only `-A INPUT` lines count, the policy line
  and foreign chains are ignored, a v4 dump never matches the v6 target.
- Audit finding M9 "Rules ahead of ZFW-IN in INPUT": fixed only when
  ZFW-IN is first; open otherwise, with the predecessors named in the
  detail so the operator can judge them — ZFW cannot.
- Firewall tab: "INPUT position #n" (green at 1, amber otherwise) with the
  rules ahead listed under the label; same item for IPv6.
- OpenAPI: the four new Status fields.

Deliberately not: re-inserting ZFW-IN at position 1 on a timer. Two tools
that both insist on being first shove each other in a loop. ZFW reports and
leaves the decision to the operator.

Tests: four inputOrder cases (real-world dump with ZFW-IN at #3, first /
absent / empty dump, match rules kept verbatim and foreign chains ignored,
predecessor cap does not alter the count) and the M9 handler test
(open at #3 with predecessors named, fixed at #1, open when unhooked).
Each was run against a sabotaged implementation once: three of the four
inputOrder tests and the M9 test go red under the old contains-only
semantics. Firewall tab rendered headless with stubbed status for both
states; the grid shows "#3 · 2 rules ahead: IPBLOCKLIST, ts-input" and
"#1" respectively.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRUX9ZKesnvfMooL7UFPtX
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.

1 participant