Skip to content

Commit 93e86e2

Browse files
itcmsgrclaude
andcommitted
ci(v1.100 PR-26-code-C): add G4-RESTORE-CRON-MANIFEST-INTEGRITY structural gate
Strengthens the Restore Canonization workflow with the §46 cron- manifest integrity gate locked at §51.6 entry criteria for code-C. Authority: - §42 cron backup / A.4 contract (manifest-only restore) - §46 CI gate requirements (structural, not loose grep) - §46.1 line-skipping discipline (production-code-only, comment-stripped) Gate scope (writer + reader cross-pin): WRITER required symbols (internal/installer/switchop/cron_manifest.go): - CronManifestSchemaVersion = "1.0.0" const - CronManifestDir / CronManifestFile constants pinned to the exact /var/lib/nftban/state/csf-cron-backup/{,manifest.json} paths - CronCSFSrcPath / CronLFDSrcPath constants pinned to the exact /etc/cron.d/{csf-cron,lfd-cron} source paths - func ComputeCronBackupSHA256(content []byte) string — single source of truth for the sha256 helper - func WriteCronBackupManifest(...), ReadCronBackupManifest(...), VerifyCronBackupEntry(...) — the three exported API points - sha256.Sum256 — proves the writer actually computes sha256 (not a no-op stub) Pattern shape: whitespace-flexible ([[:space:]]+) so the patterns don't break when gofmt re-aligns the const block. READER required symbols (cmd/nftban-installer/restore_deps_csf.go): - switchop.ReadCronBackupManifest( — A.4 reads the manifest - switchop.VerifyCronBackupEntry( — A.4 verifies sha256 BEFORE restoring (this is the integrity guarantee §42.2-D requires) - ErrCSFRestoreCronManifestCorrupt — the typed sentinel surfaced on integrity failure If any required symbol is absent, the gate fails — proves the integrity check is consumed, not just imported. WRITER + READER forbidden patterns: - \bcustombuild\b — defense-in-depth (§34: no DirectAdmin custombuild) - iptables-restore — defense-in-depth (§34: csf manages its own) - "/etc/cron.d/*" glob literal — no broad cron sweep - WriteFile to /etc/cron.d/* with non-csf-prefixed leaf (rough check) READER allow-list pin: - Every WriteFileAtomic call in restore_deps_csf.go that targets a /etc/cron.d/* literal MUST equal one of the two §42.2-locked literals: "/etc/cron.d/csf-cron" OR "/etc/cron.d/lfd-cron". - The reader uses the named constants csfCronPath / lfdCronPath, so in practice this grep returns zero matches (named-constant reference, not string-literal in WriteFileAtomic args). Defense- in-depth structural pin against accidental future literal-arg drift. §46.1 discipline applied: production-code-only files, comment- stripped before pattern matching. Avoids the false-positive class that hit Policy Gates on PR #511 (//-comment text matching forbidden substrings). Local replay against the PR-26-code-C1 + C2 source: WRITER_MISS / READER_MISS / FORBIDDEN_HIT / BAD_LITERAL: all 0 FAIL=0 Verified on lab2 (Ubuntu 24.04, go1.22.2): - go build ./... clean - go test ./... PASS (64 packages) - go test -race -count=1 ./cmd/nftban-installer ./internal/installer/restore/... ./internal/installer/state/... ./internal/installer/switchop/... PASS - go vet ./... clean - go mod tidy no-op Auditor checkpoint: C1 + C2 + CI gate are now all locally compiled, tested, and gate-replayed clean. Awaiting focused auditor pass before push. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c5767f4 commit 93e86e2

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

.github/workflows/ci-restore-canonization.yml

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,146 @@ jobs:
403403
404404
echo "G4-RESTORE-EXEC-NO-OUT-OF-TARGET PASS — restore_deps_csf.go mutation surface is closed"
405405
406+
# ------------------------------------------------------------------
407+
# G4-RESTORE-CRON-MANIFEST-INTEGRITY (PR-26-code-C / §46) —
408+
# structural pin on the CSF/LFD cron-backup writer + reader.
409+
#
410+
# The §42.2 lock authorizes A.4 cron-restore ONLY when the
411+
# install-time writer recorded the file content with sha256 and
412+
# ONLY for the two locked source paths
413+
# (/etc/cron.d/csf-cron, /etc/cron.d/lfd-cron). This gate
414+
# structurally enforces:
415+
#
416+
# - the writer file (switchop/cron_manifest.go) declares the
417+
# manifest-dir constant, the schema-version constant, and
418+
# both source-path constants verbatim
419+
# - the writer file uses the shared sha256 helper symbol
420+
# ComputeCronBackupSHA256 to compute the manifest entry
421+
# - the reader file (cmd/nftban-installer/restore_deps_csf.go)
422+
# references both ReadCronBackupManifest and
423+
# VerifyCronBackupEntry — i.e. the integrity check is
424+
# consumed, not just imported
425+
# - neither file references DirectAdmin custombuild,
426+
# iptables-restore, or a broad /etc/cron.d/* glob
427+
#
428+
# Per §46.1 discipline: production-code-only, comment-stripped.
429+
# ------------------------------------------------------------------
430+
- name: G4-RESTORE-CRON-MANIFEST-INTEGRITY — writer + reader structural pin
431+
shell: bash
432+
run: |
433+
set -Eeuo pipefail
434+
435+
writer=internal/installer/switchop/cron_manifest.go
436+
reader=cmd/nftban-installer/restore_deps_csf.go
437+
438+
for f in "$writer" "$reader"; do
439+
if [[ ! -f "$f" ]]; then
440+
echo "::error::G4-RESTORE-CRON-MANIFEST-INTEGRITY: $f not found"
441+
exit 1
442+
fi
443+
done
444+
445+
# §46.1 production-only scan: strip line-leading // comments.
446+
writer_src=$(grep -vE '^[[:space:]]*//' "$writer" || true)
447+
reader_src=$(grep -vE '^[[:space:]]*//' "$reader" || true)
448+
449+
fail=0
450+
451+
# ---- WRITER required symbols -------------------------------
452+
# Each pattern is a structural element the writer MUST contain.
453+
# Whitespace-flexible matchers ([[:space:]]+) so the patterns
454+
# don't break when gofmt re-aligns the const block.
455+
writer_required=(
456+
'CronManifestSchemaVersion[[:space:]]+=[[:space:]]+"1\.0\.0"'
457+
'CronManifestDir[[:space:]]+=[[:space:]]+"/var/lib/nftban/state/csf-cron-backup"'
458+
'CronManifestFile[[:space:]]+=[[:space:]]+"/var/lib/nftban/state/csf-cron-backup/manifest\.json"'
459+
'CronCSFSrcPath[[:space:]]+=[[:space:]]+"/etc/cron\.d/csf-cron"'
460+
'CronLFDSrcPath[[:space:]]+=[[:space:]]+"/etc/cron\.d/lfd-cron"'
461+
'func ComputeCronBackupSHA256\(content \[\]byte\) string'
462+
'func WriteCronBackupManifest\('
463+
'func ReadCronBackupManifest\('
464+
'func VerifyCronBackupEntry\('
465+
'sha256\.Sum256'
466+
)
467+
for pat in "${writer_required[@]}"; do
468+
if ! echo "$writer_src" | grep -qE "$pat"; then
469+
echo "::error::G4-RESTORE-CRON-MANIFEST-INTEGRITY: writer ($writer) missing required symbol matching '$pat'"
470+
fail=1
471+
fi
472+
done
473+
474+
# ---- READER required symbols -------------------------------
475+
# The A.4 reader path MUST consume both the manifest reader
476+
# and the integrity-verifier — i.e. the sha256 check is
477+
# actually performed before A.4 acts.
478+
reader_required=(
479+
'switchop\.ReadCronBackupManifest\('
480+
'switchop\.VerifyCronBackupEntry\('
481+
'ErrCSFRestoreCronManifestCorrupt'
482+
)
483+
for pat in "${reader_required[@]}"; do
484+
if ! echo "$reader_src" | grep -qE "$pat"; then
485+
echo "::error::G4-RESTORE-CRON-MANIFEST-INTEGRITY: reader ($reader) missing required symbol matching '$pat'"
486+
fail=1
487+
fi
488+
done
489+
490+
# ---- WRITER + READER forbidden symbols ---------------------
491+
# Defense-in-depth: even though the strengthened
492+
# G4-RESTORE-EXEC-NO-OUT-OF-TARGET already covers some of
493+
# these, restate the cron-specific bans.
494+
forbidden=(
495+
'\bcustombuild\b'
496+
'iptables-restore'
497+
'"/etc/cron\.d/\*"'
498+
'WriteFile.*"/etc/cron\.d/[^c]'
499+
)
500+
for pat in "${forbidden[@]}"; do
501+
if echo "$writer_src" | grep -qE "$pat"; then
502+
echo "::error::G4-RESTORE-CRON-MANIFEST-INTEGRITY: writer ($writer) contains forbidden pattern '$pat'"
503+
fail=1
504+
fi
505+
if echo "$reader_src" | grep -qE "$pat"; then
506+
echo "::error::G4-RESTORE-CRON-MANIFEST-INTEGRITY: reader ($reader) contains forbidden pattern '$pat'"
507+
fail=1
508+
fi
509+
done
510+
511+
# ---- Reader's authorized target-path allow-list -----------
512+
# The A.4 reader's WriteFileAtomic call MUST target only one
513+
# of the two §42.2-locked paths. Structural check: every
514+
# WriteFileAtomic call in restore_deps_csf.go that targets a
515+
# /etc/cron.d/* path MUST use one of csfCronPath / lfdCronPath
516+
# as the first argument. We grep for any /etc/cron.d/ literal
517+
# in WriteFileAtomic args and require it equal one of the two
518+
# locked literals. Path constants live in restore_deps_csf.go
519+
# (csfCronPath = "/etc/cron.d/csf-cron";
520+
# lfdCronPath = "/etc/cron.d/lfd-cron"), so the reader uses
521+
# the constants — string-literal grep should find zero
522+
# /etc/cron.d/ matches inside WriteFileAtomic argument lists.
523+
while IFS= read -r line; do
524+
# Capture WriteFileAtomic(... "literal" ...) pattern.
525+
literal=$(echo "$line" | grep -oE 'WriteFileAtomic\([^)]*"/etc/cron\.d/[^"]*"' | grep -oE '"/etc/cron\.d/[^"]*"' || true)
526+
if [[ -n "$literal" ]]; then
527+
case "$literal" in
528+
'"/etc/cron.d/csf-cron"'|'"/etc/cron.d/lfd-cron"')
529+
;;
530+
*)
531+
echo "::error::G4-RESTORE-CRON-MANIFEST-INTEGRITY: reader writes unauthorized cron literal: $literal"
532+
fail=1
533+
;;
534+
esac
535+
fi
536+
done < <(echo "$reader_src" | grep -nE 'WriteFileAtomic\([^)]*"/etc/cron\.d/' || true)
537+
538+
if [[ "$fail" -ne 0 ]]; then
539+
echo "::error::§42.2 / §46 violation — cron-backup manifest integrity not enforced."
540+
echo "::error::See internal/installer/restore/contract.md §42 + §46 for the lock."
541+
exit 1
542+
fi
543+
544+
echo "G4-RESTORE-CRON-MANIFEST-INTEGRITY PASS — writer + reader structurally consume the shared sha256 + manifest API"
545+
406546
restore-canonization-summary:
407547
name: Restore Canonization summary
408548
runs-on: ubuntu-24.04

0 commit comments

Comments
 (0)