Files
truf-server/tests/test_container_import_config.py
T
2026-09-30 20:30:56 +03:00

266 lines
17 KiB
Python

"""Offline stdlib tests: python -I -S -B tests/test_container_import_config.py."""
from copy import deepcopy
import importlib.util
from pathlib import Path
import unittest
from unittest import mock
HELPER = Path(__file__).resolve().parents[1] / 'app' / 'container_import_config.py'
SPEC = importlib.util.spec_from_file_location('container_import_config', HELPER)
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
translate_windows_config = MODULE.translate_windows_config
class ContainerImportConfigTests(unittest.TestCase):
def setUp(self):
self.baseline = {
'global': {
'root_dir': '/opt/truf', 'project_dir': '{root_dir}/app',
'runtime_dir': '/data/runtime-linux', 'postgres_data_dir': '/data/postgres-linux',
'postgres_bin_dir': '/usr/lib/postgresql/16/bin',
'result_bundle_dir': '/data/scanner-result-bundles', 'work_dir': '/data/scanner-work',
'control_dir': '/run/truf/control', 'trufflehog_path': '/usr/local/bin/trufflehog',
'proxy_file': '{runtime_dir}/proxy.txt', 'secrets_file': '/data/config/secrets.yaml',
'trufflehog_config': '{project_dir}/trufflehog-custom-detectors.yaml',
'max_active_scans': 1, 'opportunistic_scan_slots': 0,
'trufflehog_job_memory_limit_bytes': 4294967296,
'trufflehog_windows_job_cpu_weight': 2, 'trufflehog_windows_memory_priority': 4,
'no_verification': False, 'api_proxy_enabled': False, 'min_free_gb': 20,
'database_url': '', 'baseline_only_policy': True,
},
'supervisor': {
'control_dir': '{control_dir}', 'instance_file': '{control_dir}/supervisor.instance.json',
'lock_file': '{control_dir}/supervisor.lock', 'control_host': '127.0.0.1', 'control_port': 8765,
'interactive': False, 'autostart': True, 'enabled_sources': ['dockerhub'],
'dashboard': {'enabled': False, 'port': 5000},
},
'sources': {
'dockerhub': {'enabled': False, 'trufflehog_job_memory_limit_bytes': 6442450944},
'linux_only': {'enabled': True},
},
'keychecks': {'enabled': False, 'services': ['baseline-only']},
'providers': {'baseline_only': {}}, 'query_policy': {'rejected': []},
}
self.original = {
'global': {
'root_dir': r'D:\truf', 'project_dir': '.', 'runtime_dir': r'{root_dir}\runtime',
'postgres_data_dir': r'S:\postgres-data', 'result_bundle_dir': r'S:\scanner-result-bundles',
'work_dir': r'S:\scanner-work', 'trufflehog_path': r'C:\Tools\trufflehog.exe',
'control_dir': r'{runtime_dir}\control', 'proxy_file': r'{runtime_dir}\proxy.txt',
'secrets_file': r'{project_dir}\secrets.yaml',
'trufflehog_config': r'{project_dir}\trufflehog-custom-detectors.yaml',
'max_active_scans': 3, 'opportunistic_scan_slots': 1,
'trufflehog_job_memory_limit_bytes': 8589934592,
'trufflehog_windows_job_cpu_weight': 9, 'trufflehog_windows_memory_priority': 5,
'no_verification': True, 'api_proxy_enabled': True, 'download_proxy_enabled': False,
'min_free_gb': 31, 'detectors': ['fixture'], 'drop_detectors': r'fixture\regex',
'database_url': r'postgresql://fixture:fake\password@fixture.invalid/db',
},
'supervisor': {
'control_dir': '{control_dir}', 'instance_file': r'{control_dir}\supervisor.instance.json',
'lock_file': r'{control_dir}\supervisor.lock', 'control_host': 'localhost', 'control_port': 8888,
'interactive': True, 'autostart': False, 'enabled_sources': ['github', 'keychecks'],
'dashboard': {'enabled': True, 'port': 5444},
'defaults': {'once': True, 'extra_args': ['--query', r'literal\query']},
'sources': {'github': {'enabled': True, 'interval': 123}},
},
'sources': {
'github': {
'enabled': True, 'workers': 17,
'queries': [r'path:/src\d+/ "sk-\w{6}"', r'C:\literal\query'],
'query_overrides': {r'literal\query': {'pages': 7}},
'auth_pool': [{'name': r'fixture\account', 'token': r'fake\token'}],
'url': r'https://fixture.invalid/search?q=\d+&path=C:\literal',
'regex': r'\bfixture[\\/]\w+\s*$',
},
'dockerhub': {'enabled': True, 'trufflehog_job_memory_limit_bytes': 12884901888},
},
'keychecks': {
'enabled': True, 'services': ['fixture'], 'retry_valid': True,
'service_args': {'fixture': ['--pattern', r'\bfixture\w+']},
'env': {'FIXTURE_AUTH': r'fake\auth', 'FIXTURE_URL': r'https://fixture.invalid/\x'},
},
'providers': {'fixture': {'auth': r'fake\auth', 'path': r'C:\opaque\provider-value'}},
'query_policy': {'rejected': [{'source': 'github', 'query': r'\bfixture\w+'}]},
}
def test_preserves_queries_regex_urls_auth_and_policy_without_baseline_merge(self):
config, adjusted = translate_windows_config(self.original, self.baseline)
for key in ('providers', 'query_policy', 'keychecks'):
self.assertEqual(config[key], self.original[key])
self.assertEqual(config['sources']['github'], self.original['sources']['github'])
self.assertTrue(config['sources']['dockerhub']['enabled'])
self.assertNotIn('linux_only', config['sources'])
self.assertNotIn('baseline_only_policy', config['global'])
for key in ('no_verification', 'api_proxy_enabled', 'download_proxy_enabled',
'min_free_gb', 'detectors', 'drop_detectors', 'database_url'):
self.assertEqual(config['global'][key], self.original['global'][key])
for key in ('enabled_sources', 'defaults', 'sources'):
self.assertEqual(config['supervisor'][key], self.original['supervisor'][key])
self.assertFalse(any(path.startswith(('providers.', 'query_policy.')) for path in adjusted))
def test_exact_fixed_paths_do_not_depend_on_import_config_directory(self):
config, _ = translate_windows_config(self.original, self.baseline)
expected = {
'root_dir': '/opt/truf', 'project_dir': '/opt/truf/app',
'runtime_dir': '/data/runtime-linux', 'postgres_data_dir': '/data/postgres-linux',
'postgres_bin_dir': '/usr/lib/postgresql/16/bin',
'result_bundle_dir': '/data/scanner-result-bundles', 'work_dir': '/data/scanner-work',
'control_dir': '/run/truf/control', 'trufflehog_path': '/usr/local/bin/trufflehog',
'proxy_file': '/data/runtime-linux/proxy.txt', 'secrets_file': '/data/config/secrets.yaml',
'trufflehog_config': '/data/config/trufflehog-custom-detectors.yaml',
}
self.assertEqual({key: config['global'][key] for key in expected}, expected)
self.assertEqual(config['supervisor']['control_dir'], '/run/truf/control')
self.assertEqual(config['supervisor']['instance_file'], '/run/truf/control/supervisor.instance.json')
self.assertEqual(config['supervisor']['lock_file'], '/run/truf/control/supervisor.lock')
def test_only_platform_and_headless_settings_follow_baseline(self):
self.original['sources']['github']['trufflehog_windows_job_cpu_weight'] = 7
config, _ = translate_windows_config(self.original, self.baseline)
for key in ('max_active_scans', 'opportunistic_scan_slots', 'trufflehog_job_memory_limit_bytes',
'trufflehog_windows_job_cpu_weight', 'trufflehog_windows_memory_priority'):
self.assertEqual(config['global'][key], self.baseline['global'][key])
self.assertEqual(config['sources']['dockerhub']['trufflehog_job_memory_limit_bytes'], 6442450944)
self.assertEqual(config['sources']['github']['trufflehog_windows_job_cpu_weight'], 2)
for key in ('interactive', 'autostart', 'control_host', 'control_port'):
self.assertEqual(config['supervisor'][key], self.baseline['supervisor'][key])
self.assertEqual(config['supervisor']['dashboard'], {'enabled': False, 'port': 5444})
def test_normalizes_only_known_path_fields_and_keeps_templates_and_custom_names(self):
fields = {
'global': {
'result_spool_dir': r'{runtime_dir}\result_spool',
'legacy_result_spool_dir': r'{runtime_dir}\result_spool',
'results_dir': r'{runtime_dir}\results', 'queue_dir': r'{runtime_dir}\queues',
'state_dir': r'{runtime_dir}\state', 'log_dir': r'{runtime_dir}\logs',
'keycheck_dir': r'{runtime_dir}\keychecks', 'postman_cache_dir': r'{runtime_dir}\postman_cache',
'gharchive_cache_dir': r'{state_dir}\gharchive_cache',
'database_path': r'{results_dir}\scanner_active.db', 'dashboard_db_path': '{database_path}',
'state_file': r'{state_dir}\custom-state.json', 'scan_limiter_db': r'{state_dir}\scan_limiter.db',
'dockerhub_tag_cache_path': r'{state_dir}\dockerhub_tag_cache.sqlite',
'api_proxy_file': '{proxy_file}', 'download_proxy_file': '',
},
'supervisor': {
'log_dir': '{log_dir}', 'state_dir': '{state_dir}', 'supervisor_log': r'{log_dir}\supervisor.log',
'status_file': r'{log_dir}\supervisor.status.txt', 'dashboard_log': r'{log_dir}\dashboard.log',
},
'keychecks': {
'input': r'{results_dir}\found_secrets.jsonl', 'proxy_file': r'{runtime_dir}\proxy.txt',
'keycheck_dir': r'{keycheck_dir}\fixture', 'summary_tsv': r'{keycheck_dir}\summary.tsv',
'summary_json': r'{keycheck_dir}\summary.json', 'alive_summary_tsv': r'{keycheck_dir}\alive_summary.tsv',
},
}
for section, values in fields.items():
self.original[section].update(values)
source_paths = {'target_file': r'inputs\fixture.txt', 'postman_cache_dir': r'{postman_cache_dir}\fixture',
'gharchive_cache_dir': r'{state_dir}\gharchive_cache'}
self.original['sources']['github'].update(source_paths)
config, adjusted = translate_windows_config(self.original, self.baseline)
for section, values in fields.items():
for key, value in values.items():
self.assertEqual(config[section][key], value.replace('\\', '/'))
self.assertEqual(section + '.' + key in adjusted, '\\' in value)
for key, value in source_paths.items():
self.assertEqual(config['sources']['github'][key], value.replace('\\', '/'))
def test_known_copied_detector_and_proxy_references_use_imported_files(self):
for detector in (r'{project_dir}\trufflehog-custom-detectors.yaml',
r'D:\truf\app\trufflehog-custom-detectors.yaml', 'trufflehog-custom-detectors.yaml'):
with self.subTest(detector=detector):
self.original['sources']['github']['trufflehog_config'] = detector
self.original['keychecks']['proxy_file'] = r'D:\truf\runtime\proxy.txt'
config, _ = translate_windows_config(self.original, self.baseline)
self.assertEqual(config['sources']['github']['trufflehog_config'],
'/data/config/trufflehog-custom-detectors.yaml')
self.assertEqual(config['keychecks']['proxy_file'], '/data/runtime-linux/proxy.txt')
def test_inputs_and_nested_values_are_independent_on_success(self):
before = deepcopy((self.original, self.baseline))
config, _ = translate_windows_config(self.original, self.baseline)
self.assertEqual((self.original, self.baseline), before)
config['sources']['github']['queries'].append('new query')
config['sources']['github']['auth_pool'][0]['token'] = 'changed fake token'
config['keychecks']['service_args']['fixture'].append('new argument')
config['supervisor']['enabled_sources'].clear()
self.assertEqual((self.original, self.baseline), before)
def test_adjustment_report_is_exact_sorted_key_paths_and_idempotent(self):
config, adjusted = translate_windows_config(self.original, self.baseline)
expected = ['global.' + key for key in (
'root_dir', 'project_dir', 'runtime_dir', 'postgres_data_dir', 'postgres_bin_dir',
'result_bundle_dir', 'work_dir', 'control_dir', 'trufflehog_path', 'proxy_file',
'secrets_file', 'trufflehog_config', 'max_active_scans', 'opportunistic_scan_slots',
'trufflehog_job_memory_limit_bytes', 'trufflehog_windows_job_cpu_weight', 'trufflehog_windows_memory_priority',
)]
expected += ['supervisor.' + key for key in (
'control_dir', 'instance_file', 'lock_file', 'control_host', 'control_port',
'interactive', 'autostart', 'dashboard.enabled',
)]
expected.append('sources.dockerhub.trufflehog_job_memory_limit_bytes')
self.assertEqual(adjusted, sorted(expected))
self.assertEqual(translate_windows_config(config, self.baseline), (config, []))
def test_rejects_unreviewed_absolute_executables_and_data_before_overriding(self):
for key in ('root_dir', 'project_dir', 'runtime_dir', 'postgres_data_dir', 'postgres_bin_dir',
'result_bundle_dir', 'work_dir', 'trufflehog_path', 'trufflehog_config',
'proxy_file', 'secrets_file', 'database_path', 'api_proxy_file', 'download_proxy_file'):
original = deepcopy(self.original)
original['global'][key] = r'Z:\private-fixture\custom-path'
before = deepcopy((original, self.baseline))
with self.subTest(key=key), self.assertRaisesRegex(ValueError, 'global\\.' + key) as error:
translate_windows_config(original, self.baseline)
self.assertNotIn('private-fixture', str(error.exception))
self.assertEqual((original, self.baseline), before)
def test_rejects_foreign_custom_inputs_including_unc_device_and_drive_relative_paths(self):
paths = (r'C:\private-fixture\input.txt', 'C:/private-fixture/input.txt',
r'\\server\private-fixture\input.txt', '//server/private-fixture/input.txt',
r'\\?\C:\private-fixture\input.txt', r'\private-fixture\input.txt',
r'C:private-fixture\input.txt', r'D:\truf\app\custom-input.txt')
for value in paths:
for prefix, mapping, key in (
('keychecks', self.original['keychecks'], 'input'),
('sources.github', self.original['sources']['github'], 'target_file'),
('sources.github', self.original['sources']['github'], 'trufflehog_config'),
('supervisor', self.original['supervisor'], 'instance_file'),
):
with self.subTest(prefix=prefix, key=key, value=value):
previous = deepcopy(mapping)
mapping[key] = value
with self.assertRaises(ValueError) as error:
translate_windows_config(self.original, self.baseline)
self.assertIn(prefix + '.' + key, str(error.exception))
self.assertNotIn('private-fixture', str(error.exception))
mapping.clear()
mapping.update(previous)
def test_rejects_relative_custom_executable_instead_of_substituting_a_different_binary(self):
self.original['global']['trufflehog_path'] = r'custom-tools\private-fixture.exe'
with self.assertRaisesRegex(ValueError, 'global.trufflehog_path'):
translate_windows_config(self.original, self.baseline)
def test_rejects_baseline_escaping_the_fixed_storage_contract(self):
for section, key, value in (
('global', 'project_dir', '.'), ('global', 'root_dir', '/elsewhere'),
('global', 'postgres_data_dir', '/elsewhere'), ('supervisor', 'control_dir', '/data/control'),
):
baseline = deepcopy(self.baseline)
baseline[section][key] = value
with self.subTest(section=section, key=key), self.assertRaisesRegex(ValueError, 'Invalid Linux baseline path'):
translate_windows_config(self.original, baseline)
def test_translation_performs_no_io_or_environment_lookup(self):
with mock.patch('builtins.open', side_effect=AssertionError('file IO forbidden')), \
mock.patch('os.getenv', side_effect=AssertionError('environment lookup forbidden')), \
mock.patch('os.environ', {}), mock.patch('builtins.print') as output:
translate_windows_config(self.original, self.baseline)
output.assert_not_called()
if __name__ == '__main__':
unittest.main()