Files
pulse/internal/api/unified_agent_test.go
T
rcourtman 883e951023 fix(agent): refuse to serve an agent binary older than the server
/download/pulse-agent served whatever agent artifact sat on disk with no
relation to the running build. Local agent binaries are build outputs that
nothing refreshes on their own, so they go stale silently: a dev backend was
found serving a four-week-old v6.0.5 agent while reporting 6.2.0-rc.8.

Staleness is not cosmetic. The installer renders its service wrapper from the
server's current template, so an agent predating a flag that template now
passes exits immediately with "flag provided but not defined" and crash-loops
under its watchdog. That is how a real host lost its agent: the version
mismatch was reported only as an installer warning, after download, easy to
read as noise.

Validation already scanned the binary for its report-contract endpoints, so
the version check joins that same single pass and rejects a binary that does
not carry this server's agent version. Refusal is loud where the old warning
was quiet: a dev server answers 404 naming the stale path and the build
command, and a published release falls through to the existing release-asset
proxy and fetches the matching version, which makes production self-healing
rather than silently downgraded.

The expected version resolves through updates.GetCurrentVersion rather than
the compiled-in serverVersion. The first cut of this guard used serverVersion
and was inert on exactly the builds that need it: the enterprise binary
compiles in "dev-pro", no version parser accepts it, and the check disabled
itself. It passed its unit tests and still served the stale binary; only
replaying the real v6.0.5 artifact through the running server exposed it.
"dev-pro" is now pinned in the version table with that reasoning attached.

The shared download-test fixture built a binary carrying the report endpoint
but no version string, which a real agent always has, so it now stamps the
expected version. Verified non-vacuous in both directions: those tests fail
with the guard active and the thin fixture, and pass with a faithful one.

Contract-Neutral: storage-recovery is pulled in only by the broad internal/api/ Extension Points prefix and this change does not move that boundary: it constrains which agent binary /download/pulse-agent serves, touching no storage provider, backup target, recovery repository or protected-workload evidence. The agent-lifecycle, deployment-installability and api-contracts deltas staged here cover every boundary the change actually moves.
2026-08-06 15:27:39 +01:00

649 lines
25 KiB
Go

package api
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/updates"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupUnifiedAgentRouter(t *testing.T) (*Router, string) {
tempDir := t.TempDir()
// Create required directories
err := os.MkdirAll(filepath.Join(tempDir, "scripts"), 0755)
require.NoError(t, err)
err = os.MkdirAll(filepath.Join(tempDir, "bin"), 0755)
require.NoError(t, err)
router := &Router{
projectRoot: tempDir,
checksumCache: make(map[string]checksumCacheEntry),
}
return router, tempDir
}
// expectedAgentVersionForTest mirrors what the download handler will look for,
// so fixtures that stand in for a healthy agent carry the version a real build
// would. Without it every "valid" fixture reads as a stale build.
func expectedAgentVersionForTest() string {
versionInfo, err := updates.GetCurrentVersion()
if err != nil || versionInfo == nil {
return ""
}
return expectedAgentBinaryVersion(versionInfo.Version)
}
func validTestUnifiedAgentBinary(suffix string) []byte {
return []byte("ELF test binary " + canonicalUnifiedAgentReportPath + " " +
expectedAgentVersionForTest() + " " + suffix)
}
func staleTestUnifiedAgentBinary(suffix string) []byte {
return []byte("ELF stale binary " + legacyUnifiedAgentReportPath + " " + suffix)
}
func encodedTestSSHSignature(payload string) string {
return encodeSSHSignatureForHeader([]byte(payload))
}
func TestDownloadInstallScript_Local(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
// Create dummy script
scriptContent := "#!/bin/bash\necho 'installing'"
scriptPath := filepath.Join(tempDir, "scripts", "install.sh")
err := os.WriteFile(scriptPath, []byte(scriptContent), 0644)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedInstallScript(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, scriptContent, w.Body.String())
assert.Equal(t, "text/x-shellscript", w.Header().Get("Content-Type"))
}
// TestDownloadInstallScript_PublishedReleaseUnsignedLocalServesAgentInstaller
// pins the issue #1470 closure: when the local agent installer is present on a
// published-release server but its .sig/.sshsig sidecars are not, the endpoint
// must serve the LOCAL agent installer, never proxy the GitHub install.sh asset
// (which is the SERVER installer). This is the state every pre-fix LXC/systemd
// install is in until it redeploys the sidecars.
func TestDownloadInstallScript_PublishedReleaseUnsignedLocalServesAgentInstaller(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-rc.6"
// A client that fails any outbound call, so a regression that re-introduces
// the GitHub proxy fallback surfaces as a test failure rather than silently
// serving the server installer.
router.installScriptClient = newTestInstallScriptClient(t, http.MethodGet, "", 0, "", errors.New("proxy must not be used"))
scriptContent := "#!/usr/bin/env bash\n# Pulse Unified Agent Installer\necho 'agent'"
scriptPath := filepath.Join(tempDir, "scripts", "install.sh")
require.NoError(t, os.WriteFile(scriptPath, []byte(scriptContent), 0644))
// Note: no .sig / .sshsig sidecars written.
req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedInstallScript(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, scriptContent, w.Body.String())
assert.NotEqual(t, "github-fallback", w.Header().Get("X-Served-From"))
}
func TestDownloadInstallScriptPS_Local(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
// Create dummy script
scriptContent := "Write-Host 'installing'"
scriptPath := filepath.Join(tempDir, "scripts", "install.ps1")
err := os.WriteFile(scriptPath, []byte(scriptContent), 0644)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/install.ps1", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedInstallScriptPS(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, scriptContent, w.Body.String())
assert.Equal(t, "text/plain", w.Header().Get("Content-Type"))
}
func TestDownloadUnifiedAgent_Local_Generic(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
// Create dummy binary in project root / bin
binContent := validTestUnifiedAgentBinary("generic")
binPath := filepath.Join(tempDir, "bin", "pulse-agent")
err := os.WriteFile(binPath, binContent, 0755)
require.NoError(t, err)
// Since cachedSHA256 might not be initialized or working without real file usage pattern,
// checking if our manual Router setup handles it.
// cachedSHA256 needs 'checksumCache' map initialized which we did in setupUnifiedAgentRouter.
req := httptest.NewRequest(http.MethodGet, "/api/install/agent", nil)
w := httptest.NewRecorder()
// Handle calls r.cachedSHA256 which reads the file
router.handleDownloadUnifiedAgent(w, req)
// We expect success if cachedSHA256 works
if w.Code == http.StatusInternalServerError {
// If cachedSHA256 fails (maybe because it's not exported or implemented elsewhere
// and depends on something I missed), we will fail here.
// cachedSHA256 is called in unified_agent.go but defined presumably in router.go or router_utils.go (unexported).
// I initialized checksumCache so it should work.
t.Logf("Handler returned 500: %s", w.Body.String())
}
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
assert.Equal(t, string(binContent), w.Body.String())
// Verify Checksum Header
hash := sha256.Sum256(binContent)
expectedChecksum := hex.EncodeToString(hash[:])
assert.Equal(t, expectedChecksum, w.Header().Get("X-Checksum-Sha256"))
}
func TestDownloadUnifiedAgent_Local_SpecificArch(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
// Create dummy binary for linux-amd64
binContent := validTestUnifiedAgentBinary("linux-amd64")
binPath := filepath.Join(tempDir, "bin", "pulse-agent-linux-amd64")
err := os.WriteFile(binPath, binContent, 0755)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, string(binContent), w.Body.String())
}
func TestDownloadUnifiedAgent_LocalReleaseBinaryIncludesSignatureHeader(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0"
binContent := validTestUnifiedAgentBinary("linux-amd64")
binPath := filepath.Join(tempDir, "bin", "pulse-agent-linux-amd64")
err := os.WriteFile(binPath, binContent, 0755)
require.NoError(t, err)
err = os.WriteFile(binPath+".sig", []byte("signed-local-agent"), 0644)
require.NoError(t, err)
err = os.WriteFile(binPath+".sshsig", []byte("signed-local-agent-ssh"), 0644)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "signed-local-agent", w.Header().Get(signatureHeaderName))
assert.Equal(t, encodedTestSSHSignature("signed-local-agent-ssh"), w.Header().Get(sshSignatureHeaderName))
}
func TestDownloadUnifiedAgent_SkipsStaleLocalBinaryAndProxies(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-rc.1"
stalePath := filepath.Join(tempDir, "bin", "pulse-agent-linux-amd64")
require.NoError(t, os.WriteFile(stalePath, staleTestUnifiedAgentBinary("linux-amd64"), 0755))
binaryContent := "fresh github binary"
expectedURL := "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.1/pulse-agent-linux-amd64"
router.installScriptClient = newTestInstallScriptClientSequence(t, []expectedHTTPExchange{
{Method: http.MethodGet, URL: expectedURL, Status: http.StatusOK, Body: binaryContent},
{Method: http.MethodGet, URL: expectedURL + ".sig", Status: http.StatusOK, Body: "signed-agent"},
{Method: http.MethodGet, URL: expectedURL + ".sshsig", Status: http.StatusOK, Body: "signed-agent-ssh"},
})
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, binaryContent, w.Body.String())
assert.Equal(t, "github-proxy", w.Header().Get("X-Served-From"))
assert.Equal(t, "signed-agent", w.Header().Get(signatureHeaderName))
assert.Equal(t, encodedTestSSHSignature("signed-agent-ssh"), w.Header().Get(sshSignatureHeaderName))
}
func TestDownloadUnifiedAgent_DevModeRejectsStaleLocalBinary(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
router.serverVersion = "dev"
stalePath := filepath.Join(tempDir, "bin", "pulse-agent-linux-amd64")
require.NoError(t, os.WriteFile(stalePath, staleTestUnifiedAgentBinary("linux-amd64"), 0755))
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusNotFound, w.Code)
assert.Contains(t, w.Body.String(), "stale or incompatible")
assert.Contains(t, w.Body.String(), legacyUnifiedAgentReportPath)
assert.Contains(t, w.Body.String(), "go build -o bin/pulse-agent-linux-amd64")
}
func TestDownloadUnifiedAgent_ProxyFromGitHub(t *testing.T) {
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-rc.1"
// Ensure NO local files exist (temp dir is empty of binaries)
// Set up a mock HTTP client to simulate GitHub response
binaryContent := "fake binary content for proxy test"
expectedURL := "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.1/pulse-agent-linux-amd64"
router.installScriptClient = newTestInstallScriptClientSequence(t, []expectedHTTPExchange{
{Method: http.MethodGet, URL: expectedURL, Status: http.StatusOK, Body: binaryContent},
{Method: http.MethodGet, URL: expectedURL + ".sig", Status: http.StatusOK, Body: "signed-agent"},
{Method: http.MethodGet, URL: expectedURL + ".sshsig", Status: http.StatusOK, Body: "signed-agent-ssh"},
})
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
// Should proxy the binary with checksum header instead of redirecting
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, binaryContent, w.Body.String())
assert.Equal(t, "github-proxy", w.Header().Get("X-Served-From"))
// Verify checksum header is present and correct
hash := sha256.Sum256([]byte(binaryContent))
expectedChecksum := hex.EncodeToString(hash[:])
assert.Equal(t, expectedChecksum, w.Header().Get("X-Checksum-Sha256"))
assert.Equal(t, "signed-agent", w.Header().Get(signatureHeaderName))
assert.Equal(t, encodedTestSSHSignature("signed-agent-ssh"), w.Header().Get(sshSignatureHeaderName))
}
func TestDownloadUnifiedAgent_ProxyFromGitHub_UsesConfiguredRepo(t *testing.T) {
t.Setenv("PULSE_GITHUB_REPO", "example/pulse-fork")
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-rc.1"
binaryContent := "fake binary content for proxy test"
expectedURL := "https://github.com/example/pulse-fork/releases/download/v6.0.0-rc.1/pulse-agent-linux-amd64"
router.installScriptClient = newTestInstallScriptClientSequence(t, []expectedHTTPExchange{
{Method: http.MethodGet, URL: expectedURL, Status: http.StatusOK, Body: binaryContent},
{Method: http.MethodGet, URL: expectedURL + ".sig", Status: http.StatusOK, Body: "signed-agent"},
{Method: http.MethodGet, URL: expectedURL + ".sshsig", Status: http.StatusOK, Body: "signed-agent-ssh"},
})
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, binaryContent, w.Body.String())
}
func TestDownloadUnifiedAgent_ProxyFromGitHub_Windows(t *testing.T) {
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-rc.1"
binaryContent := "MZ fake windows binary"
expectedURL := "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.1/pulse-agent-windows-amd64.exe"
router.installScriptClient = newTestInstallScriptClientSequence(t, []expectedHTTPExchange{
{Method: http.MethodGet, URL: expectedURL, Status: http.StatusOK, Body: binaryContent},
{Method: http.MethodGet, URL: expectedURL + ".sig", Status: http.StatusOK, Body: "signed-agent"},
{Method: http.MethodGet, URL: expectedURL + ".sshsig", Status: http.StatusOK, Body: "signed-agent-ssh"},
})
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=windows-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, binaryContent, w.Body.String())
assert.NotEmpty(t, w.Header().Get("X-Checksum-Sha256"))
assert.Equal(t, "signed-agent", w.Header().Get(signatureHeaderName))
assert.Equal(t, encodedTestSSHSignature("signed-agent-ssh"), w.Header().Get(sshSignatureHeaderName))
}
func TestDownloadUnifiedAgent_ProxyFromGitHub_ArchiveFallback_Darwin(t *testing.T) {
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v9.9.9"
binaryContent := []byte("darwin arm64 binary payload")
archivePayload := buildTestTarGz(t, "pulse-agent-darwin-arm64", binaryContent)
binaryURL := "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/pulse-agent-darwin-arm64"
signatureURL := binaryURL + ".sig"
sshSignatureURL := binaryURL + ".sshsig"
archiveURL := "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/pulse-agent-v9.9.9-darwin-arm64.tar.gz"
router.installScriptClient = &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
switch req.URL.String() {
case binaryURL:
return &http.Response{
StatusCode: http.StatusNotFound,
Status: "404 Not Found",
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("not found")),
}, nil
case archiveURL:
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Header: make(http.Header),
Body: io.NopCloser(bytes.NewReader(archivePayload)),
}, nil
case signatureURL:
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("signed-agent")),
}, nil
case sshSignatureURL:
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("signed-agent-ssh")),
}, nil
default:
t.Fatalf("unexpected URL: %s", req.URL.String())
return nil, nil
}
}),
}
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=darwin-arm64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "github-proxy-archive", w.Header().Get("X-Served-From"))
assert.Equal(t, string(binaryContent), w.Body.String())
hash := sha256.Sum256(binaryContent)
expectedChecksum := hex.EncodeToString(hash[:])
assert.Equal(t, expectedChecksum, w.Header().Get("X-Checksum-Sha256"))
assert.Equal(t, "signed-agent", w.Header().Get(signatureHeaderName))
assert.Equal(t, encodedTestSSHSignature("signed-agent-ssh"), w.Header().Get(sshSignatureHeaderName))
}
func TestDownloadUnifiedAgent_ProxyFromGitHub_ArchiveFallback_UsesConfiguredRepo(t *testing.T) {
t.Setenv("PULSE_GITHUB_REPO", "example/pulse-fork")
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v9.9.9"
binaryContent := []byte("darwin arm64 binary payload")
archivePayload := buildTestTarGz(t, "pulse-agent-darwin-arm64", binaryContent)
binaryURL := "https://github.com/example/pulse-fork/releases/download/v9.9.9/pulse-agent-darwin-arm64"
signatureURL := binaryURL + ".sig"
sshSignatureURL := binaryURL + ".sshsig"
archiveURL := "https://github.com/example/pulse-fork/releases/download/v9.9.9/pulse-agent-v9.9.9-darwin-arm64.tar.gz"
router.installScriptClient = &http.Client{
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
switch req.URL.String() {
case binaryURL:
return &http.Response{
StatusCode: http.StatusNotFound,
Status: "404 Not Found",
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("not found")),
}, nil
case archiveURL:
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Header: make(http.Header),
Body: io.NopCloser(bytes.NewReader(archivePayload)),
}, nil
case signatureURL:
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("signed-agent")),
}, nil
case sshSignatureURL:
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("signed-agent-ssh")),
}, nil
default:
t.Fatalf("unexpected URL: %s", req.URL.String())
return nil, nil
}
}),
}
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=darwin-arm64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "github-proxy-archive", w.Header().Get("X-Served-From"))
assert.Equal(t, string(binaryContent), w.Body.String())
assert.Equal(t, "signed-agent", w.Header().Get(signatureHeaderName))
assert.Equal(t, encodedTestSSHSignature("signed-agent-ssh"), w.Header().Get(sshSignatureHeaderName))
}
func TestDownloadUnifiedAgent_ProxyFromGitHub_NotFound(t *testing.T) {
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-rc.1"
router.installScriptClient = newTestInstallScriptClientSequence(t, []expectedHTTPExchange{
{Method: http.MethodGet, URL: "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.1/pulse-agent-linux-amd64", Status: http.StatusNotFound, Body: ""},
{Method: http.MethodGet, URL: "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.1/pulse-agent-v6.0.0-rc.1-linux-amd64.tar.gz", Status: http.StatusNotFound, Body: ""},
})
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestDownloadUnifiedAgent_ProxyFromGitHub_Error(t *testing.T) {
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-rc.1"
// GitHub is unreachable
router.installScriptClient = newTestInstallScriptClient(t, http.MethodGet, "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.1/pulse-agent-linux-amd64", 0, "", errors.New("connection refused"))
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusServiceUnavailable, w.Code)
}
func TestDownloadUnifiedAgent_DevPrereleaseRejectsGitHubFallback(t *testing.T) {
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0-dev"
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusNotFound, w.Code)
assert.Contains(t, w.Body.String(), "dev mode")
assert.Contains(t, w.Body.String(), "go build -o bin/pulse-agent-linux-amd64")
}
func TestDownloadUnifiedAgent_DevModeReportsRequestedLocalBuildCommand(t *testing.T) {
router, _ := setupUnifiedAgentRouter(t)
router.serverVersion = "dev"
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=darwin-arm64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
require.Equal(t, http.StatusNotFound, w.Code)
assert.Contains(t, w.Body.String(), "Agent binary not found for \"darwin-arm64\" in dev mode.")
assert.Contains(t, w.Body.String(), "CGO_ENABLED=0 GOOS=darwin GOARCH=arm64")
assert.Contains(t, w.Body.String(), "go build -o bin/pulse-agent-darwin-arm64 ./cmd/pulse-agent")
assert.NotContains(t, w.Body.String(), "bin/pulse-agent-linux-amd64")
}
func TestUnifiedAgentLocalBuildCommandHandlesArmVariants(t *testing.T) {
assert.Equal(
t,
"CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o bin/pulse-agent-linux-armv7 ./cmd/pulse-agent",
unifiedAgentLocalBuildCommand("linux-armv7"),
)
assert.Equal(
t,
"CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -o bin/pulse-agent-linux-armv6 ./cmd/pulse-agent",
unifiedAgentLocalBuildCommand("linux-armv6"),
)
}
func TestNormalizeUnifiedAgentArch(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"amd64", "linux-amd64"},
{"x86_64", "linux-amd64"},
{"linux-amd64", "linux-amd64"},
{"arm64", "linux-arm64"},
{"aarch64", "linux-arm64"},
{"windows-amd64", "windows-amd64"},
{"darwin-arm64", "darwin-arm64"},
{"unknown", ""},
{"", ""},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
assert.Equal(t, tt.expected, normalizeUnifiedAgentArch(tt.input))
})
}
}
func buildTestTarGz(t *testing.T, name string, payload []byte) []byte {
t.Helper()
var buf bytes.Buffer
gzw := gzip.NewWriter(&buf)
tw := tar.NewWriter(gzw)
header := &tar.Header{
Name: name,
Mode: 0o755,
Size: int64(len(payload)),
}
require.NoError(t, tw.WriteHeader(header))
_, err := tw.Write(payload)
require.NoError(t, err)
require.NoError(t, tw.Close())
require.NoError(t, gzw.Close())
return buf.Bytes()
}
// writeAgentBinaryFixture builds a stand-in for a compiled agent. The real
// validator is a byte scan, so a file carrying the same needles exercises it
// faithfully without shipping a multi-megabyte binary into the test tree.
func writeAgentBinaryFixture(t *testing.T, reportPath string, versionString string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "pulse-agent")
body := "\x7fELF padding " + reportPath + " more padding " + versionString + " trailing\n"
if err := os.WriteFile(path, []byte(body), 0o755); err != nil {
t.Fatalf("write agent fixture: %v", err)
}
return path
}
func TestExpectedAgentBinaryVersionDropsBuildMetadata(t *testing.T) {
cases := map[string]string{
// A dev server carries git metadata the agent never has.
"6.2.0-rc.8+git.44.gdd72bd149.dirty": "v6.2.0-rc.8",
"6.2.0-rc.8": "v6.2.0-rc.8",
"v6.1.2": "v6.1.2",
// Unknown versions disable the check rather than rejecting everything.
// "dev-pro" is the enterprise build's compiled-in placeholder: it is
// why the caller resolves the version through updates.GetCurrentVersion
// instead of the compiled-in serverVersion, because a dev server is
// exactly where agent binaries go stale and this value would silently
// switch the freshness check off there.
"dev": "",
"dev-pro": "",
"": "",
"not-a-version": "",
}
for input, want := range cases {
if got := expectedAgentBinaryVersion(input); got != want {
t.Errorf("expectedAgentBinaryVersion(%q) = %q, want %q", input, got, want)
}
}
}
func TestValidateUnifiedAgentBinaryRejectsStaleBuild(t *testing.T) {
// The shape that broke a live host: a binary on the canonical report
// contract, so every existing check passes, but built weeks earlier.
stale := writeAgentBinaryFixture(t, canonicalUnifiedAgentReportPath, "v6.0.5-54-gc862fb0ca0")
if err := validateUnifiedAgentBinary(stale, "v6.2.0-rc.8"); err == nil {
t.Fatal("a binary that does not carry this server's agent version must be refused")
} else if !strings.Contains(err.Error(), "stale build") {
t.Fatalf("error should name staleness, got %v", err)
}
// Premise check: without the version guard this same binary is accepted,
// which is exactly how it reached a host in the first place.
if err := validateUnifiedAgentBinary(stale, ""); err != nil {
t.Fatalf("premise check failed: the stale binary is supposed to pass every other check, got %v", err)
}
}
func TestValidateUnifiedAgentBinaryAcceptsMatchingBuild(t *testing.T) {
current := writeAgentBinaryFixture(t, canonicalUnifiedAgentReportPath, "v6.2.0-rc.8")
if err := validateUnifiedAgentBinary(current, "v6.2.0-rc.8"); err != nil {
t.Fatalf("a binary carrying this server's agent version must be served: %v", err)
}
}
func TestValidateUnifiedAgentBinaryStillRejectsLegacyContract(t *testing.T) {
// The version guard must not displace the endpoint guard: a correctly
// versioned binary on the deprecated report path is still refused.
legacy := writeAgentBinaryFixture(t, legacyUnifiedAgentReportPath, "v6.2.0-rc.8")
if err := validateUnifiedAgentBinary(legacy, "v6.2.0-rc.8"); err == nil {
t.Fatal("a binary on the deprecated report endpoint must still be refused")
}
}