wrolpi/modules/repos/conftest.py
Roland 00d7816cfd
repos: pin the repo's host once, and only exempt its own host and port
A submodule on the repo's host was allowed unpinned, on any port.  The
owner of the repo's domain could serve the clone from a public address,
then answer the submodule's lookup with an internal address (DNS
rebinding), or point it at another port on that name.

The repo's host is now resolved once per download, and every fetch
(ls-remote, clone, fetch, submodule update) is pinned to that answer.  A
submodule on the repo's host *and port* is fetched from those same
addresses, still allowing a LAN forge the user chose; another port on
that name is checked like any other host.  Tests no longer use real DNS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 17:38:45 -06:00

104 lines
4.2 KiB
Python

import os
import pathlib
import subprocess
from typing import Callable
import pytest
from mock import mock
from modules.repos import lib
from modules.repos.downloader import GitDownloader
from wrolpi.downloader import DownloadManager
# Commits made by the tests have a fixed identity and do not read the developer's git config.
TEST_GIT_ENV = dict(
GIT_AUTHOR_NAME='WROLPi Test', GIT_AUTHOR_EMAIL='test@example.com',
GIT_COMMITTER_NAME='WROLPi Test', GIT_COMMITTER_EMAIL='test@example.com',
GIT_CONFIG_NOSYSTEM='1', GIT_CONFIG_GLOBAL=os.devnull,
)
class GitRemote:
"""A local repo which the Repos module sees as `https://git.example.com/<owner>/<name>`."""
def __init__(self, path: pathlib.Path, url: str):
self.path = path
self.url = url
def git(self, *args: str) -> str:
env = dict(os.environ, **TEST_GIT_ENV)
result = subprocess.run(['git', *args], cwd=self.path, env=env, check=True, capture_output=True)
return result.stdout.decode().strip()
def commit(self, files: dict, message: str = 'Commit') -> str:
"""Write `files` ({relative path: contents}), commit them, return the commit's sha."""
for name, contents in files.items():
file = self.path / name
file.parent.mkdir(parents=True, exist_ok=True)
file.write_text(contents)
self.git('add', '--all')
self.git('commit', '--quiet', '-m', message)
return self.git('rev-parse', 'HEAD')
@property
def head(self) -> str:
return self.git('rev-parse', 'HEAD')
def add_submodule(self, path: str, submodule: 'GitRemote', url: str = None) -> str:
"""Add `submodule` (at its current commit) at `path`, and commit it. `url` overrides the submodule's URL."""
gitmodules = self.path / '.gitmodules'
existing = gitmodules.read_text() if gitmodules.exists() else ''
gitmodules.write_text(f'{existing}[submodule "{path}"]\n\tpath = {path}\n\turl = {url or submodule.url}\n')
self.git('update-index', '--add', '--cacheinfo', f'160000,{submodule.head},{path}')
self.git('add', '.gitmodules')
self.git('commit', '--quiet', '-m', f'Add submodule {path}')
return self.head
@pytest.fixture
def git_remote_factory(tmp_path_factory) -> Callable[..., GitRemote]:
"""Create local git repos which are cloned over `https://git.example.com/...` URLs.
git's `url.<base>.insteadOf` rewrites the https URL to the local path, and `file://` is allowed for the test,
so the Repos module runs the same commands it runs in production."""
remotes = dict()
# Outside the media directory (which is `tmp_path`).
remotes_directory = tmp_path_factory.mktemp('remotes')
def git_env():
env = original_git_env()
env['GIT_CONFIG_COUNT'] = str(len(remotes))
for idx, (url, path) in enumerate(remotes.items()):
env[f'GIT_CONFIG_KEY_{idx}'] = f'url.file://{path}.insteadOf'
env[f'GIT_CONFIG_VALUE_{idx}'] = url
return env
original_git_env = lib.git_env
def factory(name: str = 'example', owner: str = 'owner', branch: str = 'main',
files: dict = None) -> GitRemote:
path = remotes_directory / owner / name
path.mkdir(parents=True)
url = f'https://git.example.com/{owner}/{name}'
remote = GitRemote(path, url)
remote.git('init', '--quiet', f'--initial-branch={branch}')
remote.commit(files or {'README.md': f'# {name}\n\nThe {name} repo.\n'}, 'Initial commit')
remotes[url] = path
return remote
def resolve_host_addresses(host):
# The remotes' host has a fixed public address; no test uses real DNS.
if host == 'git.example.com':
return ['93.184.215.14']
raise OSError(f'Unknown host {host}')
with mock.patch('modules.repos.lib.ALLOWED_PROTOCOLS', ('https', 'file')), \
mock.patch('modules.repos.downloader.git_env', git_env), \
mock.patch('modules.repos.lib.resolve_host_addresses', resolve_host_addresses):
yield factory
@pytest.fixture
def repos_download_manager(test_download_manager) -> DownloadManager:
test_download_manager.register_downloader(GitDownloader())
yield test_download_manager