"""postgres-exporter не должен печатать пароль БД в лог. ЧТО БЫЛО. Версия v0.16.0 при КАЖДОЙ ошибке сбора печатала полный DSN вместе с паролем: msg="error scraping dsn" err="queryNamespaceMappings returned 1 errors" dsn="postgresql://<роль>:<ПАРОЛЬ>@<хост>:5432/<база>?sslmode=disable" Строки уходили в Loki — около 210 в сутки с двух хостов, при ретенции 30 дней это тысячи паролей в хранилище логов (#3114). ЧТО ПРОВЕРЕНО ОПЫТОМ 27.08, а не взято из документации. Стенд: чистый постгрес в скретч-контейнере, роль без прав, тот же `ops/metrics/postgres/queries.yml`, что и на проде — то есть ровно та ошибка, что случалась в бою. v0.16.0 → строк с паролем: 1 v0.18.0 → строк с паролем: 0 Отдельно опровергнут обходной путь, который предлагался в issue: форма `DATA_SOURCE_URI` + `DATA_SOURCE_USER` + `DATA_SOURCE_PASS` НЕ помогает — экспортер собирает DSN внутри и печатает его целиком точно так же (тот же стенд, тот же результат: 1 строка с паролем). То есть починка «через переменные окружения» была бы работой вхолостую. Паритет метрик проверен там же: все пять пользовательских запросов отдаются обеими версиями одинаково, v0.18 добавляет три встроенные и не теряет ни одной. Тест сторожит только нижнюю границу версии: откат на v0.16.x вернул бы утечку. """ from __future__ import annotations import re from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[3] COMPOSE = REPO_ROOT / "docker-compose.metrics-agent.yml" # Первая версия, в которой пароль в лог не попадает (проверено опытом, см. шапку). MIN_SAFE = (0, 18, 0) _TAG = re.compile(r"postgres-exporter:v(\d+)\.(\d+)\.(\d+)") def _versions() -> list[tuple[int, int, int]]: text = COMPOSE.read_text(encoding="utf-8") return [(int(a), int(b), int(c)) for a, b, c in _TAG.findall(text)] def test_all_three_exporters_are_pinned() -> None: """Экспортеров три — на всех трёх версия должна быть явной. Страховка от того, что тест ниже начнёт проверять пустоту: если кто-то заменит тег на `latest` или удалит сервис, здесь станет видно. """ assert len(_versions()) == 3, ( f"ожидались три закреплённые версии postgres-exporter, найдено: {_versions()}" ) def test_no_exporter_older_than_the_leaking_one() -> None: """Ни один экспортер не смеет быть старше версии, где утечка закрыта.""" for v in _versions(): assert v >= MIN_SAFE, ( f"postgres-exporter v{'.'.join(map(str, v))} печатает пароль БД в лог " f"при каждой ошибке сбора — минимум v{'.'.join(map(str, MIN_SAFE))}" )