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

317 lines
15 KiB
Python

import copy
import os
from pathlib import Path
import sys
from unittest import mock
import pytest
APP_DIR = Path(__file__).resolve().parents[1] / 'app'
sys.path.insert(0, str(APP_DIR))
import child_bootstrap
import lifecycle_authority as authority
import scanner
@pytest.fixture
def manifested_git(tmp_path, monkeypatch):
app_dir = tmp_path / 'app'
app_dir.mkdir()
for name in (*authority.CODE_AUTHORITY_FILES, *authority.EXTERNAL_CODE_AUTHORITY_FILES):
path = app_dir / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('# fixture\n', encoding='ascii')
trufflehog = tmp_path / 'trufflehog.exe'
trufflehog.write_bytes(b'trufflehog fixture')
git = tmp_path / 'git.exe'
git.write_bytes(b'git fixture')
manifest = authority.build_code_manifest(
app_dir=app_dir, trufflehog_path=trufflehog, git_path=git,
)
monkeypatch.delenv(authority.CHILD_KIND_ENV, raising=False)
return manifest, git, app_dir, trufflehog
def clone_command(manifest, tmp_path):
return [
manifest['executables']['git']['path'],
'clone', '--no-checkout', '--no-recurse-submodules', '--',
'https://example.invalid/owner/repository.git', str(tmp_path / 'clone'),
]
def test_manifest_requires_both_content_identities(manifested_git):
manifest, git, _, _ = manifested_git
assert set(manifest['executables']) == {'trufflehog', 'git'}
assert manifest['executables']['git'] == {
'path': authority.canonical_path(git), 'sha256': authority.sha256_file(git),
}
assert authority.verify_code_manifest(manifest) == manifest
digest = authority.code_manifest_sha256(manifest)
assert child_bootstrap._verify_manifest(
{'code_manifest': manifest, 'code_manifest_sha256': digest},
{'code_manifest_sha256': digest},
) == manifest['root']
@pytest.mark.parametrize('change', ['missing', 'extra', 'entry', 'relative', 'empty', 'digest'])
def test_manifest_rejects_invalid_git_identity(manifested_git, change):
manifest = copy.deepcopy(manifested_git[0])
if change == 'missing':
del manifest['executables']['git']
elif change == 'extra':
manifest['executables']['shell'] = manifest['executables']['git'].copy()
elif change == 'entry':
manifest['executables']['git'] = None
else:
field, value = {
'relative': ('path', 'git.exe'),
'empty': ('path', ''),
'digest': ('sha256', 'z' * 64),
}[change]
manifest['executables']['git'][field] = value
with pytest.raises(authority.LifecycleAuthorityError):
authority.normalize_code_manifest(manifest)
def test_git_drift_rejected_even_with_preserved_size_and_mtime(manifested_git):
manifest, git, _, _ = manifested_git
before = git.stat()
git.write_bytes(b'GIT fixture')
os.utime(git, ns=(before.st_atime_ns, before.st_mtime_ns))
assert git.stat().st_size == before.st_size
assert git.stat().st_mtime_ns == before.st_mtime_ns
with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'):
authority.verify_code_manifest(manifest)
def test_git_requires_exact_private_acl(manifested_git, monkeypatch):
manifest, git, _, _ = manifested_git
monkeypatch.setattr(authority, 'private_file_ready', lambda path: path != authority.canonical_path(git))
with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'):
authority.verify_code_manifest(manifest, require_private_acl=True)
def test_git_included_in_existing_only_preflight(manifested_git):
_, git, app_dir, trufflehog = manifested_git
paths = authority.manifest_authority_paths(
app_dir, trufflehog, existing_only=True, git_path=git,
)
assert authority.canonical_path(git) in paths
@pytest.mark.parametrize('existing_only', [False, True])
def test_missing_git_fails_closed_in_preflight(manifested_git, existing_only):
_, git, app_dir, trufflehog = manifested_git
git.unlink()
with pytest.raises(authority.LifecycleAuthorityError, match='Git executable'):
authority.manifest_authority_paths(
app_dir, trufflehog, existing_only=existing_only, git_path=git,
)
with pytest.raises(authority.LifecycleAuthorityError, match='Git executable'):
authority.build_code_manifest(app_dir, trufflehog, git_path=git)
def test_project_private_git_preferred_for_capture_and_preflight(manifested_git):
_, _, app_dir, trufflehog = manifested_git
private_git = app_dir.parent / 'runtime' / 'git' / 'cmd' / 'git.exe'
private_git.parent.mkdir(parents=True)
private_git.write_bytes(b'private Git fixture')
with mock.patch.object(authority.shutil, 'which', side_effect=AssertionError('unexpected PATH lookup')):
manifest = authority.build_code_manifest(app_dir, trufflehog)
paths = authority.manifest_authority_paths(app_dir, trufflehog, existing_only=True)
resolved = authority.resolve_manifest_executable(None, name='git', app_dir=app_dir)
assert resolved == authority.canonical_path(private_git)
assert manifest['executables']['git']['path'] == resolved
assert manifest['executables']['git']['sha256'] == authority.sha256_file(private_git)
assert resolved in paths
def test_explicit_git_override_precedes_private_copy(manifested_git):
_, git, app_dir, trufflehog = manifested_git
private_git = app_dir.parent / 'runtime' / 'git' / 'cmd' / 'git.exe'
private_git.parent.mkdir(parents=True)
private_git.write_bytes(b'private Git fixture')
with mock.patch.object(authority.shutil, 'which', side_effect=AssertionError('unexpected PATH lookup')):
manifest = authority.build_code_manifest(app_dir, trufflehog, git_path=git)
paths = authority.manifest_authority_paths(app_dir, trufflehog, git_path=git, existing_only=True)
assert manifest['executables']['git']['path'] == authority.canonical_path(git)
assert authority.canonical_path(git) in paths
assert authority.canonical_path(private_git) not in paths
with pytest.raises(authority.LifecycleAuthorityError, match='Git executable'):
authority.build_code_manifest(app_dir, trufflehog, git_path=app_dir.parent / 'missing.exe')
def test_invalid_private_git_does_not_fall_back_to_path(manifested_git):
_, _, app_dir, trufflehog = manifested_git
private_git = app_dir.parent / 'runtime' / 'git' / 'cmd' / 'git.exe'
private_git.mkdir(parents=True)
with mock.patch.object(authority.shutil, 'which', side_effect=AssertionError('unexpected PATH lookup')):
with pytest.raises(authority.LifecycleAuthorityError, match='Git executable is not a regular file'):
authority.build_code_manifest(app_dir, trufflehog)
@pytest.mark.parametrize('private_parent_exists', [False, True])
def test_default_git_is_resolved_only_at_manifest_capture(manifested_git, monkeypatch, private_parent_exists):
_, git, app_dir, trufflehog = manifested_git
if private_parent_exists:
(app_dir.parent / 'runtime' / 'git' / 'cmd').mkdir(parents=True)
with mock.patch.object(authority.shutil, 'which', return_value=str(git)) as which:
manifest = authority.build_code_manifest(app_dir, trufflehog)
paths = authority.manifest_authority_paths(app_dir, trufflehog, existing_only=True)
assert which.call_args_list == [mock.call('git'), mock.call('git')]
assert authority.canonical_path(git) in paths
monkeypatch.setattr(scanner, '_runtime_initialized', True)
metadata = {'code_manifest': manifest}
with mock.patch.object(scanner, 'require_active_supervisor_child', return_value=metadata) as authenticate, \
mock.patch.object(scanner.shutil, 'which', side_effect=AssertionError('launch PATH lookup')):
assert scanner.get_git_cmd() == authority.canonical_path(git)
authenticate.assert_called_once_with(child_kind='scanner', require_dsn=True)
def test_uninitialized_getter_is_not_launch_authority(monkeypatch, manifested_git, tmp_path):
monkeypatch.setattr(scanner, '_runtime_initialized', False)
with mock.patch.object(scanner.shutil, 'which', return_value=None):
assert scanner.get_git_cmd() == 'git'
with mock.patch.dict(os.environ, {}, clear=True):
with pytest.raises(authority.LifecycleAuthorityError, match='direct mutation is retired'):
scanner.require_git_clone_launch_authority(clone_command(manifested_git[0], tmp_path))
def test_remote_scanner_child_prefers_manifested_git(manifested_git):
manifest, git, _, _ = manifested_git
token = scanner._client_scan_manifest.set(manifest)
try:
env = scanner.prepend_client_git_environment({'PATH': 'foreign-path'})
finally:
scanner._client_scan_manifest.reset(token)
entries = env['PATH'].split(os.pathsep)
assert os.path.normcase(entries[0]) == os.path.normcase(str(git.parent))
assert entries[1:] == ['foreign-path']
def test_exact_clone_authenticates_and_returns_metadata(manifested_git, tmp_path):
metadata = {'code_manifest': manifested_git[0]}
with mock.patch.object(scanner, 'require_active_supervisor_child', return_value=metadata) as authenticate:
assert scanner.require_git_clone_launch_authority(clone_command(manifested_git[0], tmp_path)) is metadata
authenticate.assert_called_once_with(child_kind='scanner', require_dsn=True)
@pytest.mark.parametrize('kind', ['docker-shadow', 'keycheck-provider', 'janitor'])
def test_git_requires_scanner_child_kind(manifested_git, tmp_path, monkeypatch, kind):
monkeypatch.setenv(authority.CHILD_KIND_ENV, kind)
monkeypatch.setattr(scanner, '_runtime_initialized', True)
with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate:
with pytest.raises(RuntimeError, match='requires scanner authority'):
scanner.require_git_clone_launch_authority(clone_command(manifested_git[0], tmp_path))
with pytest.raises(RuntimeError, match='requires scanner authority'):
scanner.get_git_cmd()
authenticate.assert_not_called()
@pytest.mark.parametrize('operation', ['fetch', 'checkout', 'config', 'submodule', 'init', '--version'])
def test_other_operations_rejected_before_authentication(manifested_git, tmp_path, operation):
cmd = clone_command(manifested_git[0], tmp_path)
cmd[1] = operation
with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate:
with pytest.raises(RuntimeError, match='argv contract'):
scanner.require_git_clone_launch_authority(cmd)
authenticate.assert_not_called()
@pytest.mark.parametrize('change', ['extra', 'missing', 'reordered', 'separator', 'config', 'depth', 'string', 'none', 'nonstring', 'nul'])
def test_only_exact_clone_argv_is_allowed(manifested_git, tmp_path, change):
cmd = clone_command(manifested_git[0], tmp_path)
if change == 'extra':
cmd.append('--verbose')
elif change == 'missing':
del cmd[3]
elif change == 'reordered':
cmd[2], cmd[3] = cmd[3], cmd[2]
elif change == 'separator':
cmd[4] = '--bare'
elif change in {'config', 'depth'}:
cmd[2] = '-c' if change == 'config' else '--depth=1'
elif change == 'string':
cmd = ' '.join(cmd)
elif change == 'none':
cmd = None
elif change == 'nonstring':
cmd[6] = Path(cmd[6])
elif change == 'nul':
cmd[6] += '\x00'
with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate:
with pytest.raises(RuntimeError, match='argv contract'):
scanner.require_git_clone_launch_authority(cmd)
authenticate.assert_not_called()
@pytest.mark.parametrize('source', [
'http://example.invalid/repo', 'ssh://example.invalid/repo',
'file:///fixture/repo', '/fixture/repo', 'C:\\fixture\\repo',
'git@example.invalid:repo', 'ext::command', '--upload-pack=command',
'https:///repo', 'https://example.invalid',
'https://user@example.invalid/repo', 'https://user:password@example.invalid/repo',
'https://example.invalid/repo?token=sentinel', 'https://example.invalid/repo#sentinel',
'https://example.invalid\\@other.invalid/repo', 'https://example.invalid/%20 repo',
'https://example.invalid%2fother/repo', 'https://[broken/repo',
'https://example.invalid:bad/repo', 'https://example.invalid:99999/repo',
])
def test_unsafe_sources_rejected_even_when_uninitialized(manifested_git, tmp_path, monkeypatch, source):
monkeypatch.setattr(scanner, '_runtime_initialized', False)
cmd = clone_command(manifested_git[0], tmp_path)
cmd[5] = source
with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate:
with pytest.raises(RuntimeError, match='credential-free absolute HTTPS'):
scanner.require_git_clone_launch_authority(cmd)
authenticate.assert_not_called()
@pytest.mark.parametrize('destination', ['relative', '-option', ''])
def test_invalid_destination_rejected(manifested_git, tmp_path, destination):
cmd = clone_command(manifested_git[0], tmp_path)
cmd[6] = destination
with pytest.raises(RuntimeError):
scanner.require_git_clone_launch_authority(cmd)
@pytest.mark.skipif(os.name != 'nt', reason='Windows drive-relative paths')
@pytest.mark.parametrize('destination', ['\\clone', '/clone', 'C:clone'])
def test_drive_relative_destination_rejected(manifested_git, tmp_path, destination):
cmd = clone_command(manifested_git[0], tmp_path)
cmd[6] = destination
with pytest.raises(RuntimeError, match='absolute path'):
scanner.require_git_clone_launch_authority(cmd)
@pytest.mark.parametrize('replacement', ['path', 'bare', 'trufflehog'])
def test_unmanifested_executable_is_rejected(manifested_git, tmp_path, replacement):
manifest = manifested_git[0]
cmd = clone_command(manifest, tmp_path)
cmd[0] = {
'path': str(tmp_path / 'replacement.exe'), 'bare': 'git',
'trufflehog': manifest['executables']['trufflehog']['path'],
}[replacement]
with mock.patch.object(scanner, 'require_active_supervisor_child', return_value={'code_manifest': manifest}):
with pytest.raises(RuntimeError, match='immutable supervisor authority'):
scanner.require_git_clone_launch_authority(cmd)
def test_getter_and_guard_propagate_content_verification_failure(manifested_git, tmp_path, monkeypatch):
manifest, git, _, _ = manifested_git
git.write_bytes(b'changed Git executable')
monkeypatch.setattr(scanner, '_runtime_initialized', True)
def authenticate(**kwargs):
assert kwargs == {'child_kind': 'scanner', 'require_dsn': True}
authority.verify_code_manifest(manifest)
return {'code_manifest': manifest}
monkeypatch.setattr(scanner, 'require_active_supervisor_child', authenticate)
with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'):
scanner.get_git_cmd()
with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'):
scanner.require_git_clone_launch_authority(clone_command(manifest, tmp_path))