32.20. Поддержка OAuth #
В libpq внедрена поддержка потока авторизации устройства OAuth 2.0, описанного в RFC 8628, в качестве необязательного модуля. Информация о том, как включить поддержку авторизации устройства в виде встроенного потока, представлена в документации по установке.
Если поддержка включена и необязательный модуль установлен, libpq будет использовать встроенный поток по умолчанию, если сервер в процессе аутентификации запрашивает токен типа bearer. Такой поток используется, даже если в системе, где работает клиентское приложение, отсутствует рабочий веб-браузер, например, при запуске клиента по SSH.
Встроенный поток по умолчанию выведет URL-адрес для перехода и код пользователя для ввода:
$ psql 'dbname=postgres oauth_issuer=https://example.com oauth_client_id=...' Перейдите на https://example.com/device и введите код: ABCD-EFGH
(Это приглашение можно настроить). Затем пользователь войдёт в учётную запись своего поставщика OAuth, где будет запрос на разрешение для libpq и сервера выполнять действия от его имени. Всегда рекомендуется тщательно проверять URL-адрес и отображаемые разрешения, чтобы убедиться, что они соответствуют ожиданиям, прежде чем продолжить. Не предоставляйте разрешения недоверенным третьим сторонам.
Клиентские приложения могут реализовать собственные потоки для настройки взаимодействия и интеграции с приложениями. Более подробная информация по добавлению пользовательского потока в libpq предоставлена в Подразделе 32.20.1.
Для корректной работы клиентского потока OAuth строка подключения должна содержать как минимум oauth_issuer и oauth_client_id. (Эти параметры определяются поставщиком OAuth вашей компании.) Для встроенного потока также требуется, чтобы сервер авторизации OAuth опубликовал конечную точку авторизации устройства.
Примечание
Встроенный поток авторизации устройства на данный момент не поддерживается на Windows. Однако можно использовать пользовательские клиентские потоки.
32.20.1. Обработчики Authdata #
Клиент может изменить или заменить поведение потока OAuth с помощью следующего API обработчиков:
PQsetAuthDataHook#Устанавливает
PGauthDataHook, переопределяя обработку одним или несколькими аспектами клиентского потока OAuth в libpq.void PQsetAuthDataHook(PQauthDataHook_type hook);
Если для параметра
hookзадано значениеNULL, то будет восстановлен обработчик по умолчанию. В противном случае приложение передаёт указатель на функцию-обработчик со следующей сигнатурой:int hook_fn(PGauthData type, PGconn *conn, void *data);
libpq будет вызывать её, когда от приложения потребуется какое-либо действие.
typeописывает тип выполняемого запроса,connявляется дескриптором подключения, проходящего аутентификацию, аdataуказывает на специфичные для запроса метаданные. Содержимое такого указателя определяется параметромtype; список поддерживаемых типов описан в Подразделе 32.20.1.1.Обработчики можно объединять в цепочку для обеспечения кооперативного и/или резервного поведения. Как правило, реализация обработчика должна анализировать входящий тип
type(и, возможно, метаданные запроса и/или параметры конкретного используемого подключенияconn), чтобы решить, обрабатывать ли определённый элемент данных аутентификации. Если нет, он должен делегировать обработку предыдущему обработчику в цепочке (который можно получить с помощьюPQgetAuthDataHook).В случае успеха выводится целочисленное значение больше нуля. Отрицательное целочисленное значение свидетельствует об ошибке и сбрасывает попытку подключения. (Нулевое значение зарезервировано для реализации по умолчанию.)
PQgetAuthDataHook#Выводит текущее значение
PGauthDataHook.PQauthDataHook_type PQgetAuthDataHook(void);
Во время инициализации (до первого вызова
PQsetAuthDataHook) данная функция возвращаетPQdefaultAuthDataHook.
32.20.1.1. Типы обработчиков #
Определены следующие типы PGauthData и соответствующие им структуры data:
-
PQAUTHDATA_PROMPT_OAUTH_DEVICE# Заменяет стандартное приглашение пользователя во встроенном клиентском потоке авторизации устройства.
dataуказывает на экземплярPGpromptOAuthDevice:typedef struct _PGpromptOAuthDevice { const char *verification_uri; /* verification URI to visit */ const char *user_code; /* user code to enter */ const char *verification_uri_complete; /* optional combination of URI and * code, or NULL */ int expires_in; /* seconds until user code expires */ } PGpromptOAuthDevice;Поток авторизации устройства OAuth, который можно включить в libpq, требует, чтобы конечный пользователь перешел по URL-адресу в браузере, а затем ввел код, который разрешает libpq подключиться к серверу от его имени. Приглашение по умолчанию просто выводит
verification_uriиuser_codeв стандартный поток ошибок. Альтернативные реализации могут отображать эту информацию любым предпочтительным способом, например, с помощью графического интерфейса.Этот обработчик вызывается только во встроенном потоке авторизации устройства. Если в приложении установлен пользовательский поток OAuth или если libpq был собран без поддержки встроенного потока, то этот тип данных аутентификации использоваться не будет.
Ненулевой
verification_uri_completeможет использоваться для вывода нетекстовой проверки (например, QR-кода). В таком случае URL-адрес и код пользователя должны быть видны конечному пользователю, поскольку код вручную подтверждается поставщиком, а URL-адрес позволяет пользователям продолжить, даже если они не могут использовать нетекстовый метод. Более подробная информация предоставлена в разделе 3.3.1 RFC 8628.-
PQAUTHDATA_OAUTH_BEARER_TOKEN# Добавляет пользовательскую реализацию потока, заменяя встроенный поток, если он установлен. Обработчик должен либо напрямую вернуть токен типа bearer для комбинации текущего пользователя/издателя/области доступа, если он доступен без блокировки, либо установить асинхронный обработчик для его получения.
dataуказывает на экземплярPGoauthBearerRequest, который должен быть заполнен реализацией:typedef struct PGoauthBearerRequest { /* Hook inputs (constant across all calls) */ const char *openid_configuration; /* OIDC discovery URL */ const char *scope; /* required scope(s), or NULL */ /* Hook outputs */ /* * Callback implementing a custom asynchronous OAuth flow. The signature is * platform-dependent: PQ_SOCKTYPE is SOCKET on Windows, and int everywhere * else. */ PostgresPollingStatusType (*async) (PGconn *conn, struct PGoauthBearerRequest *request, PQ_SOCKTYPE *altsock); /* Callback to clean up custom allocations. */ void (*cleanup) (PGconn *conn, struct PGoauthBearerRequest *request); char *token; /* acquired Bearer token */ void *user; /* hook-defined allocated data */ } PGoauthBearerRequest;libpq передаёт обработчику два элемента информации:
openid_configurationсодержит URL-адрес документа обнаружения OAuth, где перечисляются потоки, поддерживаемые сервером авторизации, аscopeсодержит (возможно, пустой) список необходимых для доступа к серверу идентификаторов областей доступа OAuth, разделённых пробелами. Один из параметров или оба могут иметь значениеNULL, что указывает на то, что информацию не удалось обнаружить. (В таком случае реализации могут попытаться установить требования, используя другую предварительно настроенную информацию, или могут выбрать завершение с ошибкой.)Финальным результатом работы обработчика является параметр
token, который должен указывать на действительный токен типа bearer для использования при подключении. (Этот токен должен быть выпущен oauth_issuer и содержать запрашиваемые области доступа, иначе запрос на соединение будет отклонён модулем проверки сервера.) Выделенная строка токена должна оставаться действительной до завершения подключения libpq. Обработчик должен установить функцию-обработчикcleanup, которая будет вызвана, когда токен больше не будет нужен libpq.Если реализация не может немедленно предоставить
tokenпри первоначальном вызове обработчика, то обрабатывать неблокирующие операции с сервером авторизации должна функция-обработчикasync. [16] Эта функция будет вызвана для начала потока сразу после возврата из обработчика. Когда функции-обработчику не удаётся продолжить выполнение без блокировки, она должна вернуть либоPGRES_POLLING_READING, либоPGRES_POLLING_WRITING, предварительно установив*altsockв значение файлового дескриптора, который будет помечен как готовый к чтению и записи, когда выполнение можно будет продолжить. (Этот дескриптор затем передаётся в главный цикл опроса черезPQsocket().) После установкиtokenпри успешном завершении потока возвращаетсяPGRES_POLLING_OKилиPGRES_POLLING_FAILEDпри ошибке.Реализации может потребоваться сохранять дополнительные данные для ведения учёта между вызовами функций-обработчиков
asyncиcleanup. Указательuserпредоставляется для этой цели; libpq не будет изменять его содержимое, и приложение может использовать его по своему усмотрению. (Не забывайте освобождать выделенные ресурсы во время очистки токена.)
32.20.2. Отладка и параметры для разработчика #
Режим «опасной отладки» можно включить с помощью переменной окружения PGOAUTHDEBUG=UNSAFE. Данная функциональность предоставлена исключительно для удобства локальной разработки и тестирования. Она включает действия, которые недопустимы для производственной среды:
разрешает использование незашифрованного протокола HTTP при взаимодействии с поставщиком OAuth
позволяет полностью заменить системный список сертификатов доверенных ЦС с помощью переменной окружения
PGOAUTHCAFILEвыводит HTTP-трафик (содержащий некоторые критические секреты) в стандартный поток ошибок во время потока OAuth
позволяет использовать интервалы повтора с нулевой задержкой, что может привести к бесконечному циклу на стороне клиента и бесполезной трате ресурсов процессора
Предупреждение
Не передавайте вывод трафика потока OAuth третьим сторонам. Он содержит секреты, которые могут быть использованы для атаки на ваших клиентов и серверы.
[16] Выполнение блокирующих операций во время работы обработчика PQAUTHDATA_OAUTH_BEARER_TOKEN будет мешать работе неблокирующих API подключения, таких как PQconnectPoll, и препятствовать прогрессу параллельных подключений. Приложения, которые используют только синхронные примитивы подключения, такие как PQconnectdb, могут синхронно получать токен во время работы обработчика вместо реализации функции-обработчика async, но они будут ограничены одним подключением за раз.
32.20. OAuth Support #
libpq implements support for the OAuth v2 Device Authorization client flow, documented in RFC 8628, as an optional module. See the installation documentation for information on how to enable support for Device Authorization as a builtin flow.
When support is enabled and the optional module installed, libpq will use the builtin flow by default if the server requests a bearer token during authentication. This flow can be utilized even if the system running the client application does not have a usable web browser, for example when running a client via SSH.
The builtin flow will, by default, print a URL to visit and a user code to enter there:
$ psql 'dbname=postgres oauth_issuer=https://example.com oauth_client_id=...' Visit https://example.com/device and enter the code: ABCD-EFGH
(This prompt may be customized.) The user will then log into their OAuth provider, which will ask whether to allow libpq and the server to perform actions on their behalf. It is always a good idea to carefully review the URL and permissions displayed, to ensure they match expectations, before continuing. Permissions should not be given to untrusted third parties.
Client applications may implement their own flows to customize interaction and integration with applications. See Section 32.20.1 for more information on how add a custom flow to libpq.
For an OAuth client flow to be usable, the connection string must at minimum contain oauth_issuer and oauth_client_id. (These settings are determined by your organization's OAuth provider.) The builtin flow additionally requires the OAuth authorization server to publish a device authorization endpoint.
Note
The builtin Device Authorization flow is not currently supported on Windows. Custom client flows may still be implemented.
32.20.1. Authdata Hooks #
The behavior of the OAuth flow may be modified or replaced by a client using the following hook API:
PQsetAuthDataHook#Sets the
PGauthDataHook, overriding libpq's handling of one or more aspects of its OAuth client flow.void PQsetAuthDataHook(PQauthDataHook_type hook);
If
hookisNULL, the default handler will be reinstalled. Otherwise, the application passes a pointer to a callback function with the signature:int hook_fn(PGauthData type, PGconn *conn, void *data);
which libpq will call when an action is required of the application.
typedescribes the request being made,connis the connection handle being authenticated, anddatapoints to request-specific metadata. The contents of this pointer are determined bytype; see Section 32.20.1.1 for the supported list.Hooks can be chained together to allow cooperative and/or fallback behavior. In general, a hook implementation should examine the incoming
type(and, potentially, the request metadata and/or the settings for the particularconnin use) to decide whether or not to handle a specific piece of authdata. If not, it should delegate to the previous hook in the chain (retrievable viaPQgetAuthDataHook).Success is indicated by returning an integer greater than zero. Returning a negative integer signals an error condition and abandons the connection attempt. (A zero value is reserved for the default implementation.)
PQgetAuthDataHook#Retrieves the current value of
PGauthDataHook.PQauthDataHook_type PQgetAuthDataHook(void);
At initialization time (before the first call to
PQsetAuthDataHook), this function will returnPQdefaultAuthDataHook.
32.20.1.1. Hook Types #
The following PGauthData types and their corresponding data structures are defined:
-
PQAUTHDATA_PROMPT_OAUTH_DEVICE# Replaces the default user prompt during the builtin device authorization client flow.
datapoints to an instance ofPGpromptOAuthDevice:typedef struct _PGpromptOAuthDevice { const char *verification_uri; /* verification URI to visit */ const char *user_code; /* user code to enter */ const char *verification_uri_complete; /* optional combination of URI and * code, or NULL */ int expires_in; /* seconds until user code expires */ } PGpromptOAuthDevice;The OAuth Device Authorization flow which can be included in libpq requires the end user to visit a URL with a browser, then enter a code which permits libpq to connect to the server on their behalf. The default prompt simply prints the
verification_urianduser_codeon standard error. Replacement implementations may display this information using any preferred method, for example with a GUI.This callback is only invoked during the builtin device authorization flow. If the application installs a custom OAuth flow, or libpq was not built with support for the builtin flow, this authdata type will not be used.
If a non-NULL
verification_uri_completeis provided, it may optionally be used for non-textual verification (for example, by displaying a QR code). The URL and user code should still be displayed to the end user in this case, because the code will be manually confirmed by the provider, and the URL lets users continue even if they can't use the non-textual method. For more information, see section 3.3.1 in RFC 8628.-
PQAUTHDATA_OAUTH_BEARER_TOKEN# Adds a custom implementation of a flow, replacing the builtin flow if it is installed. The hook should either directly return a Bearer token for the current user/issuer/scope combination, if one is available without blocking, or else set up an asynchronous callback to retrieve one.
datapoints to an instance ofPGoauthBearerRequest, which should be filled in by the implementation:typedef struct PGoauthBearerRequest { /* Hook inputs (constant across all calls) */ const char *openid_configuration; /* OIDC discovery URL */ const char *scope; /* required scope(s), or NULL */ /* Hook outputs */ /* * Callback implementing a custom asynchronous OAuth flow. The signature is * platform-dependent: PQ_SOCKTYPE is SOCKET on Windows, and int everywhere * else. */ PostgresPollingStatusType (*async) (PGconn *conn, struct PGoauthBearerRequest *request, PQ_SOCKTYPE *altsock); /* Callback to clean up custom allocations. */ void (*cleanup) (PGconn *conn, struct PGoauthBearerRequest *request); char *token; /* acquired Bearer token */ void *user; /* hook-defined allocated data */ } PGoauthBearerRequest;Two pieces of information are provided to the hook by libpq:
openid_configurationcontains the URL of an OAuth discovery document describing the authorization server's supported flows, andscopecontains a (possibly empty) space-separated list of OAuth scopes which are required to access the server. Either or both may beNULLto indicate that the information was not discoverable. (In this case, implementations may be able to establish the requirements using some other preconfigured knowledge, or they may choose to fail.)The final output of the hook is
token, which must point to a valid Bearer token for use on the connection. (This token should be issued by the oauth_issuer and hold the requested scopes, or the connection will be rejected by the server's validator module.) The allocated token string must remain valid until libpq is finished connecting; the hook should set acleanupcallback which will be called when libpq no longer requires it.If an implementation cannot immediately produce a
tokenduring the initial call to the hook, it should set theasynccallback to handle nonblocking communication with the authorization server. [16] This will be called to begin the flow immediately upon return from the hook. When the callback cannot make further progress without blocking, it should return eitherPGRES_POLLING_READINGorPGRES_POLLING_WRITINGafter setting*altsockto the file descriptor that will be marked ready to read/write when progress can be made again. (This descriptor is then provided to the top-level polling loop viaPQsocket().) ReturnPGRES_POLLING_OKafter settingtokenwhen the flow is complete, orPGRES_POLLING_FAILEDto indicate failure.Implementations may wish to store additional data for bookkeeping across calls to the
asyncandcleanupcallbacks. Theuserpointer is provided for this purpose; libpq will not touch its contents and the application may use it at its convenience. (Remember to free any allocations during token cleanup.)
32.20.2. Debugging and Developer Settings #
A "dangerous debugging mode" may be enabled by setting the environment variable PGOAUTHDEBUG=UNSAFE. This functionality is provided for ease of local development and testing only. It does several things that you will not want a production system to do:
permits the use of unencrypted HTTP during the OAuth provider exchange
allows the system's trusted CA list to be completely replaced using the
PGOAUTHCAFILEenvironment variableprints HTTP traffic (containing several critical secrets) to standard error during the OAuth flow
permits the use of zero-second retry intervals, which can cause the client to busy-loop and pointlessly consume CPU
Warning
Do not share the output of the OAuth flow traffic with third parties. It contains secrets that can be used to attack your clients and servers.
[16] Performing blocking operations during the PQAUTHDATA_OAUTH_BEARER_TOKEN hook callback will interfere with nonblocking connection APIs such as PQconnectPoll and prevent concurrent connections from making progress. Applications which only ever use the synchronous connection primitives, such as PQconnectdb, may synchronously retrieve a token during the hook instead of implementing the async callback, but they will necessarily be limited to one connection at a time.