Files
pulse/scripts/tests/test_server_installer_temporary_inputs.py
T
pulse-triage[bot] 843b8d82b6 Keep server installer inputs private and authenticated
Keep archive and sidecar downloads in owner-only staging, clean only owned leaves after handled failures and bootstrap exit, and preserve caller-provided inputs. Require the piped LXC installer signature before copying or execution instead of trusting PID-named files in shared tmp.

Exercise the actual download, prefetch and complete container bootstrap through confined offline doubles and real fixture SSH signatures. Preserve version, archive re-verification, staging-before-stop, service recovery, timer consent and removal boundaries; native acceptance remains separate.

Change-source: pulse-maintainer
2026-10-09 10:17:47 +01:00

347 lines
16 KiB
Python

#!/usr/bin/env python3
"""Actual installer staging functions, confined to ordinary-user fixtures.
Transport and service boundaries are doubles; signature verification is real.
Directory modes establish the staging boundary, not a native root/LXC proof.
"""
import os
from pathlib import Path
import stat
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[2]
INSTALLER = Path(os.environ.get("PULSE_INSTALLER_UNDER_TEST", ROOT / "install.sh"))
HARNESS = r'''
source "$INSTALLER"
print_info() { :; }
print_success() { :; }
print_warn() { printf '%s\n' "$*" >&2; }
print_error() { printf '%s\n' "$*" >&2; }
mktemp() {
[[ "$FAULT" != allocation ]] || return 1
local template="${@: -1}" created
local -a args=("$@")
args[${#args[@]}-1]="$FIXTURE/shared/${template##*/}"
created=$(command mktemp "${args[@]}") || return 1
printf '%s\n' "$created" >> "$FIXTURE/created"
if [[ -d "$created" ]]; then
stat -c '%a' "$created" >> "$FIXTURE/directory-modes"
fi
printf '%s\n' "$created"
}
timeout() { shift; "$@"; }
sleep() { :; }
curl() {
local url="${@: -1}"
printf '%s\n' "$url" >> "$FIXTURE/transport"
case "$url" in
https://github.com/rcourtman/Pulse/releases/download/v6.6.0/install.sh)
cat "$FIXTURE/installer-input" || return 1
[[ "$FAULT" != script_interrupt ]] || kill -TERM "$$"
[[ "$FAULT" != script_transport ]] ;;
https://github.com/rcourtman/Pulse/releases/download/v6.6.0/install.sh.sshsig)
cat "$FIXTURE/installer-input.sig" || return 1
[[ "$FAULT" != signature_transport ]] ;;
*) return 97 ;;
esac
}
resolve_install_script_download_url() {
[[ "$FAULT" != metadata ]] || return 1
echo https://github.com/rcourtman/Pulse/releases/download/v6.6.0/install.sh
}
PINNED_RELEASE_SSH_PUBLIC_KEY=$(cat "$FIXTURE/signing-key.pub")
if [[ "$FAULT" == verifier ]]; then
command() {
[[ "$*" != '-v ssh-keygen' ]] || return 1
builtin command "$@"
}
fi
INSTALL_DIR="$FIXTURE/install"
BUILD_FROM_SOURCE_MARKER="$INSTALL_DIR/BUILD_FROM_SOURCE"
CURRENT_VERSION=v6.5.0
LATEST_RELEASE=v6.6.0
detect_pulse_architecture() { echo amd64; }
ensure_update_disk_headroom() { return 0; }
resolve_target_release() { :; }
download_release_archive() {
printf '%s\n' "$3" > "$FIXTURE/archive-path"
printf 'archive fixture' > "$3"
printf 'signature fixture' > "${3}.sshsig"
[[ "$FAULT" != archive_transport ]]
}
run_upgrade_readiness_preflight() { [[ "$FAULT" != readiness ]]; }
install_pulse_archive() {
echo INSTALL >> "$FIXTURE/calls"
[[ "$FAULT" != archive_admission ]]
}
# Exercise the complete production bootstrap too; no pct, package, service,
# registration, bridge or guest command below can reach a real host/guest.
print_header() { :; }
cleanup_stale_sensor_proxy_mounts() { :; }
safe_read_with_default() { printf -v "$2" '%s' "$3"; }
detect_network_bridges() { echo vmbr0; }
ip() { printf 'default via 198.51.100.1 dev vmbr0\n'; }
ensure_debian_template() { DEBIAN_TEMPLATE=fixture.tar.zst; }
pvesh() { echo 600; }
qm() { return 1; }
pvesm() { printf 'Name Type Status Total Used Available\nlocal dir active 99999999 1 99999998\n'; }
pveam() { printf 'NAME SIZE\nlocal:vztmpl/fixture.tar.zst 1\n'; }
auto_register_pve_node() { :; }
wait_for_pulse_ready() { :; }
pct() {
case "$1" in
create|start|stop|destroy) return 0 ;;
push)
printf 'PUSH=%s\n' "$4" >> "$FIXTURE/calls"
[[ -f "$3" ]] || return 1
[[ "$FAULT" != script_copy || "$4" != /tmp/install.sh ]] || return 1
if [[ "$4" == /tmp/install.sh ]]; then
cp "$3" "$FIXTURE/copied-script"
fi
return 0 ;;
exec)
if [[ "$*" == *'hostname -I'* ]]; then
echo 198.51.100.2
elif [[ "$*" == *'bash /tmp/install.sh'* ]]; then
echo EXECUTE >> "$FIXTURE/calls"
fi
return 0 ;;
*) return 97 ;;
esac
}
umask "$MASK"
case "$FLOW" in
reserve)
path=$(create_temp_archive_path "$FIXTURE/shared/pulse-v6.6.0-linux-amd64")
printf '%s\n' "$path" > "$FIXTURE/archive-path"
infer_release_from_archive_name "$path" > "$FIXTURE/inferred"
;;
cleanup)
path=$(create_temp_archive_path "$FIXTURE/shared/pulse-v6.6.0-linux-amd64")
printf '%s\n' "$path" > "$FIXTURE/archive-path"
printf archive > "$path"
printf signature > "${path}.sshsig"
[[ "$FAULT" != extra_file ]] || touch "$(dirname "$path")/unowned"
cleanup_temp_archive_path "$path"
;;
download) download_pulse ;;
override)
ARCHIVE_OVERRIDE="$FIXTURE/pulse-v6.6.0-linux-amd64.tar.gz"
FORCE_VERSION=v6.6.0
download_pulse
;;
prefetch)
prefetched=""
prefetch_pulse_archive_for_container prefetched
printf '%s\n' "$prefetched" > "$FIXTURE/returned"
;;
bootstrap)
script_source=""
container_script_temp_dir=""
trap 'cleanup_container_install_inputs "${container_script_temp_dir:-}" "" false' EXIT
download_container_installer script_source container_script_temp_dir
printf '%s\n' "$script_source" > "$FIXTURE/returned"
cp "$script_source" "$FIXTURE/copied-script"
;;
interrupted_bootstrap)
script_source=""
container_script_temp_dir=""
trap 'cleanup_container_install_inputs "${container_script_temp_dir:-}" "" false' EXIT
trap 'exit 143' TERM
download_container_installer script_source container_script_temp_dir
kill -TERM "$$"
;;
container_cleanup)
container_archive_source=""
prefetch_pulse_archive_for_container container_archive_source
script_source=""
container_script_temp_dir=""
trap 'cleanup_container_install_inputs "${container_script_temp_dir:-}" "$container_archive_source" true' EXIT
download_container_installer script_source container_script_temp_dir
exit 42
;;
connected_container) create_lxc_container ;;
local_container)
ARCHIVE_OVERRIDE="$FIXTURE/pulse-v6.6.0-linux-amd64.tar.gz"
create_lxc_container
;;
esac
'''
class ServerInstallerTemporaryInputsTest(unittest.TestCase):
def exercise(self, flow, fault="", mask="000"):
with tempfile.TemporaryDirectory() as temporary:
fixture = Path(temporary)
(fixture / "shared").mkdir(mode=0o777)
(fixture / "shared").chmod(0o1777)
(fixture / "install").mkdir()
(fixture / "install/BUILD_FROM_SOURCE").write_text("old marker")
(fixture / "installer-input").write_text("#!/bin/bash\n# inert signed fixture\n")
key = fixture / "signing-key"
subprocess.run(["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(key)],
check=True, capture_output=True)
subprocess.run(["ssh-keygen", "-q", "-Y", "sign", "-f", str(key),
"-n", "pulse-install", str(fixture / "installer-input")],
check=True, capture_output=True)
if fault == "bad_signature":
(fixture / "installer-input").write_text("unsigned changed fixture\n")
if fault == "missing_signature":
(fixture / "installer-input.sig").unlink()
override = fixture / "pulse-v6.6.0-linux-amd64.tar.gz"
override.write_bytes(b"caller archive")
Path(str(override) + ".sshsig").write_bytes(b"caller signature")
env = {**os.environ, "INSTALLER": str(INSTALLER), "FIXTURE": str(fixture),
"FLOW": flow, "FAULT": fault, "MASK": mask}
command = ["bash", "-c", HARNESS]
if flow == "local_container":
command.append(str(fixture / "installer-input"))
result = subprocess.run(command, env=env, text=True,
capture_output=True, timeout=15)
observed = {}
for name in ("archive-path", "returned", "inferred", "calls", "transport", "directory-modes"):
p = fixture / name
observed[name] = p.read_text().splitlines() if p.exists() else []
archive_path = Path(observed["archive-path"][0]) if observed["archive-path"] else None
observed["archive_parent_is_private"] = bool(archive_path and archive_path.parent != fixture / "shared"
and archive_path.parent.is_dir() and stat.S_IMODE(archive_path.parent.stat().st_mode) == 0o700)
observed["shared_entries"] = sorted(p.name for p in (fixture / "shared").iterdir())
observed["unowned"] = sorted(str(p.relative_to(fixture / "shared"))
for p in (fixture / "shared").rglob("unowned"))
observed["copied"] = (fixture / "copied-script").read_bytes() if (fixture / "copied-script").exists() else None
observed["caller-script"] = (fixture / "installer-input").read_bytes() if (fixture / "installer-input").exists() else None
observed["override"] = (override.read_bytes(), Path(str(override) + ".sshsig").read_bytes())
return result, observed
def test_archive_and_sidecar_reserve_a_private_parent(self):
for mask in ("000", "022", "077"):
with self.subTest(umask=mask):
r, o = self.exercise("reserve", mask=mask)
self.assertEqual(r.returncode, 0, r.stderr)
self.assertTrue(o["archive_parent_is_private"], o)
self.assertEqual(o["inferred"], ["v6.6.0"])
def test_allocation_failure_cannot_return_an_unreserved_path(self):
r, o = self.exercise("reserve", "allocation")
self.assertNotEqual(r.returncode, 0)
self.assertEqual(o["archive-path"], [])
self.assertEqual(o["shared_entries"], [])
self.assertEqual(o["directory-modes"], [])
def test_cleanup_removes_archive_sidecar_and_owned_empty_directory(self):
r, o = self.exercise("cleanup")
self.assertEqual(r.returncode, 0, r.stderr)
self.assertEqual(o["shared_entries"], [])
def test_bootstrap_staging_is_private_even_with_a_permissive_umask(self):
for mask in ("000", "022", "077"):
with self.subTest(umask=mask):
r, o = self.exercise("bootstrap", mask=mask)
self.assertEqual(r.returncode, 0, r.stderr)
self.assertEqual(o["directory-modes"], ["700"])
def test_cleanup_never_recursively_erases_an_unexpected_file(self):
r, o = self.exercise("cleanup", "extra_file")
self.assertEqual(r.returncode, 0, r.stderr)
self.assertEqual(len(o["unowned"]), 1, o)
def test_download_success_and_each_failure_clean_owned_inputs(self):
for fault in ("", "archive_transport", "readiness", "archive_admission", "allocation"):
with self.subTest(fault=fault):
r, o = self.exercise("download", fault)
self.assertEqual(r.returncode, 1 if fault else 0, r.stderr)
self.assertEqual(o["shared_entries"], [], o)
self.assertEqual(o["calls"], ["INSTALL"] if fault in ("", "archive_admission") else [])
def test_explicit_archive_and_sidecar_are_never_owned_or_deleted(self):
for fault in ("", "readiness", "archive_admission"):
with self.subTest(fault=fault):
r, o = self.exercise("override", fault)
self.assertEqual(r.returncode, 1 if fault else 0, r.stderr)
self.assertEqual(o["override"], (b"caller archive", b"caller signature"))
self.assertEqual(o["shared_entries"], [])
def test_prefetch_retains_private_archive_until_handoff(self):
r, o = self.exercise("prefetch")
self.assertEqual(r.returncode, 0, r.stderr)
self.assertTrue(o["archive_parent_is_private"], o)
self.assertEqual(o["returned"], o["archive-path"])
def test_prefetch_failure_releases_its_private_inputs(self):
r, o = self.exercise("prefetch", "archive_transport")
self.assertNotEqual(r.returncode, 0)
self.assertEqual(o["shared_entries"], [])
def test_bootstrap_checks_real_signature_before_copy(self):
r, o = self.exercise("bootstrap")
self.assertEqual(r.returncode, 0, r.stderr)
self.assertEqual(o["copied"], b"#!/bin/bash\n# inert signed fixture\n")
self.assertEqual(o["caller-script"], b"#!/bin/bash\n# inert signed fixture\n")
self.assertEqual(o["transport"], [
"https://github.com/rcourtman/Pulse/releases/download/v6.6.0/install.sh",
"https://github.com/rcourtman/Pulse/releases/download/v6.6.0/install.sh.sshsig"])
self.assertEqual(o["shared_entries"], [])
def test_bootstrap_fails_closed_and_cleans_every_owned_input(self):
for fault in ("metadata", "verifier", "allocation", "script_transport",
"signature_transport", "missing_signature", "bad_signature"):
with self.subTest(fault=fault):
r, o = self.exercise("bootstrap", fault)
self.assertNotEqual(r.returncode, 0, r.stdout + r.stderr)
self.assertIsNone(o["copied"])
self.assertEqual(o["shared_entries"], [], o)
if fault in ("metadata", "verifier", "allocation"):
self.assertEqual(o["transport"], [])
def test_interrupted_bootstrap_cleans_inputs_without_a_copy(self):
r, o = self.exercise("interrupted_bootstrap")
self.assertEqual(r.returncode, 143, r.stderr)
self.assertIsNone(o["copied"])
self.assertEqual(o["shared_entries"], [])
def test_container_failure_cleans_both_owned_staging_directories(self):
r, o = self.exercise("container_cleanup")
self.assertEqual(r.returncode, 42, r.stderr)
self.assertEqual(o["shared_entries"], [], o)
def test_production_container_bootstrap_copies_only_the_verified_installer(self):
r, o = self.exercise("connected_container")
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
self.assertEqual(o["copied"], b"#!/bin/bash\n# inert signed fixture\n")
self.assertIn("EXECUTE", o["calls"])
self.assertEqual(o["shared_entries"], [])
def test_production_container_never_copies_invalid_or_unavailable_signed_input(self):
for fault in ("bad_signature", "script_transport", "signature_transport", "missing_signature", "script_interrupt"):
with self.subTest(fault=fault):
r, o = self.exercise("connected_container", fault)
self.assertEqual(r.returncode, 1, r.stdout + r.stderr)
self.assertIsNone(o["copied"])
self.assertNotIn("EXECUTE", o["calls"])
self.assertEqual(o["shared_entries"], [])
def test_production_container_failed_script_copy_cleans_prefetched_inputs_too(self):
r, o = self.exercise("connected_container", "script_copy")
self.assertEqual(r.returncode, 1, r.stdout + r.stderr)
self.assertIsNone(o["copied"])
self.assertNotIn("EXECUTE", o["calls"])
self.assertEqual(o["shared_entries"], [])
def test_production_local_installer_and_archive_remain_caller_owned(self):
r, o = self.exercise("local_container")
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
self.assertEqual(o["transport"], [])
self.assertEqual(o["override"], (b"caller archive", b"caller signature"))
self.assertEqual(o["copied"], b"#!/bin/bash\n# inert signed fixture\n")
self.assertEqual(o["caller-script"], b"#!/bin/bash\n# inert signed fixture\n")
self.assertEqual(o["shared_entries"], [])
if __name__ == "__main__":
unittest.main()