Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 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
38 changes: 24 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,22 @@ 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 only. Backends that are already checked out keep the values
captured when their pool was created. On the next client reconnect,
pg_doorman runs `auth_query` again, reads the updated row, rebuilds the
dynamic pool, and uses the new values for later backend starts.

## What pg_doorman does with the values

pg_doorman adds the resolved parameter set to the PostgreSQL
Expand Down Expand Up @@ -92,21 +99,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
Loading
Loading