mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
a3e36d787f
Root fix for the recurring class behind issue #1470. The served /install.sh and
/install.ps1 endpoints existed to hand out the unified AGENT installer, but their
GitHub fallback fetched the top-level install.sh release asset, which since
49412357a is the SERVER installer. Prior commits made that fallback unreachable
in normal deployments (deploy the sidecars; serve the local script even when
unsigned), but the endpoint was still structurally capable of serving the wrong
script in the no-local-bundle case.
The agent installer is a per-build artifact bundled into every release tarball and
Docker image, not a release asset, so the endpoint has no business proxying a
release asset at all. Remove proxyInstallScriptFromGitHub and its
installScriptReleaseAssetURL wrapper. handleDownloadInstallScriptCommon now serves
the locally bundled agent installer (signed when sidecars are present, unsigned
otherwise) or fails closed with 503 when no bundled script exists. Serving the
SERVER installer at this endpoint is now structurally impossible, not merely
unreachable.
The shared version-pinning (releaseAssetTag/releaseAssetURL) and installScriptClient
remain for the agent-BINARY download proxy, which legitimately fetches published
release assets; its version-pinning stays covered by the agentBinaryReleaseAssetURL
contract tests.
Replace the obsolete install-script proxy tests with fail-closed assertions
(including a guard that the endpoint makes no outbound call), drop the four
installScriptReleaseAssetURL contract tests, and revise the four subsystem
contracts that pinned the install-script fallback transport (api-contracts items
8 and 27, agent-lifecycle item 14, storage-recovery item 14, plus the
deployment-installability note) to state that install scripts are served local or
fail closed with no GitHub fallback.
206 lines
6.0 KiB
Go
206 lines
6.0 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
return f(req)
|
|
}
|
|
|
|
func newTestInstallScriptClient(t *testing.T, expectedMethod, expectedURL string, status int, body string, err error) *http.Client {
|
|
t.Helper()
|
|
|
|
return &http.Client{
|
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
if expectedMethod != "" && req.Method != expectedMethod {
|
|
t.Fatalf("unexpected method: %s", req.Method)
|
|
}
|
|
if expectedURL != "" && req.URL.String() != expectedURL {
|
|
t.Fatalf("unexpected URL: %s", req.URL.String())
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &http.Response{
|
|
StatusCode: status,
|
|
Status: fmt.Sprintf("%d %s", status, http.StatusText(status)),
|
|
Header: make(http.Header),
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
}, nil
|
|
}),
|
|
}
|
|
}
|
|
|
|
type expectedHTTPExchange struct {
|
|
Method string
|
|
URL string
|
|
Status int
|
|
Body string
|
|
Err error
|
|
}
|
|
|
|
func newTestInstallScriptClientSequence(t *testing.T, exchanges []expectedHTTPExchange) *http.Client {
|
|
t.Helper()
|
|
|
|
var index int
|
|
return &http.Client{
|
|
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
if index >= len(exchanges) {
|
|
t.Fatalf("unexpected extra request: %s %s", req.Method, req.URL.String())
|
|
}
|
|
exchange := exchanges[index]
|
|
index++
|
|
if exchange.Method != "" && req.Method != exchange.Method {
|
|
t.Fatalf("unexpected method at request %d: %s", index, req.Method)
|
|
}
|
|
if exchange.URL != "" && req.URL.String() != exchange.URL {
|
|
t.Fatalf("unexpected URL at request %d: %s", index, req.URL.String())
|
|
}
|
|
if exchange.Err != nil {
|
|
return nil, exchange.Err
|
|
}
|
|
return &http.Response{
|
|
StatusCode: exchange.Status,
|
|
Status: fmt.Sprintf("%d %s", exchange.Status, http.StatusText(exchange.Status)),
|
|
Header: make(http.Header),
|
|
Body: io.NopCloser(strings.NewReader(exchange.Body)),
|
|
}, nil
|
|
}),
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedInstallScript_MethodNotAllowed(t *testing.T) {
|
|
router := &Router{}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/install.sh", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedInstallScript(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedInstallScriptPS_MethodNotAllowed(t *testing.T) {
|
|
router := &Router{}
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/install.ps1", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedInstallScriptPS(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedInstallScript_NoLocalScriptFailsClosed(t *testing.T) {
|
|
router, _ := setupUnifiedAgentRouter(t)
|
|
router.serverVersion = "v6.0.0-rc.6"
|
|
// No agent installer is bundled. The endpoint must fail closed and must never
|
|
// proxy the GitHub install.sh release asset (the SERVER installer). Any outbound
|
|
// HTTP call from this endpoint is a regression of the issue #1470 root fix.
|
|
router.installScriptClient = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
t.Errorf("install-script endpoint must not make outbound calls; got %s %s", req.Method, req.URL)
|
|
return nil, fmt.Errorf("unexpected outbound call")
|
|
})}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedInstallScript(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("expected 503, got %d", w.Code)
|
|
}
|
|
if got := w.Header().Get("X-Served-From"); got != "" {
|
|
t.Fatalf("endpoint must not proxy; X-Served-From = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedInstallScriptPS_NoLocalScriptFailsClosed(t *testing.T) {
|
|
router, _ := setupUnifiedAgentRouter(t)
|
|
router.serverVersion = "v6.0.0"
|
|
router.installScriptClient = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
t.Errorf("install-script endpoint must not make outbound calls; got %s %s", req.Method, req.URL)
|
|
return nil, fmt.Errorf("unexpected outbound call")
|
|
})}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/install.ps1", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedInstallScriptPS(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("expected 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedInstallScript_NoLocalScriptFailsClosedForDevBuild(t *testing.T) {
|
|
router, _ := setupUnifiedAgentRouter(t)
|
|
router.serverVersion = "dev"
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedInstallScript(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("expected 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedInstallScript_NoLocalScriptFailsClosedHEAD(t *testing.T) {
|
|
router, _ := setupUnifiedAgentRouter(t)
|
|
router.serverVersion = "v6.0.0-rc.6"
|
|
|
|
req := httptest.NewRequest(http.MethodHead, "/install.sh", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedInstallScript(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("expected 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedAgent_MethodNotAllowed(t *testing.T) {
|
|
router := &Router{}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/download/pulse-agent", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedAgent(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadUnifiedAgent_NoArchNotFound(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
t.Setenv("PULSE_BIN_DIR", tempDir)
|
|
|
|
router := &Router{projectRoot: tempDir}
|
|
req := httptest.NewRequest(http.MethodGet, "/download/pulse-agent", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.handleDownloadUnifiedAgent(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d", w.Code)
|
|
}
|
|
if !strings.Contains(w.Body.String(), "Specify ?arch=linux-amd64") {
|
|
t.Fatalf("expected guidance message")
|
|
}
|
|
}
|