import copy import io import json from pathlib import Path import tempfile import unittest from unittest.mock import patch from samsara.collectors.windows import (collect_local_windows, run_section, verify_server, validate_section, WindowsCollectionError, SECTIONS) from samsara.collectors.linux import CollectionCancelled from samsara.models.inventory import validate_inventory from samsara.collectors.importer import load_inventory from samsara.analyzers.assessment import assess from report_helpers import export_html, export_json def fixture(year='2022'): return json.loads(Path(f'tests/fixtures/windows/server-{year}.json').read_text()) def record_for(data, section): record = {'section': section, **copy.deepcopy(data['checks'][section])} if section == 'system': record['data'] = copy.deepcopy(data['server']) elif section == 'shares': record['data'] = [r for r in data['inventory']['storage'] if r['type'] == 'SMB Share'] elif section == 'storage': record['data'] = [r for r in data['inventory']['storage'] if r['type'] != 'SMB Share'] else: record['data'] = copy.deepcopy(data['inventory'][section]) return record class WindowsTests(unittest.TestCase): def collect(self, data=None, runner=None, **kwargs): data = data or fixture() if runner is None: runner = lambda section, executable, **options: record_for(data, section) with patch('samsara.collectors.windows.platform.system', return_value='Windows'), patch('samsara.collectors.windows.powershell_executable', return_value=Path('C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe')): return collect_local_windows(runner=runner, **kwargs) def test_all_server_contracts_profiles_and_arrays(self): for year in ['2016','2019','2022','2025']: with self.subTest(year=year): data=fixture(year) validate_inventory(data) result=self.collect(data, profile='windows:'+year) self.assertEqual(result['server']['os_version'],year) self.assertFalse(result['synthetic']) self.assertEqual(result['server']['collection_source'],'Local Windows read-only collection') self.assertEqual(len(result['inventory']['storage']),2) self.assertEqual(set(result['checks']),set(SECTIONS)|{'privacy'}) self.assertTrue(all(isinstance(rows,list) for rows in result['inventory'].values())) self.assertTrue(all('started_at' in c and 'completed_at' in c for k,c in result['checks'].items() if k!='privacy')) def test_profiles_and_workstation_cannot_masquerade_as_server(self): for mutation in ['client','build','mismatch']: data=fixture() if mutation=='client': data['server']['product_type']=1 elif mutation=='build': data['server']['os_build']='99999' with self.subTest(mutation=mutation), self.assertRaises(ValueError): self.collect(data,profile='windows:2016' if mutation=='mismatch' else 'auto') def test_platform_guard_never_runs_powershell_on_linux(self): with patch('samsara.collectors.windows.platform.system', return_value='Linux'), patch('samsara.collectors.windows.run_section') as runner: with self.assertRaises(ValueError): collect_local_windows() runner.assert_not_called() def test_timeout_and_partial_permission_results_preserve_other_sections(self): def runner(section, executable, **kwargs): if section=='roles': raise WindowsCollectionError('timeout','The local section exceeded its time limit.') record=record_for(fixture(),section) if section=='certificates': record['status']='partial' record['errors']=[{'code':'access_denied','scope':'Local machine certificate metadata','message':'Current account cannot read this scope.'}] record['permission_limitations']=['One store denied.'] return record data=self.collect(runner=runner) self.assertEqual(data['checks']['roles']['status'],'failed') self.assertEqual(data['checks']['roles']['errors'][0]['code'],'timeout') self.assertTrue(data['inventory']['services']) self.assertTrue(data['inventory']['certificates']) self.assertEqual(data['checks']['certificates']['status'],'partial') self.assertTrue(any('roles:' in f['evidence'] for f in assess(data)['findings'])) def test_identity_failures_are_fatal_and_bad_sections_are_explicit(self): with self.assertRaises(ValueError): self.collect(runner=lambda *args,**kwargs: {}) def runner(section, executable, **kwargs): record=record_for(fixture(),section) if section=='network': record['data']=[{'state':'Listen'}] return record data=self.collect(runner=runner) self.assertEqual(data['checks']['network']['errors'][0]['code'],'invalid_output') self.assertEqual(data['inventory']['network'],[]) def test_global_timeout_marks_unattempted_sections(self): # First iteration/system fits; remaining iterations exceed the deadline. times=iter([0,0,*([10]*8)]) with patch('samsara.collectors.windows.time.monotonic',side_effect=lambda:next(times)): data=self.collect(total_timeout=1) self.assertEqual(data['checks']['services']['status'],'not_collected') self.assertEqual(data['checks']['services']['errors'][0]['code'],'overall_timeout') def test_cancellation_and_output_limits(self): with self.assertRaises(CollectionCancelled): self.collect(cancelled=lambda:True) with patch('samsara.collectors.windows.MAX_OUTPUT',1), self.assertRaises(ValueError): self.collect() def test_secret_fields_are_excluded_and_check_metadata_survives_import(self): def runner(section,executable,**kwargs): record=record_for(fixture(),section) if section=='services': record['data'][0]['arguments']='SECRET' record['data'][0]['PathName']='SECRET' return record data=self.collect(runner=runner) self.assertNotIn('SECRET',json.dumps(data)) with tempfile.TemporaryDirectory() as folder: out=Path(folder)/'inventory.json' export_json(out,data,assess(data)) imported=load_inventory(out) self.assertNotIn('collection_source',imported['server']) self.assertEqual(imported['checks']['system']['started_at'],data['checks']['system']['started_at']) self.assertIn('permission_limitations',imported['checks']['services']) html=Path(folder)/'report.html' export_html(html,data,assess(data)) text=html.read_text() self.assertIn('LOCAL WINDOWS INVENTORY',text) self.assertIn('started_at',text) self.assertIn('permission_limitations',text) self.assertNotIn('SECRET',text) def test_invalid_section_cannot_be_interpolated(self): with self.assertRaises(ValueError): run_section('services;whoami','powershell.exe') class FakeProcess: def __init__(self,args,**kwargs): self.stdout=io.BytesIO() self.returncode=None self.args=args self.kwargs=kwargs self.killed=False self.waited=False def poll(self): return self.returncode def kill(self): self.killed=True; self.returncode=-1 def wait(self,timeout=None): self.waited=True; return self.returncode class ProcessTests(unittest.TestCase): def test_serialization_exit_and_diagnostic_switch(self): from samsara.core.process import ProcessFailure def fake_capture(args, **kwargs): self.assertIn('-Diagnostic', args) raise ProcessFailure('collection_failed', 3) with patch('samsara.collectors.windows.capture', side_effect=fake_capture): with self.assertRaises(WindowsCollectionError) as error: run_section('tasks', 'powershell.exe', diagnostic=True) self.assertEqual(error.exception.code, 'serialization_failed') def test_timeout_and_cancel_kill_and_reap_without_shell(self): for cancel in [False,True]: processes=[] def create(args,**kwargs): process=FakeProcess(args,**kwargs);processes.append(process);return process calls=iter([False,False,True]) with self.subTest(cancel=cancel), patch('samsara.collectors.windows.subprocess.Popen',side_effect=create): with self.assertRaises(CollectionCancelled if cancel else WindowsCollectionError): run_section('services',Path('C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe'), cancelled=(lambda:next(calls)) if cancel else (lambda:False),timeout=0) process=processes[0] self.assertTrue(process.killed and process.waited) self.assertFalse(process.kwargs['shell']) self.assertIn('-NoProfile',process.args) self.assertIn('-File',process.args) self.assertNotIn('-Command',process.args) self.assertNotIn('-ExecutionPolicy',process.args) self.assertEqual(process.kwargs['env']['PSModulePath'],str(Path('C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe').parent / 'Modules')) def test_invalid_stdout_and_start_failure_are_sanitized(self): def create(args,**kwargs): process=FakeProcess(args,**kwargs);process.returncode=0 process.stdout.write(b'not JSON SECRET') process.stdout.seek(0) return process with patch('samsara.collectors.windows.subprocess.Popen',side_effect=create): with self.assertRaises(WindowsCollectionError) as error: run_section('services','powershell.exe') self.assertEqual(error.exception.code,'invalid_output') self.assertNotIn('SECRET',str(error.exception)) with patch('samsara.collectors.windows.subprocess.Popen',side_effect=OSError('SECRET')): with self.assertRaises(WindowsCollectionError) as error: run_section('services','powershell.exe') self.assertNotIn('SECRET',str(error.exception)) def test_successful_utf8_bom_zero_rows_and_nonzero_exit(self): processes=[] def create(args,**kwargs): process=FakeProcess(args,**kwargs) process.returncode=0 record=record_for(fixture(),'services') record['data']=[] process.stdout.write(b'\xef\xbb\xbf'+json.dumps(record).encode('utf-8')) processes.append(process) process.stdout.seek(0) return process with patch('samsara.collectors.windows.subprocess.Popen',side_effect=create): record=run_section('services','powershell.exe') validate_section(record,'services') self.assertEqual(record['data'],[]) self.assertTrue(processes[0].waited) def failed(args,**kwargs): process=FakeProcess(args,**kwargs);process.returncode=1 process.stdout.write(b'SECRET') process.stdout.seek(0) return process with patch('samsara.collectors.windows.subprocess.Popen',side_effect=failed): with self.assertRaises(WindowsCollectionError) as error: run_section('services','powershell.exe') self.assertEqual(error.exception.code,'collection_failed') self.assertNotIn('SECRET',str(error.exception)) def test_completed_process_output_limit(self): def create(args,**kwargs): process=FakeProcess(args,**kwargs);process.returncode=0 process.stdout.write(b'x'*20) process.stdout.seek(0) return process with patch('samsara.collectors.windows.subprocess.Popen',side_effect=create), patch('samsara.collectors.windows.MAX_OUTPUT',10): with self.assertRaises(WindowsCollectionError) as error: run_section('services','powershell.exe') self.assertEqual(error.exception.code,'output_limit')