fix(package): integrate WAPT ZIP correction

This commit is contained in:
PierreCsn committed 2026-10-28 17:49:49 +01:00
1 parent 1afb3a2b73
commit f817d41189
6 files changed
+609 -401

No files matched your search

+84 -58
View File
@@ -14,7 +14,27 @@ import shutil
from unittest import mock
ROOT = Path(__file__).resolve().parents[1]
sys.modules.setdefault('setuphelpers', types.ModuleType('setuphelpers'))
setuphelpers_stub = types.ModuleType('setuphelpers')
setuphelpers_stub.makepath = lambda *parts: os.path.join(*(str(part) for part in parts))
setuphelpers_stub.isfile = os.path.isfile
setuphelpers_stub.isdir = os.path.isdir
setuphelpers_stub.mkdirs = lambda path: os.makedirs(path, exist_ok=True)
setuphelpers_stub.ensure_dir = lambda path: os.makedirs(os.path.dirname(path), exist_ok=True)
setuphelpers_stub.filecopyto = lambda source, target: shutil.copy2(source, target)
setuphelpers_stub.copytree2 = lambda source, target, follow_symlinks=False: shutil.copytree(
source, target, dirs_exist_ok=True, symlinks=not follow_symlinks)
setuphelpers_stub.remove_tree = lambda path, ignore_errors=False: shutil.rmtree(
path, ignore_errors=ignore_errors)
setuphelpers_stub.remove_file = lambda path: os.remove(path) if os.path.exists(path) else None
setuphelpers_stub.get_sha256 = lambda path: hashlib.sha256(Path(path).read_bytes()).hexdigest()
setuphelpers_stub.json_load_file = lambda path: json.loads(Path(path).read_text(encoding='utf-8'))
setuphelpers_stub.json_write_file = lambda path, data, **kwargs: Path(path).write_text(
json.dumps(data, **kwargs) + '\n', encoding='utf-8')
setuphelpers_stub.system32 = lambda: os.path.join(os.environ.get('SystemRoot', '/tmp'), 'System32')
setuphelpers_stub.run = lambda *args, **kwargs: ''
setuphelpers_stub.wget = lambda *args, **kwargs: (_ for _ in ()).throw(
AssertionError('unexpected network download in synthetic tests'))
sys.modules.setdefault('setuphelpers', setuphelpers_stub)
spec = importlib.util.spec_from_file_location('wapt_hardeningkitty_setup', ROOT / 'setup.py')
pkg = importlib.util.module_from_spec(spec)
spec.loader.exec_module(pkg)
@@ -57,6 +77,10 @@ def make_source(root):
(root / 'audit_config.json').write_text('{}\n', encoding='utf-8')
asset_root = root / 'vendor'
assets = {x: ('sample ' + x).encode('utf-8') for x in vendor.REQUIRED}
assets['HardeningKitty.psd1'] = b"@{\n ModuleVersion = '0.9.5'\n}\n"
csv_count = sum(name.startswith('lists/') and name.endswith('.csv') for name in assets)
assets.update({'lists/synthetic_machine_%02d.csv' % i: ('synthetic list %d' % i).encode('utf-8')
for i in range(max(0, 50 - csv_count))})
for name, data in assets.items():
path = asset_root.joinpath(*name.split('/'))
path.parent.mkdir(parents=True, exist_ok=True)
@@ -218,6 +242,8 @@ class PackageTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
assets = {x: ('sample ' + x).encode('utf-8') for x in vendor.REQUIRED}
assets.update({'lists/synthetic_machine_%02d.csv' % i: ('synthetic list %d' % i).encode('utf-8')
for i in range(max(0, 50 - len(assets)))})
for name, data in assets.items():
path = root.joinpath(*name.split('/'))
path.parent.mkdir(parents=True, exist_ok=True)
@@ -238,22 +264,23 @@ class PackageTests(unittest.TestCase):
shutil.copy2(ROOT / 'tools' / 'vendor_assets.py', package_source / 'tools' / 'vendor_assets.py')
shutil.copy2(ROOT / 'tools' / 'package_verify.py', package_source / 'tools' / 'package_verify.py')
verify.write_source_manifest(package_source)
prior = (pkg._source_directory, pkg.INSTALL_ROOT, pkg.REPORT_ROOT)
prior = (pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT)
try:
pkg._source_directory = lambda: str(package_source)
pkg._package_source = lambda: str(package_source)
pkg.INSTALL_ROOT = str(root / 'programdata' / 'engine')
pkg.REPORT_ROOT = str(root / 'programdata' / 'reports')
pkg.install()
self.assertEqual(pkg._verify_vendor(pkg.INSTALL_ROOT, allow_runtime=True)['upstream_commit'], vendor.UPSTREAM_COMMIT)
self.assertEqual(pkg._verify_vendor(pkg.INSTALL_ROOT, runtime=True)['upstream_commit'], vendor.UPSTREAM_COMMIT)
self.assertTrue(Path(pkg.REPORT_ROOT).is_dir())
foreign = Path(pkg.INSTALL_ROOT, 'operator-note.txt')
foreign.write_text('keep me', encoding='utf-8')
pkg.uninstall()
self.assertTrue(foreign.is_file())
self.assertFalse(Path(pkg.INSTALL_ROOT, 'HardeningKitty.psm1').exists())
self.assertTrue(Path(pkg.INSTALL_ROOT).is_dir())
self.assertTrue(Path(pkg.REPORT_ROOT).is_dir())
finally:
pkg._source_directory, pkg.INSTALL_ROOT, pkg.REPORT_ROOT = prior
pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT = prior
def test_installer_refuses_to_replace_foreign_existing_directory(self):
with tempfile.TemporaryDirectory() as tmp:
@@ -263,19 +290,19 @@ class PackageTests(unittest.TestCase):
shutil.copy2(ROOT / 'tools' / 'vendor_assets.py', source / 'tools' / 'vendor_assets.py')
shutil.copy2(ROOT / 'tools' / 'package_verify.py', source / 'tools' / 'package_verify.py')
verify.write_source_manifest(source)
old = (pkg._source_directory, pkg.INSTALL_ROOT, pkg.REPORT_ROOT)
old = (pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT)
try:
pkg._source_directory = lambda: str(source)
pkg._package_source = lambda: str(source)
pkg.INSTALL_ROOT = str(root / 'programdata' / 'engine')
pkg.REPORT_ROOT = str(root / 'programdata' / 'reports')
Path(pkg.INSTALL_ROOT).mkdir(parents=True)
foreign = Path(pkg.INSTALL_ROOT, 'foreign.txt')
foreign.write_text('do not delete', encoding='utf-8')
with self.assertRaisesRegex(RuntimeError, 'not identified'):
with self.assertRaisesRegex(RuntimeError, 'not owned'):
pkg.install()
self.assertEqual(foreign.read_text(encoding='utf-8'), 'do not delete')
finally:
pkg._source_directory, pkg.INSTALL_ROOT, pkg.REPORT_ROOT = old
pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT = old
def test_published_shards_and_keys(self):
events = {}
@@ -356,48 +383,53 @@ class PackageTests(unittest.TestCase):
original_cwd = os.getcwd()
try:
os.chdir(tmp)
source_root = updater._source_directory()
source_root = updater._source_dir()
self.assertFalse(os.path.isabs(source_root))
self.assertTrue((Path(source_root) / 'WAPT' / 'control').is_file())
self.assertEqual(Path(source_root).resolve(), ROOT)
os.chdir(ROOT)
self.assertEqual(updater._source_directory(), Path('.'))
self.assertEqual(updater._source_dir(), '.')
finally:
os.chdir(original_cwd)
calls = []
class FakeVendor:
MAX_ARCHIVE_BYTES = vendor.MAX_ARCHIVE_BYTES
@staticmethod
def _download_snapshot():
return b'synthetic-pinned-archive'
@staticmethod
def vendor_from_archive(data, dest):
calls.append(('vendor', data, Path(dest)))
class FakeVerifier:
@staticmethod
def write_source_manifest(root):
calls.append(('manifest', Path(root)))
@staticmethod
def verify_source(root):
calls.append(('verify', Path(root)))
old_loader = updater._load_tool
try:
updater._load_tool = lambda root, name: FakeVendor if name == 'vendor_assets' else FakeVerifier
updater.update_package()
updater.update_package()
finally:
updater._load_tool = old_loader
self.assertEqual([call[0] for call in calls], ['vendor', 'manifest', 'verify'] * 2)
with tempfile.TemporaryDirectory() as tmp:
package_source = make_source(Path(tmp, 'package'))
(package_source / 'tools').mkdir(exist_ok=True)
shutil.copy2(ROOT / 'tools' / 'vendor_assets.py', package_source / 'tools' / 'vendor_assets.py')
shutil.copy2(ROOT / 'tools' / 'package_verify.py', package_source / 'tools' / 'package_verify.py')
verify.write_source_manifest(package_source)
try:
updater._verify_vendor(str(package_source / 'vendor'))
except Exception as exc:
self.fail('synthetic vendor did not satisfy updater: %s' % exc)
old_source = updater._source_dir
old_control = getattr(updater, 'control', None)
class FakeControl:
version = '0.9.9-0'
def inc_build(self):
raise AssertionError('idempotent update must not increment the build')
def save_control_to_wapt(self):
raise AssertionError('idempotent update must not write control')
try:
updater._source_dir = lambda: str(package_source)
updater.control = FakeControl()
updater.update_package()
updater.update_package()
finally:
updater._source_dir = old_source
if old_control is None:
del updater.control
else:
updater.control = old_control
def test_setup_source_directory_is_relative_to_package_root(self):
original_cwd = os.getcwd()
try:
os.chdir(ROOT)
self.assertEqual(pkg._source_directory(), '.')
self.assertEqual(pkg._package_source(), '.')
with tempfile.TemporaryDirectory() as tmp:
os.chdir(tmp)
source_root = pkg._source_directory()
source_root = pkg._package_source()
self.assertFalse(os.path.isabs(source_root))
self.assertTrue(os.path.isfile(os.path.join(source_root, 'WAPT', 'control')))
finally:
@@ -408,34 +440,28 @@ class PackageTests(unittest.TestCase):
archive = Path(tmp, 'archive.zip')
archive.write_bytes(b'synthetic')
expected = hashlib.sha256(b'synthetic').hexdigest()
old_source = updater._source_dir
old_isfile = updater.isfile
old_isdir = updater.isdir
old_env = dict(os.environ)
old_loader = updater._load_tool
old_source = updater._source_directory
class FakeVendor:
MAX_ARCHIVE_BYTES = 1024
@staticmethod
def sha256(data): return hashlib.sha256(data).hexdigest()
@staticmethod
def vendor_from_archive(data, dest): pass
class FakeVerifier:
@staticmethod
def write_source_manifest(root): pass
@staticmethod
def verify_source(root): pass
try:
updater._source_dir = lambda: str(ROOT)
updater.isfile = os.path.isfile
updater.isdir = os.path.isdir
with self.assertRaisesRegex(RuntimeError, 'SHA-256 is required'):
updater.update_package(snapshot_zip=str(archive))
with self.assertRaisesRegex(RuntimeError, 'does not match'):
updater.update_package(snapshot_zip=str(archive), snapshot_sha256='0' * 64)
os.environ['HARDENINGKITTY_ARCHIVE'] = str(archive)
os.environ.pop('HARDENINGKITTY_ARCHIVE_SHA256', None)
updater._source_directory = lambda: ROOT
updater._load_tool = lambda root, name: FakeVendor if name == 'vendor_assets' else FakeVerifier
with self.assertRaisesRegex(RuntimeError, 'required for offline'):
updater.update_package()
os.environ['HARDENINGKITTY_ARCHIVE_SHA256'] = expected
updater.update_package()
with self.assertRaises(zipfile.BadZipFile):
updater.update_package()
finally:
updater._source_dir = old_source
updater.isfile = old_isfile
updater.isdir = old_isdir
os.environ.clear()
os.environ.update(old_env)
updater._load_tool = old_loader
updater._source_directory = old_source
def test_prebuild_verifier_rejects_stale_package_manifest(self):
with tempfile.TemporaryDirectory() as tmp: