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. */
    PostgresPollingStatusType (*async) (PGconn *conn,
                                        struct PGoauthBearerRequest *request,
                                        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, предварительно установив *pgsocket в значение файлового дескриптора, который будет помечен как готовый к чтению и записи, когда выполнение можно будет продолжить. (Этот дескриптор затем передаётся в главный цикл опроса через 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, но они будут ограничены одним подключением за раз.