mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
@@ -269,12 +269,17 @@ process or unit may seed later recovery attempts, but it must not be logged or
|
||||
treated as recovered update state. That fallback must still run when an
|
||||
operator supplies the update URL explicitly but token, identity, feature-flag,
|
||||
or trust continuity remains recoverable only from a legacy v5 process or
|
||||
service. Because v5.1.x agents were launched by Go flag parsing, that legacy
|
||||
fallback must treat single-dash and double-dash agent flag spellings as the
|
||||
same recovered state while preserving the same fail-closed URL-plus-token
|
||||
threshold. Windows stale-agent update commands remain on the existing
|
||||
token-gated install transport until the Windows installer owns an equivalent
|
||||
saved-state update mode.
|
||||
service. Legacy v5.1.x Linux services that omitted `--token` and
|
||||
`--token-file` because the Go agent read `/var/lib/pulse-agent/token`
|
||||
implicitly may complete recovery from that installer state-dir/default token
|
||||
file only after a running process, service unit, or saved state supplies local
|
||||
agent connection context; the token file alone must not hide a missing
|
||||
control-plane URL. Because v5.1.x agents were launched by Go flag parsing,
|
||||
that legacy fallback must treat single-dash and double-dash agent flag
|
||||
spellings as the same recovered state while preserving the same fail-closed
|
||||
URL-plus-token threshold. Windows stale-agent update commands remain on the
|
||||
existing token-gated install transport until the Windows installer owns an
|
||||
equivalent saved-state update mode.
|
||||
Agent Fleet Doctor diagnostics extend that same read-only lifecycle triage
|
||||
surface: `GET /api/agents/diagnostics` may explain stale versions, missing
|
||||
reports, profile deployment drift, expected Docker/Kubernetes telemetry gaps,
|
||||
|
||||
@@ -279,10 +279,14 @@ TLS floor in the dynamic config.
|
||||
raw token in process arguments. That fallback remains required when the
|
||||
operator supplies `--url` on the update command but token, identity,
|
||||
feature-flag, or trust continuity still exists only in legacy process or
|
||||
service state. Because v5.1.x agents used Go's single-dash flag spelling,
|
||||
the installer-owned recovery path must accept both single-dash and
|
||||
double-dash forms for recovered agent args without weakening the existing
|
||||
missing-state failure behavior.
|
||||
service state. Legacy v5.1.x Linux services that relied on the Go agent's
|
||||
implicit `/var/lib/pulse-agent/token` fallback may recover that default
|
||||
token file only after local process, service, or saved-state context has
|
||||
supplied the agent connection shape; the token file alone is not enough to
|
||||
convert a missing-state update into a new install. Because v5.1.x agents
|
||||
used Go's single-dash flag spelling, the installer-owned recovery path must
|
||||
accept both single-dash and double-dash forms for recovered agent args
|
||||
without weakening the existing missing-state failure behavior.
|
||||
The shell installer must disclose `--enable-commands` as Pulse command
|
||||
execution, disabled by default, and must name both Patrol actions and
|
||||
Proxmox LXC Docker inventory as the operator-visible reasons to enable it.
|
||||
|
||||
@@ -1479,6 +1479,28 @@ read_connection_state_value() {
|
||||
' "$file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
recover_token_from_default_agent_token_file() {
|
||||
local token_path=""
|
||||
local recovered_token=""
|
||||
|
||||
if [[ -n "$PULSE_TOKEN" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# v5.1.x Linux services could omit --token and --token-file because the
|
||||
# Go agent read this default file itself.
|
||||
for token_path in "${STATE_DIR%/}/token" "/var/lib/pulse-agent/token" "$TRUENAS_STATE_DIR/token"; do
|
||||
[[ -n "$token_path" && -f "$token_path" ]] || continue
|
||||
recovered_token=$(cat "$token_path" 2>/dev/null || true)
|
||||
if [[ -n "$recovered_token" ]]; then
|
||||
PULSE_TOKEN="$recovered_token"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
recover_connection_state() {
|
||||
local file="$1"
|
||||
|
||||
@@ -1495,6 +1517,9 @@ recover_connection_state() {
|
||||
PULSE_TOKEN=$(cat "$saved_token_file")
|
||||
fi
|
||||
fi
|
||||
if [[ -z "$PULSE_TOKEN" && -n "$PULSE_URL" ]]; then
|
||||
recover_token_from_default_agent_token_file || true
|
||||
fi
|
||||
if [[ -z "$AGENT_ID" ]]; then
|
||||
AGENT_ID=$(read_connection_state_value "$file" "PULSE_AGENT_ID")
|
||||
fi
|
||||
@@ -1705,6 +1730,10 @@ recover_connection_state_from_arg_stream() {
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$RECOVERED_AGENT_ARG_STATE" == "true" && -z "$PULSE_TOKEN" && -n "$PULSE_URL" ]]; then
|
||||
recover_token_from_default_agent_token_file || true
|
||||
fi
|
||||
|
||||
[[ "$RECOVERED_AGENT_ARG_STATE" == "true" ]] && recovered_connection_state_ready
|
||||
}
|
||||
|
||||
@@ -1755,6 +1784,10 @@ recover_connection_state_from_env_stream() {
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$RECOVERED_AGENT_ENV_STATE" == "true" && -z "$PULSE_TOKEN" && -n "$PULSE_URL" ]]; then
|
||||
recover_token_from_default_agent_token_file || true
|
||||
fi
|
||||
|
||||
[[ "$RECOVERED_AGENT_ENV_STATE" == "true" ]] && recovered_connection_state_ready
|
||||
}
|
||||
|
||||
|
||||
@@ -644,6 +644,7 @@ func TestInstallSHSupportsSavedStateUpdateMode(t *testing.T) {
|
||||
`recover_connection_state_from_running_agent`,
|
||||
`recover_connection_state_from_systemd_unit`,
|
||||
`recover_connection_state_from_arg_stream`,
|
||||
`recover_token_from_default_agent_token_file() {`,
|
||||
`normalize_recovered_agent_arg_key() {`,
|
||||
`-url|-pulse-url|-token|-token-file|-interval|-agent-id|-hostname|-cacert|-health-addr|-state-dir|-kubeconfig|-proxmox-type|-disk-exclude)`,
|
||||
`--enable-host|-enable-host|--enable-host=true|-enable-host=true)`,
|
||||
@@ -702,6 +703,7 @@ func TestInstallSHRecoversV5ProcessArgsForSavedStateUpdate(t *testing.T) {
|
||||
` + 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, "build_exec_arg_items") + `
|
||||
` + extractInstallShellFunction(t, "join_exec_arg_items") + `
|
||||
@@ -785,6 +787,7 @@ func TestInstallSHRejectsPartialRecoveredProcessConnectionState(t *testing.T) {
|
||||
` + 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") + `
|
||||
if recover_connection_state_from_arg_stream <<'ARGS'
|
||||
/usr/local/bin/pulse-agent
|
||||
@@ -821,6 +824,100 @@ ARGS
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallSHRecoversLegacyDefaultTokenFileForSavedStateUpdate(t *testing.T) {
|
||||
stateDir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(stateDir, "token"), []byte("deadbeef\n"), 0600); err != nil {
|
||||
t.Fatalf("write legacy token file: %v", err)
|
||||
}
|
||||
|
||||
script := `
|
||||
fail() { echo "FAIL:$1"; exit 99; }
|
||||
PULSE_URL="http://192.168.2.96:7655"
|
||||
PULSE_TOKEN=""
|
||||
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=""
|
||||
INSECURE="true"
|
||||
ENABLE_COMMANDS="false"
|
||||
ENROLL="false"
|
||||
HEALTH_ADDR=""
|
||||
HEALTH_ADDR_SET="false"
|
||||
AGENT_ID=""
|
||||
HOSTNAME_OVERRIDE=""
|
||||
STATE_DIR="${PULSE_TEST_STATE_DIR:?}"
|
||||
TRUENAS_STATE_DIR="${PULSE_TEST_STATE_DIR:?}/truenas"
|
||||
CURL_CA_BUNDLE=""
|
||||
KUBE_INCLUDE_ALL_PODS="false"
|
||||
KUBE_INCLUDE_ALL_DEPLOYMENTS="false"
|
||||
DISK_EXCLUDES=()
|
||||
RUNTIME_TOKEN_FILE="${STATE_DIR}/token"
|
||||
` + 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, "build_exec_arg_items") + `
|
||||
` + extractInstallShellFunction(t, "join_exec_arg_items") + `
|
||||
` + extractInstallShellFunction(t, "build_exec_args") + `
|
||||
if recover_connection_state_from_arg_stream <<'ARGS'
|
||||
/usr/local/bin/pulse-agent
|
||||
-url
|
||||
http://192.168.2.96:7655
|
||||
-interval
|
||||
30s
|
||||
-enable-host
|
||||
-enable-docker
|
||||
-agent-id
|
||||
machine-1
|
||||
-hostname
|
||||
docker1
|
||||
ARGS
|
||||
then
|
||||
echo "READY"
|
||||
else
|
||||
echo "NOT_READY"
|
||||
fi
|
||||
build_exec_args
|
||||
printf 'URL=%s\nTOKEN=%s\nDOCKER=%s\nAGENT_ID=%s\nHOSTNAME=%s\nEXEC_ARGS=%s\n' \
|
||||
"$PULSE_URL" "$PULSE_TOKEN" "$ENABLE_DOCKER" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$EXEC_ARGS"
|
||||
`
|
||||
|
||||
cmd := exec.Command("bash", "-c", script)
|
||||
cmd.Env = append(os.Environ(), "PULSE_TEST_STATE_DIR="+stateDir)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("bash: %v\n%s", err, out)
|
||||
}
|
||||
got := string(out)
|
||||
for _, needle := range []string{
|
||||
"READY",
|
||||
"URL=http://192.168.2.96:7655",
|
||||
"TOKEN=deadbeef",
|
||||
"DOCKER=true",
|
||||
"AGENT_ID=machine-1",
|
||||
"HOSTNAME=docker1",
|
||||
"--token-file",
|
||||
stateDir + "/token",
|
||||
} {
|
||||
if !strings.Contains(got, needle) {
|
||||
t.Fatalf("legacy default token recovery missing %q:\n%s", needle, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "--token deadbeef") {
|
||||
t.Fatalf("legacy default token recovery leaked raw token into service args:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallSHCombinesRecoveredProcessArgsAndEnvConnectionState(t *testing.T) {
|
||||
script := `
|
||||
PULSE_URL=""
|
||||
@@ -853,6 +950,7 @@ func TestInstallSHCombinesRecoveredProcessArgsAndEnvConnectionState(t *testing.T
|
||||
` + 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") + `
|
||||
if recover_connection_state_from_arg_stream <<'ARGS'
|
||||
@@ -930,6 +1028,7 @@ func TestInstallSHUpdateModeMergesExplicitURLWithRunningV5ProcessState(t *testin
|
||||
` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + `
|
||||
` + extractInstallShellFunction(t, "recovered_connection_state_ready") + `
|
||||
` + extractInstallShellFunction(t, "update_connection_state_incomplete") + `
|
||||
` + extractInstallShellFunction(t, "recover_token_from_default_agent_token_file") + `
|
||||
` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + `
|
||||
` + extractInstallShellFunction(t, "build_exec_arg_items") + `
|
||||
` + extractInstallShellFunction(t, "join_exec_arg_items") + `
|
||||
|
||||
Reference in New Issue
Block a user