Commit 45fc63e
feat(v1.100 PR-26-code-B): typed executor.ServiceUnmask + executor.Rename + raw-Run policy tightening (#515)
PR-26-code-B — restore verification / evidence hardening, slice B.
Executor hardening per §43 lock + §51.5-A2 invariant. CSF restore
A.1 + A.3 migrate from raw Run("systemctl","unmask",…) +
Run("mv",…) indirections to typed executor methods.
Authority:
- PR #512 / contract.md Part IV §§37-50
- PR #513 / §51 lock record (§51.5-A2: read-only typed introspection
is outside the mutation cap; this commit's ServiceUnmask + Rename
ARE mutation surfaces, but already enumerated by §44 row 2)
- PR #514 / code-A merge 4e98ff5
- §43 executor hardening
- §46 CI gate requirements
- §51 code-B authorization
Behavior delta:
- Before: A.1 used exec.Run("systemctl","unmask",csfServiceUnit) wrapped
by helper unmaskCSFService; A.3 used exec.Run("mv",old,new) wrapped
by helper renameAtomicViaExec.
- After: A.1 uses m.exec.ServiceUnmask(csfServiceUnit); A.3 uses
m.exec.Rename(csfBinaryDisabled, csfBinary). Both helpers REMOVED.
Mutation surface is unchanged in operational meaning; the typed
call shape lets the CI gate enforce per-call discipline at the
symbol level instead of via fragile Run-arg parsing.
Files changed (6):
internal/installer/executor/executor.go
- Executor interface gains:
ServiceUnmask(unit string) error // inverse of ServiceMask
Rename(oldpath, newpath string) error // atomic same-FS rename
- Header doc updated to list both new methods.
internal/installer/executor/real.go
- RealExecutor.ServiceUnmask runs systemctl unmask via Run with the
same typed error wrapping pattern as ServiceMask.
- RealExecutor.Rename calls os.Rename directly (consistent with
WriteFileAtomic's existing os.Rename usage). No process spawn.
internal/installer/executor/mock.go
- MockExecutor.ServiceUnmask records ("systemctl","unmask",unit) and
returns m.ServiceUnmaskErr (nil by default).
- MockExecutor.Rename records ("rename",oldpath,newpath), returns
m.RenameErr if non-nil; otherwise simulates the rename in the
in-memory Files map.
- New error-injection fields: ServiceUnmaskErr, RenameErr — mirror
the RunResults exit-code injection pattern.
cmd/nftban-installer/restore_deps_csf.go
- Helpers unmaskCSFService and renameAtomicViaExec REMOVED. Replaced
by a comment block documenting the §43.2 lock.
- A.1 call site: m.exec.ServiceUnmask(csfServiceUnit).
- A.3 call site: m.exec.Rename(csfBinaryDisabled, csfBinary).
- No raw Run("systemctl","unmask",…) and no raw Run("mv",…) remain.
- Log messages preserved; error wrapping (ErrCSFRestoreUnmaskFailed
+ ErrCSFRestoreBinaryRestoreFailed) preserved.
cmd/nftban-installer/restore_deps_csf_test.go
- buildCSFFixture: unmaskFailsExit injects mock.ServiceUnmaskErr;
mvBinaryFailsExit injects mock.RenameErr (the previous
RunResults-based simulation is removed; the OnCommand callback
that simulated the rename in the Files map is also removed —
Mock.Rename does that natively now).
- TestCSFMutate_4B3csf_A3_* tests updated: assertions move from
CommandCalled("mv", …) to CommandCalled("rename", …) because
Mock.Rename records "rename", not "mv".
- HappyPath_NoOutOfTargetMutation allow-list and OrderingPin
expected sequences updated: A.3's recorded shape becomes
("rename", oldpath, newpath) instead of ("mv", oldpath, newpath).
- Seven new TestCSFMutate_PR26B_* tests added:
1. A1_ServiceUnmaskOnlyCSFService — pins ServiceUnmask called
only on csf.service
2. A3_RenameOnlyCSFBinaryRestore — pins Rename called only with
(csf.disabled → csf)
3. NoRawSystemctlUnmaskRun_FileScan — pins no raw
Run("systemctl","unmask",…) remains in production source
4. NoRawMvRun_FileScan — pins no raw Run("mv",…) remains
5. A1_UnmaskFailure_TypedErrorPreserved — error contract
preserved through the migration
6. A3_RenameFailure_TypedErrorPreserved — same for A.3
7. RemovedHelpersGone_FileScan — pins removal of the
unmaskCSFService and renameAtomicViaExec function definitions
.github/workflows/ci-restore-canonization.yml
- G4-RESTORE-EXEC-NO-OUT-OF-TARGET strengthened per §43.4 + §46.1:
* \bexec\.ServiceUnmask\( REMOVED from forbidden list (now the
authorized typed method for A.1).
* Added forbidden patterns for raw Run("systemctl",…) with any
mutating verb (start/stop/enable/disable/mask/unmask/restart/
reload/daemon-reload). Read-only Run("systemctl","is-enabled",…)
remains authorized.
* Added forbidden pattern for raw Run("mv",…). Typed Rename is
the only authorized atomic-rename path.
* §46.1 line-skipping discipline applied: gate strips
line-leading "//" comments before pattern matching, preventing
the false-positive class that broke Policy Gates on PR #511.
* Header rewritten to reflect the post-PR-26-code-B authorized
mutation set (typed ServiceUnmask / typed Rename; no raw Run
for mutating systemctl verbs or mv).
Constraints honored (per §51.6 + operator scope):
IN scope:
- typed executor.ServiceUnmask ✓
- typed executor.Rename ✓
- migration of CSF restore A.1 + A.3 to typed methods ✓
- raw Run policy tightening (CI gate) ✓
- G4-RESTORE-EXEC-NO-OUT-OF-TARGET strengthened ✓
OUT of scope (and untouched):
- cron backup / A.4 (PR-26-code-C)
- destructive soak (PR-26-code-E)
- IptablesRuleExists / iptables introspection (Option B lock)
- target-specific predicate changes (already done in code-A)
- inline verification behavior changes
- restore decision lattice
- TargetAuthority / planner
- main.go history gate
- state machine / exit codes
- contract.md
- repo hygiene / UX / GOTH / metrics / module cleanup
Verified on lab2 (Ubuntu 24.04, go1.22.2):
- go build ./... clean
- go test ./cmd/nftban-installer/... ./internal/installer/restore/... ./internal/installer/state/... ./internal/installer/executor/... PASS
- go test ./... PASS (full suite)
- go test -race -count=1 ./cmd/nftban-installer ./internal/installer/restore/... ./internal/installer/state/... PASS
- go vet (cmd + restore + state + executor) clean
- go mod tidy no-op
- 7 new TestCSFMutate_PR26B_* tests all PASS
- Local replay of strengthened G4-RESTORE-EXEC-NO-OUT-OF-TARGET gate:
FAIL=0 against the migrated restore_deps_csf.go (only authorized
NftDeleteTable("ip","nftban") + NftDeleteTable("ip6","nftban")
calls; no raw mutating Run, no os.* bypass, no
custombuild/iptables/rebuild/purge symbols).
Awaiting auditor pass before push.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 4e98ff5 commit 45fc63e
6 files changed
Lines changed: 360 additions & 94 deletions
File tree
- .github/workflows
- cmd/nftban-installer
- internal/installer/executor
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
266 | | - | |
| 266 | + | |
267 | 267 | | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
276 | 276 | | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
281 | 286 | | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
287 | 290 | | |
288 | | - | |
289 | | - | |
290 | | - | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
291 | 295 | | |
292 | 296 | | |
293 | 297 | | |
| |||
300 | 304 | | |
301 | 305 | | |
302 | 306 | | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
303 | 311 | | |
304 | 312 | | |
305 | | - | |
306 | | - | |
| 313 | + | |
| 314 | + | |
307 | 315 | | |
308 | | - | |
309 | | - | |
310 | | - | |
311 | | - | |
312 | | - | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
313 | 320 | | |
314 | 321 | | |
315 | | - | |
316 | 322 | | |
317 | | - | |
318 | | - | |
319 | | - | |
| 323 | + | |
| 324 | + | |
320 | 325 | | |
321 | 326 | | |
322 | 327 | | |
| |||
326 | 331 | | |
327 | 332 | | |
328 | 333 | | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
329 | 350 | | |
330 | 351 | | |
331 | 352 | | |
| |||
343 | 364 | | |
344 | 365 | | |
345 | 366 | | |
346 | | - | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
347 | 370 | | |
| 371 | + | |
348 | 372 | | |
349 | 373 | | |
350 | 374 | | |
| |||
354 | 378 | | |
355 | 379 | | |
356 | 380 | | |
357 | | - | |
358 | | - | |
| 381 | + | |
| 382 | + | |
359 | 383 | | |
360 | 384 | | |
361 | 385 | | |
| |||
368 | 392 | | |
369 | 393 | | |
370 | 394 | | |
371 | | - | |
| 395 | + | |
372 | 396 | | |
373 | 397 | | |
374 | 398 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
186 | | - | |
187 | | - | |
188 | | - | |
189 | | - | |
190 | | - | |
191 | | - | |
192 | | - | |
193 | | - | |
194 | | - | |
195 | | - | |
196 | | - | |
197 | | - | |
198 | | - | |
199 | | - | |
200 | | - | |
201 | | - | |
202 | | - | |
203 | | - | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
210 | | - | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
211 | 192 | | |
212 | 193 | | |
213 | 194 | | |
| |||
292 | 273 | | |
293 | 274 | | |
294 | 275 | | |
295 | | - | |
| 276 | + | |
296 | 277 | | |
297 | 278 | | |
298 | 279 | | |
| |||
317 | 298 | | |
318 | 299 | | |
319 | 300 | | |
320 | | - | |
| 301 | + | |
321 | 302 | | |
322 | 303 | | |
323 | 304 | | |
| |||
0 commit comments