fix(ops/metrics): postgres-exporter печатал пароль БД в лог при каждой ошибке #3144

Merged
lekss361 merged 1 commit from fix/3114-pg-exporter-leaks-dsn into main 2026-08-27 13:15:01 +00:00
Owner

Закрывает пункт 2 из #3114 — «защита в глубину», который был записан за мной. Попутно опровергает предложенный там способ.

Что чиню

postgres-exporter:v0.16.0 при каждой неудаче сбора печатает полный DSN вместе с паролем:

msg="error scraping dsn" err="queryNamespaceMappings returned 1 errors"
dsn="postgresql://<роль>:<ПАРОЛЬ>@<хост>:5432/<база>?sslmode=disable"

Гранты pg_monitor (пункт 1, уже сделан) убрали текущую причину ошибок, но не саму способность печатать пароль: любая другая неудача сбора вернёт утечку.

Опыт, а не документация

Стенд на скретч-контейнерах Poincare: чистый постгрес, роль без прав, тот же ops/metrics/postgres/queries.yml, что на проде — то есть ровно та ошибка, что случалась в бою.

версия строк с паролем
v0.16.0 (сейчас) 1
v0.18.0 0

Предложенный в issue обходной путь не работает

Это важнее самой правки. В issue записано: передавать параметры через DATA_SOURCE_URI + DATA_SOURCE_USER + DATA_SOURCE_PASS_FILE вместо единой строки — «тогда в лог попадать нечему».

Проверил на том же стенде:

форма строк с паролем
DATA_SOURCE_NAME (как сейчас) 1
DATA_SOURCE_URI + USER + PASS 1

Экспортер собирает DSN внутри и печатает его целиком точно так же. Реализация этого варианта была бы работой вхолостую — при полном ощущении, что дыра закрыта. Утечку закрывает только версия образа.

Паритет метрик проверен

На том же стенде, обе версии против одной базы:

пользовательский запрос v0.16.0 v0.18.0
pg_wal_bytes 2 2
pg_activity_horizon 5 5
pg_database_size_bytes_detail 1 1
pg_table_write_amplification 0 0
pg_table_size_detail 0 0
всего метрик 514 517

PG_EXPORTER_EXTEND_QUERY_PATH в v0.18 работает, ни одна метрика не теряется, добавляются три встроенные. Дашборды не пострадают.

Тесты

Два: все три экспортера закреплены явной версией (страховка от latest и от того, что тест начнёт проверять пустоту), и ни один не старше версии, где утечка закрыта.

$ uv run python -m pytest tests/ops/ -q
80 passed

$ uv run ruff check …
All checks passed!

Что остаётся владельцу

Пункт 3 из #3114 — ротация паролей gendesign_reader / grafana_ro. В Loki уже лежат пароли за прошедшие сутки и пролежат до конца ретенции. Ротация трогает боевые подключения, поэтому за вами.

Refs #3114

Закрывает пункт 2 из #3114 — «защита в глубину», который был записан за мной. Попутно опровергает предложенный там способ. ## Что чиню `postgres-exporter:v0.16.0` при **каждой** неудаче сбора печатает полный DSN вместе с паролем: ``` msg="error scraping dsn" err="queryNamespaceMappings returned 1 errors" dsn="postgresql://<роль>:<ПАРОЛЬ>@<хост>:5432/<база>?sslmode=disable" ``` Гранты `pg_monitor` (пункт 1, уже сделан) убрали текущую причину ошибок, но не саму способность печатать пароль: любая другая неудача сбора вернёт утечку. ## Опыт, а не документация Стенд на скретч-контейнерах Poincare: чистый постгрес, роль без прав, тот же `ops/metrics/postgres/queries.yml`, что на проде — то есть ровно та ошибка, что случалась в бою. | версия | строк с паролем | |---|---:| | v0.16.0 (сейчас) | **1** | | v0.18.0 | **0** | ## Предложенный в issue обходной путь не работает Это важнее самой правки. В issue записано: передавать параметры через `DATA_SOURCE_URI` + `DATA_SOURCE_USER` + `DATA_SOURCE_PASS_FILE` вместо единой строки — «тогда в лог попадать нечему». Проверил на том же стенде: | форма | строк с паролем | |---|---:| | `DATA_SOURCE_NAME` (как сейчас) | 1 | | `DATA_SOURCE_URI` + `USER` + `PASS` | **1** | Экспортер собирает DSN внутри и печатает его целиком точно так же. Реализация этого варианта была бы работой вхолостую — при полном ощущении, что дыра закрыта. Утечку закрывает только версия образа. ## Паритет метрик проверен На том же стенде, обе версии против одной базы: | пользовательский запрос | v0.16.0 | v0.18.0 | |---|---:|---:| | `pg_wal_bytes` | 2 | 2 | | `pg_activity_horizon` | 5 | 5 | | `pg_database_size_bytes_detail` | 1 | 1 | | `pg_table_write_amplification` | 0 | 0 | | `pg_table_size_detail` | 0 | 0 | | **всего метрик** | 514 | **517** | `PG_EXPORTER_EXTEND_QUERY_PATH` в v0.18 работает, ни одна метрика не теряется, добавляются три встроенные. Дашборды не пострадают. ## Тесты Два: все три экспортера закреплены явной версией (страховка от `latest` и от того, что тест начнёт проверять пустоту), и ни один не старше версии, где утечка закрыта. ``` $ uv run python -m pytest tests/ops/ -q 80 passed $ uv run ruff check … All checks passed! ``` ## Что остаётся владельцу Пункт 3 из #3114 — ротация паролей `gendesign_reader` / `grafana_ro`. В Loki уже лежат пароли за прошедшие сутки и пролежат до конца ретенции. Ротация трогает боевые подключения, поэтому за вами. Refs #3114
lekss361 added 1 commit 2026-08-27 12:55:56 +00:00
fix(ops/metrics): postgres-exporter печатал пароль БД в лог при каждой ошибке
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m58s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Successful in 17m34s
b746134976
Версия v0.16.0 при КАЖДОЙ неудаче сбора печатала полный DSN вместе с паролем:

    msg="error scraping dsn" err="queryNamespaceMappings returned 1 errors"
    dsn="postgresql://<роль>:<ПАРОЛЬ>@<хост>:5432/<база>?sslmode=disable"

Строки уходят в Loki — около 210 в сутки с двух хостов, при ретенции 30 дней
это тысячи паролей в хранилище логов (#3114).

Проверял опытом, а не документацией. Стенд на скретч-контейнерах: чистый
постгрес, роль без прав, тот же queries.yml что на проде — то есть ровно та
ошибка, что случалась в бою.

    v0.16.0 → строк с паролем: 1
    v0.18.0 → строк с паролем: 0

ОБХОДНОЙ ПУТЬ ИЗ ISSUE НЕ РАБОТАЕТ, и это важнее самой правки. Предлагалось
передавать параметры через DATA_SOURCE_URI + DATA_SOURCE_USER + DATA_SOURCE_PASS
вместо единой строки — «тогда в лог попадать нечему». Проверил на том же
стенде: экспортер собирает DSN внутри и печатает его целиком точно так же,
1 строка с паролем. Реализация этого варианта была бы работой вхолостую при
полном ощущении, что дыра закрыта.

Паритет метрик проверен там же: все пять пользовательских запросов из
queries.yml отдаются обеими версиями одинаково (PG_EXPORTER_EXTEND_QUERY_PATH
в v0.18 работает), v0.18 добавляет три встроенные метрики и не теряет ни одной.

Два теста сторожат нижнюю границу версии на всех трёх экспортерах.
Прогон: 80 ops-тестов зелёные, ruff чист.

Refs #3114
lekss361 merged commit f469903adf into main 2026-08-27 13:15:01 +00:00
lekss361 deleted branch fix/3114-pg-exporter-leaks-dsn 2026-08-27 13:15:02 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3144
No description provided.