Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/bdd-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,13 @@ jobs:
packages: read
strategy:
fail-fast: false
# GitHub-hosted runners share CPU under heavy matrix fan-out, and
# the timing-sensitive scenarios (sleep-based retain/lifetime
# waits, SCRAM passthrough reconnect) lose their margin when 20+
# BDD jobs run in parallel. Cap concurrency so each suite gets a
# less contested runner.
max-parallel: 4
# Six concurrent suites is the cap this matrix was tuned for.
# GitHub-hosted runners share CPU under heavy fan-out; with 20+
# BDD jobs in parallel, the sleep-based lifecycle tests and the
# SCRAM passthrough reconnect tests lost timing margin. This cap
# keeps those suites stable while reducing total wall time versus
# the previous limit of four.
max-parallel: 6
matrix:
suite:
- { name: "Go", cargo: "test --test bdd -- --tags @go" }
Expand All @@ -249,6 +250,7 @@ jobs:
- { name: "Server TLS", cargo: "test --test bdd -- --tags @server-tls" }
- { name: "TLS migration (vendored OpenSSL)", cargo: "test --features tls-migration --test bdd -- --tags @tls-migration" }
- { name: "Startup parameters", cargo: "test --test bdd -- --tags @startup-parameters" }
- { name: "Web UI", cargo: "test --test bdd -- --tags @web-ui" }
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/dashboard-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [master]
paths:
- "grafana/**"
- "monitoring/prometheus-rules/**"
- "scripts/dashboard-*"
- "scripts/docker-smoke.sh"
- "src/web/metrics/**"
Expand All @@ -17,6 +18,7 @@ on:
pull_request:
paths:
- "grafana/**"
- "monitoring/prometheus-rules/**"
- "scripts/dashboard-*"
- "scripts/docker-smoke.sh"
- "src/web/metrics/**"
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ iota = { version = "0.2.3" }
pin-project-lite = "0.2.16"
pam-client = { version = "0.5.0", optional = true }
postgres = "0.19.10"
tokio-postgres = "0.7"
# `with-serde_json-1` lets `Row::try_get` decode `json`/`jsonb`
# columns directly into `serde_json::Value`, which allows auth_query
# startup_parameters to come from a native JSON column.
tokio-postgres = { version = "0.7", features = ["with-serde_json-1"] }
postgres-native-tls = "0.5.1"
flate2 = "1.0.28"
sd-notify = "0.4"
Expand Down
36 changes: 35 additions & 1 deletion documentation/en/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@

### 3.9.1

Web admin console refresh.
Web admin console refresh and a follow-up pass on `startup_parameters`.

Upgrade notes for operators monitoring 3.9.0:

- The pg_doorman-side budget rejection now returns `SQLSTATE 53400`
(`configuration_limit_exceeded`) instead of `54000`. Alert rules
and log filters keyed on `54000` need to switch.
- `PgDoormanStartupParameterPgRejection` is now `severity: warning`
(was `critical` in 3.9.0). Cascade-overflow stays `critical`. Review
the Alertmanager / on-call routing if you key on severity to page.

#### Web admin console

- Light theme by default. Three-position theme toggle (Light / System / Dark)
in the sidebar footer; choice persists in localStorage.
Expand Down Expand Up @@ -39,6 +50,29 @@ Backend: `web/access_log.rs` demotes authenticated 2xx reads to debug.

Docs: `guides/web-ui.md` rewritten for the new pages and shortcuts.

#### startup_parameters follow-up

- If the resolved `startup_parameters` set exceeds the startup packet
budget, backend startup now fails with `SQLSTATE 53400`. A
deterministic `general + pool` overflow is rejected at config load.
- The final `ParameterStatus` messages sent to the client no longer
overwrite operator-managed GUC names, so the client-visible values
match the backend checkout state.
- `auth_query` now rebuilds a dynamic pool after a successful MD5
refetch, rejects the stale-overlay race in `create_dynamic_pool`, and
accepts native `json`/`jsonb` startup_parameter columns without a
`::text` cast.
- `/api/config` and `/api/pools` show literal startup_parameter values
only to `Admin`; SSO readers get the masked view. `/api/config` also
marks `general.host`, `general.port`, `web.host`, and `web.port` as
restart-required.
- Prometheus rules now cover PostgreSQL-side rejection, budget overflow,
malformed auth_query columns, dedicated-mode drops, and rejected SSO
credentials sent over insecure transport.
- Each pool now precomputes the merged startup map, budget decision, and
canonical operator-key set. Backend checkout reuses those cached
values instead of cloning and recalculating the map each time.

### 3.9.0

Per-pool PostgreSQL startup parameters. pg_doorman can now add
Expand Down
2 changes: 1 addition & 1 deletion documentation/en/src/guides/web-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ proxy:
| `sso_groups_claim` | Name of the JWT claim that carries the user's group memberships. Read together with `sso_admin_groups`. | `"groups"` |
| `sso_admin_groups` | Group names that promote an SSO user to `Admin`. Empty keeps every SSO login at the read-only `Sso` role. | `[]` |
| `sso_require_https` | Reject Bearer/cookie/query SSO credentials presented over plain HTTP. The listener treats a request as secure only when the TCP peer is in `trusted_proxies` and `X-Forwarded-Proto: https` is forwarded. Defaults to off so SSO keeps working through a TLS-terminating proxy that reaches pg_doorman over a private HTTP leg. | `false` |
| `trusted_proxies` | CIDR ranges trusted to set `X-Forwarded-For` / `Forwarded` / `X-Forwarded-Proto`. Empty trusts only the listener's own peer. See [Access log](#access-log). | `[]` |
| `trusted_proxies` | CIDR ranges trusted to set `X-Forwarded-For` / `Forwarded` / `X-Forwarded-Proto`. With an empty list, pg_doorman ignores forwarded headers and uses the TCP peer address. If `sso_require_https = true` is behind a TLS-terminating proxy, add that proxy CIDR so `X-Forwarded-Proto: https` is accepted. See [Access log](#access-log). | `[]` |

### Promoting SSO users to Admin via group claim

Expand Down
43 changes: 29 additions & 14 deletions documentation/en/src/tutorials/startup-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,27 @@ FROM pg_authid
WHERE rolname = $1;
```

The column must serialize as `text`. If the SQL returns `json` or
`jsonb`, add an explicit `::text` cast. pg_doorman reads the column
as `text` and logs a warning for that fetched row when the type does
not match.
The column may be `text`, `json`, or `jsonb`; pg_doorman dispatches by
the column type without requiring a cast. The content must be a JSON
object whose values are strings. Other PostgreSQL types (or a custom
domain on top of `jsonb`) log a warning and the per-user overlay is
ignored.

Dedicated `auth_query` mode (`server_user` set) ignores the per-user
column and logs once per (pool, username): one shared backend serves
many users, so a per-user override cannot apply.

Changes to a per-user `startup_parameters` row apply to **new** backend
connections, but only after pg_doorman re-reads the row. The
`auth_query` cache holds positive entries for `auth_query.cache_ttl`
(default one hour) and on a refresh detects the overlay change and
drops the dynamic pool so the next login rebuilds it against the new
values. Until the cache entry expires, reconnecting clients still see
the old overlay. To force an immediate rollout: lower `cache_ttl` and
reload the config, restart pg_doorman, or wait for the TTL to elapse.
Backends that are already checked out keep the values captured when
their pool was created.

## What pg_doorman does with the values

pg_doorman adds the resolved parameter set to the PostgreSQL
Expand Down Expand Up @@ -92,21 +104,24 @@ At config load:
- Keys must match PG GUC naming `^[A-Za-z_][A-Za-z0-9_.]*$`. Namespaced
names like `auto_explain.log_min_duration` are accepted; arbitrary
punctuation is not.
- Reserved keys (`user`, `database`, `replication`, `options`, and
anything starting with `_pq_.`) are refused. pg_doorman manages
them itself or PG treats them specially in the StartupMessage.
- Reserved keys (`user`, `database`, `replication`, `options`, `role`,
`session_authorization`, and anything starting with `_pq_.`) are
refused. pg_doorman manages them itself or PG treats them specially in
the StartupMessage.
- Values must not contain null bytes.
- Each level (general or per-pool) must fit within the startup-parameter
budget: `MAX_STARTUP_PACKET_LENGTH` (10 000 bytes) minus 512 bytes
reserved for pg_doorman-managed keys.

Before each backend spawn pg_doorman checks the resolved parameter set
against the same cap. Two layers that fit on their own can overflow once
`auth_query` adds a third layer. If only the `auth_query` layer pushes
the set over the cap, pg_doorman drops that layer and keeps the
general/pool baseline. If the baseline itself or the full startup packet
does not fit, pg_doorman skips all configured parameters for that
spawn and logs the byte counts.
Before each backend start, pg_doorman checks the resolved parameter set
against the same cap. Layers that fit individually can exceed the limit
after merging: `general + pool` can already be too large, and an
`auth_query` row can push a valid baseline over the limit. Any overflow
now returns a PostgreSQL-style error (`SQLSTATE 53400`) to the client
instead of sending a partial or empty `StartupMessage`. The warning log
records the byte counts, and
`pg_doorman_startup_parameters_dropped_total` increments for each
rejected backend start.

## What happens when PG rejects a parameter

Expand Down
9 changes: 5 additions & 4 deletions documentation/ru/src/authentication/auth-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ pools:

Запрос должен возвращать колонку с именем `passwd` или `password`,
содержащую хеш MD5 или SCRAM. Дополнительные колонки игнорируются, кроме
необязательной `startup_parameters`. В passthrough-режиме pg_doorman
читает её как JSON-объект в `text` с пользовательскими параметрами
запуска PostgreSQL. Dedicated-режим игнорирует её и пишет
предупреждение.
необязательной `startup_parameters`. В сквозном режиме pg_doorman
читает эту колонку как `text`, `json` или `jsonb`; значение должно быть
JSON-объектом с параметрами запуска PostgreSQL для конкретного
пользователя. В выделенном режиме эта колонка игнорируется, а в лог
пишется предупреждение.

`user` и `password` — это учётные данные, под которыми pg_doorman выполняет lookup-запрос. У них должно быть право читать колонку с учётными данными. Либо выдайте доступ к специально созданному представлению (рекомендуется), либо используйте пользователя из группы `pg_read_server_files`.

Expand Down
2 changes: 1 addition & 1 deletion documentation/ru/src/guides/web-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ SSO опциональный. По умолчанию (`[web].sso_enabled = fals
| `sso_groups_claim` | Имя JWT-claim, в котором лежат группы пользователя. Читается вместе с `sso_admin_groups`. | `"groups"` |
| `sso_admin_groups` | Группы, которые поднимают SSO-пользователя до `Admin`. Пустой список оставляет каждый SSO-логин на роли `Sso` только для чтения. | `[]` |
| `sso_require_https` | Отклонять Bearer/cookie/query SSO-credentials, пришедшие по plain HTTP. Запрос считается защищённым только если TCP-peer входит в `trusted_proxies` и прокси прислал `X-Forwarded-Proto: https`. По умолчанию выключено, чтобы SSO продолжал работать в схеме «TLS терминирует прокси → pg_doorman слушает HTTP во внутренней сети». | `false` |
| `trusted_proxies` | CIDR доверенных обратных прокси (используется для `X-Forwarded-For` / `Forwarded` / `X-Forwarded-Proto`). Пустой список — доверять только непосредственному TCP-peer. См. [Журнал доступа](#журнал-доступа). | `[]` |
| `trusted_proxies` | CIDR доверенных обратных прокси для `X-Forwarded-For`, `Forwarded` и `X-Forwarded-Proto`. При пустом списке pg_doorman игнорирует эти заголовки и берёт адрес прямого TCP-пира. Если `sso_require_https = true` работает за прокси, который завершает TLS, добавьте CIDR этого прокси, чтобы доверять `X-Forwarded-Proto: https`. См. [Журнал доступа](#журнал-доступа). | `[]` |

### Поднятие SSO-пользователя до Admin через claim с группами

Expand Down
8 changes: 5 additions & 3 deletions documentation/ru/src/reference/general.md
Original file line number Diff line number Diff line change
Expand Up @@ -684,9 +684,11 @@ hostnossl all all 192.168.1.0/24 trust
протокольные ключи (`user`, `database`, `replication`, `options`,
`_pq_.*`), имена GUC, нулевые байты и размер этого уровня. Перед каждым
запуском бэкенда объединённый набор параметров снова проверяется по
лимиту `MAX_STARTUP_PACKET_LENGTH` PostgreSQL; если он не помещается,
pg_doorman пропускает операторские параметры для этого запуска и пишет
предупреждение.
лимиту `MAX_STARTUP_PACKET_LENGTH` PostgreSQL (10 000 байт). Каскад и
проверка пакета отклоняют запуск бэкенда с SQLSTATE 53400
(`configuration_limit_exceeded`); если каскад выходит за бюджет только
из-за overlay из `auth_query`, pg_doorman сбрасывает этот overlay и
продолжает с baseline.

Если PostgreSQL отвергает параметр при запуске бэкенда, pg_doorman
возвращает клиенту `ErrorResponse` PostgreSQL без изменений: повторной
Expand Down
Loading
Loading