libpqは、RFC 8628に記載されているOAuth v2デバイス認証クライアントフローのサポートを、オプションのモジュールとして実装しています。 組み込みフローとしてデバイス認証のサポートを有効にする方法については、インストール文書を参照してください。
サポートが有効化されておりオプションのモジュールがインストールされている場合、認証中にサーバがベアラトークンを要求すると、libpqはデフォルトで組み込みフローを使用します。 このフローは、たとえばSSH経由でクライアントを実行している場合など、クライアントアプリケーションを実行しているシステムに使用可能なWebブラウザがない場合でも利用できます。
組み込みフローでは、デフォルトでアクセス先のURLとそこで入力するユーザコードを表示します。
$ psql 'dbname=postgres oauth_issuer=https://example.com oauth_client_id=...' Visit https://example.com/device and enter the code: ABCD-EFGH
(このプロンプトはカスタマイズされているかもしれません。) ユーザがOAuthプロバイダにログインすると、OAuthプロバイダはlibpqとサーバがユーザの代わりにアクションを実行することを許可するかどうかを尋ねます。 続行する前に、表示されたURLと権限を注意深く確認し、期待通りであることを確認することをお勧めします。 信頼できない第三者に許可を与えてはいけません。
クライアントアプリケーションは、独自のフローを実装してアプリケーションとの対話や統合をカスタマイズできます。 libpqに独自のフローを追加する方法の詳細については、32.20.1を参照してください。
OAuthクライアントフローを使用できるようにするには、接続文字列に少なくともoauth_issuerとoauth_client_idが含まれている必要があります。 (これらの設定は、利用者の組織のOAuthプロバイダによって決定されます。) さらに組み込みフローでは、OAuth認証サーバがデバイス認証エンドポイントを公開する必要があります。
組み込みデバイス認証フローは、現在Windowsではサポートされていません。 カスタムクライアントフローは引き続き実装できます。
OAuthフローの動作は、次のフックAPIを使用するクライアントにより変更または置き換えることができます。
PQsetAuthDataHook #
PGauthDataHookを設定し、libpqによるOAuthクライアントフローの1つ以上の処理を上書きします。
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経由で取得可能)に委任する必要があります。
0より大きい整数を返すと、成功していることを示します。 負の整数を返すとエラー状態であることを示しており、接続試行が中止されます。 (デフォルトの実装では0の値は予約されています。)
PQgetAuthDataHook #
現在のPGauthDataHookの値を取得します。
PQauthDataHook_type PQgetAuthDataHook(void);
初期化時(PQsetAuthDataHookを最初に呼び出す前)には、この関数はPQdefaultAuthDataHookを返します。
次のPGauthData型とそれに対応するdata構造体が定義されています。
PQAUTHDATA_PROMPT_OAUTH_DEVICE
#
組み込みデバイス認証クライアントフロー中の、デフォルトのユーザプロンプトを置き換えます。
dataはPGpromptOAuthDeviceのインスタンスを指しています。
typedef struct _PGpromptOAuthDevice
{
const char *verification_uri; /* アクセスする検証URI */
const char *user_code; /* 入力するユーザコード */
const char *verification_uri_complete; /* URIとコードのオプションの組み合わせ、
* またはNULL */
int expires_in; /* ユーザコードの有効期限が切れるまでの秒数 */
} PGpromptOAuthDevice;
libpqに含めることができるOAuthデバイス認証フローでは、エンドユーザがブラウザでURLにアクセスし、libpqがユーザに代わってサーバに接続することを許可するコードを入力する必要があります。
デフォルトのプロンプトは、標準エラーにverification_uriとuser_codeを出力するだけです。
代替の実装では、この情報をGUIなど好みの方法を使用して表示できます。
このコールバックは、組み込みデバイス認証フロー中にのみ呼び出されます。 アプリケーションがカスタムOAuthフローをインストールしている場合や、libpqが組み込みフローをサポートするようにビルドされていない場合、この認証データ型は使用されません。
非NULLのverification_uri_completeが提供されている場合は、テキスト以外の検証(QRコードの表示など)にも使用できます。
この場合でも、URLとユーザコードは引き続きエンドユーザに表示する必要があります。
これは、そのコードがプロバイダによって手動で確認されるため、またユーザがテキスト以外の方法を使用できない場合でもURLを使用して続行できるようにするためです。
詳細については、RFC 8628のセクション3.3.1を参照してください。
PQAUTHDATA_OAUTH_BEARER_TOKEN
#独自実装したフローを追加し、組み込みフローがインストールされている場合はそれを置き換えます。 フックは、ブロッキングなしで使用可能な場合は現在のユーザ/発行者/スコープの組み合わせのベアラトークンを直接返すか、それ以外の場合は非同期コールバックを設定してベアラトークンを取得する必要があります。
dataはPGoauthBearerRequestのインスタンスを指しています。
このインスタンスは実装側で入力する必要があります。
typedef struct PGoauthBearerRequest
{
/* フックの入力(全ての呼び出しで一定) */
const char *openid_configuration; /* OIDCディスカバリーURL */
const char *scope; /* 必要なスコープ、またはNULL */
/* フックの出力 */
/* カスタム非同期OAuthフローを実装するコールバック。 */
PostgresPollingStatusType (*async) (PGconn *conn,
struct PGoauthBearerRequest *request,
SOCKTYPE *altsock);
/* カスタム割り当てをクリーンアップするためのコールバック。 */
void (*cleanup) (PGconn *conn, struct PGoauthBearerRequest *request);
char *token; /* 取得したベアラトークン */
void *user; /* フックで定義される割り当てデータ */
} PGoauthBearerRequest;
libpqでは、このフックで2つの情報を提供しています。
openid_configurationには、認証サーバがサポートするフローを説明するOAuthディスカバリー文書のURLが含まれ、scopeには、サーバへのアクセスに必要なOAuthスコープの一覧(空の場合もあります)がスペースで区切られて含まれています。
どちらか一方または両方がNULLの場合は、情報が検出できなかったことを示します。
(この場合、実装側で事前に設定された他の知識を使用して要件を確立できる場合もあれば、失敗を選択する場合もあります。)
フックの最終出力はtokenであり、これは接続で使用できる有効なベアラトークンを指している必要があります。
(このトークンはoauth_issuerによって発行され、要求されたスコープを保持する必要があります。
そうしないと、サーバの検証モジュールによって接続が拒否されます。)
割り当てられたトークン文字列は、libpqが接続を終了するまで有効なままでなければなりません。
フックは、libpqが接続を必要としなくなったときに呼び出されるcleanupコールバックを設定する必要があります。
実装上フックの最初の呼び出し中にすぐにtokenを生成できない場合は、認証サーバとの非ブロッキング通信を処理するように非同期コールバックを設定する必要があります。
[16]
これは、フックから戻るとすぐにフローを開始するために呼び出されます。
コールバックがブロッキングなしでこれ以上先に進めない場合は、*pgsocketに、処理が再開できるようになったときに読み取り/書き込みの準備完了とマークされるファイルディスクリプタを設定した後、PGRES_POLLING_READINGまたはPGRES_POLLING_WRITINGのいずれかを返す必要があります。
(その後、このディスクリプタはPQsocket()を介して最上位のポーリングループに提供されます。)
フローが完了したらtokenを設定した後にPGRES_POLLING_OKを返し、失敗した場合はPGRES_POLLING_FAILEDを返します。
実装によっては、asyncコールバックとcleanupコールバックの呼び出しをまたぐ情報に対し、追加のデータを格納したい場合があります。
userポインタはこの目的のために提供されています。
libpqはその内容に触れることはなく、アプリケーションは都合の良いときにこの追加のデータを使用できます。
(トークンのクリーンアップ中に割り当てを解放することを忘れないでください。)
環境変数PGOAUTHDEBUG=UNSAFEを設定することで、「危険なデバッグモード」を有効にできます。
この機能は、ローカルでの開発とテストを容易にするためにのみ提供されています。
この機能は、本番システムでは実行したくないいくつかのことを実行します。
OAuthプロバイダの交換中に暗号化されていないHTTPを使用することを許可します
PGOAUTHCAFILE環境変数を使用して、システムの信頼するCAリストを完全に置き換えることができます
OAuthフロー中にHTTPトラフィック(いくつかの重要なシークレットを含む)を標準エラーに出力します
0秒の再試行間隔の使用を許可します。 これにより、クライアントはビジーループ状態になり、CPUを無意味に消費する可能性があります
OAuthフロートラフィックの出力を第三者と共有しないでください。 クライアントやサーバを攻撃するために使用できるシークレットが含まれています。
[16]
PQAUTHDATA_OAUTH_BEARER_TOKENフックコールバック中にブロッキング操作を実行すると、PQconnectPollなどの非ブロッキングな接続APIが妨害され、同時接続の進行が妨げられます。
PQconnectdbのように同期接続プリミティブのみを使用するアプリケーションでは、asyncコールバックを実装する代わりにフック中にトークンを同期的に取得できますが、一度に1つの接続に制限される必要があります。