# -*- coding: utf-8 -*- """Comitari conservative Windows hardening package. Registry operations use WAPT setuphelpers only. The supported settings are kept as data in SETTINGS so a later update_package.py can validate operator choices and serialize an approved configuration without generating code. """ from setuphelpers import * import os uninstallkey = [] # Relative subdirectories under ProgramData used for this package's rollback state. STATE_DIRECTORY = ('Comitari', 'HardeningSafe') # Stores original registry values plus their package target for conditional rollback. STATE_FILENAME = 'original-values.json' # Intentionally limited machine-wide controls. Keep state-file paths in the # legacy PowerShell format so existing installations can roll back safely. # # SETTINGS entry parameters: # Path - HKLM registry key in the format accepted by _registry_path(). # These keys apply to the computer, rather than one signed-in user. # Name - exact Windows policy value name within that key. # Value - desired integer REG_DWORD written by _write_value(). SETTINGS = ( # Set the machine policy that disables AutoRun commands. {'Path': r'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer', 'Name': 'NoAutorun', 'Value': 1}, # Disable AutoPlay for every drive type (0xFF mask). {'Path': r'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer', 'Name': 'NoDriveTypeAutoRun', 'Value': 255}, # Disable AutoPlay for devices that do not expose a volume. {'Path': r'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer', 'Name': 'NoAutoplayfornonVolume', 'Value': 1}, # Prevent use of the camera from the Windows lock screen. {'Path': r'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization', 'Name': 'NoLockScreenCamera', 'Value': 1}, # Prevent enabling a slideshow on the Windows lock screen. {'Path': r'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization', 'Name': 'NoLockScreenSlideshow', 'Value': 1}, ) def _state_path(): return makepath(programdata(), *STATE_DIRECTORY, STATE_FILENAME) def _registry_path(path): prefix = 'HKLM:\\' if not isinstance(path, str) or not path.startswith(prefix): raise RuntimeError('Refusing an unsupported registry root in package state.') return path[len(prefix):] def _read_value(path, name): """Read one HKLM value without hiding access or registry errors.""" subkey = _registry_path(path) if not reg_key_exists(HKEY_LOCAL_MACHINE, subkey): return {'Exists': False, 'Value': None} missing = object() with reg_openkey_noredir(HKEY_LOCAL_MACHINE, subkey) as key: value = reg_getvalue(key, name, default=missing) if value is missing: return {'Exists': False, 'Value': None} if value is None: raise RuntimeError('Registry value disappeared while it was being read: %s' % name) try: return {'Exists': True, 'Value': int(value)} except (TypeError, ValueError): raise RuntimeError('Registry value is not an integer DWORD: %s' % name) def _write_value(path, name, value): registry_set(HKEY_LOCAL_MACHINE, _registry_path(path), name, int(value), type=REG_DWORD) def _delete_value(path, name): subkey = _registry_path(path) if not reg_key_exists(HKEY_LOCAL_MACHINE, subkey): return with reg_openkey_noredir(HKEY_LOCAL_MACHINE, subkey, sam=KEY_ALL_ACCESS) as key: reg_delvalue(key, name) def _capture_original_values(): original = [] for setting in SETTINGS: current = _read_value(setting['Path'], setting['Name']) original.append({ 'Path': setting['Path'], 'Name': setting['Name'], 'Exists': current['Exists'], 'Value': current['Value'], 'Target': setting['Value'], }) return original def _write_state_once(state_file, state): """Persist the rollback baseline before changing any registry value.""" state_dir = os.path.dirname(state_file) mkdirs(state_dir) temporary = state_file + '.tmp' try: json_write_file(temporary, state, indent=2, sort_keys=True) # Same-volume replace keeps a prior baseline intact until the new file # is complete. WAPT serializes package installations on each endpoint. os.replace(temporary, state_file) finally: if isfile(temporary): remove_file(temporary) def _load_state(state_file): if not isfile(state_file): raise RuntimeError('No saved baseline; refusing blind rollback.') try: state = json_load_file(state_file) except Exception as exc: raise RuntimeError('Saved baseline is unreadable; refusing registry changes.') from exc if not isinstance(state, list) or len(state) != len(SETTINGS): raise RuntimeError('Saved baseline is incomplete; refusing registry changes.') expected = {(item['Path'], item['Name']): item for item in SETTINGS} seen = set() for item in state: if not isinstance(item, dict): raise RuntimeError('Saved baseline has an invalid entry; refusing registry changes.') key = (item.get('Path'), item.get('Name')) setting = expected.get(key) if setting is None or key in seen: raise RuntimeError('Saved baseline contains an unsupported setting; refusing registry changes.') seen.add(key) target = item.get('Target') if isinstance(target, bool) or not isinstance(target, int) or target != setting['Value']: raise RuntimeError('Saved baseline target differs from this package; refusing registry changes.') exists = item.get('Exists') value = item.get('Value') if not isinstance(exists, bool): raise RuntimeError('Saved baseline has an invalid existence flag; refusing registry changes.') if exists and (isinstance(value, bool) or not isinstance(value, int)): raise RuntimeError('Saved baseline has an invalid original DWORD; refusing registry changes.') if not exists and value is not None: raise RuntimeError('Saved baseline has an inconsistent absent value; refusing registry changes.') if seen != set(expected): raise RuntimeError('Saved baseline is missing settings; refusing registry changes.') return state def install(): """Save the pre-package state once, then apply the five approved DWORDs.""" state_file = _state_path() if not isfile(state_file): original = _capture_original_values() _write_state_once(state_file, original) for setting in SETTINGS: _write_value(setting['Path'], setting['Name'], setting['Value']) print('Installed conservative hardening; LapsByWAPT untouched.') def audit(): """Read-only compliance check for the configured package settings.""" try: failed = [] for setting in SETTINGS: actual = _read_value(setting['Path'], setting['Name']) if not actual['Exists'] or actual['Value'] != setting['Value']: failed.append('%s != %s' % (setting['Name'], setting['Value'])) if failed: for item in failed: print(item) return 'WARNING' print('All %s settings compliant.' % len(SETTINGS)) return 'OK' except Exception as exc: print('Hardening audit failed: %s' % type(exc).__name__) return 'WARNING' def uninstall(): """Conditionally restore only values still owned by this package.""" state = _load_state(_state_path()) for item in state: current = _read_value(item['Path'], item['Name']) if not current['Exists'] or current['Value'] != int(item['Target']): print('Skipped externally changed setting: %s' % item['Name']) continue if item['Exists']: _write_value(item['Path'], item['Name'], item['Value']) else: _delete_value(item['Path'], item['Name']) # Preserve the baseline as a forensic record, matching the previous # PowerShell implementation and allowing repeated uninstall safely. print('Conditional rollback completed; LapsByWAPT untouched.')