mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-24 15:52:20 +02:00
Repair SearXNG system and certifi stores, validate native amd64/arm64 runtime images through Compose, and publish the exact verified artifacts through a trusted CI job. Adopt released Python, certifi, grpcio, uv and Docker CLI trust fixes, with strict controls for all fourteen public incident chains. Part of #6784 and #6790. Co-authored-by: Léo <leograndcontact@gmail.com>
107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
"""A network failure must not be mistaken for rejection of an incident chain."""
|
|
|
|
import asyncio
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
from types import ModuleType, SimpleNamespace
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
INCIDENT = json.loads((ROOT / "tests/fixtures/ca-trust/incident.json").read_text())
|
|
|
|
|
|
@pytest.fixture
|
|
def validator():
|
|
repair = ModuleType("repair_trust")
|
|
repair.check_bundle = lambda path: None
|
|
spec = importlib.util.spec_from_file_location(
|
|
"searxng_validation", ROOT / "docker/searxng/validate_image.py"
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
with patch.dict(sys.modules, {"repair_trust": repair}):
|
|
spec.loader.exec_module(module)
|
|
module.__file__ = str(ROOT / "tests/fixtures/ca-trust/validate_image.py")
|
|
return module
|
|
|
|
|
|
class RequestFailure(Exception):
|
|
def __init__(self, code):
|
|
super().__init__(f"curl error {code}")
|
|
self.code = code
|
|
|
|
|
|
@pytest.mark.parametrize("code", [60, 6, 28, 35, None])
|
|
def test_live_probe_requires_certificate_errors_for_every_host(validator, code):
|
|
hosts = []
|
|
|
|
async def get(url, **kwargs):
|
|
if url == "https://pypi.org/simple/certifi/":
|
|
return SimpleNamespace(status_code=200)
|
|
hosts.append(url.removeprefix("https://").removesuffix("/"))
|
|
assert kwargs["allow_redirects"] is False
|
|
if code is not None:
|
|
raise RequestFailure(code)
|
|
return SimpleNamespace(status_code=200)
|
|
|
|
client = SimpleNamespace(get=get, aclose=AsyncMock())
|
|
factory = ModuleType("searx.network.client")
|
|
|
|
def new_client(**kwargs):
|
|
assert kwargs["verify"] is True
|
|
return client
|
|
|
|
factory.new_client = new_client
|
|
errors = ModuleType("curl_cffi.requests.exceptions")
|
|
errors.RequestException = RequestFailure
|
|
modules = {"searx.network.client": factory, "curl_cffi.requests.exceptions": errors}
|
|
with patch.dict(sys.modules, modules), patch.object(sys, "path", list(sys.path)):
|
|
if code == 60:
|
|
asyncio.run(validator.check_https())
|
|
assert set(hosts) == set(INCIDENT["chains"])
|
|
else:
|
|
expected = "unexpectedly accepted" if code is None else "inconclusive"
|
|
with pytest.raises(RuntimeError, match=expected):
|
|
asyncio.run(validator.check_https())
|
|
client.aclose.assert_awaited_once()
|
|
|
|
|
|
def test_offline_probe_rejects_openssl_execution_failure(validator):
|
|
result = SimpleNamespace(returncode=1, stderr="openssl: unknown option", stdout="")
|
|
with patch.dict(validator.os.environ, {"SSL_CERT_FILE": "/unused.pem", "SSL_CERT_DIR": "/unused"}), \
|
|
patch.object(validator.subprocess, "run", return_value=result):
|
|
with pytest.raises(RuntimeError, match="not a certificate trust rejection"):
|
|
validator.check_chains()
|
|
|
|
|
|
@pytest.mark.parametrize("results", [[{"title": "ordinary result"}], []])
|
|
def test_running_probe_checks_existing_service_without_repair_or_second_boot(validator, tmp_path, results):
|
|
certificate = tmp_path / "01234567.0"
|
|
certificate.write_text("certificate double")
|
|
(tmp_path / "unrelated").write_text("ignored")
|
|
health = io.BytesIO(b"OK")
|
|
health.status = 200
|
|
search = io.BytesIO(json.dumps({"results": results}).encode())
|
|
with patch.dict(validator.os.environ, {"SSL_CERT_DIR": str(tmp_path), "GRANIAN_PORT": "8080"}), \
|
|
patch.object(validator.urllib.request, "build_opener") as opener, \
|
|
patch.object(validator, "check_chains") as chains, \
|
|
patch.object(validator, "check_bundle") as bundle, \
|
|
patch.object(validator, "check_https", new_callable=AsyncMock) as https, \
|
|
patch.object(validator.subprocess, "Popen") as boot:
|
|
opener.return_value.open.side_effect = [health, search]
|
|
if results:
|
|
validator.check_running(live=True)
|
|
else:
|
|
with pytest.raises(RuntimeError, match="no results"):
|
|
validator.check_running(live=True)
|
|
assert opener.return_value.open.call_args_list[0].args == ("http://127.0.0.1:8080/healthz",)
|
|
chains.assert_called_once_with()
|
|
bundle.assert_called_once_with(certificate)
|
|
https.assert_awaited_once()
|
|
boot.assert_not_called()
|