mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
Support agent retargeting after server moves
This commit is contained in:
parent
c3faa48973
commit
323da54067
11 changed files with 321 additions and 16 deletions
|
|
@ -97,8 +97,10 @@ ENABLE_KUBERNETES="" # Empty means "auto-detect"
|
|||
ENABLE_PROXMOX="" # Empty means "auto-detect"
|
||||
PROXMOX_TYPE=""
|
||||
UPDATE_ONLY="false"
|
||||
RETARGET_ONLY="false"
|
||||
UNINSTALL="false"
|
||||
INSECURE="false"
|
||||
INSECURE_EXPLICIT="false"
|
||||
SERVER_FINGERPRINT="${PULSE_SERVER_FINGERPRINT:-}"
|
||||
OBSERVERS_FILE="${PULSE_OBSERVERS_FILE:-}"
|
||||
AGENT_ID=""
|
||||
|
|
@ -125,7 +127,7 @@ AGENT_LOG_FILE="" # When set, pass --log-file so the agent's rotating log writer
|
|||
DEFAULT_STATE_DIR="/var/lib/pulse-agent"
|
||||
STATE_DIR="$DEFAULT_STATE_DIR" # Persistent state directory (overridden per platform)
|
||||
STATE_DIR_SOURCE="default" # default, explicit, recovered, or platform
|
||||
CURL_CA_BUNDLE="" # Path to CA bundle for curl and agent TLS (sets SSL_CERT_FILE)
|
||||
CURL_CA_BUNDLE="${PULSE_CACERT:-}" # Path to CA bundle for curl and agent TLS (sets SSL_CERT_FILE)
|
||||
NON_INTERACTIVE="false"
|
||||
TOKEN_FILE_PATH="" # Path to file containing the token
|
||||
RUNTIME_TOKEN_FILE="" # Secure token file passed to the installed service
|
||||
|
|
@ -576,6 +578,7 @@ Options:
|
|||
--safe-profile-rollback Restore the prior collector/helper snapshot from the last committed safe-profile migration
|
||||
--enroll Exchange bootstrap token for runtime token (deploy wizard)
|
||||
--update Update an existing agent using saved connection state
|
||||
--retarget Point an existing agent at --url using saved identity and token
|
||||
--uninstall Remove the agent
|
||||
--non-interactive Skip TTY prompts (for automated/scripted installs)
|
||||
--token-file <path> Read token from file (alternative to --token)
|
||||
|
|
@ -3325,17 +3328,17 @@ recover_connection_state() {
|
|||
if [[ -z "$REPORT_IP" ]]; then
|
||||
REPORT_IP=$(read_connection_state_value "$file" "PULSE_REPORT_IP")
|
||||
fi
|
||||
if [[ "$INSECURE" != "true" ]]; then
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" && "$INSECURE" != "true" ]]; then
|
||||
local saved_insecure=""
|
||||
saved_insecure=$(read_connection_state_value "$file" "PULSE_INSECURE_SKIP_VERIFY")
|
||||
if [[ "$saved_insecure" == "true" ]]; then
|
||||
INSECURE="true"
|
||||
fi
|
||||
fi
|
||||
if [[ -z "$SERVER_FINGERPRINT" ]]; then
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" && -z "$SERVER_FINGERPRINT" ]]; then
|
||||
SERVER_FINGERPRINT=$(read_connection_state_value "$file" "PULSE_SERVER_FINGERPRINT")
|
||||
fi
|
||||
if [[ -z "$CURL_CA_BUNDLE" ]]; then
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" && -z "$CURL_CA_BUNDLE" ]]; then
|
||||
CURL_CA_BUNDLE=$(read_connection_state_value "$file" "PULSE_CACERT")
|
||||
fi
|
||||
}
|
||||
|
|
@ -3398,11 +3401,11 @@ apply_recovered_agent_arg_value() {
|
|||
RECOVERED_AGENT_ARG_STATE="true"
|
||||
;;
|
||||
cacert)
|
||||
if [[ -z "$CURL_CA_BUNDLE" ]]; then CURL_CA_BUNDLE="$value"; fi
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" && -z "$CURL_CA_BUNDLE" ]]; then CURL_CA_BUNDLE="$value"; fi
|
||||
RECOVERED_AGENT_ARG_STATE="true"
|
||||
;;
|
||||
server-fingerprint)
|
||||
if [[ -z "$SERVER_FINGERPRINT" ]]; then SERVER_FINGERPRINT="$value"; fi
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" && -z "$SERVER_FINGERPRINT" ]]; then SERVER_FINGERPRINT="$value"; fi
|
||||
RECOVERED_AGENT_ARG_STATE="true"
|
||||
;;
|
||||
observers-file)
|
||||
|
|
@ -3565,7 +3568,9 @@ recover_connection_state_from_arg_stream() {
|
|||
RECOVERED_AGENT_ARG_STATE="true"
|
||||
;;
|
||||
--insecure|-insecure)
|
||||
INSECURE="true"
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" || "${INSECURE_EXPLICIT:-false}" == "true" ]]; then
|
||||
INSECURE="true"
|
||||
fi
|
||||
RECOVERED_AGENT_ARG_STATE="true"
|
||||
;;
|
||||
--enable-commands|-enable-commands)
|
||||
|
|
@ -3645,17 +3650,19 @@ recover_connection_state_from_env_stream() {
|
|||
RECOVERED_AGENT_ENV_STATE="true"
|
||||
;;
|
||||
PULSE_INSECURE_SKIP_VERIFY=true)
|
||||
INSECURE="true"
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" || "${INSECURE_EXPLICIT:-false}" == "true" ]]; then
|
||||
INSECURE="true"
|
||||
fi
|
||||
RECOVERED_AGENT_ENV_STATE="true"
|
||||
;;
|
||||
PULSE_CACERT=*)
|
||||
value="${env_line#*=}"
|
||||
if [[ -z "$CURL_CA_BUNDLE" ]]; then CURL_CA_BUNDLE="$value"; fi
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" && -z "$CURL_CA_BUNDLE" ]]; then CURL_CA_BUNDLE="$value"; fi
|
||||
RECOVERED_AGENT_ENV_STATE="true"
|
||||
;;
|
||||
PULSE_SERVER_FINGERPRINT=*)
|
||||
value="${env_line#*=}"
|
||||
if [[ -z "$SERVER_FINGERPRINT" ]]; then SERVER_FINGERPRINT="$value"; fi
|
||||
if [[ "${RETARGET_ONLY:-false}" != "true" && -z "$SERVER_FINGERPRINT" ]]; then SERVER_FINGERPRINT="$value"; fi
|
||||
RECOVERED_AGENT_ENV_STATE="true"
|
||||
;;
|
||||
esac
|
||||
|
|
@ -4090,7 +4097,7 @@ while [[ $# -gt 0 ]]; do
|
|||
--enable-proxmox=false) ENABLE_PROXMOX="false"; PROXMOX_EXPLICIT="true"; shift ;;
|
||||
--disable-proxmox) ENABLE_PROXMOX="false"; PROXMOX_EXPLICIT="true"; shift ;;
|
||||
--proxmox-type) PROXMOX_TYPE="$2"; shift 2 ;;
|
||||
--insecure) INSECURE="true"; shift ;;
|
||||
--insecure) INSECURE="true"; INSECURE_EXPLICIT="true"; shift ;;
|
||||
--cacert) CURL_CA_BUNDLE="$2"; shift 2 ;;
|
||||
--server-fingerprint) SERVER_FINGERPRINT="$2"; shift 2 ;;
|
||||
--observers-file) OBSERVERS_FILE="$2"; shift 2 ;;
|
||||
|
|
@ -4111,6 +4118,7 @@ while [[ $# -gt 0 ]]; do
|
|||
--safe-profile-rollback) SAFE_PROFILE_ACTION="rollback"; shift ;;
|
||||
--enroll) ENROLL="true"; shift ;;
|
||||
--update) UPDATE_ONLY="true"; shift ;;
|
||||
--retarget) RETARGET_ONLY="true"; UPDATE_ONLY="true"; shift ;;
|
||||
--uninstall) UNINSTALL="true"; shift ;;
|
||||
--agent-id) AGENT_ID="$2"; shift 2 ;;
|
||||
--hostname) HOSTNAME_OVERRIDE="$2"; shift 2 ;;
|
||||
|
|
@ -4129,6 +4137,13 @@ while [[ $# -gt 0 ]]; do
|
|||
esac
|
||||
done
|
||||
|
||||
if [[ "$RETARGET_ONLY" == "true" && -z "$PULSE_URL" ]]; then
|
||||
fail "--retarget requires the new Pulse endpoint in --url" "$EXIT_MISSING_ARGS"
|
||||
fi
|
||||
if [[ "$RETARGET_ONLY" == "true" && "$UNINSTALL" == "true" ]]; then
|
||||
fail "--retarget cannot be combined with --uninstall" "$EXIT_MISSING_ARGS"
|
||||
fi
|
||||
|
||||
case "$SAFE_PROFILE_ACTION" in
|
||||
"") ;;
|
||||
inspect)
|
||||
|
|
|
|||
|
|
@ -889,8 +889,12 @@ func TestInstallSHSupportsSavedStateUpdateMode(t *testing.T) {
|
|||
script := string(content)
|
||||
required := []string{
|
||||
`--update Update an existing agent using saved connection state`,
|
||||
`--retarget Point an existing agent at --url using saved identity and token`,
|
||||
`UPDATE_ONLY="false"`,
|
||||
`RETARGET_ONLY="false"`,
|
||||
`--update) UPDATE_ONLY="true"; shift ;;`,
|
||||
`--retarget) RETARGET_ONLY="true"; UPDATE_ONLY="true"; shift ;;`,
|
||||
`--retarget requires the new Pulse endpoint in --url`,
|
||||
`if [[ "$UPDATE_ONLY" == "true" || "$UNINSTALL" == "true" ]]; then`,
|
||||
`lifecycle_conn_env=$(find_connection_state_file || true)`,
|
||||
`recover_connection_state "$lifecycle_conn_env"`,
|
||||
|
|
@ -927,6 +931,158 @@ func TestInstallSHSupportsSavedStateUpdateMode(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInstallSHRetargetPreservesIdentityWithoutOldEndpointTrust(t *testing.T) {
|
||||
stateDir := t.TempDir()
|
||||
tokenPath := filepath.Join(stateDir, "token")
|
||||
if err := os.WriteFile(tokenPath, []byte("deadbeef\n"), 0600); err != nil {
|
||||
t.Fatalf("write token fixture: %v", err)
|
||||
}
|
||||
connectionPath := filepath.Join(stateDir, "connection.env")
|
||||
connection := strings.Join([]string{
|
||||
"PULSE_URL='https://old-pulse.example.test:7655'",
|
||||
"PULSE_TOKEN_FILE='" + tokenPath + "'",
|
||||
"PULSE_AGENT_ID='agent-123'",
|
||||
"PULSE_HOSTNAME='pve-one'",
|
||||
"PULSE_INSECURE_SKIP_VERIFY='true'",
|
||||
"PULSE_SERVER_FINGERPRINT='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'",
|
||||
"PULSE_CACERT='/etc/pulse/old-ca.pem'",
|
||||
}, "\n") + "\n"
|
||||
if err := os.WriteFile(connectionPath, []byte(connection), 0600); err != nil {
|
||||
t.Fatalf("write connection fixture: %v", err)
|
||||
}
|
||||
|
||||
script := `
|
||||
PULSE_URL="https://new-pulse.example.test:7655"
|
||||
PULSE_TOKEN=""
|
||||
RETARGET_ONLY="true"
|
||||
INSECURE="false"
|
||||
SERVER_FINGERPRINT=""
|
||||
CURL_CA_BUNDLE=""
|
||||
AGENT_ID=""
|
||||
HOSTNAME_OVERRIDE=""
|
||||
REPORT_IP=""
|
||||
STATE_DIR="/var/lib/pulse-agent"
|
||||
STATE_DIR_SOURCE="default"
|
||||
DEFAULT_STATE_DIR="/var/lib/pulse-agent"
|
||||
TRUENAS_STATE_DIR="/data/pulse-agent"
|
||||
` + extractInstallShellFunction(t, "read_connection_state_value") + `
|
||||
` + extractInstallShellFunction(t, "recover_token_from_default_agent_token_file") + `
|
||||
` + extractInstallShellFunction(t, "recover_connection_state") + `
|
||||
recover_connection_state "${PULSE_TEST_CONNECTION:?}"
|
||||
printf 'URL=%s\nTOKEN=%s\nAGENT_ID=%s\nHOSTNAME=%s\nINSECURE=%s\nFINGERPRINT=%s\nCACERT=%s\n' \
|
||||
"$PULSE_URL" "$PULSE_TOKEN" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$INSECURE" "$SERVER_FINGERPRINT" "$CURL_CA_BUNDLE"
|
||||
`
|
||||
|
||||
cmd := exec.Command("bash", "-c", script)
|
||||
cmd.Env = append(os.Environ(), "PULSE_TEST_CONNECTION="+connectionPath)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("bash: %v\n%s", err, out)
|
||||
}
|
||||
got := string(out)
|
||||
for _, needle := range []string{
|
||||
"URL=https://new-pulse.example.test:7655",
|
||||
"TOKEN=deadbeef",
|
||||
"AGENT_ID=agent-123",
|
||||
"HOSTNAME=pve-one",
|
||||
"INSECURE=false",
|
||||
"FINGERPRINT=",
|
||||
"CACERT=",
|
||||
} {
|
||||
if !strings.Contains(got, needle) {
|
||||
t.Fatalf("retarget state missing %q:\n%s", needle, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "old-pulse") || strings.Contains(got, "old-ca") || strings.Contains(got, "aaaaaaaa") {
|
||||
t.Fatalf("retarget carried old endpoint trust or URL:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallSHRetargetDoesNotRecoverLegacyServiceTrust(t *testing.T) {
|
||||
script := `
|
||||
PULSE_URL="https://new-pulse.example.test:7655"
|
||||
PULSE_TOKEN=""
|
||||
RETARGET_ONLY="true"
|
||||
INSECURE="false"
|
||||
INSECURE_EXPLICIT="false"
|
||||
SERVER_FINGERPRINT=""
|
||||
CURL_CA_BUNDLE=""
|
||||
INTERVAL="30s"
|
||||
INTERVAL_EXPLICIT="false"
|
||||
ENABLE_HOST="true"
|
||||
HOST_EXPLICIT="false"
|
||||
ENABLE_DOCKER=""
|
||||
DOCKER_EXPLICIT="false"
|
||||
ENABLE_KUBERNETES=""
|
||||
KUBERNETES_EXPLICIT="false"
|
||||
KUBECONFIG_PATH=""
|
||||
ENABLE_PROXMOX=""
|
||||
PROXMOX_EXPLICIT="false"
|
||||
PROXMOX_TYPE=""
|
||||
ENABLE_COMMANDS="false"
|
||||
ENROLL="false"
|
||||
COMMAND_AUTHORITY_SOURCE=""
|
||||
HEALTH_ADDR=""
|
||||
HEALTH_ADDR_SET="false"
|
||||
AGENT_ID=""
|
||||
HOSTNAME_OVERRIDE=""
|
||||
REPORT_IP=""
|
||||
STATE_DIR="/var/lib/pulse-agent"
|
||||
STATE_DIR_SOURCE="default"
|
||||
OBSERVERS_FILE=""
|
||||
KUBE_INCLUDE_ALL_PODS="false"
|
||||
KUBE_INCLUDE_ALL_DEPLOYMENTS="false"
|
||||
DISK_EXCLUDES=()
|
||||
DISK_INCLUDES=()
|
||||
` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + `
|
||||
` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + `
|
||||
` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + `
|
||||
` + extractInstallShellFunction(t, "recovered_connection_state_ready") + `
|
||||
` + extractInstallShellFunction(t, "recover_token_from_default_agent_token_file") + `
|
||||
` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + `
|
||||
` + extractInstallShellFunction(t, "recover_connection_state_from_env_stream") + `
|
||||
recover_connection_state_from_arg_stream <<'ARGS'
|
||||
/usr/local/bin/pulse-agent
|
||||
--url=https://old-pulse.example.test:7655
|
||||
--token=deadbeef
|
||||
--agent-id=agent-123
|
||||
--hostname=pve-one
|
||||
--insecure
|
||||
--cacert=/etc/pulse/old-ca.pem
|
||||
--server-fingerprint=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
ARGS
|
||||
recover_connection_state_from_env_stream <<'ENV'
|
||||
PULSE_INSECURE_SKIP_VERIFY=true
|
||||
PULSE_CACERT=/etc/pulse/older-ca.pem
|
||||
PULSE_SERVER_FINGERPRINT=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
ENV
|
||||
printf 'URL=%s\nTOKEN=%s\nAGENT_ID=%s\nHOSTNAME=%s\nINSECURE=%s\nFINGERPRINT=%s\nCACERT=%s\n' \
|
||||
"$PULSE_URL" "$PULSE_TOKEN" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$INSECURE" "$SERVER_FINGERPRINT" "$CURL_CA_BUNDLE"
|
||||
`
|
||||
|
||||
out, err := exec.Command("bash", "-c", script).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("bash: %v\n%s", err, out)
|
||||
}
|
||||
got := string(out)
|
||||
for _, needle := range []string{
|
||||
"URL=https://new-pulse.example.test:7655",
|
||||
"TOKEN=deadbeef",
|
||||
"AGENT_ID=agent-123",
|
||||
"HOSTNAME=pve-one",
|
||||
"INSECURE=false",
|
||||
"FINGERPRINT=",
|
||||
"CACERT=",
|
||||
} {
|
||||
if !strings.Contains(got, needle) {
|
||||
t.Fatalf("retarget service recovery missing %q:\n%s", needle, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "old-pulse") || strings.Contains(got, "old-ca") || strings.Contains(got, "aaaaaaaa") || strings.Contains(got, "bbbbbbbb") {
|
||||
t.Fatalf("retarget carried old service trust or URL:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallSHRecoversV5ProcessArgsForSavedStateUpdate(t *testing.T) {
|
||||
script := `
|
||||
fail() { echo "FAIL:$1"; exit 99; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue