АйТи Фреш
Главная / Статьи / Безопасность
Безопасность

OAuth в PostgreSQL 18 с Keycloak: что нужно кроме строки в pg_hba.conf

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~29 мин чтения
OAuth в PostgreSQL 18 с Keycloak: что нужно кроме строки в pg_hba.conf
Иллюстрация к статье «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, где видны порядок, разобранные опции и синтаксические ошибки.

Если сервер отказывает до запуска device flow, сначала проверьте `oauth_validator_libraries`, порядок HBA и ошибки в `pg_hba_file_rules`. Правка issuer не поможет, пока запрос не дошёл до OAuth-обмена.
Цифры и версии: Почему одной строки oauth в pg_hba.conf недостаточно — схема
Цифры и версии: Почему одной строки oauth в 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.

Команда `ldd` для `libpq.so` не является надёжной проверкой OAuth в Debian: модуль `libpq-oauth-18.so` загружается отложенно. Проверяйте пакет, список его файлов и реальное тестовое подключение.
OAuth в PostgreSQL 18 с Keycloak: что нужно кроме строки в pg_hba.conf — схема
Схема к статье. Открыть схему в полном размере

Разбор стенда: фармдистрибьютор «Медфарм-Опт», 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 при офлайн-проверке может оставаться действительным до конца своего срока. Поэтому фраза «достаточно выключить учётную запись — и доступ исчезнет мгновенно» для такого валидатора была бы неверной.

Не обещайте единый вход во всех программах только потому, что его поддерживает PostgreSQL. Клиент на libpq, Java-клиент и серверный веб-инструмент могут иметь три разных реализации аутентификации.
Памятка: Разбор стенда: фармдистрибьютор «Медфарм-Опт», 90 сотрудников — схема
Памятка: Разбор стенда: фармдистрибьютор «Медфарм-Опт», 90 сотрудников. Открыть схему в полном размере

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`, но сначала убедитесь, что Keycloak публикует правильный внешний адрес. Исправляйте `hostname` и reverse proxy, а не закрепляйте ошибочный внутренний URL в HBA.

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 остаются обязательными.

Выбирайте claim как ключ авторизации, а не как красивую подпись. Для `email` документируйте процедуру изменения адреса; для `sub` храните связь UUID с сотрудником в управляемой карте.

Безопасность валидатора: название проекта ничего не гарантирует

Самая опасная ошибка в исходной конфигурации — считать любую библиотеку с названием 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 без этих данных фактически добавляет непроверенный код в процесс аутентификации базы.

Ни успешный вход, ни наличие JWKS в коде не заменяют security review. Валидатор находится в критическом пути аутентификации и должен доказуемо отклонять каждый неподходящий токен.

Диагностика по слоям: порядок, который экономит вечер

Я диагностирую соединение сверху вниз и после каждого шага фиксирую, какой компонент уже доказанно работает. Это быстрее, чем одновременно менять 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;
Не меняйте несколько слоёв одновременно. Появление device code уже доказывает работу части цепочки; отказ после выдачи токена надо искать у валидатора и в карте, а не в загрузке клиентского модуля.
Порядок действий: Диагностика по слоям: порядок, который экономит вечер — схема
Порядок действий: Диагностика по слоям: порядок, который экономит вечер. Открыть схему в полном размере

Кому это подходит в 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, а ещё один контур безопасности, который надо владеть и сопровождать.

OAuth не должен быть единственной дверью в кластер. Оставьте локальный, сетево ограниченный и регулярно проверяемый способ аварийного входа, независимый от Keycloak.

Частые вопросы

Почему 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.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 💬 MAX: +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи