mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
87ee3cd449
Project freshly minted installer command intent onto the stable host before returning remote config, and consume that intent once so later admin policy changes remain authoritative.
415 lines
17 KiB
Go
415 lines
17 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/api/agenttokens"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNormalizeProxmoxInstallType(t *testing.T) {
|
|
installType, err := normalizeProxmoxInstallType(" PBS ")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "pbs", installType)
|
|
|
|
_, err = normalizeProxmoxInstallType("vmware")
|
|
require.Error(t, err)
|
|
require.Equal(t, "Type must be 'pve' or 'pbs'", err.Error())
|
|
}
|
|
|
|
func TestAgentInstallCommandCarriesCommandPolicyIntent(t *testing.T) {
|
|
cfg := &config.Config{DataPath: t.TempDir(), AuthUser: "admin", AuthPass: "hashed-password"}
|
|
handler := newTestConfigHandlers(t, cfg)
|
|
req := httptest.NewRequest(
|
|
http.MethodPost,
|
|
"/api/agent-install-command",
|
|
bytes.NewBufferString(`{"type":"pve","enableCommands":true}`),
|
|
)
|
|
req.Host = "pulse.example:7655"
|
|
rec := httptest.NewRecorder()
|
|
handler.HandleAgentInstallCommand(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("install command status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
var response AgentInstallCommandResponse
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("decode install command response: %v", err)
|
|
}
|
|
if len(cfg.APITokens) != 1 || response.Token == "" {
|
|
t.Fatalf("install command did not mint one token: response=%+v tokens=%d", response, len(cfg.APITokens))
|
|
}
|
|
if got := cfg.APITokens[0].Metadata[agenttokens.CommandPolicyIntentMetadataKey]; got != agenttokens.CommandPolicyIntentEnabled {
|
|
t.Fatalf("command policy intent = %q, want enabled", got)
|
|
}
|
|
if !strings.Contains(response.Command, "--enable-commands") {
|
|
t.Fatalf("commands-enabled install omitted flag: %s", response.Command)
|
|
}
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand(t *testing.T) {
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: "https://pulse.example.com/",
|
|
Token: "token-123",
|
|
InstallType: "pbs",
|
|
IncludeInstallType: true,
|
|
})
|
|
require.Contains(t, command, posixShellQuote("https://pulse.example.com/install.sh"))
|
|
require.Contains(t, command, "printf %s "+posixShellQuote("token-123")+` > "$token_file"`)
|
|
require.Contains(t, command, `--token-file "$token_file"`)
|
|
require.Contains(t, command, `rm -f "$token_file"`)
|
|
require.Contains(t, command, "--proxmox-type "+posixShellQuote("pbs"))
|
|
require.NotContains(t, command, "--enable-commands")
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand_IncludesInsecureForPlainHTTP(t *testing.T) {
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: "http://pulse.example.com:7655/",
|
|
Token: "token-123",
|
|
InstallType: "pve",
|
|
IncludeInstallType: true,
|
|
})
|
|
|
|
require.Contains(t, command, posixShellQuote("http://pulse.example.com:7655/install.sh"))
|
|
require.Contains(t, command, "--url "+posixShellQuote("http://pulse.example.com:7655"))
|
|
require.Contains(t, command, "--insecure")
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand_IncludesExplicitInsecureForSelfSignedHTTPS(t *testing.T) {
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: "https://pulse.example.com:7655/",
|
|
Token: "token-123",
|
|
InstallType: "pve",
|
|
IncludeInstallType: true,
|
|
Insecure: true,
|
|
})
|
|
|
|
require.Contains(t, command, "curl -kfsSL "+posixShellQuote("https://pulse.example.com:7655/install.sh"))
|
|
require.Contains(t, command, "--url "+posixShellQuote("https://pulse.example.com:7655"))
|
|
require.Contains(t, command, "--insecure")
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand_UsesPrivilegeEscalationWrapper(t *testing.T) {
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: "https://pulse.example.com/",
|
|
Token: "token-123",
|
|
InstallType: "pve",
|
|
IncludeInstallType: true,
|
|
})
|
|
|
|
require.Contains(t, command, `| { if [ "$(id -u)" -eq 0 ]; then bash -s --`)
|
|
require.Contains(t, command, `elif command -v sudo >/dev/null 2>&1; then sudo bash -s --`)
|
|
require.Contains(t, command, `else echo "Root privileges required. Run as root (su -) and retry." >&2; exit 1; fi; }`)
|
|
require.NotContains(t, command, "| bash -s -- --url")
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand_OmitsTokenWhenNotProvided(t *testing.T) {
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: "https://pulse.example.com/",
|
|
Token: "",
|
|
InstallType: "pbs",
|
|
IncludeInstallType: true,
|
|
})
|
|
|
|
require.Contains(t, command, "--url "+posixShellQuote("https://pulse.example.com"))
|
|
require.Contains(t, command, "--proxmox-type "+posixShellQuote("pbs"))
|
|
require.NotContains(t, command, "--token")
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand_ShellEscapesArguments(t *testing.T) {
|
|
baseURL := "https://pulse.example.com' && touch /tmp/pwned #"
|
|
token := "tok'en"
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: baseURL,
|
|
Token: token,
|
|
InstallType: "pve",
|
|
IncludeInstallType: true,
|
|
})
|
|
|
|
require.Contains(t, command, posixShellQuote(baseURL+"/install.sh"))
|
|
require.Contains(t, command, "--url "+posixShellQuote(baseURL))
|
|
require.Contains(t, command, "printf %s "+posixShellQuote(token)+` > "$token_file"`)
|
|
require.Contains(t, command, `--token-file "$token_file"`)
|
|
require.Contains(t, command, "--proxmox-type "+posixShellQuote("pve"))
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand_NormalizesTrailingSlashes(t *testing.T) {
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: "https://pulse.example.com/base///",
|
|
Token: "token-123",
|
|
InstallType: "pve",
|
|
IncludeInstallType: true,
|
|
})
|
|
|
|
require.Contains(t, command, posixShellQuote("https://pulse.example.com/base/install.sh"))
|
|
require.Contains(t, command, "--url "+posixShellQuote("https://pulse.example.com/base"))
|
|
require.Contains(t, command, `--token-file "$token_file"`)
|
|
require.NotContains(t, command, "//install.sh")
|
|
require.NotContains(t, command, "--url "+posixShellQuote("https://pulse.example.com/base/"))
|
|
}
|
|
|
|
func TestBuildProxmoxAgentInstallCommand_IncludesCommandsWhenRequested(t *testing.T) {
|
|
command := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
|
|
BaseURL: "https://pulse.example.com/",
|
|
Token: "token-123",
|
|
InstallType: "pve",
|
|
IncludeInstallType: true,
|
|
EnableCommands: true,
|
|
})
|
|
|
|
require.Contains(t, command, "--enable-proxmox")
|
|
require.Contains(t, command, "--proxmox-type "+posixShellQuote("pve"))
|
|
require.Contains(t, command, "--enable-commands")
|
|
require.Contains(t, command, `| { if [ "$(id -u)" -eq 0 ]; then bash -s --`)
|
|
require.Contains(t, command, `rm -f "$token_file"`)
|
|
}
|
|
|
|
func TestBuildContainerRuntimeAgentInstallCommand_UsesLifecycleTransport(t *testing.T) {
|
|
command := buildContainerRuntimeAgentInstallCommand("https://pulse.example.com/base", "token-123", true)
|
|
|
|
require.Contains(t, command, posixShellQuote("https://pulse.example.com/base/install.sh"))
|
|
require.Contains(t, command, "--url "+posixShellQuote("https://pulse.example.com/base"))
|
|
require.Contains(t, command, "--token "+posixShellQuote("token-123"))
|
|
require.Contains(t, command, "--enable-docker")
|
|
require.Contains(t, command, "--enable-host")
|
|
require.NotContains(t, command, "--enable-host=false")
|
|
require.Contains(t, command, "--interval 30s")
|
|
require.Contains(t, command, `| { if [ "$(id -u)" -eq 0 ]; then bash -s --`)
|
|
require.Contains(t, command, `elif command -v sudo >/dev/null 2>&1; then sudo bash -s --`)
|
|
require.NotContains(t, command, "| sudo bash -s -- --url")
|
|
}
|
|
|
|
func TestBuildContainerRuntimeAgentInstallCommand_OmitsTokenAndAddsInsecureForHTTP(t *testing.T) {
|
|
command := buildContainerRuntimeAgentInstallCommand("http://pulse.example.com:7655/", "", true)
|
|
|
|
require.Contains(t, command, posixShellQuote("http://pulse.example.com:7655/install.sh"))
|
|
require.Contains(t, command, "--url "+posixShellQuote("http://pulse.example.com:7655"))
|
|
require.NotContains(t, command, "--token")
|
|
require.Contains(t, command, "--insecure")
|
|
}
|
|
|
|
func TestBuildContainerRuntimeAgentInstallCommand_PreservesWorkloadOnlyMode(t *testing.T) {
|
|
command := buildContainerRuntimeAgentInstallCommand("https://pulse.example.com", "token-123", false)
|
|
|
|
require.Contains(t, command, "--enable-docker")
|
|
require.Contains(t, command, "--enable-host=false")
|
|
}
|
|
|
|
func TestContainerRuntimeAgentScopesFollowHostMode(t *testing.T) {
|
|
dualMode := containerRuntimeAgentScopes(true)
|
|
require.ElementsMatch(t, []string{
|
|
config.ScopeDockerReport,
|
|
config.ScopeAgentReport,
|
|
config.ScopeAgentConfigRead,
|
|
config.ScopeAgentManage,
|
|
}, dualMode)
|
|
|
|
require.Equal(t, []string{config.ScopeDockerReport}, containerRuntimeAgentScopes(false))
|
|
}
|
|
|
|
func TestBuildSetupScriptCommand_UsesFailFastQuotedTransport(t *testing.T) {
|
|
command := buildSetupScriptCommand("https://pulse.example.com/api/setup-script?type=pve&host=pve1.local", "token-123")
|
|
|
|
require.Contains(t, command, "curl -fsSL "+posixShellQuote("https://pulse.example.com/api/setup-script?type=pve&host=pve1.local")+" | ")
|
|
require.Contains(t, command, `if [ "$(id -u)" -eq 0 ]; then PULSE_SETUP_TOKEN=`+posixShellQuote("token-123")+` bash`)
|
|
require.Contains(t, command, `elif command -v sudo >/dev/null 2>&1; then sudo env PULSE_SETUP_TOKEN=`+posixShellQuote("token-123")+` bash`)
|
|
require.Contains(t, command, `else echo "Root privileges required. Run as root (su -) and retry." >&2; exit 1; fi; }`)
|
|
}
|
|
|
|
func TestBuildSetupScriptCommand_OmitsTokenWhenNotProvided(t *testing.T) {
|
|
command := buildSetupScriptCommand("https://pulse.example.com/api/setup-script?type=pbs", "")
|
|
|
|
require.Contains(t, command, "curl -fsSL "+posixShellQuote("https://pulse.example.com/api/setup-script?type=pbs")+" | ")
|
|
require.Contains(t, command, `if [ "$(id -u)" -eq 0 ]; then bash`)
|
|
require.Contains(t, command, `elif command -v sudo >/dev/null 2>&1; then sudo bash`)
|
|
require.NotContains(t, command, "PULSE_SETUP_TOKEN=")
|
|
}
|
|
|
|
func TestBuildSetupScriptURL_PreservesEncodedHostAndBackupPerms(t *testing.T) {
|
|
scriptURL := buildSetupScriptURL(
|
|
"https://pulse.example.com:7656/",
|
|
"pve",
|
|
"https://[2001:db8::1]:8006",
|
|
"https://pulse.example.com:7656",
|
|
true,
|
|
)
|
|
|
|
require.Equal(
|
|
t,
|
|
"https://pulse.example.com:7656/api/setup-script?backup_perms=true&host=https%3A%2F%2F%5B2001%3Adb8%3A%3A1%5D%3A8006&pulse_url=https%3A%2F%2Fpulse.example.com%3A7656&type=pve",
|
|
scriptURL,
|
|
)
|
|
}
|
|
|
|
func TestBuildSetupScriptInstallArtifact_UsesSharedBackendShape(t *testing.T) {
|
|
expiresAt := time.Unix(1900000000, 0).UTC().Unix()
|
|
|
|
artifact := buildSetupScriptInstallArtifact(
|
|
"https://pulse.example",
|
|
"pve",
|
|
"https://pve1.local:8006",
|
|
"https://pulse.example",
|
|
true,
|
|
"setup-token-123",
|
|
expiresAt,
|
|
)
|
|
|
|
require.Equal(t, "pve", artifact.Type)
|
|
require.Equal(t, "https://pve1.local:8006", artifact.Host)
|
|
require.Contains(t, artifact.URL, "/api/setup-script?")
|
|
require.Contains(t, artifact.DownloadURL, "setup_token=setup-token-123")
|
|
require.Equal(t, "pulse-setup-pve.sh", artifact.ScriptFileName)
|
|
require.Equal(t, artifact.Command, artifact.CommandWithEnv)
|
|
require.Contains(t, artifact.CommandWithEnv, "PULSE_SETUP_TOKEN='setup-token-123'")
|
|
require.NotContains(t, artifact.CommandWithoutEnv, "PULSE_SETUP_TOKEN=")
|
|
require.Equal(t, expiresAt, artifact.Expires)
|
|
require.Equal(t, "setup-token-123", artifact.SetupToken)
|
|
require.Equal(t, "set…123", artifact.TokenHint)
|
|
}
|
|
|
|
func TestRenderSetupScript_UsesSharedInstallArtifactShape(t *testing.T) {
|
|
artifact := buildSetupScriptInstallArtifact(
|
|
"https://pulse.example",
|
|
"pve",
|
|
"https://pve1.local:8006",
|
|
"https://pulse.example",
|
|
true,
|
|
"setup-token-123",
|
|
0,
|
|
)
|
|
|
|
script := renderSetupScript("pve", setupScriptRenderContext{
|
|
ServerName: deriveSetupScriptServerName("https://pve1.local:8006"),
|
|
PulseURL: "https://pulse.example",
|
|
ServerHost: "https://pve1.local:8006",
|
|
SetupToken: "setup-token-123",
|
|
TokenName: buildPulseMonitorTokenName("https://pulse.example"),
|
|
TokenMatchPrefix: buildPulseMonitorTokenName("https://pulse.example"),
|
|
StoragePerms: `
|
|
pveum aclmod /storage -user pulse-monitor@pve -role PVEDatastoreAdmin
|
|
if [ "$TOKEN_CREATED" = true ]; then
|
|
pveum aclmod /storage -token "$PULSE_TOKEN_ID" -role PVEDatastoreAdmin
|
|
fi`,
|
|
SensorsPublicKey: "ssh-ed25519 AAAATEST pulse@test",
|
|
Artifact: artifact,
|
|
})
|
|
|
|
require.Contains(t, script, `SETUP_SCRIPT_URL="https://pulse.example/api/setup-script?backup_perms=true&host=https%3A%2F%2Fpve1.local%3A8006&pulse_url=https%3A%2F%2Fpulse.example&type=pve"`)
|
|
require.Contains(t, script, `PULSE_BOOTSTRAP_COMMAND_WITH_ENV='curl -fsSL '"'"'https://pulse.example/api/setup-script?backup_perms=true&host=https%3A%2F%2Fpve1.local%3A8006&pulse_url=https%3A%2F%2Fpulse.example&type=pve'"'"' | `)
|
|
require.Contains(t, script, `PULSE_SETUP_TOKEN="${PULSE_SETUP_TOKEN:-setup-token-123}"`)
|
|
require.Contains(t, script, `pveum aclmod /storage -user pulse-monitor@pve -role PVEDatastoreAdmin`)
|
|
require.Contains(t, script, `pveum aclmod /storage -token "$PULSE_TOKEN_ID" -role PVEDatastoreAdmin`)
|
|
}
|
|
|
|
func TestInstallBaseURLRequiresInsecure(t *testing.T) {
|
|
require.True(t, installBaseURLRequiresInsecure("http://pulse.example.com:7655"))
|
|
require.True(t, installBaseURLRequiresInsecure(" HTTP://pulse.example.com:7655/ "))
|
|
require.False(t, installBaseURLRequiresInsecure("https://pulse.example.com"))
|
|
require.False(t, installBaseURLRequiresInsecure(""))
|
|
}
|
|
|
|
func TestResolveConfigAgentInstallBaseURL(t *testing.T) {
|
|
cfg := &config.Config{
|
|
AgentConnectURL: "https://agents.example.com/",
|
|
PublicURL: "https://public.example.com",
|
|
FrontendPort: 7655,
|
|
}
|
|
req := httptest.NewRequest("POST", "/api/agent-install-command", nil)
|
|
req.Host = "127.0.0.1:7655"
|
|
req.Header.Set("X-Forwarded-Proto", "https")
|
|
|
|
require.Equal(t, "https://agents.example.com", resolveConfigAgentInstallBaseURL(req, cfg, false))
|
|
|
|
cfg.AgentConnectURL = ""
|
|
require.Equal(t, "https://public.example.com", resolveConfigAgentInstallBaseURL(req, cfg, false))
|
|
}
|
|
|
|
func TestResolveConfigAgentInstallBaseURL_PreservesConfiguredPublicSchemeOnLoopback(t *testing.T) {
|
|
cfg := &config.Config{
|
|
PublicURL: "https://public.example.com/base/",
|
|
FrontendPort: 7655,
|
|
}
|
|
req := httptest.NewRequest("POST", "/api/agent-install-command", nil)
|
|
req.Host = "127.0.0.1:7655"
|
|
|
|
require.Equal(t, "https://public.example.com/base", resolveConfigAgentInstallBaseURL(req, cfg, false))
|
|
}
|
|
|
|
func TestResolveConfigAgentInstallBaseURL_UsesConfiguredPublicURLForIPv6Loopback(t *testing.T) {
|
|
cfg := &config.Config{
|
|
PublicURL: "https://public.example.com",
|
|
FrontendPort: 7655,
|
|
}
|
|
req := httptest.NewRequest("POST", "/api/agent-install-command", nil)
|
|
req.Host = "[::1]:7655"
|
|
|
|
require.Equal(t, "https://public.example.com", resolveConfigAgentInstallBaseURL(req, cfg, false))
|
|
}
|
|
|
|
func TestIssueAndPersistAgentInstallToken(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
cfg := &config.Config{DataPath: dataDir}
|
|
persistence := config.NewConfigPersistence(dataDir)
|
|
|
|
rawToken, record, err := issueAndPersistAgentInstallToken(cfg, persistence, issueAgentInstallTokenOptions{
|
|
TokenName: "test-install-token",
|
|
OrgID: "acme",
|
|
OwnerUserID: "alice",
|
|
Metadata: map[string]string{
|
|
"install_type": "pbs",
|
|
"issued_via": "test",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, rawToken)
|
|
require.NotNil(t, record)
|
|
require.Equal(t, "acme", record.OrgID)
|
|
require.Equal(t, "alice", record.Metadata[apiTokenMetadataOwnerUserID])
|
|
require.Equal(t, "pbs", record.Metadata["install_type"])
|
|
require.Equal(t, "test", record.Metadata["issued_via"])
|
|
|
|
require.Len(t, cfg.APITokens, 1)
|
|
savedRecord, ok := cfg.ValidateAPIToken(rawToken)
|
|
require.True(t, ok)
|
|
require.Equal(t, "acme", savedRecord.OrgID)
|
|
require.Equal(t, "alice", savedRecord.Metadata[apiTokenMetadataOwnerUserID])
|
|
|
|
tokens, err := persistence.LoadAPITokens()
|
|
require.NoError(t, err)
|
|
require.Len(t, tokens, 1)
|
|
require.True(t, strings.HasPrefix(tokens[0].Name, "test-install-token"))
|
|
require.Equal(t, "alice", tokens[0].Metadata[apiTokenMetadataOwnerUserID])
|
|
}
|
|
|
|
func TestIssueAndPersistAgentInstallTokenRejectsOwnerMetadataOverride(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
cfg := &config.Config{DataPath: dataDir}
|
|
persistence := config.NewConfigPersistence(dataDir)
|
|
|
|
rawToken, record, err := issueAndPersistAgentInstallToken(cfg, persistence, issueAgentInstallTokenOptions{
|
|
TokenName: "bad-install-token",
|
|
OrgID: "acme",
|
|
OwnerUserID: "alice",
|
|
Metadata: map[string]string{
|
|
apiTokenMetadataOwnerUserID: "mallory",
|
|
"install_type": "pbs",
|
|
},
|
|
})
|
|
require.Error(t, err)
|
|
require.ErrorIs(t, err, errAgentInstallTokenRecord)
|
|
require.Contains(t, err.Error(), "reserved token metadata key")
|
|
require.Empty(t, rawToken)
|
|
require.Nil(t, record)
|
|
require.Empty(t, cfg.APITokens)
|
|
|
|
tokens, loadErr := persistence.LoadAPITokens()
|
|
require.NoError(t, loadErr)
|
|
require.Empty(t, tokens)
|
|
}
|