Skip to content

PR26.6: takeover preserves non-nftban authority (table classifier + lock-in tests)#533

Merged
itcmsgr merged 1 commit intomainfrom
feat/pr26.6-takeover-preserves-authority
Apr 30, 2026
Merged

PR26.6: takeover preserves non-nftban authority (table classifier + lock-in tests)#533
itcmsgr merged 1 commit intomainfrom
feat/pr26.6-takeover-preserves-authority

Conversation

@itcmsgr
Copy link
Copy Markdown
Owner

@itcmsgr itcmsgr commented Apr 30, 2026

Summary

  • 6A: Replaces shell-rebuild allowlist sweep with a 4-class nft table classifier. dns2 (2026-04-30) install wiped operator-retained inet ssh_safety because _firewall_rebuild_core step 4 silently nft delete table'd anything outside its tight regex. New classifier (cli/lib/nftban/core/nftban_table_classify.sh) splits tables into NFTBAN_OWNED / EXTERNAL_AUTHORITY_GHOST / KERNEL_DEFAULT / OPERATOR_SAFETY and WARNs-and-preserves the last class. helpers/autoheal.sh is rerouted through the same classifier.
  • 6B: Go lock-in tests in internal/installer/switchop/takeover_pr26_6_test.go pin current Go-side takeover behavior — /usr/sbin/csf is renamed to csf.disabled with byte-identical sha256 (reversible disarm, NOT destructive); /usr/sbin/lfd untouched; /etc/csf / /usr/local/csf / /var/lib/csf preserved; cron-backup manifest written before cron rm; ghost-table cleanup never targets operator or kernel-default tables.
  • 6C: Pure-shell fixture cli/lib/nftban/tests/test_table_classifier.sh with 27 assertions covering all 4 classes including inet ssh_safety survival.

Invariant

TAKEOVER-PRESERVES-NON-NFTBAN-AUTHORITY-001

During takeover and rebuild, nftban may disable external firewall
authority through reversible lifecycle operations, but must not
destructively delete non-nftban-owned authority or operator-safety
assets.

Default policy is WARN-and-preserve. --strict-unknown-tables / --purge-unknown-tables modes are deferred — PR26.6 does not change install-success semantics for normal hosts.

Corrected narrative — CSF binary

The dns2 evidence reported "/usr/sbin/csf MISSING". Code survey of internal/installer/switchop/takeover.go:117-125 showed the actual operation is mv /usr/sbin/csf /usr/sbin/csf.disabled — a reversible rename. PR-26 amendment 2 (#520) already accepts the .disabled sibling for restore-to-CSF preflight. The dns2 path-absence was real but the binary content was preserved. The 6B tests now lock that contract.

Test plan

  • bash cli/lib/nftban/tests/test_table_classifier.sh27/27 PASS on lab2
  • go test -count=1 -v -run TestPR26_6_ ./internal/installer/switchop/...5/5 PASS on lab2
  • go test -count=1 ./internal/installer/...17/17 packages PASS on lab2
  • go vet ./internal/installer/switchop/... — clean
  • CI green
  • Operator review

Out of scope

  • --strict-unknown-tables / --purge-unknown-tables / --refuse-on-unknown-tables flags
  • cPanel/Plesk adapters (PR26.7, PR26.8 — gated on dns2 retry green)
  • Source-install payload work (PR26.5 already merged)
  • Restore redesign
  • Destructive dns2 retry — gated on this PR + green CI + Tier 1 rebuild

🤖 Generated with Claude Code

…ON-NFTBAN-AUTHORITY-001)

dns2 (2026-04-30) install evidence reported `inet ssh_safety` (operator-
retained kernel safety table) silently wiped during PR26.4 install.
Survey traced root cause to `cli/lib/nftban/cli/cmd_firewall.sh` step 4
of `_firewall_rebuild_core`, which used an allowlist-regex sweep:

    ALLOWED_TABLES_PATTERN="^table (ip|ip6) (nftban|raw)$|^table inet (filter|nftban|nftban_install_emergency)$"
    ... if NOT matching ... nft delete table "$TABLE_SPEC" 2>/dev/null

Anything not matching was silently deleted. `inet ssh_safety` failed
the regex and was destroyed. The 2>/dev/null hid the only log signal.
The same shape existed in `helpers/autoheal.sh` section 9 with an
even tighter regex.

Fix (6A) — replace both with a 4-class table classifier:

  cli/lib/nftban/core/nftban_table_classify.sh

    NFTBAN_OWNED              -> flush, no nft-delete
    EXTERNAL_AUTHORITY_GHOST  -> delete (CSF/firewalld iptables-nft compat)
    KERNEL_DEFAULT            -> preserve silently (ip raw, ip6 raw)
    OPERATOR_SAFETY           -> WARN to stderr, preserve, continue

Default policy is WARN-and-preserve. Strict refuse / explicit purge
modes are deferred to a future PR; PR26.6 must not change install
success semantics for normal hosts.

Lock-in (6B) — Go takeover behavior tests under switchop:

  - csf binary RENAMED to /usr/sbin/csf.disabled with sha256 match
    (reversible disarm; PR-26 amendment 2 already accepts this for
    restore-to-CSF preflight). NOT a destructive deletion.
  - /usr/sbin/lfd untouched (no mv, no rm) — mask-only contract.
  - /etc/csf, /usr/local/csf, /var/lib/csf preserved.
  - cron-backup manifest written before /etc/cron.d/{csf,lfd}-cron rm.
  - Go ghost cleanup also preserves operator and kernel-default tables
    (defense-in-depth, in case future refactors move logic there).

Fixture (6C) — cli/lib/nftban/tests/test_table_classifier.sh:

  27 assertions covering all 4 classes including `inet ssh_safety`
  survival. Pure shell test, runs without nft / root.

Lab proof on lab2 (Ubuntu 24.04, go1.22.2):
  - bash test_table_classifier.sh: 27/27 PASS
  - go test -run TestPR26_6_ ./internal/installer/switchop/...: 5/5 PASS
  - go test ./internal/installer/...: all 17 installer packages PASS
  - go vet ./internal/installer/switchop/...: clean

Out of scope (deferred):
  - --strict-unknown-tables / --purge-unknown-tables flags
  - cPanel/Plesk adapters (PR26.7 / PR26.8)
  - source-install payload work (PR26.5 already merged)
  - restore redesign
  - destructive dns2 retry

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@itcmsgr itcmsgr merged commit 1e48b79 into main Apr 30, 2026
54 checks passed
@itcmsgr itcmsgr deleted the feat/pr26.6-takeover-preserves-authority branch April 30, 2026 19:15
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