refactor: inline Windows hardening with WAPT helpers
This commit is contained in:
1 parent
11f99a588f
commit
3a046ef57d
4 files changed
+340
-83
No files matched your search
@@ -0,0 +1,163 @@
|
||||
"""Synthetic tests for the WAPT package; no Windows registry is accessed."""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REGISTRY = {}
|
||||
WRITES = []
|
||||
DELETES = []
|
||||
HKLM = 'synthetic-HKLM'
|
||||
|
||||
|
||||
class RegistryHandle:
|
||||
def __init__(self, root, path):
|
||||
self.address = (root, path)
|
||||
|
||||
def __enter__(self):
|
||||
return self.address
|
||||
|
||||
def __exit__(self, *args):
|
||||
return False
|
||||
|
||||
|
||||
def registry_set(root, path, name, value, type=None):
|
||||
REGISTRY.setdefault((root, path), {})[name] = value
|
||||
WRITES.append((root, path, name, value, type))
|
||||
|
||||
|
||||
def _load_setup_module():
|
||||
helpers = types.ModuleType('setuphelpers')
|
||||
helpers.HKEY_LOCAL_MACHINE = HKLM
|
||||
helpers.KEY_ALL_ACCESS = 0xF003F
|
||||
helpers.REG_DWORD = 4
|
||||
helpers.makepath = lambda *parts: os.path.join(*(str(part) for part in parts))
|
||||
helpers.programdata = lambda: tempfile.gettempdir()
|
||||
helpers.mkdirs = lambda path: os.makedirs(path, exist_ok=True)
|
||||
helpers.isfile = os.path.isfile
|
||||
helpers.remove_file = lambda path: os.remove(path) if os.path.exists(path) else None
|
||||
helpers.json_write_file = lambda path, data, **kwargs: Path(path).write_text(
|
||||
json.dumps(data, **kwargs), encoding='utf-8')
|
||||
helpers.json_load_file = lambda path: json.loads(Path(path).read_text(encoding='utf-8'))
|
||||
helpers.reg_key_exists = lambda root, path: (root, path) in REGISTRY
|
||||
helpers.reg_value_exists = lambda root, path, name: name in REGISTRY.get((root, path), {})
|
||||
helpers.reg_openkey_noredir = lambda root, path, **kwargs: RegistryHandle(root, path)
|
||||
helpers.reg_getvalue = lambda key, name, default=None: REGISTRY.get(key, {}).get(name, default)
|
||||
helpers.registry_set = registry_set
|
||||
|
||||
def delete_value(key, name):
|
||||
DELETES.append((key[0], key[1], name))
|
||||
REGISTRY.get(key, {}).pop(name, None)
|
||||
helpers.reg_delvalue = delete_value
|
||||
|
||||
with mock.patch.dict(sys.modules, {'setuphelpers': helpers}):
|
||||
spec = importlib.util.spec_from_file_location('hardening_setup', ROOT / 'setup.py')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
setup = _load_setup_module()
|
||||
|
||||
|
||||
class HardeningSetupTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
REGISTRY.clear()
|
||||
WRITES.clear()
|
||||
DELETES.clear()
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.state_dir = Path(self.temp.name) / 'Comitari' / 'HardeningSafe'
|
||||
self.state_file = self.state_dir / 'original-values.json'
|
||||
self.programdata_patch = mock.patch.object(setup, 'programdata', lambda: self.temp.name)
|
||||
self.programdata_patch.start()
|
||||
self.addCleanup(self.programdata_patch.stop)
|
||||
|
||||
def _seed(self, setting, value):
|
||||
address = (HKLM, setup._registry_path(setting['Path']))
|
||||
REGISTRY.setdefault(address, {})[setting['Name']] = value
|
||||
|
||||
def test_install_saves_originals_once_before_applying_dwords(self):
|
||||
setting = setup.SETTINGS[0]
|
||||
self._seed(setting, 0)
|
||||
setup.install()
|
||||
state = json.loads(self.state_file.read_text(encoding='utf-8'))
|
||||
self.assertEqual(len(state), 5)
|
||||
self.assertEqual(state[0]['Value'], 0)
|
||||
self.assertTrue(state[0]['Exists'])
|
||||
self.assertEqual(state[0]['Target'], 1)
|
||||
self.assertEqual(len(WRITES), 5)
|
||||
self.assertTrue(all(write[4] == setup.REG_DWORD for write in WRITES))
|
||||
|
||||
self._seed(setting, 7)
|
||||
setup.install()
|
||||
state_again = json.loads(self.state_file.read_text(encoding='utf-8'))
|
||||
self.assertEqual(state_again[0]['Value'], 0)
|
||||
|
||||
def test_audit_is_read_only_and_reports_noncompliance(self):
|
||||
setup.install()
|
||||
self.assertEqual(setup.audit(), 'OK')
|
||||
before = list(WRITES)
|
||||
self._seed(setup.SETTINGS[1], 0)
|
||||
self.assertEqual(setup.audit(), 'WARNING')
|
||||
self.assertEqual(WRITES, before)
|
||||
|
||||
def test_audit_read_error_is_not_reported_as_healthy(self):
|
||||
with mock.patch.object(setup, 'reg_key_exists', side_effect=PermissionError):
|
||||
self.assertEqual(setup.audit(), 'WARNING')
|
||||
|
||||
def test_uninstall_restores_owned_values_and_preserves_external_changes(self):
|
||||
existing = setup.SETTINGS[0]
|
||||
absent = setup.SETTINGS[1]
|
||||
externally_changed = setup.SETTINGS[2]
|
||||
self._seed(existing, 0)
|
||||
setup.install()
|
||||
self._seed(externally_changed, 77)
|
||||
|
||||
setup.uninstall()
|
||||
|
||||
self.assertEqual(REGISTRY[(HKLM, setup._registry_path(existing['Path']))][existing['Name']], 0)
|
||||
self.assertNotIn(absent['Name'], REGISTRY[(HKLM, setup._registry_path(absent['Path']))])
|
||||
self.assertEqual(REGISTRY[(HKLM, setup._registry_path(externally_changed['Path']))]
|
||||
[externally_changed['Name']], 77)
|
||||
self.assertIn((HKLM, setup._registry_path(absent['Path']), absent['Name']), DELETES)
|
||||
self.assertTrue(self.state_file.is_file())
|
||||
|
||||
def test_uninstall_without_baseline_refuses_changes(self):
|
||||
with self.assertRaisesRegex(RuntimeError, 'No saved baseline'):
|
||||
setup.uninstall()
|
||||
self.assertEqual(WRITES, [])
|
||||
self.assertEqual(DELETES, [])
|
||||
|
||||
def test_invalid_baseline_refuses_all_registry_changes(self):
|
||||
self.state_dir.mkdir(parents=True)
|
||||
self.state_file.write_text(json.dumps([{'Path': 'HKLM:\\SOFTWARE\\Elsewhere',
|
||||
'Name': 'Synthetic', 'Exists': False,
|
||||
'Value': None, 'Target': 1}]), encoding='utf-8')
|
||||
with self.assertRaisesRegex(RuntimeError, 'incomplete'):
|
||||
setup.uninstall()
|
||||
self.assertEqual(WRITES, [])
|
||||
self.assertEqual(DELETES, [])
|
||||
|
||||
def test_failure_to_persist_baseline_prevents_registry_modification(self):
|
||||
with mock.patch.object(setup, 'json_write_file', side_effect=OSError('synthetic disk error')):
|
||||
with self.assertRaises(OSError):
|
||||
setup.install()
|
||||
self.assertEqual(WRITES, [])
|
||||
|
||||
def test_no_powershell_or_child_process_dependency_remains(self):
|
||||
source = (ROOT / 'setup.py').read_text(encoding='utf-8').lower()
|
||||
self.assertNotIn('subprocess', source)
|
||||
self.assertNotIn('powershell.exe', source)
|
||||
self.assertNotIn('hardening.ps1', source)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user