Commit 4e98ff5
feat(v1.100 PR-26-code-A): target-specific safety predicate + firewallType plumbing (§§51.3-51.4) (#514)
PR-26-code-A — restore verification / evidence hardening, slice A.
Tightens the safety-net-safe predicate from PR-25's any-external-FW
heuristic to the target-specific check operator-locked at §51.3
Option B. Plumbs the resolved firewallType into the inline-verify
dep per §51.4. No new mutation primitives. No iptables introspection
(Option A explicitly deferred to a future amendment).
Authority:
- PR #512 merge cd76842 (PR-26-doc / Part IV §§37-50)
- PR #513 merge 52fadcc (PR-26 operator lock record §51)
Behavior delta (target-specific predicate):
- PR-25 / 4B-4: IsSafetyNetRemovalSafe accepted ANY service in
{csf, ufw, firewalld, iptables, netfilter-persistent} being active.
- PR-26-code-A: IsSafetyNetRemovalSafe requires the RESOLVED TARGET's
specific service unit (csf.service for csf restore) to be active.
A non-target external firewall being active no longer satisfies CSF
restore safety. The §41 looseness is closed.
Files changed (5):
cmd/nftban-installer/restore_deps.go
- productionInlineVerifyDep struct gains read-only firewallType field
(constructor-injected; never re-derived per §17.3).
- IsSafetyNetRemovalSafe rewritten:
* SSH port via detect.SSHPort (kept).
* Defensive guard: empty firewallType → ErrInlineVerifyTargetFirewallTypeMissing.
* Defensive guard: firewallType not in §18.2 set → ErrInlineVerifyUnknownFirewall.
* Amendment-1 §30.2 lock: firewallType != csf → ErrInlineVerifyOnlyCSFAuthorized.
* Target-specific check: only the resolved unit's ServiceActive
counts. Other external FW services are no longer probed.
- IsTargetFirewallActive gains defensive cross-check: caller-passed
firewallType must match v.firewallType when the constructor injected
one. Mismatch → ErrInlineVerifyTargetMismatch.
- New sentinels: ErrInlineVerifyTargetFirewallTypeMissing,
ErrInlineVerifyTargetMismatch.
- Removed: var inlineVerifyExternalFirewallServices (the any-FW list
is dead under §51.3 Option B).
- newProductionRestoreDeps + newProductionRestoreDepsWithEvidence
signatures take firewallType. Old (4-arg) call sites in production
routed through the new 5-arg signature with "" for the back-compat
shim; tests pass "csf" or omit per fixture intent.
- restoreDepsFactory function-pointer type extended to 5 args.
- New helper resolveFirewallTypeForDeps maps TargetAuthority →
firewallType (§18.3 invariants + §20 panel mapping). Lives in
restore_deps.go (NOT restore_decide.go) to keep
TargetAuthorityKind* constants out of dispatcher source — the
no-Group-Kind-mapping invariant in TestDispatcher_NoLocalGroupKindMapping
stays green.
cmd/nftban-installer/restore_decide.go
- runRestoreExecutionFromProceed gains a single line: resolves
firewallType via resolveFirewallTypeForDeps(target) BEFORE calling
the factory. On resolution failure: persists
StateRestoreFailedExecution + the standard exit code.
- newRestoreDeps call updated to the new 5-arg signature.
- No Group→Kind mapping leaks into this file.
- No state-machine / exit-code / history-gate change.
cmd/nftban-installer/restore_decide_test.go
- withFakeDeps + withFakeDepsRecordingEvidence updated to the new
5-arg factory signature.
- recordingFactoryCall struct gains firewallType field.
cmd/nftban-installer/restore_deps_csf_test.go
- TestCSFMutate_4B3csf_PR25NonShipping_PredicateUnwiredByDefault call
to newProductionRestoreDepsWithEvidence updated to the 5-arg form.
- (Test still asserts the predicate is wired non-nil per 4B-4 lock;
PR-26-code-A does not change that wiring — it changes what the
predicate evaluates.)
cmd/nftban-installer/restore_deps_inlineverify_test.go
- All existing IsSafetyNetRemovalSafe tests migrated to use the new
newInlineVerifyDepWithTarget(t, mock, firewallType) helper. The
tests' semantic assertions are preserved (TrueOnlyWhenTargetFWActive,
FalseWhenOnlyEmergencyProtects, FalseWhenSSHPortUnknown, NoMutation,
FactoryWiresSafetyNetPredicate, A7_DeletesWhenPredicateTrue,
A7_RefusesWhenPredicateFalse, FullRun_ChecksThreeAssertionsOnly).
- Test #6 renamed from TrueOnlyWhenExternalFWActive to
TrueOnlyWhenTargetFWActive to reflect the §51.3 lock.
- 10 new PR-26-code-A tests (TestInlineVerify_PR26A_*):
1. NonTargetFWDoesNotSatisfy (4 sub-cases: ufw / firewalld /
iptables / netfilter-persistent active does NOT satisfy CSF
restore when csf.service is inactive)
2. EmptyFirewallType_DefensiveGuard
3. FactoryWiresFirewallTypeIntoInlineVerify
4. A7GateUsesTargetSpecificPredicate (mutation-side integration)
5. TargetCSFActive_SafeToRemove (happy path under tightened rule)
6. NonCSFTarget_TypedUnsupported (3 sub-cases: ufw / firewalld /
iptables → ErrInlineVerifyOnlyCSFAuthorized)
7. UnknownTarget_TypedUnknown (4 sub-cases: shorewall / pf /
CSF / "csf " → ErrInlineVerifyUnknownFirewall)
8. IsTargetFirewallActive_MismatchGuard (caller-passed firewallType
must match v.firewallType when injected)
9. OldExternalFWListRemoved_FileScan (compile-time + grep pin
that the old any-external-FW list is gone)
10. FactorySignatureCarriesFirewallType (compile-time pin that
the factory signature requires firewallType)
Constraints honored (per §51.6 entry criteria + operator scope):
IN scope:
- target-specific safety predicate ✓
- inline verification hardening ✓
- §51.3 Option B semantics (kernel SSH-rule evidence ADVISORY) ✓
- §51.4 firewallType plumbing ✓
- tests proving non-target external FWs do not satisfy CSF restore ✓
OUT of scope (and untouched):
- cron backup / A.4 (PR-26-code-C)
- typed executor.ServiceUnmask / Rename (PR-26-code-B)
- destructive soak (PR-26-code-E)
- IptablesRuleExists / iptables introspection (Option A — needs amendment)
- iptables / ip6tables / iptables-save / nft list parsing (none added)
- repo hygiene / UX / GOTH / metrics / module cleanup (untouched)
- service lifecycle audit (untouched)
- rebuild/idempotency audit (untouched)
- PR-25 state terminals or exit codes (untouched)
- main.go history gate (untouched)
- restore_deps_csf.go production logic (untouched — only its companion
test file received the 1-line factory-signature update)
- contract.md (untouched)
- workflows (untouched)
Verified on lab2 (Ubuntu 24.04, go1.22.2):
- go build ./... clean
- go test ./cmd/nftban-installer/... ./internal/installer/restore/... ./internal/installer/state/... PASS
- go test ./... PASS (full suite)
- go test -race -count=1 ./cmd/nftban-installer ./internal/installer/restore/... ./internal/installer/state/... PASS
- go vet clean
- go mod tidy no-op
- 10 new PR-26-code-A tests + 18 sub-tests all PASS
- TestDispatcher_NoLocalGroupKindMapping still green (helper lives
in restore_deps.go, not restore_decide.go)
Awaiting auditor pass before push.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 52fadcc commit 4e98ff5
5 files changed
Lines changed: 510 additions & 79 deletions
File tree
- cmd/nftban-installer
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
274 | 274 | | |
275 | 275 | | |
276 | 276 | | |
277 | | - | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
278 | 293 | | |
279 | 294 | | |
280 | 295 | | |
| |||
302 | 317 | | |
303 | 318 | | |
304 | 319 | | |
| 320 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
349 | 349 | | |
350 | 350 | | |
351 | 351 | | |
| 352 | + | |
352 | 353 | | |
353 | 354 | | |
354 | 355 | | |
| |||
575 | 576 | | |
576 | 577 | | |
577 | 578 | | |
578 | | - | |
579 | | - | |
580 | | - | |
581 | | - | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
582 | 584 | | |
583 | 585 | | |
584 | 586 | | |
| |||
594 | 596 | | |
595 | 597 | | |
596 | 598 | | |
| 599 | + | |
597 | 600 | | |
598 | 601 | | |
599 | | - | |
600 | | - | |
601 | | - | |
602 | | - | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
603 | 607 | | |
604 | 608 | | |
605 | 609 | | |
| |||
0 commit comments