288 lines
12 KiB
Python
288 lines
12 KiB
Python
"""Local baseline policy: fake only package, network, and root privilege boundaries."""
|
|
import hashlib
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import stat
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
from engine import overlay
|
|
from engine.lib.host import Host
|
|
from engine.lib.sys import Sys
|
|
|
|
packages = overlay.load_module(
|
|
str(Path(__file__).resolve().parents[1] / "actions/config/packages.py"),
|
|
"apex_test_configure_base",
|
|
)
|
|
|
|
|
|
helper = overlay.load_module(
|
|
str(Path(__file__).resolve().parents[1] / "actions/config/ufw-docker.py"),
|
|
"apex_test_ufw_docker",
|
|
)
|
|
|
|
|
|
class TestBasePackages(unittest.TestCase):
|
|
def setUp(self):
|
|
self.system = Mock()
|
|
self.installed = set(packages.BASE_PACKAGES)
|
|
self.system.run.side_effect = self.query
|
|
self.system.sudo.return_value = subprocess.CompletedProcess([], 0, stdout="")
|
|
|
|
def query(self, command, **kwargs):
|
|
self.assertEqual(command[:3], ["dpkg-query", "-W", "-f=${Status}"])
|
|
status = "install ok installed" if command[-1] in self.installed else "unknown ok not-installed"
|
|
return subprocess.CompletedProcess(command, 0, stdout=status)
|
|
|
|
def test_installed_rsyslog_and_ufw_do_not_require_executable_or_apt_update(self):
|
|
packages._install_missing(self.system, packages.BASE_PACKAGES)
|
|
self.assertEqual({call.args[0][-1] for call in self.system.run.call_args_list},
|
|
{"curl", "wget", "git", "rsyslog", "ufw"})
|
|
self.system.sudo.assert_not_called()
|
|
|
|
def test_only_missing_packages_are_installed_after_safe_simulation(self):
|
|
self.installed.remove("ufw")
|
|
self.system.sudo.return_value = subprocess.CompletedProcess(
|
|
[], 0, stdout="Inst ufw (0.36.2 Debian:13 [all])\nConf ufw (0.36.2 Debian:13 [all])\n",
|
|
)
|
|
packages._install_missing(self.system, packages.BASE_PACKAGES)
|
|
commands = [call.args[0] for call in self.system.sudo.call_args_list]
|
|
self.assertEqual(len(commands), 3)
|
|
self.assertIn("update", commands[0])
|
|
self.assertEqual(commands[1], [*commands[2], "--simulate"])
|
|
install = commands[2]
|
|
self.assertEqual(install[install.index("install"):], [
|
|
"install", "-y", "--no-install-recommends", "--no-upgrade", "--no-remove", "ufw",
|
|
])
|
|
|
|
def test_privileged_apt_environment_survives_sudo_filtering(self):
|
|
execute = subprocess.run
|
|
operations = []
|
|
|
|
def filtered_sudo(command, **kwargs):
|
|
if command[0] == "dpkg-query":
|
|
return subprocess.CompletedProcess(command, 1, stdout="")
|
|
self.assertEqual(command[0], "sudo")
|
|
incoming = kwargs.get("env") or os.environ
|
|
filtered = {"PATH": os.defpath}
|
|
if "LC_ALL" in incoming:
|
|
filtered["LC_ALL"] = incoming["LC_ALL"]
|
|
apt_index = command.index("apt-get")
|
|
probe = execute([
|
|
*command[1:apt_index], sys.executable, "-I", "-c",
|
|
"import json, os; print(json.dumps([os.getenv('LC_ALL'), "
|
|
"os.getenv('DEBIAN_FRONTEND')]))",
|
|
], env=filtered, check=True, text=True, capture_output=True)
|
|
self.assertEqual(json.loads(probe.stdout), ["C", "noninteractive"])
|
|
operations.append(command[apt_index:])
|
|
plan = "Inst ufw (0.36.2 Debian:13 [all])\n" if "--simulate" in command else ""
|
|
return subprocess.CompletedProcess(command, 0, stdout=plan)
|
|
|
|
with patch("engine.lib.sys.subprocess.run", side_effect=filtered_sudo):
|
|
packages._install_missing(Sys(Mock()), ["ufw"])
|
|
self.assertEqual(len(operations), 3)
|
|
self.assertIn("update", operations[0])
|
|
self.assertIn("--simulate", operations[1])
|
|
self.assertIn("install", operations[2])
|
|
self.assertNotIn("--simulate", operations[2])
|
|
|
|
def test_dependency_upgrades_and_removals_abort_before_installation(self):
|
|
self.installed.remove("curl")
|
|
for plan in ("Inst libcurl4 [7.0] (8.0 Debian:13 [amd64])\n",
|
|
"Remv libcurl4 [7.0]\n"):
|
|
with self.subTest(plan=plan):
|
|
self.system.sudo.reset_mock()
|
|
self.system.sudo.return_value = subprocess.CompletedProcess([], 0, stdout=plan)
|
|
with self.assertRaisesRegex(RuntimeError, "upgrades/removals"):
|
|
packages._install_missing(self.system, packages.BASE_PACKAGES)
|
|
self.assertEqual(self.system.sudo.call_count, 2)
|
|
|
|
def test_failed_package_query_is_not_treated_as_installed(self):
|
|
self.system.run.return_value = subprocess.CompletedProcess([], 1, stdout="")
|
|
self.system.run.side_effect = None
|
|
packages._install_missing(self.system, ["rsyslog"])
|
|
self.assertEqual(self.system.sudo.call_args.args[0][-1], "rsyslog")
|
|
|
|
|
|
class TestPinnedHelper(unittest.TestCase):
|
|
def setUp(self):
|
|
temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(temporary.cleanup)
|
|
self.root = Path(temporary.name).resolve()
|
|
self.path = self.root / "bin" / "ufw-docker"
|
|
self.path.parent.mkdir()
|
|
self.payload = b"#!/bin/sh\nprintf 'verified fixture\\n'\n"
|
|
self.wrong_owner = None
|
|
self.wrong_group = None
|
|
self.system = Mock()
|
|
self.system.sudo.side_effect = self.sudo
|
|
self.ctx = SimpleNamespace(log=Mock(), sys=self.system)
|
|
self.ctx.host = Host(self.ctx.log, self.system)
|
|
real_lstat = Path.lstat
|
|
|
|
def root_lstat(path, *args, **kwargs):
|
|
info = list(real_lstat(path, *args, **kwargs))
|
|
info[4] = 1001 if path == self.wrong_owner else 0
|
|
info[5] = 1001 if path == self.wrong_group else 0
|
|
if path not in (self.path, self.path.parent, self.root):
|
|
info[0] &= ~0o022
|
|
return os.stat_result(info)
|
|
|
|
# Model root-owned ancestry without sudo/chown; content, modes and links stay real.
|
|
self.addCleanup(patch.stopall)
|
|
patch.object(Path, "lstat", autospec=True, side_effect=root_lstat).start()
|
|
patch.object(helper, "UFW_DOCKER_PATH", self.path).start()
|
|
patch.object(helper, "UFW_DOCKER_SHA256", hashlib.sha256(self.payload).hexdigest()).start()
|
|
self.download = patch.object(helper.urllib.request, "urlopen").start()
|
|
self.download.return_value = io.BytesIO(self.payload)
|
|
|
|
def sudo(self, command, **kwargs):
|
|
if command[:3] == ["python3", "-I", "-c"]:
|
|
return subprocess.run([sys.executable, *command[1:]], check=True, text=True, **kwargs)
|
|
self.assertEqual(command, [str(self.path), "install"])
|
|
self.assertEqual(self.path.read_bytes(), self.payload)
|
|
self.assertEqual(stat.S_IMODE(self.path.stat().st_mode), 0o755)
|
|
return subprocess.CompletedProcess(command, 0)
|
|
|
|
def write_helper(self, payload=None, mode=0o755):
|
|
self.path.write_bytes(self.payload if payload is None else payload)
|
|
self.path.chmod(mode)
|
|
|
|
def test_verified_existing_helper_executes_without_download(self):
|
|
self.write_helper()
|
|
helper._install_helper(self.ctx)
|
|
self.download.assert_not_called()
|
|
self.assertEqual(self.system.sudo.call_args.args[0], [str(self.path), "install"])
|
|
self.assertEqual(self.system.sudo.call_count, 1)
|
|
|
|
def test_wrong_executable_content_is_replaced_only_with_verified_download(self):
|
|
self.write_helper(b"#!/bin/sh\necho wrong\n")
|
|
helper._install_helper(self.ctx)
|
|
self.assertEqual(self.path.read_bytes(), self.payload)
|
|
self.assertEqual(self.system.sudo.call_count, 2)
|
|
self.download.assert_called_once_with(helper.UFW_DOCKER_URL, timeout=30)
|
|
|
|
def test_missing_helper_is_published_with_executable_mode(self):
|
|
helper._install_helper(self.ctx)
|
|
self.assertEqual(self.path.read_bytes(), self.payload)
|
|
self.assertEqual(stat.S_IMODE(self.path.stat().st_mode), 0o755)
|
|
self.assertEqual(list(self.path.parent.iterdir()), [self.path])
|
|
|
|
def test_verified_nonexecutable_content_is_reused_and_mode_repaired(self):
|
|
self.write_helper(mode=0o600)
|
|
helper._install_helper(self.ctx)
|
|
self.download.assert_not_called()
|
|
self.assertEqual(stat.S_IMODE(self.path.stat().st_mode), 0o755)
|
|
self.assertEqual(self.system.sudo.call_count, 2)
|
|
|
|
def test_bad_checksum_never_publishes_or_executes(self):
|
|
self.download.return_value = io.BytesIO(b"not the pinned helper")
|
|
with self.assertRaisesRegex(RuntimeError, "checksum mismatch"):
|
|
helper._install_helper(self.ctx)
|
|
self.system.sudo.assert_not_called()
|
|
self.assertFalse(self.path.exists())
|
|
|
|
def test_bad_download_preserves_existing_wrong_helper_without_executing_it(self):
|
|
self.write_helper(b"old unknown executable")
|
|
self.download.return_value = io.BytesIO(b"bad download")
|
|
with self.assertRaisesRegex(RuntimeError, "checksum mismatch"):
|
|
helper._install_helper(self.ctx)
|
|
self.system.sudo.assert_not_called()
|
|
self.assertEqual(self.path.read_bytes(), b"old unknown executable")
|
|
|
|
def assert_rejected_before_network_or_sudo(self):
|
|
with self.assertRaisesRegex(RuntimeError, "Unsafe ufw-docker"):
|
|
helper._install_helper(self.ctx)
|
|
self.download.assert_not_called()
|
|
self.system.sudo.assert_not_called()
|
|
|
|
def test_symlink_target_is_rejected(self):
|
|
target = self.root / "untrusted"
|
|
target.write_bytes(self.payload)
|
|
self.path.symlink_to(target)
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_dangling_symlink_target_is_rejected(self):
|
|
self.path.symlink_to(self.root / "missing")
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_symlink_ancestor_is_rejected(self):
|
|
self.path.parent.rmdir()
|
|
target = self.root / "real-bin"
|
|
target.mkdir()
|
|
self.path.parent.symlink_to(target)
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_writable_ancestor_is_rejected(self):
|
|
self.path.parent.chmod(0o777)
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_unsafe_helper_preflight_precedes_package_network_or_mutation(self):
|
|
self.path.parent.chmod(0o777)
|
|
with patch.object(helper.os.path, "isfile", return_value=True):
|
|
with self.assertRaisesRegex(RuntimeError, "Unsafe ufw-docker"):
|
|
helper.run(self.ctx, None)
|
|
self.download.assert_not_called()
|
|
self.system.run.assert_not_called()
|
|
self.system.sudo.assert_not_called()
|
|
|
|
def test_untrusted_owner_ancestor_is_rejected(self):
|
|
self.wrong_owner = self.path.parent
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_untrusted_file_owner_is_rejected(self):
|
|
self.write_helper()
|
|
self.wrong_owner = self.path
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_untrusted_file_group_is_rejected(self):
|
|
self.write_helper()
|
|
self.wrong_group = self.path
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_writable_helper_is_rejected(self):
|
|
self.write_helper(mode=0o775)
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_hardlinked_helper_is_rejected(self):
|
|
self.write_helper()
|
|
os.link(self.path, self.root / "other-link")
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_nonregular_helper_is_rejected(self):
|
|
self.path.mkdir()
|
|
self.assert_rejected_before_network_or_sudo()
|
|
|
|
def test_path_changed_during_download_is_rejected_before_publication(self):
|
|
def download(*args, **kwargs):
|
|
self.path.symlink_to(self.root / "untrusted")
|
|
return io.BytesIO(self.payload)
|
|
|
|
self.download.side_effect = download
|
|
with self.assertRaisesRegex(RuntimeError, "Unsafe ufw-docker"):
|
|
helper._install_helper(self.ctx)
|
|
self.system.sudo.assert_not_called()
|
|
|
|
def test_changed_content_after_publication_is_not_executed(self):
|
|
real_sudo = self.sudo
|
|
|
|
def tamper(command, **kwargs):
|
|
result = real_sudo(command, **kwargs)
|
|
self.path.write_bytes(b"changed after publication")
|
|
return result
|
|
|
|
self.system.sudo.side_effect = tamper
|
|
with self.assertRaisesRegex(RuntimeError, "verification failed"):
|
|
helper._install_helper(self.ctx)
|
|
self.assertEqual(self.system.sudo.call_count, 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|