mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Fix portable root ownership for installer lifecycle state
Issue #1890 reports macOS agent updates stopping because the root group does not exist. Use numeric superuser ownership in the two shared lifecycle writes without relaxing failure handling or the least-privilege group boundary. Add a regression fixture that rejects named root ownership and checks that chown failures still prevent replacement. Change-source: pulse-maintainer
This commit is contained in:
@@ -15,6 +15,21 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
### Portable installer lifecycle ownership
|
||||
|
||||
The shared shell installer lifecycle directory (outside the least-privilege
|
||||
profile) and atomic lifecycle-file staging use numeric UID/GID `0:0` when
|
||||
running as root. They must not depend on a group named `root`, which is absent
|
||||
on macOS. The least-privilege directory retains its dedicated collector group
|
||||
and 0750 mode; ordinary root lifecycle directories retain 0700 mode.
|
||||
Ownership failure aborts directory preparation or file installation before
|
||||
replacement of the saved installer. The runtime shell fixture
|
||||
`TestInstallSHLifecyclePortableRootOwnership` in
|
||||
`scripts/installtests/install_sh_test.go` rejects named ownership and proves
|
||||
that a failed chown leaves the previously saved installer intact. This fixture
|
||||
is not native macOS upgrade qualification.
|
||||
|
||||
|
||||
`models.PBSBackup` may carry monitoring-owned write-activity evidence for an
|
||||
incomplete PBS snapshot. `inProgress` means the snapshot has no completed
|
||||
manifest; `writeActivityObserved` and `writeActive` distinguish a successfully
|
||||
|
||||
@@ -15,6 +15,21 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
### Portable installer lifecycle ownership
|
||||
|
||||
The shared shell installer lifecycle directory (outside the least-privilege
|
||||
profile) and atomic lifecycle-file staging use numeric UID/GID `0:0` when
|
||||
running as root. They must not depend on a group named `root`, which is absent
|
||||
on macOS. The least-privilege directory retains its dedicated collector group
|
||||
and 0750 mode; ordinary root lifecycle directories retain 0700 mode.
|
||||
Ownership failure aborts directory preparation or file installation before
|
||||
replacement of the saved installer. The runtime shell fixture
|
||||
`TestInstallSHLifecyclePortableRootOwnership` in
|
||||
`scripts/installtests/install_sh_test.go` rejects named ownership and proves
|
||||
that a failed chown leaves the previously saved installer intact. This fixture
|
||||
is not native macOS upgrade qualification.
|
||||
|
||||
|
||||
Own server installation, deployment bootstrap behavior, provider-hosted MSP
|
||||
deployment artifacts, update planning, and server-side update execution
|
||||
surfaces.
|
||||
|
||||
+3
-2
@@ -3099,7 +3099,8 @@ prepare_installer_lifecycle_dir() {
|
||||
chown "root:${LEAST_PRIVILEGE_USER}" "$lifecycle_dir" || return 1
|
||||
chmod 0750 "$lifecycle_dir" || return 1
|
||||
else
|
||||
chown root:root "$lifecycle_dir" || return 1
|
||||
# Numeric IDs also work on macOS, whose group 0 is wheel, not root.
|
||||
chown 0:0 "$lifecycle_dir" || return 1
|
||||
chmod 0700 "$lifecycle_dir" || return 1
|
||||
fi
|
||||
else
|
||||
@@ -3123,7 +3124,7 @@ install_lifecycle_file_atomically() {
|
||||
cp "$source_path" "$target_tmp" || return 1
|
||||
chmod "$target_mode" "$target_tmp" || return 1
|
||||
if [[ "$(id -u)" == "0" ]]; then
|
||||
chown root:root "$target_tmp" || return 1
|
||||
chown 0:0 "$target_tmp" || return 1
|
||||
fi
|
||||
sync_lifecycle_path "$target_tmp" || return 1
|
||||
mv -f "$target_tmp" "$target_path" || return 1
|
||||
|
||||
@@ -7885,3 +7885,32 @@ func TestInstallSHTypedPrivilegedHelperProtectsCredentialsAfterStateChown(t *tes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// macOS has no root group. Exercise both shared lifecycle writes with a
|
||||
// chown stub that accepts only portable numeric superuser ownership.
|
||||
func TestInstallSHLifecyclePortableRootOwnership(t *testing.T) {
|
||||
script := `set -euo pipefail
|
||||
LEAST_PRIVILEGE=false
|
||||
TMP_FILES=()
|
||||
id() { echo 0; }
|
||||
chown() { [[ "$1" == "0:0" ]]; }
|
||||
sync_lifecycle_path() { :; }
|
||||
` + extractInstallShellFunction(t, "prepare_installer_lifecycle_dir") + "\n" +
|
||||
extractInstallShellFunction(t, "install_lifecycle_file_atomically") + `
|
||||
prepare_installer_lifecycle_dir "$PWD/state"
|
||||
printf 'saved installer\n' > "$PWD/source"
|
||||
install_lifecycle_file_atomically "$PWD/source" "$PWD/state/install.sh" 700
|
||||
cmp "$PWD/source" "$PWD/state/install.sh"
|
||||
# A failed ownership operation must still prevent replacement.
|
||||
chown() { return 1; }
|
||||
if prepare_installer_lifecycle_dir "$PWD/rejected"; then exit 10; fi
|
||||
printf 'replacement\n' > "$PWD/source"
|
||||
if install_lifecycle_file_atomically "$PWD/source" "$PWD/state/install.sh" 700; then exit 11; fi
|
||||
grep -qx 'saved installer' "$PWD/state/install.sh"
|
||||
`
|
||||
cmd := exec.Command("bash", "-c", script)
|
||||
cmd.Dir = t.TempDir()
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("lifecycle ownership: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user