eggcracker / tests /test_installer_security.py
noqt's picture
fix: resume interrupted uninstallation
e579122
Raw History Blame Contribute Delete
4.64 kB
from __future__ import annotations
import os
import subprocess
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
INSTALLER = ROOT / "scripts" / "install.py"
UNINSTALLER = ROOT / "scripts" / "uninstall.py"
@unittest.skipUnless(os.name == "posix" and Path("/usr/bin/python3").is_file(), "Linux only")
class InstallerSecurityTests(unittest.TestCase):
def test_documented_isolated_invocation_ignores_shadow_modules_and_startup_hooks(
self,
) -> None:
with tempfile.TemporaryDirectory() as raw:
root = Path(raw)
marker = root / "imported"
(root / "argparse.py").write_text(
f"open({str(marker)!r}, 'w').write('argparse')\nraise RuntimeError('shadowed')\n",
encoding="utf-8",
)
(root / "sitecustomize.py").write_text(
f"open({str(marker)!r}, 'w').write('sitecustomize')\n",
encoding="utf-8",
)
environment = dict(os.environ)
environment["PYTHONPATH"] = str(root)
result = subprocess.run(
["/usr/bin/python3", "-I", "-S", str(INSTALLER)],
cwd=root,
env=environment,
capture_output=True,
text=True,
check=False,
timeout=15,
)
self.assertNotEqual(0, result.returncode)
self.assertFalse(marker.exists())
def test_symlinked_privileged_installer_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as raw:
link = Path(raw) / "install-link.py"
link.symlink_to(INSTALLER)
result = subprocess.run(
["/usr/bin/python3", "-I", "-S", str(link)],
capture_output=True,
text=True,
check=False,
timeout=15,
)
self.assertNotEqual(0, result.returncode)
self.assertIn("symlinked privileged installer", result.stderr)
def test_uninstaller_help_is_non_destructive(self) -> None:
result = subprocess.run(
["/usr/bin/python3", "-I", "-S", str(UNINSTALLER), "--help"],
capture_output=True,
text=True,
check=False,
timeout=15,
)
self.assertEqual(0, result.returncode)
self.assertIn("usage:", result.stdout)
self.assertIn("manifest-verified", result.stdout)
self.assertNotIn("UNINSTALLED", result.stdout)
def test_privileged_scripts_require_isolated_no_site_python(self) -> None:
for name in ("install.py", "uninstall.py", "verify_uninstalled.py"):
value = (ROOT / "scripts" / name).read_text(encoding="utf-8")
self.assertIn("flags.isolated", value)
self.assertIn("flags.no_site", value)
installer = INSTALLER.read_text(encoding="utf-8")
self.assertIn("--expected-sha256", installer)
self.assertIn("/proc/self/fd/{artifact_descriptor}", installer)
self.assertIn("read_stable_regular", installer)
self.assertIn("artifact_source_commit", installer)
self.assertIn("INSTALL_JOURNAL_SCHEMA", installer)
self.assertIn("recover_interrupted_install", installer)
self.assertIn("acquire_lifecycle_lock", installer)
self.assertIn("release_lifecycle_lock", installer)
for name in ("upgrade.py", "uninstall.py"):
lifecycle = (ROOT / "scripts" / name).read_text(encoding="utf-8")
self.assertIn("acquire_lifecycle_lock", lifecycle)
self.assertIn("release_lifecycle_lock", lifecycle)
uninstaller = UNINSTALLER.read_text(encoding="utf-8")
self.assertIn("UNINSTALL_JOURNAL_SCHEMA", uninstaller)
self.assertIn("recover_uninstall", uninstaller)
def test_install_tracks_cold_boot_network_namespace_runtime(self) -> None:
installer = INSTALLER.read_text(encoding="utf-8")
upgrader = (ROOT / "scripts" / "upgrade.py").read_text(encoding="utf-8")
uninstaller = UNINSTALLER.read_text(encoding="utf-8")
verifier = (ROOT / "scripts" / "verify_uninstalled.py").read_text(
encoding="utf-8"
)
for value in (installer, uninstaller, verifier):
self.assertIn("/etc/tmpfiles.d/lumi-eggcracker.conf", value)
self.assertIn("d /run/netns 0755 root root -", installer)
self.assertIn("ensure_netns_runtime()", installer)
self.assertIn("installer.TMPFILES", upgrader)
self.assertIn("installer.ensure_netns_runtime()", upgrader)