fix(agent): surface par2/install/NFS failures instead of degrading silently

- usenet: Par2Verify/Repair return ErrPar2NotInstalled (was nil="verified");
  pipeline surfaces it via Result.VerifyNote + WARNING — a download that
  shipped parity but couldn't be checked is delivered UNVERIFIED, not verified.
- funnel: pin cloudflared version + verify a baked-in SHA-256 (was `latest` +
  ELF-magic only) — a malicious/broken upstream release isn't pulled silently.
- stream: makeReadable verifies the file actually opens after chmod and warns
  clearly (NFS root_squash / SMB uid mapping) instead of a cryptic later EPERM.
- WireGuard endpoint pin dropped from the debt list (reseller uses direct
  config, no pin).
This commit is contained in:
Deivid Soto 2026-06-01 15:52:54 +02:00
parent 27bee8cdf4
commit 3d51013935
9 changed files with 319 additions and 43 deletions

View file

@ -1,24 +1,36 @@
package postprocess
import (
"errors"
"fmt"
"log"
"os/exec"
"strings"
)
// Par2Available checks if par2cmdline is installed.
func Par2Available() bool {
// ErrPar2NotInstalled is returned by Par2Verify/Par2Repair when parity data is
// present but the `par2` binary is missing. The caller MUST surface this rather
// than treat it as "verified OK" — a download that shipped parity but could not
// be checked is delivered UNVERIFIED, not verified.
var ErrPar2NotInstalled = errors.New("par2 not installed")
// par2Lookup probes whether the par2 binary is on PATH. It's a package var so
// tests can simulate a missing binary without touching the real PATH.
var par2Lookup = func() bool {
_, err := exec.LookPath("par2")
return err == nil
}
// Par2Verify verifies files using a par2 file.
// Returns nil if verification passes, error otherwise.
// Par2Available checks if par2cmdline is installed.
func Par2Available() bool { return par2Lookup() }
// Par2Verify verifies files using a par2 file. Returns nil on success,
// ErrPar2NotInstalled when the binary is missing (parity present but unchecked —
// the caller must surface it, NOT treat it as verified), a *Par2RepairableError
// when repair is possible, or another error on failure.
func Par2Verify(par2File string) error {
if !Par2Available() {
log.Printf("[usenet] par2 not installed, skipping verification")
return nil
return ErrPar2NotInstalled
}
cmd := exec.Command("par2", "verify", par2File)
@ -42,7 +54,7 @@ func Par2Verify(par2File string) error {
// Par2Repair attempts to repair files using par2 parity data.
func Par2Repair(par2File string) error {
if !Par2Available() {
return fmt.Errorf("par2 not installed")
return ErrPar2NotInstalled
}
cmd := exec.Command("par2", "repair", par2File)

View file

@ -1,6 +1,7 @@
package postprocess
import (
"errors"
"fmt"
"log"
"os"
@ -16,6 +17,12 @@ type Result struct {
Files []string // all final files
Repaired bool // whether par2 repair was needed
Extracted bool // whether archive extraction was performed
// VerifyNote is non-empty when par2 verification was DEGRADED — parity shipped
// but could not be confirmed (par2 missing, repair failed, verify error). The
// download is still delivered, but the caller surfaces this so the user knows
// the file is unverified rather than silently assuming it's good. Empty means
// either "verified OK" or "no parity shipped" — both are non-degraded.
VerifyNote string
}
// Options configures post-processing behavior.
@ -29,21 +36,37 @@ type Options struct {
func Process(dir string, downloadedFiles map[string]string, opts Options) (*Result, error) {
result := &Result{}
// Step 1: Par2 verification and repair
// Step 1: Par2 verification and repair. Parity is optional, so a missing
// binary or a failed repair does NOT abort the download — but it MUST be
// surfaced (result.VerifyNote + a WARNING) instead of silently delivering an
// unverified file as if it had passed.
par2File := findPar2File(downloadedFiles)
if par2File != "" {
var repairable *Par2RepairableError
err := Par2Verify(par2File)
if err != nil {
if _, ok := err.(*Par2RepairableError); ok {
log.Printf("[usenet] attempting par2 repair...")
if repairErr := Par2Repair(par2File); repairErr != nil {
log.Printf("[usenet] par2 repair failed: %v", repairErr)
} else {
result.Repaired = true
}
} else {
log.Printf("[usenet] par2 verification error: %v", err)
switch {
case err == nil:
// Verified OK — nothing to surface.
case errors.Is(err, ErrPar2NotInstalled):
result.VerifyNote = "par2 parity present but `par2` is not installed — delivered UNVERIFIED (install par2cmdline to enable verification/repair)"
log.Printf("[usenet] WARNING: %s", result.VerifyNote)
case errors.As(err, &repairable):
log.Printf("[usenet] par2: corruption detected, attempting repair...")
repairErr := Par2Repair(par2File)
switch {
case repairErr == nil:
result.Repaired = true
log.Printf("[usenet] par2: repair successful")
case errors.Is(repairErr, ErrPar2NotInstalled):
result.VerifyNote = "par2 corruption detected but `par2` is not installed — cannot repair, delivered POSSIBLY CORRUPT"
log.Printf("[usenet] WARNING: %s", result.VerifyNote)
default:
result.VerifyNote = fmt.Sprintf("par2 repair failed — file may be corrupt: %v", repairErr)
log.Printf("[usenet] WARNING: %s", result.VerifyNote)
}
default:
result.VerifyNote = fmt.Sprintf("par2 verification error — file may be corrupt: %v", err)
log.Printf("[usenet] WARNING: %s", result.VerifyNote)
}
}

View file

@ -6,6 +6,38 @@ import (
"testing"
)
// TestProcess_Par2MissingSurfaced verifies that when parity is present but the
// par2 binary is missing, Process does NOT silently report success: it surfaces
// the degraded state via VerifyNote and leaves Verified false (while still
// delivering the file).
func TestProcess_Par2MissingSurfaced(t *testing.T) {
orig := par2Lookup
par2Lookup = func() bool { return false }
defer func() { par2Lookup = orig }()
dir := t.TempDir()
par2Path := filepath.Join(dir, "release.par2")
if err := os.WriteFile(par2Path, []byte("fake parity"), 0o644); err != nil {
t.Fatal(err)
}
vid := filepath.Join(dir, "movie.mkv")
if err := os.WriteFile(vid, []byte("video data"), 0o644); err != nil {
t.Fatal(err)
}
files := map[string]string{"release.par2": par2Path, "movie.mkv": vid}
res, err := Process(dir, files, Options{})
if err != nil {
t.Fatalf("Process: %v", err)
}
if res.VerifyNote == "" {
t.Error("VerifyNote must be set (not silent) when par2 is missing")
}
if res.FinalPath != vid {
t.Errorf("FinalPath = %q, want %q (file still delivered)", res.FinalPath, vid)
}
}
func TestFindPar2File(t *testing.T) {
dir := t.TempDir()