mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
Support agent retargeting after server moves
This commit is contained in:
@@ -370,6 +370,11 @@ You can auto-import an encrypted backup on first startup. This is useful for aut
|
||||
|
||||
> **Note**: `PULSE_INIT_CONFIG_URL` is only supported by the hidden `pulse config auto-import` command, not by the server startup auto-import.
|
||||
|
||||
Configuration import restores server-side agent credentials, but it cannot
|
||||
change the primary Pulse URL persisted on remote agents. When restoring onto a
|
||||
server with a different address, retarget those agents after import. See
|
||||
[Moving Pulse to a new address](UNIFIED_AGENT.md#moving-pulse-to-a-new-address).
|
||||
|
||||
### Developer/Test Overrides (Environment Variables)
|
||||
|
||||
These are primarily for development or test harnesses and should not be used in production.
|
||||
|
||||
@@ -31,6 +31,35 @@ Run it on the host that already has the v5 `pulse-agent` service to replace the
|
||||
binary and service configuration in place; do not uninstall the old service
|
||||
first unless you are intentionally removing that host from Pulse.
|
||||
|
||||
### Moving Pulse to a new address
|
||||
|
||||
Configuration export/import restores the server-side agent records and API
|
||||
tokens. It cannot rewrite the primary Pulse URL on remote machines because
|
||||
agents initiate the connection. Prefer a stable DNS name for the primary URL
|
||||
so replacing the Pulse host does not require an agent migration.
|
||||
|
||||
After importing the configuration on a Pulse server with a different address,
|
||||
retarget each existing standard Linux agent from that agent machine:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://pulse.example.com:7655/install.sh | \
|
||||
sudo bash -s -- --retarget --url https://pulse.example.com:7655
|
||||
```
|
||||
|
||||
The retarget operation recovers the existing token, agent ID, enabled
|
||||
collectors, and other service options. It does not carry the old endpoint's
|
||||
TLS bypass, custom CA, or certificate fingerprint to the new address. Supply
|
||||
`--cacert`, `--server-fingerprint`, or (only on a trusted network)
|
||||
`--insecure` explicitly when the new endpoint requires it. The script must
|
||||
come from the new server so it supports the retarget operation. A newly
|
||||
generated full installation command from **Settings → Infrastructure → Install
|
||||
on a host** remains the fallback.
|
||||
|
||||
On Windows, run the full generated PowerShell installation command from the
|
||||
new Pulse server as Administrator, including the desired collector options.
|
||||
Do not expect the configuration import itself to make agent-only machines
|
||||
appear at the new address.
|
||||
|
||||
An installed agent has one **primary** Pulse URL and token. The primary is the
|
||||
only server allowed to supply remote configuration, commands, enrollment, or
|
||||
updates. The same collection can also be sent to explicitly configured,
|
||||
|
||||
@@ -370,6 +370,11 @@ You can auto-import an encrypted backup on first startup. This is useful for aut
|
||||
|
||||
> **Note**: `PULSE_INIT_CONFIG_URL` is only supported by the hidden `pulse config auto-import` command, not by the server startup auto-import.
|
||||
|
||||
Configuration import restores server-side agent credentials, but it cannot
|
||||
change the primary Pulse URL persisted on remote agents. When restoring onto a
|
||||
server with a different address, retarget those agents after import. See
|
||||
[Moving Pulse to a new address](UNIFIED_AGENT.md#moving-pulse-to-a-new-address).
|
||||
|
||||
### Developer/Test Overrides (Environment Variables)
|
||||
|
||||
These are primarily for development or test harnesses and should not be used in production.
|
||||
|
||||
@@ -31,6 +31,35 @@ Run it on the host that already has the v5 `pulse-agent` service to replace the
|
||||
binary and service configuration in place; do not uninstall the old service
|
||||
first unless you are intentionally removing that host from Pulse.
|
||||
|
||||
### Moving Pulse to a new address
|
||||
|
||||
Configuration export/import restores the server-side agent records and API
|
||||
tokens. It cannot rewrite the primary Pulse URL on remote machines because
|
||||
agents initiate the connection. Prefer a stable DNS name for the primary URL
|
||||
so replacing the Pulse host does not require an agent migration.
|
||||
|
||||
After importing the configuration on a Pulse server with a different address,
|
||||
retarget each existing standard Linux agent from that agent machine:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://pulse.example.com:7655/install.sh | \
|
||||
sudo bash -s -- --retarget --url https://pulse.example.com:7655
|
||||
```
|
||||
|
||||
The retarget operation recovers the existing token, agent ID, enabled
|
||||
collectors, and other service options. It does not carry the old endpoint's
|
||||
TLS bypass, custom CA, or certificate fingerprint to the new address. Supply
|
||||
`--cacert`, `--server-fingerprint`, or (only on a trusted network)
|
||||
`--insecure` explicitly when the new endpoint requires it. The script must
|
||||
come from the new server so it supports the retarget operation. A newly
|
||||
generated full installation command from **Settings → Infrastructure → Install
|
||||
on a host** remains the fallback.
|
||||
|
||||
On Windows, run the full generated PowerShell installation command from the
|
||||
new Pulse server as Administrator, including the desired collector options.
|
||||
Do not expect the configuration import itself to make agent-only machines
|
||||
appear at the new address.
|
||||
|
||||
An installed agent has one **primary** Pulse URL and token. The primary is the
|
||||
only server allowed to supply remote configuration, commands, enrollment, or
|
||||
updates. The same collection can also be sent to explicitly configured,
|
||||
|
||||
@@ -10,9 +10,13 @@ const targetDocsDir = path.join(frontendRoot, 'public', 'docs');
|
||||
|
||||
const shippedDocs = [
|
||||
{ source: path.join(repoRoot, 'docs', 'README.md'), target: 'README.md' },
|
||||
{ source: path.join(repoRoot, 'docs', 'MIGRATION_UNIFIED_NAV.md'), target: 'MIGRATION_UNIFIED_NAV.md' },
|
||||
{
|
||||
source: path.join(repoRoot, 'docs', 'MIGRATION_UNIFIED_NAV.md'),
|
||||
target: 'MIGRATION_UNIFIED_NAV.md',
|
||||
},
|
||||
{ source: path.join(repoRoot, 'docs', 'PRIVACY.md'), target: 'PRIVACY.md' },
|
||||
{ source: path.join(repoRoot, 'docs', 'CONFIGURATION.md'), target: 'CONFIGURATION.md' },
|
||||
{ source: path.join(repoRoot, 'docs', 'UNIFIED_AGENT.md'), target: 'UNIFIED_AGENT.md' },
|
||||
{ source: path.join(repoRoot, 'docs', 'PROXY_AUTH.md'), target: 'PROXY_AUTH.md' },
|
||||
{ source: path.join(repoRoot, 'docs', 'TRUENAS.md'), target: 'TRUENAS.md' },
|
||||
{ source: path.join(repoRoot, 'SECURITY.md'), target: 'SECURITY.md' },
|
||||
|
||||
@@ -270,10 +270,17 @@ export const BackupTransferDialogs: Component<BackupTransferDialogsProps> = (pro
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-800 rounded p-3">
|
||||
<p class="text-xs text-yellow-700 dark:text-yellow-300">
|
||||
<strong>Warning:</strong> Importing will replace all current configuration. This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
<div class="space-y-2 text-xs text-yellow-700 dark:text-yellow-300">
|
||||
<p>
|
||||
<strong>Warning:</strong> Importing will replace all current configuration. This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Agent migration:</strong> The backup restores server-side agent records
|
||||
and credentials, but it cannot change the Pulse URL stored on remote agents. If
|
||||
this server has a different address, retarget each agent after the import.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { cleanup, render, screen } from '@solidjs/testing-library';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { BackupTransferDialogs } from '../BackupTransferDialogs';
|
||||
|
||||
describe('BackupTransferDialogs', () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('discloses that configuration import cannot retarget remote agents', () => {
|
||||
render(() => (
|
||||
<BackupTransferDialogs
|
||||
securityStatus={() => null}
|
||||
exportPassphrase={() => ''}
|
||||
setExportPassphrase={vi.fn()}
|
||||
useCustomPassphrase={() => false}
|
||||
setUseCustomPassphrase={vi.fn()}
|
||||
importPassphrase={() => 'secure-passphrase'}
|
||||
setImportPassphrase={vi.fn()}
|
||||
importFile={() => null}
|
||||
setImportFile={vi.fn()}
|
||||
showExportDialog={() => false}
|
||||
showImportDialog={() => true}
|
||||
showApiTokenModal={() => false}
|
||||
apiTokenInput={() => ''}
|
||||
setApiTokenInput={vi.fn()}
|
||||
handleExport={vi.fn()}
|
||||
handleImport={vi.fn()}
|
||||
closeExportDialog={vi.fn()}
|
||||
closeImportDialog={vi.fn()}
|
||||
closeApiTokenModal={vi.fn()}
|
||||
handleApiTokenAuthenticate={vi.fn()}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.getByText('Agent migration:')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/it cannot change the Pulse URL stored on remote agents/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -207,5 +207,9 @@ func (h *ConfigHandlers) handleImportConfig(w http.ResponseWriter, r *http.Reque
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "success",
|
||||
"message": "Configuration imported successfully",
|
||||
"remoteAgentEndpoints": map[string]bool{
|
||||
"reconfigured": false,
|
||||
"actionRequiredIfServerAddressChanged": true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -174,6 +174,16 @@ func TestHandleImportConfig(t *testing.T) {
|
||||
if resp["status"] != "success" {
|
||||
t.Errorf("expected status success, got %v", resp["status"])
|
||||
}
|
||||
remoteEndpoints, ok := resp["remoteAgentEndpoints"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected remote agent endpoint import disclosure, got %T", resp["remoteAgentEndpoints"])
|
||||
}
|
||||
if remoteEndpoints["reconfigured"] != false {
|
||||
t.Errorf("configuration import must not claim to reconfigure remote agents: %v", remoteEndpoints)
|
||||
}
|
||||
if remoteEndpoints["actionRequiredIfServerAddressChanged"] != true {
|
||||
t.Errorf("expected changed-address action disclosure, got %v", remoteEndpoints)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+26
-11
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user