Repository navigation
fix(uninstall): Remove brig0's CNI firewall rules - #31
Conversation
|
@claude-fleet review |
1 similar comment
|
@claude-fleet review |
|
Review task queued. Task: 4f8754aa-a851-4067-b2e9-dbefc04e6a10 |
There was a problem hiding this comment.
Approve: no blocking or should_fix findings. One nit.
Findings:
- nit, scripts/build-bundle.sh:896 —
-wwith no timeout can block forever, and2>/dev/nulldiscards the reason behind the warning
Verified:
- The awk filter matches only
-Alines whose chain startsCNI-, with an exact-i/-ooperand equal to the bridge, sobrig0xandFORWARD -i brig0survive — scripts/build-bundle.sh:891-894, asserted by tests/uninstall-cni-rules.sh:93,97 against the expected file built at 104-107. - Deletion is by rule spec, not index, so deleting one rule does not invalidate the specs still queued from the same
iptables-savesnapshot — scripts/build-bundle.sh:890-896. - The quoted
--commentnever reaches the shell as a word: the saved line goes toiptables-restorestdin verbatim after stripping-A— scripts/build-bundle.sh:895-896. - "The rules go whether or not the bridge is still up": the outer condition dropped
ip link show, which now only gates the link deletion — scripts/build-bundle.sh:906-913; covered by tests/uninstall-cni-rules.sh:198-203. - A user install runs no iptables:
INSTALL_MODE != userstill gates the whole block — scripts/build-bundle.sh:906, asserted at tests/uninstall-cni-rules.sh:214-219. - Shared chains survive: only
-Dlines are emitted, never-F/-X, and the chain-declaration lines are not reproduced — scripts/build-bundle.sh:895; asserted at tests/uninstall-cni-rules.sh:187. - A failed delete warns and the uninstall completes — scripts/build-bundle.sh:897-899; tests/uninstall-cni-rules.sh:205-211 checks both the five warnings and that the rule files are unchanged.
set -euis on in the generated script anddrop_bridge_rulescannot return non-zero:|| continueyields 0, and an empty awk result leaves thewhileat 0 — generated uninstaller line 13, scripts/build-bundle.sh:886-902.drop_bridge_rulesis the only bridge-teardown path;install.shtouches no iptables rules — onlyinstall.sh:240(a presence check) andinstall.sh:894(BRIDGE_NAME=brig0) mention either.- CI picks the new test up automatically (
for t in tests/*.sh) and shellchecks the extracted uninstaller — .github/workflows/ci.yml:53-55,67.
Checks run:
sh tests/uninstall-cni-rules.sh(dash 0.5.12, mawk) → all 6 cases pass, including the no-iptables case.- Revert check: replaced
scripts/build-bundle.shwith0d865d5's copy and reran the test →FAIL: the brig0 rules are not exactly the ones removed: iptables.filter, with the diff naming exactly the two leakedCNI-ISOLATION-STAGE-1/-2rules. File restored;git status --porcelainshows only untrackednode_modules. sh -non the extracted uninstaller → clean. shellcheck is not installed here.iptables-restore -w --noflush --testand theip6tables/iptables-legacyvariants on 1.8.10 (nf_tables) → all reach "Permission denied (you must be root)", so-wwith no value followed by--noflushparses as intended and--noflushis not eaten as-w's optional argument.
Smaller things:
- The match is on the operand alone, so a negated
! -i brig0in aCNI-*chain would also be deleted. The firewall plugin never emits one standalone (its! -o <br>is on the same rule as-i <br>), butCNI-ADMINis admin-populated and is aCNI-*chain. Low risk, and removing a brig0 rule when brig0 is going away is defensible — worth one sentence in the function comment rather than a code change. - On a system install whose bridge is already gone, this is now the first thing to run
iptables-save -t nat, which can modprobeiptable_naton a host that never loaded it. An uninstall loading a kernel module is odd but harmless. - README:389 still opens the list with "all recorded in
.install-stamp". The rules are not recorded there;BRIDGE_NAMEis, and they are derived from it. The new item 6 reads fine on its own. tests/uninstall-cni-rules.sh:158-161carries a good comment about whyFAKE_*are re-exported by hand. Theunset FAKE_RESTORE_FAIL FAKE_BRIDGEat line 212 is load-bearing for the two cases after it — the${FAKE_BRIDGE:-up}defaults make that non-obvious.
Not checked:
- That the CNI firewall plugin adds exactly these two rules and never removes them on DEL — the plugin source is not in this repo and there is no network here. Taken from the PR description and the real-host numbers in it.
- The host runs reported in the description (Ubuntu 24.04, the rc18 bundle, the
iptables-nftnamespace run). The stub-based test reproduces the rule arithmetic but not the real binaries. scripts/build-bundle.shoutside the diffed hunk.
Not checked:
- The CNI firewall plugin's own source: that it adds exactly these two rules per bridge and never removes them on DEL. Not vendored here and no network access; taken from the PR description.
- The real-host verification runs described in the PR (Ubuntu 24.04 with the rc18 bundle, and the iptables-nft namespace run). Only the stubbed test was executed.
- scripts/build-bundle.sh outside the changed hunk at lines 879-914.
Task 4f8754aa-a851-4067-b2e9-dbefc04e6a10 on ci-16gb-fsn1-1, model claude-opus-5, effort medium.
| if (($i == "-i" || $i == "-o") && $(i + 1) == br) { print; next } | ||
| }' | while IFS= read -r rule; do | ||
| if printf '*%s\n-D %s\nCOMMIT\n' "$table" "${rule#-A }" \ | ||
| | "$ipt-restore" -w --noflush 2>/dev/null; then |
There was a problem hiding this comment.
nit: iptables-restore -w with no value waits for the xtables lock indefinitely, and 2>/dev/null throws away the reason for a failure.
Failure: if another process (a container runtime, firewalld, a parallel brig rm) holds /run/xtables.lock, brig-uninstall.sh hangs at this line with no output and no timeout — the preceding info for the previous rule is the last thing the user sees. Separately, when a delete does fail, WARNING: could not remove iptables -t filter CNI-... gives the operator nothing to act on, since iptables' own message ("Bad rule (does a matching rule exist in that chain?)", a lock timeout, a missing module) went to /dev/null.
Evidence: scripts/build-bundle.sh:896 pipes into "$ipt-restore" -w --noflush 2>/dev/null; the warn branch at :898 prints only the rule text. Verified -w parses with a following --noflush on iptables 1.8.10 nf_tables, so the flag itself is fine.
Fix: -w 5 bounds the wait, and capturing stderr into the warning (err="$(... 2>&1)", or dropping the redirect so iptables' message lands next to the warning) makes a failed delete diagnosable.
|
Review task queued. Task: eb1d3b66-c41a-4bc5-876c-5148486da713 |
There was a problem hiding this comment.
Previous round at 443ed7e: 1 finding.
| Previous finding | Classification | Cited line |
|---|---|---|
scripts/build-bundle.sh:896 (nit): iptables-restore -w with no value waits for the xtables lock indefinitely, and 2>/dev/null throws away the reason for a failure. Failure: if another process (a container runtime, firewalld, a parallel brig rm) holds /run/xtables.lock, brig-uninstall.sh hangs at this line with no output and no timeout — the preceding info for the previous rule is the last thing the user sees. Separately, when a delete does fail, WARNING: could not remove iptables -t filter CNI-... gives the operator nothing to act on, since iptables' own message ("Bad rule (does a matching rule exist in that chain?)", a lock timeout, a missing module) went to /dev/null. Evidence: scripts/build-bundle.sh:896 pipes into "$ipt-restore" -w --noflush 2>/dev/null; the warn branch at :898 prints only the rule text. Verified -w parses with a following --noflush on iptables 1.8.10 nf_tables, so the flag itself is fine. Fix: -w 5 bounds the wait, and capturing stderr into the warning (err="$(... 2>&1)", or dropping the redirect so iptables' message lands next to the warning) makes a failed delete diagnosable. |
open | scripts/build-bundle.sh:896 |
Approve: no new blocking or should_fix findings. Head is unchanged since the last review (git diff 443ed7e..HEAD empty), so the one earlier nit stands as written.
Findings:
- nit, scripts/build-bundle.sh:896 — unbounded
-wand discarded iptables stderr (previous finding 1, still open)
Verified:
- The awk only matches
-Alines whose chain starts withCNI-, soFORWARD -i brig0 -j ACCEPTsurvives — scripts/build-bundle.sh:890-893, fixture tests/uninstall-cni-rules.sh:93 vs want file at :104-107 brig0xdoes not match: the comparison is$(i+1) == br, a whole-field equality — scripts/build-bundle.sh:892, covered by tests/uninstall-cni-rules.sh:97- The rule text is never re-parsed by the shell: it goes back to
iptables-restorebyte-for-byte with only-Astripped — scripts/build-bundle.sh:895-896 - Rules go with the bridge already gone:
drop_bridge_rulesmoved outside theip link showbranch — scripts/build-bundle.sh:903-911 - A user install runs no iptables at all: the mode guard is on the outer
if— scripts/build-bundle.sh:903; asserted at tests/uninstall-cni-rules.sh:217 - The uninstaller heredoc is quoted (
<<'UNEOF'), so$1,$i,$(i + 1)reach the generated script unexpanded — scripts/build-bundle.sh:741 - Chains are not flushed or deleted: only
-Dlines are emitted,--noflushis passed, and the test asserts:CNI-ISOLATION-STAGE-2survives — scripts/build-bundle.sh:894-896, tests/uninstall-cni-rules.sh:187 - README item 6 matches the code (rules named, chains kept) — README.md:392-395
Checks run:
sh tests/uninstall-cni-rules.sh→ all 6 cases ok- Revert check: ran the new test against main's
scripts/build-bundle.sh(git show 0d865d5:scripts/build-bundle.sh) →FAIL: the brig0 rules are not exactly the ones removed: iptables.filter, with both leakedCNI-ISOLATION-STAGE-1/-2rules in the diff. The PR's "fails against main" claim holds. for t in tests/*.sh; do sh "$t"; done→ brig-ctl, fetch-hvi, source-pins, uninstall-cni-rules pass; the four install/rootless tests SKIP here for lack of a subuid range (CI adds one)- Confirmed the 5 expected deletes in the fixture (IPv4 filter 2, IPv4 nat 1, IPv6 filter 2) match the assertion at tests/uninstall-cni-rules.sh:185
Smaller things:
CNI-ADMINmatches^CNI-, and the firewall plugin documents that chain as the operator's own. An admin rule like-A CNI-ADMIN -i brig0 -j ACCEPTis deleted by the uninstall, while the equivalent rule inFORWARDis kept. Defensible (the bridge is gone anyway), but it is narrower than the README's "only rules in the CNI chains that name brig0" framing suggests, since those are hand-written and not recreated.- awk field-splits the whole saved line, so a rule in a
CNI-*chain whose--commenttext happens to contain-i brig0would match and be deleted. Contrived; no such comment exists in practice. ip link showat :904 runs without acommand -v ipguard, unlike the iptables path. Pre-existing, and a missingiponly yields 127 inside anif.
Not checked:
- shellcheck (not installed in this workspace; CI's shell job covers it)
- The host observations in the description (rc18 bundle on Ubuntu 24.04, rootless namespace rule counts,
iptables-nftrun) — no such host here.
Not checked:
- shellcheck over the CI file list (not installed in this workspace)
- The described runs on a real Ubuntu 24.04 host, including the rootless-namespace rule counts and the iptables-nft run
Task eb1d3b66-c41a-4bc5-876c-5148486da713 on rp1, model claude-opus-5, effort medium.
| if (($i == "-i" || $i == "-o") && $(i + 1) == br) { print; next } | ||
| }' | while IFS= read -r rule; do | ||
| if printf '*%s\n-D %s\nCOMMIT\n' "$table" "${rule#-A }" \ | ||
| | "$ipt-restore" -w --noflush 2>/dev/null; then |
There was a problem hiding this comment.
nit: Still open since 443ed7e. iptables-restore -w with no value waits for the xtables lock indefinitely, and 2>/dev/null throws away the reason for a failure.
Failure: if another process (a container runtime, firewalld, a parallel brig rm) holds /run/xtables.lock, brig-uninstall.sh hangs at this line with no output and no timeout — the preceding info for the previous rule is the last thing the user sees. Separately, when a delete does fail, WARNING: could not remove iptables -t filter CNI-... gives the operator nothing to act on, since iptables' own message ("Bad rule (does a matching rule exist in that chain?)", a lock timeout, a missing module) went to /dev/null.
Evidence: scripts/build-bundle.sh:896 pipes into "$ipt-restore" -w --noflush 2>/dev/null; the warn branch at :898 prints only the rule text. Verified -w parses with a following --noflush on iptables 1.8.10 nf_tables, so the flag itself is fine.
Fix: -w 5 bounds the wait, and capturing stderr into the warning (err="$(... 2>&1)", or dropping the redirect so iptables' message lands next to the warning) makes a failed delete diagnosable.
443ed7e to
f7878e8
Compare
|
@claude-fleet review |
|
Review task queued. Task: e203ddf9-7800-4e3d-bafe-0e817ea1f9d2 |
There was a problem hiding this comment.
Previous round at 443ed7e: 1 finding.
| Previous finding | Classification | Cited line |
|---|---|---|
scripts/build-bundle.sh:896 (nit): iptables-restore -w with no value waits for the xtables lock indefinitely, and 2>/dev/null throws away the reason for a failure. Failure: if another process (a container runtime, firewalld, a parallel brig rm) holds /run/xtables.lock, brig-uninstall.sh hangs at this line with no output and no timeout — the preceding info for the previous rule is the last thing the user sees. Separately, when a delete does fail, WARNING: could not remove iptables -t filter CNI-... gives the operator nothing to act on, since iptables' own message ("Bad rule (does a matching rule exist in that chain?)", a lock timeout, a missing module) went to /dev/null. Evidence: scripts/build-bundle.sh:896 pipes into "$ipt-restore" -w --noflush 2>/dev/null; the warn branch at :898 prints only the rule text. Verified -w parses with a following --noflush on iptables 1.8.10 nf_tables, so the flag itself is fine. Fix: -w 5 bounds the wait, and capturing stderr into the warning (err="$(... 2>&1)", or dropping the redirect so iptables' message lands next to the warning) makes a failed delete diagnosable. |
fixed | scripts/build-bundle.sh:898 |
Approve.
Findings:
- nit, scripts/build-bundle.sh:893 — the
CNI-*sweep also takes an operator'sCNI-ADMINrules forbrig0
Verified:
- previous finding 1 fixed:
-w 5bounds the lock wait anderr="$(... 2>&1)"carries iptables' reason into the warning — scripts/build-bundle.sh:897-901 -w 5 --noflushparses on real iptables (1.8.9 legacy): deleted exactly theCNI-ISOLATION-STAGE-2 -o brig0rule, keptFORWARD -i brig0and-o br-other; a missing rule returns 1 withiptables-restore: line 2 failedon stderr, now captured- the conflist really runs the firewall plugin with
ingressPolicy: same-bridge, which is what adds the two leaked rules — scripts/build-bundle.sh:1418 - the rules go whether or not the bridge is up:
drop_bridge_rulessits outside theip link showbranch — scripts/build-bundle.sh:908-916 brig0xdoes not match: the awk compares$(i+1) == brexactly, and the fixture carries abrig0xrule that survives — tests/uninstall-cni-rules.sh:97,105-107- user install runs no iptables: guarded by
INSTALL_MODE != userat :908, asserted at tests/uninstall-cni-rules.sh:220 - the five expected deletes are IPv4 filter 2 + nat 1 + IPv6 filter 2, matching the fixtures at tests/uninstall-cni-rules.sh:96,100,116,121
- CI already picks the new test up:
for t in tests/*.shand the shellcheck list both glob it — .github/workflows/ci.yml:57,67
Checks run:
sh tests/uninstall-cni-rules.sh→ all 6 cases ok- revert check:
git checkout 0d865d5 -- scripts/build-bundle.shthen the same test →FAIL: the brig0 rules are not exactly the ones removed: iptables.filter, with the two leakedCNI-ISOLATION-STAGE-1/-2rules in the diff. File restored, tree clean. - real iptables in
unshare -rnmwith a tmpfs/run: delete path and failure path, as above
Smaller things:
- README:389 still opens with "Six things, all recorded in
.install-stamp". The rules are not in the stamp; they are derived fromBRIDGE_NAME, which is. Reword if the sentence is meant to be exact. - The PR description's "Tested" section still describes the
-wform, not-w 5. The namespace run above covers-w 5on legacy iptables, but the text is stale.
Not checked:
- the
iptables-nftbackend (onlyiptables-legacy1.8.9 is present here) and iptables older than 1.6.2, which has no-woniptables-restore. All platforms README names (Ubuntu 20.04+, Debian 12) are above that. - the on-host behaviour claims in the description (the rc18 bundle run on Ubuntu 24.04, the rootless namespace counts) — no such host here.
Not checked:
- the iptables-nft backend and iptables older than 1.6.2 (only iptables-legacy 1.8.9 available here)
- the on-host verification in the PR description: the rc18 bundle install/uninstall on Ubuntu 24.04 and the rootless namespace rule counts
Task e203ddf9-7800-4e3d-bafe-0e817ea1f9d2 on rp2, model claude-opus-5, effort medium.
| command -v "$ipt-restore" >/dev/null 2>&1 || continue | ||
| for table in filter nat; do | ||
| "$ipt-save" -t "$table" 2>/dev/null | awk -v br="$1" ' | ||
| $1 == "-A" && $2 ~ /^CNI-/ { |
There was a problem hiding this comment.
nit: The sweep matches any chain named CNI-*, which includes CNI-ADMIN — the chain the firewall plugin creates precisely so an operator can add their own rules. So the uninstaller deletes operator rules, not only the plugin's two.
Failure: an operator who added -A CNI-ADMIN -i brig0 -j ACCEPT (the documented override point; the jump to it is in your own fixture at tests/uninstall-cni-rules.sh:94) sees it removed, logged as removed iptables -t filter CNI-ADMIN -i brig0 -j ACCEPT. This contradicts the description's "rules outside CNI-* (eg. an admin's FORWARD -i brig0) stay", which reads as "admin rules stay" — here the location decides, not the author. Direction is fairly benign, since brig0 is going away in the same run.
Evidence: scripts/build-bundle.sh:893 is the only chain filter. Fed -A CNI-ADMIN -i brig0 -j ACCEPT and -A FORWARD -i brig0 -j ACCEPT to that exact awk program: it printed the CNI-ADMIN line. The fixture has a :CNI-ADMIN chain and the jump at tests/uninstall-cni-rules.sh:94 but no admin rule naming brig0, so nothing covers this.
Fix: either narrow to the chains the plugin owns (CNI-ISOLATION-STAGE-1|2, CNI-FORWARD), or keep the wide sweep and say in the comment at :879-886 and in README:389 that CNI-ADMIN overrides for this bridge go too — and add a fixture rule for it.
The CNI firewall plugin adds two rules for brig0 to the shared CNI-ISOLATION-STAGE-1 and -2 chains and never removes them. A system uninstall deleted the bridge and left both rules behind. - brig-uninstall.sh reads iptables-save and ip6tables-save, filter and nat, and deletes every rule in a CNI-* chain that names brig0 with -i or -o. Each rule goes back through iptables-restore --noflush as -D, so the shell never parses the quoted comment. - The chains, other bridges' rules and rules outside CNI-* stay. - The rules go whether or not the bridge is still up. Nothing matching means nothing is run or printed, and a failed delete only warns. - A user install skips this: its rules live in the rootlesskit network namespace and go with it. - tests/uninstall-cni-rules.sh runs the generated uninstaller against stub iptables-save and iptables-restore. Signed-off-by: Anastassios Nanos <ananos@nofire.ai>
f7878e8 to
32487ff
Compare
A system uninstall deleted the
brig0bridge but left the two rules the CNI firewall plugin adds for it, for v0.1.0-rc19:The plugin never removes these on CNI DEL. The chains themselves are shared with every other CNI network on the host, so the uninstaller must not flush or delete them.
Changes
brig-uninstall.sh(generated byscripts/build-bundle.sh): newdrop_bridge_rules. It readsiptables-saveandip6tables-savefor the filter and nat tables and deletes every rule in aCNI-*chain that namesbrig0with-ior-o. Each rule goes back toiptables-restore -w --noflushas-D, exactly asiptables-saveprinted it, so the shell never parses the quoted comment.CNI-*(eg. an admin'sFORWARD -i brig0) stay.brig0xdoes not matchbrig0.brig0rules live there and go with it. On a test host, a rootless--network sharedsandbox put 2brig0rules in that namespace and 0 on the host, and a restart of the user unit gave a fresh namespace with 0.tests/uninstall-cni-rules.sh(new): runs the generated uninstaller on a fake system install, with stubiptables-save/iptables-restorethat answer from per-table rule files. It checks that exactly thebrig0rules go (IPv4 filter and nat, IPv6 filter), that a second run is quiet, that the rules go with the bridge already gone, that a failingiptables-restoredoes not stop the uninstall, that a user uninstall runs no iptables, and that a host without iptables still finishes. Against main's uninstaller it fails on the two leaked rules.Tested
Locally (macOS):
sh -nand shellcheck 0.11.0 over the CI file list, andtests/uninstall-cni-rules.shunder sh and dash. On Ubuntu 24.04 (iptables 1.8.10 legacy) everytests/*.shpasses, as CI's shell job runs them.Ubuntu 24.04, next to a rootless user install: system install of the published v0.1.0-rc18
linux-amd64bundle with itsinstall.sh, a rootbrig run -d --network shared ubuntuon hvi,brig rm, then this PR'sbrig-uninstall.shcopied over the installed one:The uninstall logged both deletes. Apart from the two
brig0rules, the rules and chains after the uninstall are identical to those before it, including two rules added by hand for a second bridge. The user install still boots (brig run -d ubuntuon hvi, thenbrig rm).drop_bridge_rulesalso ran againstiptables-nftandip6tables-nftin a private network namespace. It removed thebrig0rules, keptbr-otherandFORWARD -i brig0, and a second run printed nothing.Not in scope: brig's own per-sandbox networks (
br-<id>, the default--network) leave the same two rules each afterbrig rm, and a system uninstall leaves the CNI IPAM state under/var/lib/cni. Both deserve their own follow-up.Release sequence
For v0.1.0-rc19. Independent of #30: both touch
scripts/build-bundle.sh, in different places. Merge either first, rebase the other onto main, merge it, then tagv0.1.0-rc19on main.🤖 Generated with Claude Code