mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 21:47:20 +00:00
491e872adb
The checkout label alone cannot establish source bytes after frontend preparation. Require an unchanged tracked tree before compiling the private pair; the exact recipe control now rejects dirty source before either compiler or root command and preserves all existing failure propagation. Contract-Neutral: Implements existing exact-source admission on tracked bytes; native runtime, input and completion contracts are unchanged. Change-source: pulse-maintainer
348 lines
22 KiB
Python
348 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
"""Discriminating controls for the native CI recipe; no host manager/root use."""
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SPEC = importlib.util.spec_from_file_location("native_updater", ROOT / "scripts/installtests/auto_update_systemd.py")
|
|
native = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(native)
|
|
WORKFLOW = ROOT / ".github/workflows/build-and-test.yml"
|
|
STEP = "Check current-source unattended recovery on native systemd"
|
|
|
|
|
|
def recipe(source):
|
|
job = re.search(r"(?ms)^ frontend:\n(.*?)(?=^ [a-z][a-z0-9-]*:|\Z)", source)[1]
|
|
steps = dict(re.findall(r"(?ms)^ - name: ([^\n]+)\n(.*?)(?=^ - name:|\Z)", job))
|
|
order = list(steps)
|
|
assert order.index("Verify backend with real frontend embed") < order.index(STEP) < order.index("Require frontend dependency audit")
|
|
step = steps[STEP]
|
|
assert not re.search(r"^ (if|continue-on-error|working-directory):", step, re.M)
|
|
assert "runs-on: ubuntu-24.04" in job and "timeout-minutes: 30" in job
|
|
assert "persist-credentials: false" in steps["Checkout repository"]
|
|
assert "EXPECTED_SOURCE_SHA: ${{ github.sha }}" in step
|
|
shell = re.search(r"(?ms)^ run: \|\n((?: [^\n]*\n)+)", step)[1]
|
|
shell = "\n".join(line[10:] for line in shell.splitlines()) + "\n"
|
|
for command in ('test "$(git rev-parse HEAD)" = "$EXPECTED_SOURCE_SHA"',
|
|
"git diff --quiet HEAD",
|
|
"diff -qr --no-dereference frontend-modern/dist internal/api/frontend-modern/dist",
|
|
"-ldflags='-X main.Version=v0.0.1'", "-ldflags='-X main.Version=v0.0.2'",
|
|
"go build -mod=readonly -p=2", "sudo env -i",
|
|
"GITHUB_ACTIONS=true RUNNER_ENVIRONMENT=github-hosted GITHUB_REPOSITORY=rcourtman/Pulse",
|
|
"python3 scripts/installtests/auto_update_systemd.py",
|
|
'--receipt "$RUNNER_TEMP/pulse-native-updater.json"'):
|
|
assert command in shell, command
|
|
assert "GH_TOKEN" not in shell and "secrets." not in step
|
|
retention = steps["Retain bounded native updater receipt"]
|
|
assert "if: always()" in retention and "retention-days: 7" in retention
|
|
assert "path: ${{ runner.temp }}/pulse-native-updater.json" in retention
|
|
assert "*.log" not in retention and "pulse-native-before" not in retention
|
|
return shell
|
|
|
|
|
|
class NativeContracts(unittest.TestCase):
|
|
def test_definition_boundary_is_exact_and_rejects_drift(self):
|
|
source = (ROOT / "install.sh").read_text()
|
|
definitions = native.installer_definitions(source)
|
|
self.assertEqual(definitions, source[:source.index(native.PARSE_MARKER)])
|
|
for changed in (source.replace(native.PARSE_MARKER, "# moved\n"),
|
|
source.replace(native.PARSE_MARKER, native.PARSE_MARKER * 2),
|
|
source.replace(native.PARSE_MARKER, native.PARSE_MARKER + "echo different entry\n")):
|
|
with self.assertRaises(ValueError): native.installer_definitions(changed)
|
|
|
|
def test_host_admission_requires_fresh_public_vm_not_worker_or_container(self):
|
|
env = dict(GITHUB_ACTIONS="true", RUNNER_ENVIRONMENT="github-hosted", GITHUB_REPOSITORY="rcourtman/Pulse")
|
|
with patch.object(Path, "exists", return_value=False):
|
|
native.hosted_admission(env, 0, "a" * 40)
|
|
for changed, uid, sha in ((env, 1000, "a" * 40), ({}, 0, "a" * 40),
|
|
({**env, "RUNNER_ENVIRONMENT": "self-hosted"}, 0, "a" * 40),
|
|
({**env, "GITHUB_REPOSITORY": "other/repo"}, 0, "a" * 40),
|
|
(env, 0, "main")):
|
|
with self.assertRaises(ValueError): native.hosted_admission(changed, uid, sha)
|
|
with patch.object(Path, "exists", return_value=True):
|
|
with self.assertRaises(ValueError): native.hosted_admission(env, 0, "a" * 40)
|
|
|
|
def test_transport_and_private_allocator_have_no_online_fallback(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory); (root / "tmp").mkdir()
|
|
script = root / "curl"; script.write_text(native.TRANSPORT); script.chmod(0o755)
|
|
(root / "installer").write_text("signed fixture\n")
|
|
(root / "installer.sshsig").write_text("signature sidecar\n")
|
|
env = {**os.environ, "PULSE_NATIVE_ROOT": str(root)}
|
|
for tag in ("latest/download", "download/v0.0.1", "download/v0.0.2"):
|
|
for suffix in ("", ".sshsig"):
|
|
result = subprocess.run([str(script), "-fsSL", f"https://github.com/rcourtman/Pulse/releases/{tag}/install.sh{suffix}"], env=env, capture_output=True)
|
|
self.assertEqual(result.returncode, 0)
|
|
self.assertEqual(result.stdout, (root / ("installer" + suffix)).read_bytes())
|
|
for url in ("https://example.com/install.sh", "https://github.com/rcourtman/Pulse/releases/download/v6.5.0/install.sh"):
|
|
result = subprocess.run([str(script), "-fsSL", url, "-o", str(root / "unexpected")], env=env, capture_output=True)
|
|
self.assertEqual(result.returncode, 97)
|
|
self.assertFalse((root / "unexpected").exists())
|
|
allocator = root / "mktemp"; allocator.write_text(native.TRANSPORT); allocator.chmod(0o755)
|
|
result = subprocess.run([str(allocator), "-d", "/tmp/pulse-backup.XXXXXX"], env=env, capture_output=True, check=True)
|
|
self.assertEqual(Path(result.stdout.decode().strip()).parent, root / "tmp")
|
|
|
|
def test_real_verifier_accepts_fixture_and_refuses_tampering(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
lab = native.NativeLab(root, ROOT, root / "a", root / "b", "a" * 40)
|
|
(root / "signer").mkdir()
|
|
lab.run("ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(root / "signer/key"))
|
|
installer = root / "installer"; installer.write_text(native.INSTALLER)
|
|
lab.sign(installer)
|
|
shell = ('source "$PULSE_NATIVE_UPDATER"; '
|
|
'PINNED_RELEASE_SSH_PUBLIC_KEY=$(cat "$PULSE_NATIVE_ROOT/signer/key.pub"); '
|
|
'verify_release_signature "$PULSE_NATIVE_ROOT/installer" "$PULSE_NATIVE_ROOT/installer.sshsig" fixture')
|
|
self.assertEqual(lab.shell(shell).returncode, 0)
|
|
installer.write_text(native.INSTALLER + "# tampered\n")
|
|
result = subprocess.run(["/bin/bash", "-c", shell], env=lab.env, capture_output=True)
|
|
self.assertEqual(result.returncode, 1)
|
|
self.assertIn(b"Cryptographic signature verification failed", result.stdout)
|
|
|
|
def make_phase(self, directory, mutation=""):
|
|
root = Path(directory)
|
|
for name in ("install/bin", "data", "tmp"):
|
|
(root / name).mkdir(parents=True)
|
|
before, after = root / "a", root / "b"
|
|
before.write_bytes(b"baseline executable\n"); after.write_bytes(b"changed executable\n")
|
|
(root / "install/bin/pulse").write_bytes(after.read_bytes())
|
|
(root / "install/VERSION").write_text("v0.0.2\n")
|
|
(root / "data/sentinel").write_text("original\n")
|
|
lab = native.NativeLab(root, ROOT, before, after, "a" * 40)
|
|
lab.baseline_data = {"sentinel": native.digest(root / "data/sentinel")}
|
|
lab.baseline_mode = 0o644
|
|
lab.receipt["binaries"] = {"v0.0.1": native.digest(before), "v0.0.2": native.digest(after)}
|
|
states = [{"LoadState": "loaded", "ActiveState": "inactive", "UnitFileState": "disabled", "MainPID": "0"}] * 2
|
|
if mutation == "mask": states[1] = {**states[1], "LoadState": "masked", "UnitFileState": "masked"}
|
|
if mutation == "activity": states[1] = {**states[1], "ActiveState": "active", "MainPID": "123"}
|
|
lab.state = unittest.mock.Mock(side_effect=states)
|
|
def run(*args, **kwargs):
|
|
if mutation == "persistent": (root / "data/sentinel").write_text("changed\n")
|
|
if mutation == "binary": (root / "install/bin/pulse").write_text("different\n")
|
|
if mutation == "version": (root / "install/VERSION").write_text("v0.0.1\n")
|
|
if mutation == "backup": (root / "tmp/pulse-backup.extra").mkdir()
|
|
if mutation == "staging": (root / "install/bin/pulse.rollback.leak").write_text("leaked\n")
|
|
return subprocess.CompletedProcess(args, 0, b"fixture command, not native execution\n")
|
|
lab.run = run
|
|
return lab
|
|
|
|
def test_phase_cannot_accept_wrong_bytes_state_mask_persistence_or_backup(self):
|
|
for mutation in ("", "binary", "version", "mask", "activity", "persistent", "backup", "staging"):
|
|
with self.subTest(mutation=mutation), tempfile.TemporaryDirectory() as directory:
|
|
lab = self.make_phase(directory, mutation)
|
|
if mutation:
|
|
with self.assertRaises(RuntimeError): lab.phase("forward", "v0.0.2", active="inactive")
|
|
self.assertEqual(lab.receipt["phases"], [])
|
|
else:
|
|
lab.phase("forward", "v0.0.2", active="inactive")
|
|
self.assertTrue(lab.receipt["phases"][0]["passed"])
|
|
|
|
def test_managed_process_identity_follows_served_readiness_and_rejects_residuals(self):
|
|
for mutation in ("", "process-bytes", "stopped-after-readiness", "inactive-pid"):
|
|
with self.subTest(mutation=mutation), tempfile.TemporaryDirectory() as directory:
|
|
lab = self.make_phase(directory)
|
|
state = dict(LoadState="loaded", ActiveState="active", UnitFileState="disabled", MainPID="1")
|
|
observed = {**state, "MainPID": "2"}
|
|
if mutation == "stopped-after-readiness":
|
|
observed.update(ActiveState="inactive", MainPID="0")
|
|
if mutation == "inactive-pid":
|
|
lab.state = unittest.mock.Mock(return_value={**state, "ActiveState": "inactive"})
|
|
with self.assertRaises(RuntimeError): lab.assert_runtime("v0.0.2", "inactive")
|
|
continue
|
|
lab.state = unittest.mock.Mock(side_effect=[state, observed])
|
|
order = []
|
|
def response(url, **kwargs):
|
|
endpoint = url.rsplit("/", 1)[-1]
|
|
order.append(endpoint)
|
|
return io.BytesIO(json.dumps({"status": "healthy"} if endpoint == "health"
|
|
else {"version": "v0.0.2"}).encode())
|
|
actual_digest = native.digest
|
|
def process_digest(path):
|
|
if str(path).startswith("/proc/"):
|
|
order.append("process")
|
|
self.assertEqual(str(path), "/proc/2/exe")
|
|
return "wrong" if mutation == "process-bytes" else lab.receipt["binaries"]["v0.0.2"]
|
|
return actual_digest(path)
|
|
with patch.object(native.urllib.request, "urlopen", side_effect=response), patch.object(native, "digest", side_effect=process_digest):
|
|
if mutation:
|
|
with self.assertRaises(RuntimeError): lab.assert_runtime("v0.0.2", "active")
|
|
else:
|
|
self.assertEqual(lab.assert_runtime("v0.0.2", "active"), observed)
|
|
self.assertEqual(order[:2], ["health", "version"])
|
|
self.assertEqual(order[2:], [] if mutation == "stopped-after-readiness" else ["process"])
|
|
|
|
def test_cleanup_never_deletes_preexisting_unowned_scratch(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory); marker = root / "prior"; marker.write_text("not ours\n")
|
|
lab = native.NativeLab(root, ROOT, root / "a", root / "b", "a" * 40)
|
|
with patch.object(lab, "run", side_effect=AssertionError("unowned manager operation")):
|
|
lab.cleanup()
|
|
self.assertTrue(marker.exists())
|
|
self.assertFalse(lab.receipt["cleanup"]["owned_fixture_removed"])
|
|
|
|
def run_recipe(self, failure="", mismatch=False):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory); tools = root / "tools"; tools.mkdir()
|
|
for name in ("frontend-modern/dist", "internal/api/frontend-modern/dist"):
|
|
(root / name).mkdir(parents=True); (root / name / "index.html").write_text("real-embed fixture\n")
|
|
trace = root / "trace"
|
|
fake = '''#!/usr/bin/python3
|
|
import json, os, pathlib, sys
|
|
args = sys.argv[1:]; name = pathlib.Path(sys.argv[0]).name
|
|
with pathlib.Path(os.environ["TRACE"]).open("a") as out: out.write(json.dumps([name, *args])+"\\n")
|
|
if name == "git":
|
|
if args == ["rev-parse", "HEAD"]: print("b"*40 if os.environ["MISMATCH"] == "true" else "a"*40)
|
|
elif args == ["diff", "--quiet", "HEAD"]:
|
|
if os.environ["FAIL_BUILD"] == "source-modified": sys.exit(49)
|
|
else: sys.exit(97)
|
|
if name == "go":
|
|
if args[args.index("-o")+1].endswith(os.environ["FAIL_BUILD"]) and os.environ["FAIL_BUILD"]: sys.exit(43)
|
|
pathlib.Path(args[args.index("-o")+1]).write_text("compiler double, not a product build\\n")
|
|
if name == "sudo":
|
|
if os.environ["FAIL_BUILD"] == "native": sys.exit(45)
|
|
pathlib.Path(os.environ["RUNNER_TEMP"]+"/pulse-native-updater.json").write_text("{}\\n")
|
|
'''
|
|
for name in ("git", "go", "sudo"):
|
|
path = tools / name; path.write_text(fake); path.chmod(0o755)
|
|
env = {**os.environ, "PATH": str(tools) + ":" + os.environ["PATH"], "TRACE": str(trace),
|
|
"RUNNER_TEMP": str(root), "EXPECTED_SOURCE_SHA": "a" * 40,
|
|
"FAIL_BUILD": failure, "MISMATCH": "true" if mismatch else "false"}
|
|
result = subprocess.run(["/bin/bash", "-c", recipe(WORKFLOW.read_text())], env=env, cwd=root, capture_output=True)
|
|
return result, [json.loads(line) for line in trace.read_text().splitlines()], (root / "pulse-native-updater.json").exists()
|
|
|
|
def test_exact_workflow_recipe_is_scrubbed_and_propagates_all_failures(self):
|
|
result, calls, receipt = self.run_recipe()
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual([call[0] for call in calls], ["git", "git", "go", "go", "sudo"])
|
|
self.assertTrue(receipt)
|
|
self.assertEqual(calls[-1][1:3], ["env", "-i"])
|
|
for failure, code, count in (("source-modified", 49, 2), ("before", 43, 3), ("after", 43, 4), ("native", 45, 5)):
|
|
result, calls, receipt = self.run_recipe(failure)
|
|
self.assertEqual(result.returncode, code)
|
|
self.assertEqual(len(calls), count)
|
|
self.assertFalse(receipt)
|
|
result, calls, receipt = self.run_recipe(mismatch=True)
|
|
self.assertEqual(result.returncode, 1)
|
|
self.assertEqual([call[0] for call in calls], ["git"])
|
|
self.assertFalse(receipt)
|
|
|
|
def test_connected_signed_archive_and_recovery_controls_without_host_manager(self):
|
|
# Same orchestration and production shell functions, real signatures and
|
|
# ELF staging. Only this test replaces the manager, account/chown and
|
|
# unit output path. It is not native or real-Pulse runtime acceptance.
|
|
manager = r'''#!/usr/bin/python3
|
|
import os, pathlib, sys
|
|
root = pathlib.Path(os.environ["PULSE_NATIVE_ROOT"])
|
|
unit = root / "pulse-source-updater.service"
|
|
args = sys.argv[1:]
|
|
if any(".service" in a and a != "pulse-source-updater.service" for a in args): sys.exit(97)
|
|
load = "masked" if unit.is_symlink() else ("loaded" if unit.exists() else "not-found")
|
|
statefile = root / "manager-state"
|
|
active = statefile.read_text().strip() if statefile.exists() and load != "not-found" else "inactive"
|
|
values = dict(LoadState=load, ActiveState=active, UnitFileState="masked" if load == "masked" else "disabled", MainPID="123" if active == "active" else "0")
|
|
if args[0] == "show":
|
|
properties = [p for a in args if a.startswith("--property=") for p in a.split("=", 1)[1].split(",")]
|
|
if not properties or len(properties) != len(set(properties)) or any(p not in values for p in properties): sys.exit(97)
|
|
for p in properties: print(values[p] if "--value" in args else p+"="+values[p])
|
|
elif args[0] in ("start", "stop"):
|
|
if args[0] == "start" and load != "loaded": sys.exit(1)
|
|
statefile.write_text("active" if args[0] == "start" else "inactive")
|
|
if args[0] == "start" and not (root / "data/.encryption.key").exists():
|
|
(root / "data/.encryption.key").write_text("synthetic persistence marker, not a key\n")
|
|
elif args[0] == "mask": unit.symlink_to("/dev/null")
|
|
elif args[0] == "unmask": unit.unlink()
|
|
elif args[0] not in ("daemon-reload", "reset-failed"): sys.exit(97)
|
|
'''
|
|
class FixtureLab(native.NativeLab):
|
|
def assert_runtime(self, tag, active):
|
|
if native.digest(self.root / "install/bin/pulse") != self.receipt["binaries"][tag]:
|
|
raise RuntimeError("fixture executable mismatch")
|
|
if (self.root / "install/VERSION").read_text() != tag + "\n":
|
|
raise RuntimeError("fixture version mismatch")
|
|
state = self.state()
|
|
if state["ActiveState"] != active: raise RuntimeError("fixture activity mismatch")
|
|
return state
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
base = Path(directory); tools = base / "tools"; tools.mkdir()
|
|
path = tools / "systemctl"; path.write_text(manager); path.chmod(0o755)
|
|
account = r'''#!/usr/bin/python3
|
|
import os, pathlib, sys
|
|
marker = pathlib.Path(os.environ["PULSE_NATIVE_ROOT"]) / "fixture-user"
|
|
if pathlib.Path(sys.argv[0]).name == "id":
|
|
if sys.argv[1:] != ["-u", "pulse"]: sys.exit(97)
|
|
if not marker.exists(): sys.exit(1)
|
|
print("1234")
|
|
elif pathlib.Path(sys.argv[0]).name == "useradd":
|
|
if sys.argv[1:] != ["--system", "--home-dir", str(marker.parent / "install"), "--shell", "/bin/false", "pulse"]: sys.exit(97)
|
|
marker.write_text("owned confined fixture account\n")
|
|
elif pathlib.Path(sys.argv[0]).name == "userdel":
|
|
if sys.argv[1:] != ["pulse"]: sys.exit(97)
|
|
marker.unlink()
|
|
elif pathlib.Path(sys.argv[0]).name == "chown":
|
|
args = [arg for arg in sys.argv[1:] if arg != "-R"]
|
|
if len(args) < 2 or args[0] != "pulse:pulse": sys.exit(97)
|
|
if any(not pathlib.Path(p).is_relative_to(marker.parent) for p in args[1:]): sys.exit(97)
|
|
else: sys.exit(97)
|
|
'''
|
|
for name in ("id", "useradd", "userdel", "chown"):
|
|
tool = tools / name; tool.write_text(account); tool.chmod(0o755)
|
|
program = base / "fixture.go"
|
|
program.write_text('package main\nimport "fmt"\nvar Version string\nfunc main(){fmt.Println("Pulse " + Version)}\n')
|
|
binaries = []
|
|
for tag in native.TAGS:
|
|
binary = base / tag
|
|
subprocess.run(["go", "build", "-o", str(binary), "-ldflags=-X main.Version=" + tag, str(program)],
|
|
check=True, capture_output=True, timeout=60)
|
|
binaries.append(binary)
|
|
lab = FixtureLab(base / "lab", ROOT, *binaries, "a" * 40)
|
|
lab.unit = lab.root / "pulse-source-updater.service"
|
|
lab.env["PATH"] = str(lab.root / "tools") + ":" + str(tools) + ":/usr/sbin:/usr/bin:/sbin:/bin"
|
|
original_write = Path.write_text
|
|
def relocate(path, data, *args, **kwargs):
|
|
if path.name == "installer-definitions":
|
|
before = "cat > /etc/systemd/system/$SERVICE_NAME.service << EOF"
|
|
self.assertEqual(data.count(before), 1)
|
|
append = "cat >> /etc/systemd/system/$SERVICE_NAME.service << EOF"
|
|
self.assertEqual(data.count(append), 2)
|
|
data = data.replace(before, 'cat > "$PULSE_NATIVE_ROOT/pulse-source-updater.service" << EOF')
|
|
data = data.replace(append, 'cat >> "$PULSE_NATIVE_ROOT/pulse-source-updater.service" << EOF')
|
|
return original_write(path, data, *args, **kwargs)
|
|
try:
|
|
with patch.object(Path, "write_text", new=relocate), patch.object(native.time, "sleep"):
|
|
try:
|
|
lab.prepare()
|
|
self.assertEqual((lab.root / "data/.env").read_text(),
|
|
"PULSE_TELEMETRY=false\nBIND_ADDRESS=127.0.0.1\n")
|
|
lab.exercise()
|
|
except RuntimeError as error:
|
|
self.fail(str(error) + "\nConfined fixture output:\n" + lab._last_output.decode())
|
|
self.assertEqual([p["name"] for p in lab.receipt["phases"]],
|
|
["forward", "manual-reverse", "failed-active", "failed-inactive",
|
|
"failed-masked", "bad-signature", "incomplete-restore"])
|
|
self.assertTrue(lab.receipt["phases"][-1]["backup_retained"])
|
|
finally:
|
|
lab.cleanup()
|
|
self.assertTrue(lab.receipt["cleanup"]["complete"])
|
|
self.assertFalse(lab.root.exists())
|
|
|
|
def test_workflow_cannot_hide_or_rebind_native_step(self):
|
|
source = WORKFLOW.read_text()
|
|
for changed in (source.replace("- name: " + STEP + "\n", "- name: " + STEP + "\n continue-on-error: true\n"),
|
|
source.replace("sudo env -i", "sudo env"),
|
|
source.replace("main.Version=v0.0.2", "main.Version=v0.0.1"),
|
|
source.replace("EXPECTED_SOURCE_SHA: ${{ github.sha }}", "EXPECTED_SOURCE_SHA: main"),
|
|
source.replace("runs-on: ubuntu-24.04", "runs-on: self-hosted")):
|
|
with self.assertRaises(AssertionError): recipe(changed)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|