Files
Samsara/tests/test_linux.py
oceans2alaska 610a3bb447 Privacy hardening, remote-peer IP masking and review fixes
- Add privacy projection (core/privacy.py), private report storage, bounded
  process capture; omit identities, free text and unapproved paths
- Mask remote peer IPs: only loopback/host/wildcard stay numeric, others are
  reduced to remote_scope private/public (schema + scoring updated)
- Map ss TCP states to canonical names; ASCII-only numeric fields; fix
  "Microsoft Visual C++" product matching (Python and PowerShell)
- Merge 7fed3b9: -Diagnostic switch, serialization exit code 3, PS7 module
  path, UI error detail; keep hardened task/certificate collectors
- Add audit docs, privacy docs and regression tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 18:50:24 -08:00

127 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from samsara.collectors.linux import (collect_local_linux, parse_connections,
parse_units, run_command, CollectionCancelled)
from samsara.collectors.demo import collect_demo, LINUX_DEMO_HOST
from samsara.analyzers.assessment import assess
from report_helpers import export_html
class LinuxTests(unittest.TestCase):
def test_service_states_and_startup(self):
rows = parse_units('nginx.service loaded active running Web server\nexample.service loaded inactive dead Example\n',
'nginx.service enabled enabled\nexample.service disabled disabled\n')
self.assertEqual(rows[0]['state'], 'Running')
self.assertEqual(rows[0]['startup'], 'enabled')
self.assertEqual(rows[1]['state'], 'inactive/dead')
def test_ipv4_ipv6_and_unprivileged_connections(self):
rows = parse_connections('LISTEN 0 128 0.0.0.0:22 0.0.0.0:*\nESTAB 0 0 [::1]:443 [::1]:51234 users:(("nginx",pid=123,fd=7))\n')
self.assertEqual(rows[0]['remote_port'], 0)
self.assertIsNone(rows[0]['pid'])
self.assertEqual(rows[1]['local_address'], '::1')
self.assertEqual(rows[1]['state'], 'Established')
self.assertEqual(rows[1]['process'], 'nginx')
self.assertEqual(rows[1]['direction'], 'Unknown')
@patch('samsara.collectors.linux.platform.system', return_value='Windows')
def test_wrong_execution_platform(self, system):
with self.assertRaises(ValueError):
collect_local_linux()
@patch('samsara.collectors.linux.platform.system', return_value='Linux')
def test_partial_failures_never_use_demo(self, system):
def unavailable(args, **kwargs):
raise RuntimeError('SECRET diagnostics must not be reported')
data = collect_local_linux(runner=unavailable)
self.assertFalse(data['synthetic'])
self.assertEqual(data['checks']['services']['status'], 'failed')
self.assertEqual(data['checks']['network']['status'], 'failed')
self.assertEqual(data['checks']['software']['status'], 'failed')
self.assertEqual(data['inventory']['services'], [])
self.assertNotIn('SECRET', json.dumps(data))
self.assertEqual(data['checks']['certificates']['status'], 'not_collected')
@patch('samsara.collectors.linux.platform.system', return_value='Linux')
def test_cancel(self, system):
with self.assertRaises(CollectionCancelled):
collect_local_linux(cancelled=lambda: True)
def test_mint_and_rhel_package_parsing(self):
for rpm in [False, True]:
def command(args, **kwargs):
if args[0] == 'ip':
return '[]'
if args[0] == 'rpm':
return 'nginx\t1.2-1\tExample vendor\n'
if args[0] == 'dpkg-query':
return 'installed\tnginx:amd64\t1.2\nconfig-files\tremoved\t1.0\n'
return ''
with self.subTest(rpm=rpm), patch('samsara.collectors.linux.platform.system', return_value='Linux'), patch('samsara.collectors.linux.Path.is_file', return_value=rpm):
data = collect_local_linux(runner=command)
self.assertEqual(len(data['inventory']['software']), 1)
self.assertIn('nginx', data['inventory']['software'][0]['name'])
self.assertEqual(data['checks']['software']['status'], 'partial')
def test_local_report_source_and_unclassified_services(self):
data = collect_demo(LINUX_DEMO_HOST)
data['synthetic'] = False
data['server']['collection_source'] = 'Local Linux read-only collection'
data['inventory']['services'] = [{'name': 'desktop-example.service', 'state': 'Running'}]
result = assess(data)
self.assertFalse(any(f['category'] == 'Active service' for f in result['findings']))
self.assertTrue(any(f['category'] == 'Unclassified services' for f in result['findings']))
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / 'report.html'
export_html(path, data, result)
self.assertIn('LOCAL LINUX INVENTORY', path.read_text())
self.assertNotIn('IMPORTED INVENTORY', path.read_text())
@unittest.skipUnless(__import__('sys').platform.startswith('linux'), 'Linux command test')
def test_command_timeout_and_cancellation(self):
with self.assertRaisesRegex(RuntimeError, 'timeout'):
run_command(['sleep', '1'], timeout=.05)
with self.assertRaises(CollectionCancelled):
run_command(['sleep', '1'], cancelled=lambda: True)
class RegressionReviewTests(unittest.TestCase):
def test_ss_states_map_to_canonical_names(self):
from samsara.collectors.linux import parse_connections
rows = parse_connections('TIME-WAIT 0 0 10.0.0.5:44321 10.0.0.9:443\nCLOSE-WAIT 0 0 10.0.0.5:1 10.0.0.9:2\nFIN-WAIT-1 0 0 10.0.0.5:3 10.0.0.9:4\n')
self.assertEqual([r['state'] for r in rows], ['TimeWait', 'CloseWait', 'FinWait1'])
def test_non_ascii_digits_and_cpp_product_label(self):
import json
from pathlib import Path
from samsara.core.privacy import minimize_inventory, numeric_version, safe_time
self.assertEqual(numeric_version('١٢.٣٤'), 'Not collected')
self.assertIsNone(safe_time('٢٠٢٦-٠١-٠١T٠٠:٠٠:٠٠Z'))
data = json.loads(Path('samsara/assets/demo_inventory.json').read_text())
data['server']['os_build'] = '١٢٣٤٥'
data['inventory']['software'] = [{'name': 'Microsoft Visual C++ 2015-2022 Redistributable (x64)', 'version': '14.38.33135'}]
clean = minimize_inventory(data)
self.assertNotEqual(clean['server']['os_build'], '١٢٣٤٥')
self.assertEqual(clean['inventory']['software'][0]['name'], 'Microsoft Visual C++')
def test_remote_peer_addresses_are_masked_but_scoring_survives(self):
import copy
from samsara.core.privacy import minimize_inventory
from samsara.models.inventory import validate_inventory
data = json.loads(Path('tests/fixtures/scoring/active_sql_server.json').read_text())
before = assess(minimize_inventory(copy.deepcopy(data)))['score']
for row in data['inventory']['network']:
if row.get('remote_address') not in ('', '0.0.0.0', '::'):
row['remote_address'] = '93.184.216.34'
clean = minimize_inventory(data)
validate_inventory(clean)
self.assertNotIn('93.184.216.34', json.dumps(clean))
self.assertEqual(minimize_inventory(clean), clean)
self.assertEqual(assess(clean)['score'], before)
self.assertNotIn('93.184.216.34', json.dumps(assess(clean)))
self.assertIn('public', {r['remote_scope'] for r in clean['inventory']['network']})