fix(package): integrate WAPT ZIP correction
This commit is contained in:
1 parent
1afb3a2b73
commit
f817d41189
6 files changed
+609
-401
No files matched your search
+84
-58
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user