mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Remove safe profile state on uninstall
This commit is contained in:
@@ -7331,7 +7331,8 @@ as removed, rejects that bearer thereafter, and returns the matching agent ID;
|
||||
the following legacy-migration phase uses a distinct replacement enrollment
|
||||
credential rather than resurrecting the removed one. Final cleanup must commit
|
||||
and verify the replacement binding's removal too, then remove the fixed
|
||||
root-owned helper state boundary.
|
||||
root-owned helper and safe-profile transaction state boundaries through exact
|
||||
path-authorized deletion.
|
||||
The wrapper exercises each runtime in an isolated state root and emits the
|
||||
standalone `secure-runtime-rootless-v1` receipt only after exact socket
|
||||
ownership, daemon rootless attestation, installer pinning, direct telemetry,
|
||||
|
||||
@@ -4426,9 +4426,10 @@ redirect-denying CA/fingerprint lifecycle client before deleting services,
|
||||
credentials, or recovery state. An unreachable or untrusted server is a
|
||||
repair-required uninstall, not permission for local-only credential loss.
|
||||
Once server removal is confirmed, full shell uninstall must also remove the
|
||||
fixed root-owned privileged-helper activation/staging state. That recursive
|
||||
cleanup requires an exact installer-established helper lifecycle authority;
|
||||
uncertain or mismatched paths remain intact for explicit repair.
|
||||
fixed root-owned privileged-helper activation/staging state and safe-profile
|
||||
transaction archive. Those recursive cleanups require exact
|
||||
installer-established helper and profile lifecycle authorities; uncertain or
|
||||
mismatched paths remain intact for explicit repair.
|
||||
TrueNAS boot recovery must follow the same rule: SCALE and CORE bootstrap
|
||||
scripts may differ only in their service-manager adapter, while binary sync,
|
||||
service-link recreation, and boot-time start flow stay on one installer-owned
|
||||
|
||||
@@ -195,6 +195,7 @@ TMP_ACTION_RUNNER_BIN=""
|
||||
# leaves these unset and therefore never enters a migration transaction.
|
||||
SAFE_PROFILE_ACTION=""
|
||||
SAFE_PROFILE_STATE_DIR="/var/lib/pulse-agent-profile"
|
||||
SAFE_PROFILE_STATE_DIR_REMOVAL_AUTHORITY="$SAFE_PROFILE_STATE_DIR"
|
||||
SAFE_PROFILE_CURRENT_FILE="${SAFE_PROFILE_STATE_DIR}/current.env"
|
||||
SAFE_PROFILE_COLLECTOR_UNIT="/etc/systemd/system/${AGENT_NAME}.service"
|
||||
SAFE_PROFILE_TRANSACTION_DIR=""
|
||||
@@ -3206,6 +3207,10 @@ remove_privileged_helper_state_dir() {
|
||||
remove_authorized_runtime_dir "privileged helper state" "$PRIVILEGED_HELPER_STATE_DIR" "${PRIVILEGED_HELPER_STATE_DIR_REMOVAL_AUTHORITY:-}"
|
||||
}
|
||||
|
||||
remove_safe_profile_state_dir() {
|
||||
remove_authorized_runtime_dir "safe-profile state" "$SAFE_PROFILE_STATE_DIR" "${SAFE_PROFILE_STATE_DIR_REMOVAL_AUTHORITY:-}"
|
||||
}
|
||||
|
||||
detect_qnap_data_volume() {
|
||||
local qnap_vol=""
|
||||
local candidate=""
|
||||
@@ -5542,6 +5547,9 @@ if [[ "$UNINSTALL" == "true" ]]; then
|
||||
if ! remove_privileged_helper_state_dir; then
|
||||
log_warn "Retained privileged helper state at ${PRIVILEGED_HELPER_STATE_DIR}; its path was not authorized by the fixed helper lifecycle boundary."
|
||||
fi
|
||||
if ! remove_safe_profile_state_dir; then
|
||||
log_warn "Retained safe-profile state at ${SAFE_PROFILE_STATE_DIR}; its path was not authorized by the fixed profile lifecycle boundary."
|
||||
fi
|
||||
|
||||
# Remove least-privilege helper artifacts. The pulse-agent system user is
|
||||
# deliberately left behind: deleting accounts can orphan files elsewhere,
|
||||
|
||||
@@ -4055,7 +4055,53 @@ func TestInstallSHPrivilegedHelperStateRemovalRequiresExactLifecycleAuthority(t
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallSHFullUninstallRemovesPrivilegedHelperStateOnlyAfterServerConfirmation(t *testing.T) {
|
||||
func TestInstallSHSafeProfileStateRemovalRequiresExactLifecycleAuthority(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
authority func(string) string
|
||||
wantRemove bool
|
||||
}{
|
||||
{name: "exact fixed boundary", authority: func(path string) string { return path }, wantRemove: true},
|
||||
{name: "mismatched boundary", authority: func(path string) string { return path + "-other" }, wantRemove: false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
profileState := filepath.Join(t.TempDir(), "pulse-agent-profile")
|
||||
if err := os.MkdirAll(profileState, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(profileState, "current.env"), []byte("retained-or-removed"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
script := `
|
||||
set -u
|
||||
SAFE_PROFILE_STATE_DIR="` + profileState + `"
|
||||
SAFE_PROFILE_STATE_DIR_REMOVAL_AUTHORITY="` + tc.authority(profileState) + `"
|
||||
log_warn() { :; }
|
||||
` + extractInstallShellFunction(t, "remove_authorized_runtime_dir") + `
|
||||
` + extractInstallShellFunction(t, "remove_safe_profile_state_dir") + `
|
||||
if remove_safe_profile_state_dir; then
|
||||
printf 'removed\n'
|
||||
else
|
||||
printf 'retained\n'
|
||||
fi
|
||||
`
|
||||
out, err := exec.Command("bash", "-c", script).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("safe-profile-state cleanup harness: %v\n%s", err, out)
|
||||
}
|
||||
_, statErr := os.Stat(profileState)
|
||||
if tc.wantRemove {
|
||||
if !os.IsNotExist(statErr) || string(out) != "removed\n" {
|
||||
t.Fatalf("authorized safe-profile state survived: stat=%v output=%q", statErr, out)
|
||||
}
|
||||
} else if statErr != nil || string(out) != "retained\n" {
|
||||
t.Fatalf("unauthorized safe-profile state changed: stat=%v output=%q", statErr, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallSHFullUninstallRemovesRuntimeStateOnlyAfterServerConfirmation(t *testing.T) {
|
||||
content, err := os.ReadFile(repoFile("scripts", "install.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read install.sh: %v", err)
|
||||
@@ -4063,11 +4109,12 @@ func TestInstallSHFullUninstallRemovesPrivilegedHelperStateOnlyAfterServerConfir
|
||||
script := string(content)
|
||||
confirmation := strings.LastIndex(script, `if ! uninstall_collector_registration; then`)
|
||||
helperCleanup := strings.LastIndex(script, `if ! remove_privileged_helper_state_dir; then`)
|
||||
if confirmation < 0 || helperCleanup < 0 {
|
||||
t.Fatalf("full uninstall lifecycle is missing server confirmation or helper-state cleanup: confirmation=%d cleanup=%d", confirmation, helperCleanup)
|
||||
profileCleanup := strings.LastIndex(script, `if ! remove_safe_profile_state_dir; then`)
|
||||
if confirmation < 0 || helperCleanup < 0 || profileCleanup < 0 {
|
||||
t.Fatalf("full uninstall lifecycle is missing server confirmation or fixed-state cleanup: confirmation=%d helper=%d profile=%d", confirmation, helperCleanup, profileCleanup)
|
||||
}
|
||||
if helperCleanup <= confirmation {
|
||||
t.Fatal("privileged helper state cleanup can run before authenticated server removal")
|
||||
if helperCleanup <= confirmation || profileCleanup <= confirmation {
|
||||
t.Fatal("fixed privileged runtime state cleanup can run before authenticated server removal")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user