Files
felhom.eu/hub/internal/web/r349_agent_binary_test.go
T
admin a5b4d29af4 R-349 (hub half): host page shows agent-binary drift from the reported agent_sha256
When the box reports the vouched agent version, its running binary's sha256 is compared with the
vouched AgentSHA256: same bytes -> "matches vouched", different -> amber DRIFT. An empty hash (older
agent) or another version is not comparable and shows nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-05 21:45:04 +02:00

64 lines
3.2 KiB
Go

package web
import (
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// R-349 (hub half): the agent reports the sha256 of the binary it RUNS (top-level agent_sha256). The
// hub compares it with the vouched AgentSHA256 only when the reported version IS the vouched version;
// an empty hash is UNKNOWN and never drift.
const r349Vouched = "a56a92a7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
const r349Hand = "256e0829bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
func TestR349_AgentBinaryDrift(t *testing.T) {
m := store.ArtifactManifest{AgentVersion: "0.130.0", AgentSHA256: strings.ToUpper(r349Vouched)}
cases := []struct{ name, report, want string }{
{"same version, same bytes", `{"agent_version":"0.130.0","agent_sha256":"` + r349Vouched + `"}`, "ok"},
{"same version, different bytes (the R-349 case)", `{"agent_version":"0.130.0","agent_sha256":"` + r349Hand + `"}`, "mismatch"},
{"v-prefixed version still compares", `{"agent_version":"v0.130.0","agent_sha256":"` + r349Hand + `"}`, "mismatch"},
{"older agent: no hash = unknown", `{"agent_version":"0.130.0"}`, ""},
{"empty hash = unknown", `{"agent_version":"0.130.0","agent_sha256":""}`, ""},
{"other version = not comparable", `{"agent_version":"0.129.0","agent_sha256":"` + r349Hand + `"}`, ""},
{"nested host.agent_sha256 is not the field", `{"agent_version":"0.130.0","host":{"agent_sha256":"` + r349Hand + `"}}`, ""},
{"no report", ``, ""},
{"malformed", `{nope`, ""},
}
for _, tc := range cases {
if got, _ := agentBinaryDrift(tc.report, m); got != tc.want {
t.Errorf("%s: drift = %q, want %q", tc.name, got, tc.want)
}
}
if got, _ := agentBinaryDrift(`{"agent_version":"0.130.0","agent_sha256":"`+r349Hand+`"}`, store.ArtifactManifest{AgentVersion: "0.130.0"}); got != "" {
t.Errorf("un-vouched hash: drift = %q, want unknown", got)
}
}
// The page, per branch of the template gate: drift (amber, both hashes), ok, and the unknown case
// (no line at all).
func TestR349_HostPageShowsAgentBinaryDrift(t *testing.T) {
s, st, _ := newRevealServer(t)
cookie, _ := newRevealSession(t, s)
if err := st.SetArtifactManifest(store.ArtifactManifest{AgentVersion: "0.130.0", AgentSHA256: r349Vouched}); err != nil {
t.Fatal(err)
}
seedNetHost(t, st, "h-drift", "0.130.0", `{"agent_version":"0.130.0","agent_sha256":"`+r349Hand+`"}`, "")
seedNetHost(t, st, "h-ok", "0.130.0", `{"agent_version":"0.130.0","agent_sha256":"`+r349Vouched+`"}`, "")
seedNetHost(t, st, "h-old", "0.130.0", `{"agent_version":"0.130.0"}`, "")
b := getHostPage(t, s, cookie, "h-drift")
if !strings.Contains(b, `id="agent-binary-drift"`) || !strings.Contains(b, r349Hand[:12]) || !strings.Contains(b, r349Vouched[:12]) {
t.Fatal("same version, different bytes: the host page shows no agent-binary DRIFT with both hashes")
}
b = getHostPage(t, s, cookie, "h-ok")
if !strings.Contains(b, `id="agent-binary"`) || strings.Contains(b, `id="agent-binary-drift"`) {
t.Fatal("matching bytes: want the 'matches vouched' line and no drift")
}
b = getHostPage(t, s, cookie, "h-old")
if strings.Contains(b, `id="agent-binary"`) {
t.Fatal("an agent that reports no hash must show no agent-binary line (unknown, never drift)")
}
}