OAuth в PostgreSQL 18 с Keycloak: что нужно кроме строки в pg_hba.conf
Вы добавили метод `oauth` в `pg_hba.conf`, перечитали строку трижды, перезагрузили конфигурацию — а PostgreSQL 18 всё равно отказывает ещё до показа кода входа Keycloak. Я видел этот сценарий не раз. В PostgreSQL появился не готовый коннектор к любому IdP, а протокольная основа: сервер запрашивает bearer-токен и передаёт его внешнему валидатору, клиент получает токен через поддерживаемый OAuth flow, а администратор отдельно задаёт сопоставление внешней личности с ролью базы. Ниже я собрал проверенную схему настройки, разбор стенда фармдистрибьютора «Медфарм-Опт», 90 сотрудников, и ограничения, из-за которых этот механизм пока нельзя бездумно включать для всех программ и сервисных учётных записей.
Почему одной строки oauth в pg_hba.conf недостаточно
Сбой обычно начинается одинаково. Администратор обновляет тестовый кластер до PostgreSQL 18, видит новый метод аутентификации oauth, добавляет правило в pg_hba.conf и пробует подключиться через psql. Сервер отказывает настолько рано, что пользователь не успевает увидеть ни адрес страницы Keycloak, ни device code. После этого начинают переставлять кавычки, менять scope и проверять сертификат, хотя до обращения к провайдеру дело ещё не дошло.
Первая проверка — значение oauth_validator_libraries. Документация PostgreSQL 18 недвусмысленна: пустая строка является значением по умолчанию, и при ней все OAuth-подключения отклоняются. PostgreSQL также не поставляет готовый модуль проверки токенов. В ядре есть интерфейс валидаторов и код серверного обмена, но библиотеку, которая проверит подпись или выполнит introspection, оператор должен получить отдельно.
Проверить состояние сервера можно из привилегированной сессии. Заодно я смотрю точную минорную версию: на дату проверки актуальный выпуск ветки 18 — PostgreSQL 18.6 от 13 августа 2026 года. Указывать просто «18» в архитектурной схеме нормально, но стенд и прод должны получать исправления из поддерживаемой минорной ветки.
SHOW server_version;
SHOW oauth_validator_libraries;
SHOW hba_file;
SHOW ident_file;Причина такого устройства не в незавершённости синтаксиса HBA. Bearer-токен в протоколе — непрозрачная для PostgreSQL строка, а его реальный формат выбирает провайдер. Keycloak обычно выдаёт подписанный JWT, другой сервер авторизации может выдавать случайный идентификатор для проверки через endpoint introspection. Даже у JWT надо определить доверенные ключи, допустимый issuer, audience, срок действия, обязательные claims и правила отзыва. Универсальная проверка внутри ядра получилась бы либо небезопасной, либо привязанной к конкретному поставщику.
Сервер PostgreSQL отвечает за другую часть работы: сообщает клиенту требуемые issuer и scope, принимает bearer-токен через механизм OAUTHBEARER, вызывает выбранный валидатор и затем применяет обычное сопоставление через pg_ident.conf. Именно поэтому корректная HBA-запись при пустом oauth_validator_libraries всё равно обязана завершиться отказом.
Наконец, ранний отказ бывает не только из-за валидатора. PostgreSQL читает pg_hba.conf сверху вниз и выбирает первое подходящее правило без перехода к следующему при неудачной аутентификации. Если OAuth-запись находится после широкого правила с scram-sha-256, клиент вообще не увидит OAuth challenge. Поэтому я проверяю не только текст файла, но и представление pg_hba_file_rules, где видны порядок, разобранные опции и синтаксические ошибки.
- Пустой `oauth_validator_libraries` означает безусловный отказ всем OAuth-подключениям.
- PostgreSQL 18 не включает штатный валидатор bearer-токенов.
- Параметры `issuer` и `scope` обязательны в OAuth-записи HBA.
- При нескольких библиотеках каждая OAuth-запись должна выбрать одну через `validator`.
- Первое подходящее правило `pg_hba.conf` применяется без перехода к нижележащим правилам.
Пять обязательных частей рабочей схемы
Когда ко мне приходят с формулировкой «OAuth настроили, но он не работает», я раскладываю соединение на пять независимых частей. Обычно готовы две или три, а отсутствие оставшихся пытаются компенсировать очередной правкой pg_hba.conf. Такой подход только смешивает ошибки клиентской и серверной стороны.
Первая часть — серверный валидатор. Его имя указывается в oauth_validator_libraries, а сама разделяемая библиотека должна находиться в каталоге серверных модулей. Если перечислена одна библиотека, PostgreSQL использует её по умолчанию. Если библиотек несколько, параметр validator становится обязательным в каждой OAuth-записи HBA и должен точно совпадать с одним из имён в списке.
Вторая часть — клиент, способный получить токен. В исходной сборке PostgreSQL встроенный Device Authorization flow включается параметром конфигурации --with-libcurl; требуется libcurl версии 7.61.0 или новее. В Debian и репозитории PGDG реализация вынесена в динамически загружаемый пакет libpq-oauth, чтобы обычный libpq5 не зависел от libcurl. Поэтому установка одного postgresql-client-18 ещё не доказывает наличие OAuth flow.
sudo apt-get update
sudo apt-get install postgresql-client-18 libpq-oauth
dpkg-query -W -f='${Package}\t${Version}\n' postgresql-client-18 libpq5 libpq-oauth
dpkg -L libpq-oauth | grep 'libpq-oauth-18\.so$'Третья часть — доверенный issuer, одинаковый в HBA, в discovery-документе и в клиентском параметре oauth_issuer. Четвёртая — обязательный HBA-параметр scope: это список через пробел, согласованный с сервером авторизации и валидатором. Пятая — финальное решение, под какой ролью PostgreSQL разрешить вход. Обычно для него используют map и pg_ident.conf.
Есть и шестая, эксплуатационная часть, которую легко забыть: TLS-доверие и доступность DNS. Встроенный flow обращается к Keycloak с клиентского компьютера, а валидатор может получать discovery-документ и JWKS с сервера PostgreSQL. Имя issuer должно разрешаться в обеих точках, цепочка сертификата должна быть доверенной обеим операционным системам, а браузер пользователя должен открывать адрес подтверждения.
На стороне Keycloak для командного клиента нужен OIDC client с включённым переключателем OAuth 2.0 Device Authorization Grant. Для psql это обычно public client: параметр Client authentication выключен, поэтому постоянный client secret на рабочей станции не нужен. Сам oauth_client_id, напротив, обязателен для встроенного flow libpq.
Важно не смешивать поддержку протокола сервером и поддержку конкретной программой. psql использует libpq, но DBeaver по умолчанию работает через чистый Java-драйвер pgJDBC и не загружает установленный в системе libpq-oauth. Установка Debian-пакета не добавит OAuth в DBeaver. Для каждого GUI-клиента и пулера нужна отдельная проверка его драйвера и поддерживаемого flow.
- Сервер: проверенный модуль-валидатор и `oauth_validator_libraries`.
- Клиент libpq: сборка с `--with-libcurl` либо соответствующий пакет `libpq-oauth`.
- Keycloak: OIDC client с разрешённым Device Authorization Grant.
- Точное совпадение `issuer` и доступность его discovery- и JWKS-адресов.
- Обязательный `scope` и явное сопоставление внешней личности с ограниченной ролью.
Разбор стенда: фармдистрибьютор «Медфарм-Опт», 90 сотрудников
Условный клиент из этого разбора — фармдистрибьютор «Медфарм-Опт», 90 сотрудников. Внутренний Keycloak уже обслуживал портал и несколько корпоративных сервисов. Три аналитика подключались к отдельной read-only реплике через psql, DBeaver и pgAdmin, а пароли PostgreSQL приходилось отдельно выдавать, менять и отзывать. Целью пилота стал единый вход людей, но не перевод приложений и репликации на OAuth.
На момент повторной проверки стенд обновлён до PostgreSQL 18.6 и Keycloak 26.7.3; операционная система сервера — Debian 12. Это не означает, что пилот начался на этих же patch-релизах: за более чем полгода компоненты обновлялись. Такой порядок важен и для описания кейса, и для сопровождения — OAuth не отменяет установку минорных исправлений PostgreSQL и security-релизов Keycloak.
Для Debian 12 готовые экспериментальные сборки pg_oidc_validator, перечисленные в README проекта Percona, не подходят напрямую: там названы Ubuntu 24.04 и семейство OL8/OL9. Поэтому библиотеку собрали из исходников, зафиксировали использованный commit во внутреннем пакете и сначала проверили на отдельной ВМ. Репозиторий требует компилятор и стандартную библиотеку с поддержкой C++23, а документированная команда сборки через PGXS выглядит так:
git clone --recurse-submodules https://github.com/percona/pg_oidc_validator.git
cd pg_oidc_validator
make USE_PGXS=1 install -jПосле установки серверная конфигурация и карта пользователей выглядели так. В примере сохранён email как идентификатор, поскольку это было исходным требованием клиента. Я разрешил запрос только одной роли и отдельной базе, а не сочетанию all all. Резервная SCRAM-запись доступна лишь локально и стоит выше OAuth-правила.
# /etc/postgresql/18/main/postgresql.conf
oauth_validator_libraries = 'pg_oidc_validator'
pg_oidc_validator.authn_field = 'email'
# /etc/postgresql/18/main/pg_hba.conf
host all breakglass_admin 127.0.0.1/32 scram-sha-256
hostssl analytics analyst_ro 10.20.30.0/24 oauth issuer="https://sso.medfarm-opt.local/realms/dbrealm" scope="openid email pgscope" map=kcmap
# /etc/postgresql/18/main/pg_ident.conf
# MAPNAME SYSTEM-USERNAME PG-USERNAME
kcmap ivanov@medfarm-opt.ru analyst_ro
kcmap petrova@medfarm-opt.ru analyst_ro
kcmap sidorov@medfarm-opt.ru analyst_roРоль analyst_ro оставили с возможностью входа, но без административных атрибутов, а доступ выдали только к аналитической базе и нужной схеме. OAuth подтверждает личность, но не создаёт роль и не выдаёт ей права автоматически.
CREATE ROLE analyst_ro LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
GRANT CONNECT ON DATABASE analytics TO analyst_ro;
GRANT USAGE ON SCHEMA reporting TO analyst_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO analyst_ro;Затем мы последовательно поймали три исходные ошибки, каждая отняла примерно по сорок минут. На клиентских Debian-системах был postgresql-client-18, но отсутствовал отдельный libpq-oauth. В discovery-документе Keycloak публиковал issuer с портом 8443, тогда как HBA указывал внешний HTTPS-адрес без порта. Наконец, client scope pgscope был создан, но не назначен клиенту так, чтобы попасть в выдаваемый токен.
Настройка самих параметров заняла около часа, однако весь первый проход растянулся на вечер и половину следующего дня из-за диагностики этих трёх разрывов. После исправления psql заработал через device flow. Для GUI результат оказался неодинаковым: DBeaver с обычным pgJDBC не стал поддерживать PostgreSQL OAuth только от установки libpq-oauth, поэтому его не объявляли готовым. pgAdmin проверяли отдельной сборкой; начиная с pgAdmin 4 версии 9.16 официальный Docker-образ включает libpq-oauth-18.so и libcurl, но параметры соединения и конкретный образ всё равно надо тестировать.
За более чем полгода на read-only доступе не было инцидентов, вызванных самим обменом OAuth. Обновления валидатора выполнялись вручную после стендовых негативных тестов. Отключение пользователя в Keycloak прекращает получение новых токенов, но уже выданный JWT при офлайн-проверке может оставаться действительным до конца своего срока. Поэтому фраза «достаточно выключить учётную запись — и доступ исчезнет мгновенно» для такого валидатора была бы неверной.
- Клиент: фармдистрибьютор «Медфарм-Опт», 90 сотрудников; OAuth-пилот для трёх аналитиков.
- Текущий стенд: PostgreSQL 18.6, Debian 12 и Keycloak 26.7.3.
- Настройка параметров — около часа; три диагностических эпизода — примерно по сорок минут.
- Для Debian 12 валидатор собран и упакован самостоятельно, а не установлен из Ubuntu-пакета.
- Подтверждённый клиент — `psql`; совместимость GUI рассматривается отдельно для каждого драйвера.
- OAuth используется только для ограниченной аналитической роли на реплике.
Issuer должен совпадать точно и быть доступен всем участникам
Требование к issuer в PostgreSQL 18 жёсткое: значение, которое сервер передаёт из HBA, должно точно совпадать с идентификатором в discovery-документе и с клиентским oauth_issuer. Документация отдельно запрещает вариации регистра и форматирования. Завершающий слэш, другой регистр realm, явный порт или иной hostname превращают визуально похожие адреса в разные issuer.
Это защита от OAuth mix-up attacks. Клиент не должен безоговорочно доверять адресу авторизации, который прислал произвольный PostgreSQL-сервер: иначе скомпрометированный сервер мог бы направить пользователя к другому issuer и попытаться получить неподходящий токен. Поэтому оператор заранее задаёт доверенный oauth_issuer, а libpq сверяет его с серверным предложением и discovery-метаданными.
Для обычного issuer PostgreSQL строит адрес discovery, добавляя /.well-known/openid-configuration. Можно указать и полный well-known URI, содержащий сегмент /.well-known/; тогда он передаётся как есть. В связке с Keycloak проще использовать канонический issuer realm и проверить три важных поля: сам issuer, device_authorization_endpoint для встроенного flow и jwks_uri для офлайн-проверки JWT.
issuer='https://sso.medfarm-opt.local/realms/dbrealm'
discovery="${issuer}/.well-known/openid-configuration"
curl --fail --silent --show-error "$discovery" \
| jq -r '.issuer, .device_authorization_endpoint, .jwks_uri'
test "$(curl --fail --silent --show-error "$discovery" | jq -r '.issuer')" = "$issuer"Проверять надо и сертификат. В этом кейсе сертификат Keycloak выдан внутренним УЦ, поэтому корневой сертификат установлен в системное хранилище доверия на сервере PostgreSQL и на клиентских рабочих станциях. Отключение проверки TLS в проде не является способом починить issuer: оно убирает именно ту гарантию, на которой держится доверие к discovery и JWKS.
В Keycloak 26 адреса frontend endpoints задаёт параметр hostname. Если reverse proxy завершает TLS на стандартном порту 443, безопаснее указать полный внешний URL. При динамическом разрешении адресов надо корректно настроить proxy-headers, а прокси обязан перезаписывать, а не просто пропускать клиентские заголовки Forwarded или X-Forwarded-*. Иначе проблема превращается из ошибки конфигурации в уязвимость.
bin/kc.sh start \
--hostname https://sso.medfarm-opt.local \
--http-enabled true \
--proxy-headers xforwardedСам Keycloak может обращаться к внутренним backchannel endpoints, но публичный issuer от этого не должен меняться. Если требуется отдельный внутренний маршрут, сначала изучите hostname-backchannel-dynamic; параметр не отменяет точную проверку claim iss в токене. Не используйте pg_oidc_validator.discovery_url_override для маскировки неправильного issuer: этот параметр меняет адрес получения discovery и JWKS, но, согласно документации модуля, не меняет ожидаемый issuer при проверке JWT.
В итоге канонический адрес выбирают один раз. Он должен разрешаться из сети пользователей и с сервера PostgreSQL, соответствовать сертификату и публиковаться Keycloak. Подгонять HBA под случайный внутренний URL с портом — значит заложить отказ при следующем исправлении reverse proxy.
- Завершающий слэш меняет строку issuer.
- Порт 8443 и отсутствие явного порта — разные значения.
- Внутреннее и внешнее доменные имена не взаимозаменяемы.
- Для штатной работы issuer должен быть HTTPS URL.
- Discovery должен публиковать endpoint Device Authorization и адрес JWKS.
- Корневому УЦ должны доверять и клиент, и сервер, если валидатор обращается к Keycloak.
Scope, claims и сопоставление с ролью PostgreSQL
После получения корректно подписанного токена соединение может оборваться на двух независимых проверках: валидатор не увидит обязательный scope либо PostgreSQL не сопоставит внешнюю личность с запрошенной ролью. В клиентском сообщении эти причины легко спутать, поэтому я проверяю их отдельно.
Параметр scope обязателен в OAuth-записи pg_hba.conf и содержит список значений через пробел. Конкретные значения задают сервер авторизации и валидатор. В нашем примере openid включает OIDC-сценарий, email нужен для соответствующего claim, а pgscope обозначает согласие на доступ к базе. В Keycloak мало создать client scope: его надо назначить клиенту и настроить включение имени scope в токен.
Клиентский oauth_scope у libpq необязателен. Обычно клиент получает нужный список от PostgreSQL. Если задать oauth_scope вручную, он заменяет список, запрошенный сервером; отсутствие одного из обязательных значений приведёт к отказу валидатора. Поэтому во время обычной настройки я не дублирую scope в строке подключения без причины.
Далее валидатор возвращает PostgreSQL идентификатор authn_id. У pg_oidc_validator по умолчанию используется claim sub. В Keycloak это стабильный идентификатор учётной записи, но для человека он выглядит как UUID. Если в HBA нет map, полученный идентификатор должен точно совпасть с именем запрошенной роли PostgreSQL. Обычно роли с именем UUID нет, и вход завершается отказом уже после успешной проверки токена.
В кейсе использован pg_oidc_validator.authn_field = 'email', а явный map=kcmap разрешает только перечисленных сотрудников. Это удобно для чтения конфигурации, но создаёт обязательства: адрес должен присутствовать в access token, изменение email надо синхронно отражать в pg_ident.conf, а право пользователя самостоятельно менять или подтверждать адрес следует ограничить политикой Keycloak. Сам факт наличия строки в claim ещё не делает email подходящим авторизационным идентификатором.
Более консервативный вариант — оставить sub и хранить UUID в карте. Он хуже читается, зато изменение фамилии, логина или почты не ломает доступ. Пересоздание учётной записи даст новый sub и безопасно закроет вход до обновления карты. Это не «тихий пропуск», а отказ по умолчанию, что для доступа к базе обычно правильнее.
Содержимое загруженных файлов удобно проверять через системные представления. Они читают текущие файлы с диска и показывают ошибки разбора, но сами по себе не подтверждают, что конфигурация уже была перезагружена.
SELECT rule_number, line_number, database, user_name, address,
auth_method, options, error
FROM pg_hba_file_rules
WHERE auth_method = 'oauth' OR error IS NOT NULL
ORDER BY line_number;
SELECT map_number, line_number, map_name, sys_name, pg_username, error
FROM pg_ident_file_mappings
WHERE map_name = 'kcmap' OR error IS NOT NULL
ORDER BY line_number;
SELECT pg_reload_conf();Опция delegate_ident_mapping=1 пропускает проверку pg_ident.conf и передаёт валидатору полную ответственность за разрешение входа под запрошенной ролью. Она несовместима с map. Официальная документация называет её расширенной возможностью не для обычного применения: ошибка валидатора может позволить войти под ролью, которую токен не должен давать.
Независимо от выбранной карты пользовательскую роль делают минимально привилегированной. OAuth отвечает за представление внешней личности и часть авторизационного контекста, но не превращает общую аналитическую роль в безопасную автоматически. Запрет суперпользовательских атрибутов, точные GRANT, ограничения по базе, сети и TLS остаются обязательными.
- Без `map` внешний идентификатор должен точно совпасть с запрошенной ролью.
- `sub` обычно устойчивее изменяемых email и username, хотя менее удобен для чтения.
- `map` и `delegate_ident_mapping=1` нельзя использовать вместе.
- Ручной клиентский `oauth_scope` заменяет scope, полученный от сервера.
- Пользовательская SSO-роль не должна быть суперпользователем или технической ролью приложения.
Безопасность валидатора: название проекта ничего не гарантирует
Самая опасная ошибка в исходной конфигурации — считать любую библиотеку с названием oauth_validator пригодной для продакшена. Официальная глава PostgreSQL о разработке валидаторов требует проверить происхождение токена, подпись или результат introspection, issuer, audience, срок действия, клиентскую авторизацию через scope и идентичность пользователя. Неверно реализованный модуль хуже отсутствующей аутентификации: он создаёт ощущение защиты, хотя принимает поддельные данные.
Репозиторий TantorLabs oauth_validator сам описывает реализацию как минимальную: модуль извлекает sub и scope из payload JWT и сравнивает scopes. В разделе Extensibility проверка подписи, срока exp, audience и issuer перечислена как то, что можно добавить. Такой код нельзя предлагать как готовый боевой валидатор. Подделать неподписанный payload и записать в него нужные sub и scope — не проверка OAuth.
pg_oidc_validator от Percona устроен существенно полнее: получает discovery и JWKS, выбирает ключ по kid, настраивает допустимый алгоритм, проверяет подпись и ожидаемый issuer, а затем scopes и выбранный claim. Но это всё равно внешний проект с экспериментальными пакетами. Перед продом я проверяю конкретный commit, зависимости, обработку срока действия и audience именно для нашей конфигурации Keycloak, ротацию ключей, поведение при недоступности JWKS и отрицательные сценарии.
Нельзя заменять такую проверку успешным входом одного пользователя. Позитивный тест доказывает только, что счастливый путь работает. Нужны как минимум токен другого realm, токен с неверной подписью, истёкший токен, токен без обязательного scope, токен без выбранного claim и попытка войти под другой ролью. Каждая попытка должна завершиться отказом и понятной записью в серверном журнале без вывода самого bearer-токена.
Офлайн-проверка JWT означает, что сервер валидирует подпись и содержимое локально. Она не спрашивает Keycloak о состоянии каждой сессии, поэтому административное отключение учётной записи не обязательно аннулирует уже выданный access token мгновенно. Риск уменьшают разумный срок жизни access token, ограниченные роли и контролируемое обновление ключей. Если нужна централизованная немедленная проверка, рассматривают валидатор с token introspection, учитывая дополнительный сетевой вызов при аутентификации.
Секреты OAuth нельзя отправлять в журналы и тикеты. Встроенный клиент libpq имеет специальный режим PGOAUTHDEBUG=UNSAFE, который разрешает небезопасный HTTP, меняет доверие к УЦ и печатает чувствительный HTTP-трафик. Этот режим предназначен только для изолированной разработки. На клиентском компьютере с реальными учётными данными и тем более в проде его включать нельзя.
У валидатора должен быть собственный жизненный цикл сопровождения: перечень исходников и зависимостей, воспроизводимый пакет, тестовая среда, владелец обновлений и сценарий отката. Копирование неизвестной разделяемой библиотеки в каталог PostgreSQL без этих данных фактически добавляет непроверенный код в процесс аутентификации базы.
- TantorLabs `oauth_validator` в опубликованном виде не проверяет подпись JWT и не подходит для продакшена.
- Проверяйте конкретную версию или commit внешнего валидатора, а не только имя репозитория.
- Отрицательные тесты обязательны: неверные подпись, issuer, срок, scope, claim и роль.
- Офлайн JWT не обеспечивает мгновенный отзыв уже выданного токена.
- Bearer-токены и вывод небезопасной OAuth-отладки нельзя сохранять в рабочих логах.
Диагностика по слоям: порядок, который экономит вечер
Я диагностирую соединение сверху вниз и после каждого шага фиксирую, какой компонент уже доказанно работает. Это быстрее, чем одновременно менять HBA, Keycloak и клиент. Начинаю с версии, загрузки валидатора и разбора конфигурации сервером.
sudo -u postgres psql -XAtqc 'SHOW server_version; SHOW oauth_validator_libraries;'
test -f "$(pg_config --pkglibdir)/pg_oidc_validator.so"
sudo -u postgres psql -X -c "SELECT line_number, auth_method, options, error FROM pg_hba_file_rules ORDER BY line_number;"
sudo -u postgres psql -X -c "SELECT line_number, map_name, sys_name, pg_username, error FROM pg_ident_file_mappings ORDER BY line_number;"Если библиотека указана, но отсутствует в pkglibdir, сервер не сможет загрузить её по запросу. Если файл есть, проверяю журнал первого подключения: модуль загружается по требованию, и несовместимый ABI, недостающая зависимость или ошибка инициализации проявятся именно там. После установки стороннего модуля и изменения его параметров я выполняю контролируемый перезапуск на стенде, как рекомендует README pg_oidc_validator.
Следующий слой — клиент. На Debian проверяю не связь libpq.so с libcurl, а наличие совместимых пакетов и файла динамического модуля. Версии libpq5 и libpq-oauth должны происходить из согласованного набора пакетов; зависимость Debian обычно обеспечивает их точное совпадение.
psql --version
dpkg-query -W -f='${Package}\t${Version}\n' libpq5 libpq-oauth postgresql-client-18
dpkg -L libpq-oauth | grep 'libpq-oauth-18\.so$'После этого проверяю Keycloak с той машины, где выполняется psql, и отдельно с PostgreSQL-сервера. Нужны успешная TLS-проверка, правильный issuer, непустой device_authorization_endpoint и доступный jwks_uri. Ответ через другой DNS, прокси или сертификат на двух узлах может отличаться, поэтому одной проверки из браузера недостаточно.
curl --fail --silent --show-error \
https://sso.medfarm-opt.local/realms/dbrealm/.well-known/openid-configuration \
| jq '{issuer, device_authorization_endpoint, jwks_uri}'Только затем запускаю явное соединение. require_auth=oauth требует, чтобы сервер действительно выбрал OAuth и завершил этот метод; молчаливое попадание под scram-sha-256 или отсутствие аутентификационного challenge станет ошибкой. oauth_issuer и oauth_client_id обязательны для встроенного flow. sslmode=verify-full отдельно проверяет PostgreSQL-сервер и не заменяет TLS-проверку адреса Keycloak.
psql 'host=pg-replica.medfarm-opt.local dbname=analytics user=analyst_ro sslmode=verify-full sslrootcert=/etc/ssl/certs/ca-certificates.crt require_auth=oauth oauth_issuer=https://sso.medfarm-opt.local/realms/dbrealm oauth_client_id=pgclient'Если device code уже появился, серверный HBA и клиентский flow прошли начальные этапы. Дальнейший отказ я ищу в scopes, подписи, issuer токена, выбранном claim и карте. В PostgreSQL 18 параметр log_connections стал строковым списком; для временной диагностики полезны события authentication и authorization. log_min_messages = debug1 может показать диагностические сообщения прилично написанного валидатора.
ALTER SYSTEM SET log_min_messages = 'debug1';
ALTER SYSTEM SET log_connections = 'authentication,authorization';
SELECT pg_reload_conf();После диагностики подробность возвращаю к штатной политике, чтобы не засорять журнал. Сам токен не должен появляться даже в отладочных сообщениях валидатора.
ALTER SYSTEM RESET log_min_messages;
ALTER SYSTEM RESET log_connections;
SELECT pg_reload_conf();Наконец, проверяю результат внутри успешной сессии. current_user показывает роль PostgreSQL, а system_user позволяет увидеть внешнюю идентичность, сообщённую методом аутентификации. Это удобный способ убедиться, что карта не скрыла неожиданную личность.
SELECT current_user, session_user, system_user;- Сверить минорную версию сервера и значение `oauth_validator_libraries`.
- Проверить библиотеку валидатора и ошибки разбора HBA и ident.
- На клиенте проверить согласованные `libpq5`, `postgresql-client-18` и `libpq-oauth`.
- Получить discovery с клиента и сервера PostgreSQL.
- Испытать соединение с `require_auth=oauth`.
- Разделить отказ проверки токена и отказ пользовательской карты.
- После успешного входа сопоставить `system_user` с ролью PostgreSQL.
Кому это подходит в 2026 году, а кому пока нет
По состоянию на сентябрь 2026 года механизм PostgreSQL сделан аккуратно: точная проверка issuer на клиенте, обязательный scope, подключаемая серверная проверка и стандартный pg_ident.conf. Ограничение находится в экосистеме. Готовых валидаторов немного, их зрелость различается радикально, а поддержка клиентами не следует автоматически из поддержки сервером.
Для интерактивного psql на Unix-подобных системах сценарий понятен: встроенный libpq flow реализует OAuth 2.0 Device Authorization Grant. Пользователь получает URL и код, подтверждает вход в браузере, после чего psql повторяет подключение с токеном. Встроенный device flow PostgreSQL 18 сейчас не поддерживается на Windows, хотя приложение может предоставить собственную реализацию через OAuth hook.
DBeaver нельзя включать в обещание без отдельного теста. Его штатный PostgreSQL-драйвер — pgJDBC, а не libpq, поэтому Debian-пакет libpq-oauth на него не влияет. В актуальной документации pgJDBC 42.7.13 параметр requireAuth перечисляет password, md5, gss, sspi, scram-sha-256 и none, но не OAuth. У продукта могут появиться собственные плагины или новые драйверы, однако это уже другой клиентский механизм.
С pgAdmin ситуация лучше, но тоже зависит от поставки. В release notes pgAdmin 4 версии 9.16 прямо указано добавление libpq-oauth-18.so и libcurl в официальный Docker-образ для соединений с PostgreSQL 18 OAuth. Это конкретная проверяемая гарантия для образа, а не для любой старой desktop-установки или самодельного контейнера. Кроме того, OAuth-вход в веб-интерфейс самого pgAdmin и OAuth-аутентификация соединения pgAdmin с PostgreSQL — два разных контура.
Для живых пользователей с ограниченными аналитическими ролями выигрыш реальный: пароль базы не хранится на рабочей станции, выдача новых токенов прекращается через централизованную учётную запись, а внешний идентификатор попадает в аудит. Но надо честно учитывать срок уже выданного access token, доступность IdP и необходимость поддерживать валидатор.
Для приложений, пулеров, репликации и резервного копирования я не делал бы OAuth вариантом по умолчанию. Интерактивный device flow рассчитан на человека, а сервису нужен иной способ получения токена и безопасного обновления. Хорошо управляемый SCRAM-секрет в специализированном хранилище часто проще, предсказуемее и не требует доступности Keycloak при создании соединения.
Для небольшой компании без существующего IdP внедрять Keycloak только ради PostgreSQL обычно невыгодно. Появится ещё один критичный сервис с базой, резервным копированием, обновлениями, TLS, мониторингом и аварийным доступом. Персональные роли, SCRAM-SHA-256, сетевые ограничения и нормальная ротация секретов дадут больше результата за меньшую сложность.
Если OAuth всё же включён, оставляю контролируемую запасную дверь: отдельную локальную роль с SCRAM, длинным паролем в аварийном хранилище и доступом только с loopback или выделенного административного сегмента. Это не правило «разрешить пароль всем», а процедура восстановления, которую регулярно проверяют. Отказ Keycloak, DNS, внутреннего УЦ или валидатора не должен лишать администратора возможности восстановить базу.
Мой итог для фармдистрибьютора «Медфарм-Опт», 90 сотрудников, остался прежним по смыслу, но стал точнее по границам: OAuth полезен для трёх аналитиков и read-only реплики, подтверждённые клиенты включаются по одному, приложения остаются на SCRAM, а валидатор обновляется только после негативных тестов. Это не магическая кнопка SSO, а ещё один контур безопасности, который надо владеть и сопровождать.
- Подходит: интерактивный `psql` и проверенные клиенты для ограниченных пользовательских ролей.
- Проверять отдельно: pgAdmin, DBeaver, IDE, BI-инструменты и пулеры.
- Не переводить автоматически: приложения, репликацию, бэкапы и аварийные роли.
- Не внедрять новый IdP только ради нескольких паролей PostgreSQL.
- Обязательно иметь стенд, мониторинг IdP, процедуру обновления валидатора и аварийный доступ.
Частые вопросы
Почему PostgreSQL 18 отказывает до появления страницы или device code Keycloak?
Сначала проверьте `oauth_validator_libraries`. Пустая строка является значением по умолчанию и заставляет сервер отклонять все OAuth-подключения. Другие ранние причины — более высокое правило HBA с другим методом, ошибка разбора HBA или клиент без OAuth flow.
Есть ли в поставке PostgreSQL 18 готовый валидатор токенов?
Нет. PostgreSQL поставляет интерфейс модулей и серверный протокол, но не реализацию проверки токенов. Внешний валидатор надо отдельно установить, изучить и испытать на отрицательных сценариях.
Можно ли использовать oauth_validator от TantorLabs в продакшене?
Опубликованное описание этого проекта говорит только об извлечении `sub` и `scope` из JWT. Проверки подписи, срока действия, audience и issuer перечислены как возможные расширения. В таком виде модуль нельзя считать безопасным боевым валидатором.
Почему установленный postgresql-client-18 не запускает Device Authorization flow?
В Debian реализация flow вынесена в отдельный пакет `libpq-oauth`, который загружается через `dlopen`. Проверьте установку согласованных версий `libpq5`, `postgresql-client-18` и `libpq-oauth`, а затем наличие файла `libpq-oauth-18.so`.
Поможет ли установка libpq-oauth программе DBeaver?
Нет, если DBeaver использует стандартный pgJDBC. Это Java-драйвер, который не загружает системный модуль libpq. Поддержку OAuth надо проверять в конкретном драйвере или плагине DBeaver отдельно.
Почему issuer выглядит одинаково, но libpq отклоняет соединение?
Требуется точное совпадение значения из HBA, поля `issuer` discovery-документа и клиентского `oauth_issuer`. Проверьте завершающий слэш, регистр, hostname, схему HTTPS и явный порт. Сравнивайте фактические строки, полученные через `curl` и `jq`.
Что писать в map и можно ли обойтись без pg_ident.conf?
Без `map` идентификатор `authn_id`, возвращённый валидатором, обязан совпасть с именем роли PostgreSQL. С Keycloak это часто UUID из `sub`. Обычно безопаснее явно задать `map` и перечислить разрешённые соответствия в `pg_ident.conf`.
Что лучше использовать как идентификатор: sub, email или preferred_username?
`sub` обычно уникален и устойчив в пределах issuer, но неудобен для чтения. Email и username понятнее, однако могут изменяться или управляться пользователем. Выбор зависит от политики Keycloak; для изменяемого claim нужна формальная процедура синхронизации карты.
Отзыв пользователя в Keycloak мгновенно закрывает активный доступ?
Не обязательно. Отключение учётной записи мешает получать новые токены, но уже выданный JWT при офлайн-проверке может приниматься до окончания срока действия. Уже установленная сессия PostgreSQL также не исчезает автоматически только из-за изменения в IdP.
Стоит ли использовать delegate_ident_mapping?
Для обычной небольшой инфраструктуры я его не рекомендую. Режим отключает проверку `pg_ident.conf` и полностью доверяет валидатору решение о входе под запрошенной ролью. Ошибка сторонней библиотеки в таком режиме напрямую становится ошибкой авторизации базы.
Можно ли перевести на OAuth приложения, пулеры и репликацию?
Технический ответ зависит от клиента и его способа получения токена. Встроенный libpq flow рассчитан на интерактивный Device Authorization Grant. Для сервисов обычно проще оставить SCRAM-секрет в управляемом хранилище, пока не спроектирован отдельный неинтерактивный OAuth flow, обновление токена и отказоустойчивость IdP.
Источники
- PostgreSQL 18: OAuth Authorization/Authentication — Официальная документация PostgreSQL 18, раздел 20.15: обязательные `issuer` и `scope`, точное совпадение issuer, параметры `validator`, `map` и `delegate_ident_mapping`. https://www.postgresql.org/docs/18/auth-oauth.html
- PostgreSQL 18: oauth_validator_libraries — Официальная документация PostgreSQL 18, раздел 19.3: пустое значение по умолчанию, отказ OAuth-подключений и отсутствие штатных валидаторов. https://www.postgresql.org/docs/18/runtime-config-connection.html
- PostgreSQL 18: параметры соединения libpq — Официальная документация PostgreSQL 18, раздел 32.1: `require_auth=oauth`, `oauth_issuer`, `oauth_client_id`, `oauth_client_secret`, `oauth_scope` и защита от mix-up attacks. https://www.postgresql.org/docs/18/libpq-connect.html
- PostgreSQL 18: OAuth Support в libpq — Официальная документация PostgreSQL 18, раздел 32.20: встроенный Device Authorization flow, обязательные клиентские параметры, отсутствие встроенного flow на Windows и предупреждение о `PGOAUTHDEBUG=UNSAFE`. https://www.postgresql.org/docs/18/libpq-oauth.html
- PostgreSQL 18: безопасная разработка OAuth-валидатора — Официальная документация PostgreSQL 18, раздел 50.1: проверки подписи или introspection, issuer, audience, срока, scopes, идентичности и требования к негативным тестам. https://www.postgresql.org/docs/18/oauth-validator-design.html
- PostgreSQL 18: сборка с libcurl — Официальная документация PostgreSQL 18, раздел 17.3: параметр `--with-libcurl` и минимальная версия libcurl 7.61.0 для OAuth 2.0 client flows. https://www.postgresql.org/docs/18/install-make.html
- PostgreSQL 18.6 Release Notes — Официальные release notes PostgreSQL 18.6: дата выпуска 13.08.2026 и указания по обновлению ветки 18. https://www.postgresql.org/docs/18/release-18-6.html
- Percona pg_oidc_validator — Первичный репозиторий разработчика: поддерживаемые сборки, команда `make USE_PGXS=1 install -j`, требование C++23, параметры `authn_field` и `discovery_url_override`, примеры Keycloak. https://github.com/percona/pg_oidc_validator
- Percona Community: OIDC in PostgreSQL with Keycloak — Руководство разработчика валидатора от 19.01.2026: настройка Keycloak Device Authorization Grant, client scope, `libpq-oauth`, HBA, ident и соединения `psql`. https://percona.community/blog/2026/01/19/oidc-in-postgresql-with-keycloak/
- TantorLabs oauth_validator — Первичный репозиторий реализации: заявлена минимальная проверка `sub` и `scope`, а проверки подписи, expiration, audience и issuer перечислены только как возможные расширения. https://github.com/TantorLabs/oauth_validator
- Debian: пакет libpq-oauth — Официальная карточка пакета Debian: отдельный модуль Device Authorization flow для `libpq5`, отложенная загрузка и файл `libpq-oauth-18.so`. https://packages.debian.org/sid/amd64/libpq-oauth
- Keycloak: Configuring the hostname v2 — Официальная документация Keycloak: параметры `hostname`, `proxy-headers`, `hostname-backchannel-dynamic`, работа за reverse proxy и формирование discovery endpoints. https://www.keycloak.org/server/hostname
- Keycloak Server Administration Guide — Официальное руководство Keycloak: устройство Device Authorization Grant и переключатель OAuth 2.0 Device Authorization Grant в настройках OIDC client. https://www.keycloak.org/docs/latest/server_admin/index.html
- Keycloak 26.7.3 Release — Официальные release notes Keycloak 26.7.3 от 31.08.2026 с перечнем security fixes и исправлений. https://www.keycloak.org/2026/08/keycloak-2673-released
- pgAdmin 4 9.16 Release Notes — Официальные release notes pgAdmin: в Docker-образ добавлены `libpq-oauth-18.so` и libcurl для OAuth-соединений с PostgreSQL 18. https://www.pgadmin.org/docs/pgadmin4/9.17/release_notes_9_16.html
- DBeaver: PostgreSQL driver — Официальная документация DBeaver: стандартное подключение PostgreSQL использует JDBC-драйвер и перечисляет доступные модели аутентификации. https://dbeaver.com/docs/dbeaver/Database-driver-PostgreSQL/
- pgJDBC: PostgreSQL JDBC Driver — Первичный репозиторий pgJDBC; документация актуального драйвера перечисляет поддерживаемые значения `requireAuth` без OAuth. https://github.com/pgjdbc/pgjdbc
