Exact reviewed integration tree from 7bf8c8d8e9da45c533a73e59ade18df37eab9ed5.
Preserves installer branch fe383efe70 and its history.
Incorporates Gitea PR #168 with source UI approval and completed source regressions.
The canonical proposal uses one parent to avoid ngit's ten-commit submission guard; no source changes or history rewriting.
107 lines
5.3 KiB
Python
107 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Offline provisioning and real isolated nft policy regression checks."""
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
spec = importlib.util.spec_from_file_location('provision', HERE / 'provision.py')
|
|
p = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(p)
|
|
|
|
class ProvisionTests(unittest.TestCase):
|
|
def setUp(self):
|
|
previous_umask = os.umask(0o022)
|
|
self.addCleanup(os.umask, previous_umask)
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
for name, data in {'etc/apt/apt.conf.d/98archipelago-build-indexes': '// Archipelago image build index lock\nfixture\n',
|
|
'etc/os-release': 'ID=debian\nVERSION_CODENAME=trixie\n',
|
|
'usr/share/keyrings/debian-archive-keyring.gpg': 'fixture',
|
|
'etc/apt/sources.list.d/archipelago-build.sources': p.BUILD_MARKER.decode() + 'fixture\n'}.items():
|
|
dest = self.root / name
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
dest.write_text(data)
|
|
|
|
def test_idempotent_and_runtime_authority(self):
|
|
first = p.provision(self.root, HERE)
|
|
self.assertEqual(first, p.provision(self.root, HERE))
|
|
self.assertFalse((self.root / 'etc/apt/sources.list.d/archipelago-build.sources').exists())
|
|
for name in p.MASKED:
|
|
self.assertEqual(os.readlink(self.root / 'etc/systemd/system' / name), '/dev/null')
|
|
self.assertEqual(first['review_cadence'], 'not-appointed')
|
|
self.assertFalse((self.root/'etc/apt/apt.conf.d/98archipelago-build-indexes').exists())
|
|
|
|
def test_live_root_refused(self):
|
|
with self.assertRaises(ValueError): p.provision(Path('/'), HERE)
|
|
|
|
def test_foreign_sources_preserved_before_writes(self):
|
|
source = self.root / 'etc/apt/sources.list.d/operator.list'
|
|
source.write_text('deb https://example.invalid stable main\n')
|
|
with self.assertRaises(ValueError): p.provision(self.root, HERE)
|
|
self.assertFalse((self.root / 'etc/archipelago').exists())
|
|
self.assertIn('example.invalid', source.read_text())
|
|
|
|
def test_symlink_destination_refused(self):
|
|
(self.root / 'etc/archipelago').symlink_to(self.root / 'etc')
|
|
with self.assertRaises(ValueError): p.provision(self.root, HERE)
|
|
self.assertFalse((self.root / 'etc/host-firewall.nft').exists())
|
|
|
|
def test_foreign_mask_preserved(self):
|
|
mask = self.root / 'etc/systemd/system/apt-daily.timer'
|
|
mask.parent.mkdir(parents=True)
|
|
mask.symlink_to('/operator/unit')
|
|
with self.assertRaises(ValueError): p.provision(self.root, HERE)
|
|
self.assertEqual(os.readlink(mask), '/operator/unit')
|
|
self.assertFalse((self.root / 'etc/archipelago').exists())
|
|
|
|
def test_foreign_fips_policy_preserved(self):
|
|
policy = self.root / 'etc/fips/fips.nft'
|
|
policy.parent.mkdir(parents=True)
|
|
policy.write_text('operator policy\n')
|
|
with self.assertRaisesRegex(ValueError, 'different existing image asset preserved'):
|
|
p.provision(self.root, HERE)
|
|
self.assertEqual(policy.read_text(), 'operator policy\n')
|
|
self.assertFalse((self.root / 'etc/archipelago').exists())
|
|
|
|
def test_keyring_escape_refused(self):
|
|
keyring = self.root / 'usr/share/keyrings/debian-archive-keyring.gpg'
|
|
keyring.unlink(); keyring.symlink_to('/etc/passwd')
|
|
with self.assertRaises(ValueError): p.provision(self.root, HERE)
|
|
self.assertFalse((self.root / 'etc/archipelago').exists())
|
|
|
|
def test_manifest_profiles_are_shipped(self):
|
|
repo = HERE.parents[1]
|
|
paths = subprocess.check_output(['git', '-C', str(repo), 'ls-files', 'apps/*/manifest.yml'], text=True).splitlines()
|
|
names = set()
|
|
for path in paths:
|
|
names.update(re.findall(r'^\s+apparmor_profile:\s*([a-z0-9-]+)\s*$', (repo/path).read_text(), re.M))
|
|
names.discard('default')
|
|
self.assertEqual(names, {path.name for path in (HERE/'apparmor').iterdir()})
|
|
for name in names:
|
|
self.assertIn('profile '+name+' flags=', (HERE/'apparmor'/name).read_text())
|
|
p.provision(self.root, HERE)
|
|
dependency = (self.root/'etc/systemd/system/archipelago.service.d/21-app-profiles.conf').read_text()
|
|
self.assertIn('Requires=archipelago-app-profiles.service', dependency)
|
|
self.assertIn('After=archipelago-app-profiles.service', dependency)
|
|
|
|
def test_recipe_does_not_create_mask_collisions(self):
|
|
source = (HERE.parent/'_archived/build-auto-installer-iso.sh').read_text()
|
|
copied = set(re.findall(r'^COPY \S+ /etc/systemd/system/(\S+)$', source, re.M))
|
|
enabled = set(re.findall(r'systemctl enable ([a-zA-Z0-9@_.-]+)', source))
|
|
self.assertFalse(copied & set(p.MASKED), copied & set(p.MASKED))
|
|
self.assertFalse(enabled & set(p.MASKED), enabled & set(p.MASKED))
|
|
|
|
def test_catalog_inventory(self):
|
|
source = (HERE.parents[1] / 'core/archipelago/src/fips/app_ports.rs').read_text()
|
|
ports = list(map(int, re.findall(r'^\s+(\d+),$', source, re.M)))
|
|
self.assertEqual(ports, json.loads((HERE / 'app-ports.json').read_text()))
|
|
|
|
if __name__ == '__main__': unittest.main()
|