fix(sf): ekb_geoportal_client verify=False — росс-гос-CA не в trust-store контейнера (#1067) #1103
2 changed files with 51 additions and 5 deletions
|
|
@ -71,23 +71,34 @@ WFS_LAYERS: dict[str, str] = {
|
|||
DEFAULT_TIMEOUT = 20
|
||||
|
||||
|
||||
# Геопортал ЕКБ отдаёт TLS-цепочку, корневой CA которой НЕ в trust-store наших контейнеров
|
||||
# (российский гос-УЦ, как у НСПД). С verify=True из контейнера падает CERTIFICATE_VERIFY_FAILED
|
||||
# (подтверждено live на проде 2026-06-06). Отключаем верификацию — данные ПУБЛИЧНЫЕ open-data
|
||||
# (ПЗЗ/ППТ/КРТ), секреты не передаются → MITM-риск незначим. Прецедент: nspd_lite._SSL_CTX =
|
||||
# ssl._create_unverified_context() для тех же росс-гос-эндпоинтов.
|
||||
_VERIFY_TLS = False
|
||||
|
||||
|
||||
# ── HTTP helper (module-level — мокается в тестах) ───────────────────────────
|
||||
|
||||
|
||||
def _http_get_json(url: str, params: dict[str, str], *, timeout: int = DEFAULT_TIMEOUT) -> Any:
|
||||
"""GET WFS-эндпоинта → JSON. Вынесен на module-level для monkeypatch в тестах.
|
||||
|
||||
Геопортал ЕКБ — нормальный TLS, без auth: обычный httpx без спец-обвязки (в отличие
|
||||
от НСПД с нац-CA / WAF). Raises httpx.HTTPStatusError на 4xx/5xx (caller решает).
|
||||
verify=_VERIFY_TLS (False): росс-гос-CA геопортала не в trust-store контейнера (см. выше).
|
||||
Raises httpx.HTTPStatusError на 4xx/5xx (caller решает).
|
||||
"""
|
||||
resp = httpx.get(url, params=params, timeout=timeout)
|
||||
resp = httpx.get(url, params=params, timeout=timeout, verify=_VERIFY_TLS)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
def _http_post_json(url: str, body: dict[str, Any], *, timeout: int = DEFAULT_TIMEOUT) -> Any:
|
||||
"""POST JSON → JSON (для searchByGeom / urbanCard). Module-level для monkeypatch в тестах."""
|
||||
resp = httpx.post(url, json=body, timeout=timeout)
|
||||
"""POST JSON → JSON (searchByGeom / urbanCard). Module-level для monkeypatch в тестах.
|
||||
|
||||
verify=_VERIFY_TLS (False): см. _http_get_json — росс-гос-CA не в trust-store контейнера.
|
||||
"""
|
||||
resp = httpx.post(url, json=body, timeout=timeout, verify=_VERIFY_TLS)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
|
|
|||
|
|
@ -200,3 +200,38 @@ def test_zone_regulation_at_none_without_terzone(monkeypatch: pytest.MonkeyPatch
|
|||
lambda url, body, **kw: {"10019": [{"typeAlias": "GknParcel", "key": 1}]},
|
||||
)
|
||||
assert EKBGeoportalClient().zone_regulation_at(0.0, 0.0) is None
|
||||
|
||||
|
||||
def test_http_helpers_disable_tls_verify(monkeypatch: Any) -> None:
|
||||
"""Регрессия: _http_get_json/_http_post_json должны слать verify=False.
|
||||
|
||||
Росс-гос-CA геопортала не в trust-store контейнера → verify=True падает
|
||||
CERTIFICATE_VERIFY_FAILED на проде (см. fix #1067). Ловим молчаливый ре-брейк.
|
||||
"""
|
||||
from app.services.scrapers import ekb_geoportal_client as mod
|
||||
|
||||
captured: dict[str, Any] = {}
|
||||
|
||||
class _Resp:
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> dict[str, Any]:
|
||||
return {"ok": True}
|
||||
|
||||
def _fake_get(url: str, **kw: Any) -> _Resp:
|
||||
captured["get_verify"] = kw.get("verify")
|
||||
return _Resp()
|
||||
|
||||
def _fake_post(url: str, **kw: Any) -> _Resp:
|
||||
captured["post_verify"] = kw.get("verify")
|
||||
return _Resp()
|
||||
|
||||
monkeypatch.setattr(mod.httpx, "get", _fake_get)
|
||||
monkeypatch.setattr(mod.httpx, "post", _fake_post)
|
||||
|
||||
mod._http_get_json("https://x/y", {"a": "b"})
|
||||
mod._http_post_json("https://x/y", {"a": "b"})
|
||||
|
||||
assert captured["get_verify"] is False
|
||||
assert captured["post_verify"] is False
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue