Files
pulse/internal/api/configapi/install_command.go
T
pulse-triage[bot] 166f6d8aae Keep container diagnostics bootstrap credentials out of copied commands
Reuse the complete-download and preflight private-token entry boundary for both monitoring modes. Keep issued credentials separate and no-store, use the installer-owned token path in the diagnostic service reference, and reject structural unit injection before minting. Add executable root/sudo, history, transport, failure cleanup and systemd grammar regressions alongside the subsystem contracts.

Change-source: pulse-maintainer
2026-10-01 09:22:15 +01:00

126 lines
5.3 KiB
Go

package configapi
import (
"strings"
)
type AgentInstallCommandOptions struct {
BaseURL string
Token string
InstallType string
IncludeInstallType bool
EnableCommands bool
Insecure bool
}
type agentInstallCommandOptions = AgentInstallCommandOptions
// BuildProxmoxAgentInstallCommand never puts the credential in shell source.
// Token only selects whether private terminal input is required. The plaintext
// remains in the authenticated response's separate token field.
func BuildProxmoxAgentInstallCommand(opts AgentInstallCommandOptions) string {
baseURL := strings.TrimRight(strings.TrimSpace(opts.BaseURL), "/")
curlFlags := "-fsSL"
if opts.Insecure {
curlFlags = "-kfsSL"
}
trustArgs := ""
if opts.Insecure || strings.HasPrefix(strings.ToLower(baseURL), "http://") {
trustArgs = " --insecure"
}
args := " --url " + posixShellQuote(baseURL) + " --enable-proxmox"
if opts.IncludeInstallType {
args += " --proxmox-type " + posixShellQuote(opts.InstallType)
}
if opts.EnableCommands {
args += " --enable-commands"
}
if strings.TrimSpace(opts.Token) != "" {
args += ` --token-file "$token_file"`
}
args += trustArgs + " --non-interactive"
return privateAgentBootstrapCommand(baseURL, curlFlags, trustArgs, args, strings.TrimSpace(opts.Token) != "")
}
// BuildContainerRuntimeAgentInstallCommand uses the same complete-download,
// preflight and private credential-entry boundary as the Proxmox installer.
// Token selects the prompt; its value is never included in the copied command.
func BuildContainerRuntimeAgentInstallCommand(baseURL, token string, enableHost bool) string {
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
args := " --url " + posixShellQuote(baseURL) + " --enable-docker"
if enableHost {
args += " --enable-host"
} else {
args += " --enable-host=false"
}
args += " --interval 30s"
needsToken := strings.TrimSpace(token) != ""
if needsToken {
args += ` --token-file "$token_file"`
}
trustArgs := ""
if strings.HasPrefix(strings.ToLower(baseURL), "http://") {
trustArgs = " --insecure"
}
args += trustArgs + " --non-interactive"
return privateAgentBootstrapCommand(baseURL, "-fsSL", trustArgs, args, needsToken)
}
func privateAgentBootstrapCommand(baseURL, curlFlags, trustArgs, args string, needsToken bool) string {
preflight := "bash \"$install_script\" --url " + posixShellQuote(baseURL) +
" --preflight-only --output json --non-interactive" + trustArgs + ";"
return privateBootstrapCommand(baseURL+"/install.sh", curlFlags, preflight,
"bash \"$1\""+args+";", needsToken, "Pulse agent token")
}
// privateBootstrapCommand uses POSIX outer grammar for single-line paste hosts.
// The privileged Bash child owns both the silent input and its 0700/0600
// handoff. Neither sudo nor a child command receives the credential in argv or
// environment. Failed/partial downloads and preflight never reach that child.
func privateBootstrapCommand(scriptURL, curlFlags, preflight, run string, needsToken bool, prompt string) string {
privileged := []string{"set +xv;", "set -eu;", "umask 077;"}
if needsToken {
privileged = append(privileged,
`token_dir=$(mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX);`,
`token_file="$token_dir/token";`,
`cleanup() { unset pulse_token; rm -f -- "$token_file"; rmdir -- "$token_dir"; };`,
`trap cleanup EXIT; trap 'exit 129' HUP; trap 'exit 130' INT; trap 'exit 143' TERM;`,
`if ! IFS= read -r -s -p `+posixShellQuote(prompt+" (paste at this prompt, not in the command): ")+` pulse_token </dev/tty; then echo "A terminal is required to enter the token. No installation was attempted." >&2; exit 1; fi;`,
`printf '\n' >/dev/tty;`,
`if [ -z "$pulse_token" ] || [ "${#pulse_token}" -gt 4096 ]; then echo "A non-empty token of at most 4096 characters is required." >&2; exit 1; fi;`,
`printf %s "$pulse_token" > "$token_file"; unset pulse_token;`,
)
}
privileged = append(privileged, run)
child := "bash -c " + posixShellQuote(strings.Join(privileged, " ")) + ` pulse-bootstrap "$install_script";`
return strings.Join([]string{
"(", "set +xv;", "set -eu;", "umask 077;",
`if [ "$(id -u)" -eq 0 ]; then :; elif command -v sudo >/dev/null 2>&1; then sudo -v; else echo "Root privileges required. Run as root (su -) and retry." >&2; exit 1; fi;`,
`bootstrap_dir=$(mktemp -d /tmp/pulse-bootstrap.XXXXXX);`,
`install_script="$bootstrap_dir/install.sh";`,
`cleanup() { rm -f -- "$install_script"; rmdir -- "$bootstrap_dir"; };`,
`trap cleanup EXIT; trap 'exit 129' HUP; trap 'exit 130' INT; trap 'exit 143' TERM;`,
"curl " + curlFlags + " " + posixShellQuote(strings.TrimSpace(scriptURL)) + ` -o "$install_script";`,
preflight,
`if [ "$(id -u)" -eq 0 ]; then ` + child + " else sudo " + child + " fi;",
")",
}, " ")
}
func buildProxmoxAgentInstallCommand(opts agentInstallCommandOptions) string {
return BuildProxmoxAgentInstallCommand(opts)
}
func withPrivilegeEscalation(command string) string {
const installPipe = "| bash -s --"
idx := strings.Index(command, installPipe)
if idx == -1 {
return command
}
args := command[idx+len(installPipe):]
return command[:idx] +
`| { if [ "$(id -u)" -eq 0 ]; then bash -s --` + args +
`; elif command -v sudo >/dev/null 2>&1; then sudo bash -s --` + args +
`; else echo "Root privileges required. Run as root (su -) and retry." >&2; exit 1; fi; }`
}