mirror of
https://github.com/postgres/postgres.git
synced 2025-07-31 22:04:40 +03:00
Add support for OAUTHBEARER SASL mechanism
This commit implements OAUTHBEARER, RFC 7628, and OAuth 2.0 Device Authorization Grants, RFC 8628. In order to use this there is a new pg_hba auth method called oauth. When speaking to a OAuth- enabled server, it looks a bit like this: $ psql 'host=example.org oauth_issuer=... oauth_client_id=...' Visit https://oauth.example.org/login and enter the code: FPQ2-M4BG Device authorization is currently the only supported flow so the OAuth issuer must support that in order for users to authenticate. Third-party clients may however extend this and provide their own flows. The built-in device authorization flow is currently not supported on Windows. In order for validation to happen server side a new framework for plugging in OAuth validation modules is added. As validation is implementation specific, with no default specified in the standard, PostgreSQL does not ship with one built-in. Each pg_hba entry can specify a specific validator or be left blank for the validator installed as default. This adds a requirement on libcurl for the client side support, which is optional to build, but the server side has no additional build requirements. In order to run the tests, Python is required as this adds a https server written in Python. Tests are gated behind PG_TEST_EXTRA as they open ports. This patch has been a multi-year project with many contributors involved with reviews and in-depth discussions: Michael Paquier, Heikki Linnakangas, Zhihong Yu, Mahendrakar Srinivasarao, Andrey Chudnovsky and Stephen Frost to name a few. While Jacob Champion is the main author there have been some levels of hacking by others. Daniel Gustafsson contributed the validation module and various bits and pieces; Thomas Munro wrote the client side support for kqueue. Author: Jacob Champion <jacob.champion@enterprisedb.com> Co-authored-by: Daniel Gustafsson <daniel@yesql.se> Co-authored-by: Thomas Munro <thomas.munro@gmail.com> Reviewed-by: Daniel Gustafsson <daniel@yesql.se> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Reviewed-by: Antonin Houska <ah@cybertec.at> Reviewed-by: Kashif Zeeshan <kashi.zeeshan@gmail.com> Discussion: https://postgr.es/m/d1b467a78e0e36ed85a09adf979d04cf124a9d4b.camel@vmware.com
This commit is contained in:
@ -656,6 +656,16 @@ include_dir <replaceable>directory</replaceable>
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>oauth</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Authorize and optionally authenticate using a third-party OAuth 2.0
|
||||
identity provider. See <xref linkend="auth-oauth"/> for details.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
</para>
|
||||
@ -1143,6 +1153,12 @@ omicron bryanh guest1
|
||||
only on OpenBSD).
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<link linkend="auth-oauth">OAuth authorization/authentication</link>,
|
||||
which relies on an external OAuth 2.0 identity provider.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
@ -2329,6 +2345,242 @@ host ... radius radiusservers="server1,server2" radiussecrets="""secret one"",""
|
||||
</note>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="auth-oauth">
|
||||
<title>OAuth Authorization/Authentication</title>
|
||||
|
||||
<indexterm zone="auth-oauth">
|
||||
<primary>OAuth Authorization/Authentication</primary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
OAuth 2.0 is an industry-standard framework, defined in
|
||||
<ulink url="https://datatracker.ietf.org/doc/html/rfc6749">RFC 6749</ulink>,
|
||||
to enable third-party applications to obtain limited access to a protected
|
||||
resource.
|
||||
|
||||
OAuth client support has to be enabled when <productname>PostgreSQL</productname>
|
||||
is built, see <xref linkend="installation"/> for more information.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This documentation uses the following terminology when discussing the OAuth
|
||||
ecosystem:
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term>Resource Owner (or End User)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The user or system who owns protected resources and can grant access to
|
||||
them. This documentation also uses the term <emphasis>end user</emphasis>
|
||||
when the resource owner is a person. When you use
|
||||
<application>psql</application> to connect to the database using OAuth,
|
||||
you are the resource owner/end user.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>Client</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The system which accesses the protected resources using access
|
||||
tokens. Applications using libpq, such as <application>psql</application>,
|
||||
are the OAuth clients when connecting to a
|
||||
<productname>PostgreSQL</productname> cluster.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>Resource Server</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The system hosting the protected resources which are
|
||||
accessed by the client. The <productname>PostgreSQL</productname>
|
||||
cluster being connected to is the resource server.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>Provider</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The organization, product vendor, or other entity which develops and/or
|
||||
administers the OAuth authorization servers and clients for a given application.
|
||||
Different providers typically choose different implementation details
|
||||
for their OAuth systems; a client of one provider is not generally
|
||||
guaranteed to have access to the servers of another.
|
||||
</para>
|
||||
<para>
|
||||
This use of the term "provider" is not standard, but it seems to be in
|
||||
wide use colloquially. (It should not be confused with OpenID's similar
|
||||
term "Identity Provider". While the implementation of OAuth in
|
||||
<productname>PostgreSQL</productname> is intended to be interoperable
|
||||
and compatible with OpenID Connect/OIDC, it is not itself an OIDC client
|
||||
and does not require its use.)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>Authorization Server</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The system which receives requests from, and issues access tokens to,
|
||||
the client after the authenticated resource owner has given approval.
|
||||
<productname>PostgreSQL</productname> does not provide an authorization
|
||||
server; it is the responsibility of the OAuth provider.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term id="auth-oauth-issuer">Issuer</term>
|
||||
<listitem>
|
||||
<para>
|
||||
An identifier for an authorization server, printed as an
|
||||
<literal>https://</literal> URL, which provides a trusted "namespace"
|
||||
for OAuth clients and applications. The issuer identifier allows a
|
||||
single authorization server to talk to the clients of mutually
|
||||
untrusting entities, as long as they maintain separate issuers.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
For small deployments, there may not be a meaningful distinction between
|
||||
the "provider", "authorization server", and "issuer". However, for more
|
||||
complicated setups, there may be a one-to-many (or many-to-many)
|
||||
relationship: a provider may rent out multiple issuer identifiers to
|
||||
separate tenants, then provide multiple authorization servers, possibly
|
||||
with different supported feature sets, to interact with their clients.
|
||||
</para>
|
||||
</note>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<productname>PostgreSQL</productname> supports bearer tokens, defined in
|
||||
<ulink url="https://datatracker.ietf.org/doc/html/rfc6750">RFC 6750</ulink>,
|
||||
which are a type of access token used with OAuth 2.0 where the token is an
|
||||
opaque string. The format of the access token is implementation specific
|
||||
and is chosen by each authorization server.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The following configuration options are supported for OAuth:
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><literal>issuer</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
An HTTPS URL which is either the exact
|
||||
<link linkend="auth-oauth-issuer">issuer identifier</link> of the
|
||||
authorization server, as defined by its discovery document, or a
|
||||
well-known URI that points directly to that discovery document. This
|
||||
parameter is required.
|
||||
</para>
|
||||
<para>
|
||||
When an OAuth client connects to the server, a URL for the discovery
|
||||
document will be constructed using the issuer identifier. By default,
|
||||
this URL uses the conventions of OpenID Connect Discovery: the path
|
||||
<literal>/.well-known/openid-configuration</literal> will be appended
|
||||
to the end of the issuer identifier. Alternatively, if the
|
||||
<literal>issuer</literal> contains a <literal>/.well-known/</literal>
|
||||
path segment, that URL will be provided to the client as-is.
|
||||
</para>
|
||||
<warning>
|
||||
<para>
|
||||
The OAuth client in libpq requires the server's issuer setting to
|
||||
exactly match the issuer identifier which is provided in the discovery
|
||||
document, which must in turn match the client's
|
||||
<xref linkend="libpq-connect-oauth-issuer"/> setting. No variations in
|
||||
case or formatting are permitted.
|
||||
</para>
|
||||
</warning>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>scope</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
A space-separated list of the OAuth scopes needed for the server to
|
||||
both authorize the client and authenticate the user. Appropriate values
|
||||
are determined by the authorization server and the OAuth validation
|
||||
module used (see <xref linkend="oauth-validators" /> for more
|
||||
information on validators). This parameter is required.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>validator</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The library to use for validating bearer tokens. If given, the name must
|
||||
exactly match one of the libraries listed in
|
||||
<xref linkend="guc-oauth-validator-libraries" />. This parameter is
|
||||
optional unless <literal>oauth_validator_libraries</literal> contains
|
||||
more than one library, in which case it is required.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>map</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Allows for mapping between OAuth identity provider and database user
|
||||
names. See <xref linkend="auth-username-maps"/> for details. If a
|
||||
map is not specified, the user name associated with the token (as
|
||||
determined by the OAuth validator) must exactly match the role name
|
||||
being requested. This parameter is optional.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term id="auth-oauth-delegate-ident-mapping" xreflabel="delegate_ident_mapping">
|
||||
<literal>delegate_ident_mapping</literal>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
An advanced option which is not intended for common use.
|
||||
</para>
|
||||
<para>
|
||||
When set to <literal>1</literal>, standard user mapping with
|
||||
<filename>pg_ident.conf</filename> is skipped, and the OAuth validator
|
||||
takes full responsibility for mapping end user identities to database
|
||||
roles. If the validator authorizes the token, the server trusts that
|
||||
the user is allowed to connect under the requested role, and the
|
||||
connection is allowed to proceed regardless of the authentication
|
||||
status of the user.
|
||||
</para>
|
||||
<para>
|
||||
This parameter is incompatible with <literal>map</literal>.
|
||||
</para>
|
||||
<warning>
|
||||
<para>
|
||||
<literal>delegate_ident_mapping</literal> provides additional
|
||||
flexibility in the design of the authentication system, but it also
|
||||
requires careful implementation of the OAuth validator, which must
|
||||
determine whether the provided token carries sufficient end-user
|
||||
privileges in addition to the <link linkend="oauth-validators">standard
|
||||
checks</link> required of all validators. Use with caution.
|
||||
</para>
|
||||
</warning>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="client-authentication-problems">
|
||||
<title>Authentication Problems</title>
|
||||
|
||||
|
@ -1209,6 +1209,32 @@ include_dir 'conf.d'
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="guc-oauth-validator-libraries" xreflabel="oauth_validator_libraries">
|
||||
<term><varname>oauth_validator_libraries</varname> (<type>string</type>)
|
||||
<indexterm>
|
||||
<primary><varname>oauth_validator_libraries</varname> configuration parameter</primary>
|
||||
</indexterm>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The library/libraries to use for validating OAuth connection tokens. If
|
||||
only one validator library is provided, it will be used by default for
|
||||
any OAuth connections; otherwise, all
|
||||
<link linkend="auth-oauth"><literal>oauth</literal> HBA entries</link>
|
||||
must explicitly set a <literal>validator</literal> chosen from this
|
||||
list. If set to an empty string (the default), OAuth connections will be
|
||||
refused. This parameter can only be set in the
|
||||
<filename>postgresql.conf</filename> file.
|
||||
</para>
|
||||
<para>
|
||||
Validator modules must be implemented/obtained separately;
|
||||
<productname>PostgreSQL</productname> does not ship with any default
|
||||
implementations. For more information on implementing OAuth validators,
|
||||
see <xref linkend="oauth-validators" />.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
|
||||
|
@ -111,6 +111,7 @@
|
||||
<!ENTITY generic-wal SYSTEM "generic-wal.sgml">
|
||||
<!ENTITY custom-rmgr SYSTEM "custom-rmgr.sgml">
|
||||
<!ENTITY backup-manifest SYSTEM "backup-manifest.sgml">
|
||||
<!ENTITY oauth-validators SYSTEM "oauth-validators.sgml">
|
||||
|
||||
<!-- contrib information -->
|
||||
<!ENTITY contrib SYSTEM "contrib.sgml">
|
||||
|
@ -1143,6 +1143,19 @@ build-postgresql:
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="configure-option-with-libcurl">
|
||||
<term><option>--with-libcurl</option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Build with libcurl support for OAuth 2.0 client flows.
|
||||
Libcurl version 7.61.0 or later is required for this feature.
|
||||
Building with this will check for the required header files
|
||||
and libraries to make sure that your <productname>curl</productname>
|
||||
installation is sufficient before proceeding.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="configure-option-with-libxml">
|
||||
<term><option>--with-libxml</option></term>
|
||||
<listitem>
|
||||
@ -2584,6 +2597,20 @@ ninja install
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="configure-with-libcurl-meson">
|
||||
<term><option>-Dlibcurl={ auto | enabled | disabled }</option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Build with libcurl support for OAuth 2.0 client flows.
|
||||
Libcurl version 7.61.0 or later is required for this feature.
|
||||
Building with this will check for the required header files
|
||||
and libraries to make sure that your <productname>Curl</productname>
|
||||
installation is sufficient before proceeding. The default for this
|
||||
option is auto.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="configure-with-libxml-meson">
|
||||
<term><option>-Dlibxml={ auto | enabled | disabled }</option></term>
|
||||
<listitem>
|
||||
|
@ -1385,6 +1385,15 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>oauth</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The server must request an OAuth bearer token from the client.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>none</literal></term>
|
||||
<listitem>
|
||||
@ -2373,6 +2382,107 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-connect-oauth-issuer" xreflabel="oauth_issuer">
|
||||
<term><literal>oauth_issuer</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The HTTPS URL of a trusted issuer to contact if the server requests an
|
||||
OAuth token for the connection. This parameter is required for all OAuth
|
||||
connections; it should exactly match the <literal>issuer</literal>
|
||||
setting in <link linkend="auth-oauth">the server's HBA configuration</link>.
|
||||
</para>
|
||||
<para>
|
||||
As part of the standard authentication handshake, <application>libpq</application>
|
||||
will ask the server for a <emphasis>discovery document:</emphasis> a URL
|
||||
providing a set of OAuth configuration parameters. The server must
|
||||
provide a URL that is directly constructed from the components of the
|
||||
<literal>oauth_issuer</literal>, and this value must exactly match the
|
||||
issuer identifier that is declared in the discovery document itself, or
|
||||
the connection will fail. This is required to prevent a class of
|
||||
<ulink url="https://mailarchive.ietf.org/arch/msg/oauth/JIVxFBGsJBVtm7ljwJhPUm3Fr-w/">
|
||||
"mix-up attacks"</ulink> on OAuth clients.
|
||||
</para>
|
||||
<para>
|
||||
You may also explicitly set <literal>oauth_issuer</literal> to the
|
||||
<literal>/.well-known/</literal> URI used for OAuth discovery. In this
|
||||
case, if the server asks for a different URL, the connection will fail,
|
||||
but a <link linkend="libpq-oauth-authdata-hooks">custom OAuth flow</link>
|
||||
may be able to speed up the standard handshake by using previously
|
||||
cached tokens. (In this case, it is recommended that
|
||||
<xref linkend="libpq-connect-oauth-scope"/> be set as well, since the
|
||||
client will not have a chance to ask the server for a correct scope
|
||||
setting, and the default scopes for a token may not be sufficient to
|
||||
connect.) <application>libpq</application> currently supports the
|
||||
following well-known endpoints:
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem><para><literal>/.well-known/openid-configuration</literal></para></listitem>
|
||||
<listitem><para><literal>/.well-known/oauth-authorization-server</literal></para></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<warning>
|
||||
<para>
|
||||
Issuers are highly privileged during the OAuth connection handshake. As
|
||||
a rule of thumb, if you would not trust the operator of a URL to handle
|
||||
access to your servers, or to impersonate you directly, that URL should
|
||||
not be trusted as an <literal>oauth_issuer</literal>.
|
||||
</para>
|
||||
</warning>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-connect-oauth-client-id" xreflabel="oauth_client_id">
|
||||
<term><literal>oauth_client_id</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
An OAuth 2.0 client identifier, as issued by the authorization server.
|
||||
If the <productname>PostgreSQL</productname> server
|
||||
<link linkend="auth-oauth">requests an OAuth token</link> for the
|
||||
connection (and if no <link linkend="libpq-oauth-authdata-hooks">custom
|
||||
OAuth hook</link> is installed to provide one), then this parameter must
|
||||
be set; otherwise, the connection will fail.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-connect-oauth-client-secret" xreflabel="oauth_client_secret">
|
||||
<term><literal>oauth_client_secret</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The client password, if any, to use when contacting the OAuth
|
||||
authorization server. Whether this parameter is required or not is
|
||||
determined by the OAuth provider; "public" clients generally do not use
|
||||
a secret, whereas "confidential" clients generally do.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-connect-oauth-scope" xreflabel="oauth_scope">
|
||||
<term><literal>oauth_scope</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The scope of the access request sent to the authorization server,
|
||||
specified as a (possibly empty) space-separated list of OAuth scope
|
||||
identifiers. This parameter is optional and intended for advanced usage.
|
||||
</para>
|
||||
<para>
|
||||
Usually the client will obtain appropriate scope settings from the
|
||||
<productname>PostgreSQL</productname> server. If this parameter is used,
|
||||
the server's requested scope list will be ignored. This can prevent a
|
||||
less-trusted server from requesting inappropriate access scopes from the
|
||||
end user. However, if the client's scope setting does not contain the
|
||||
server's required scopes, the server is likely to reject the issued
|
||||
token, and the connection will fail.
|
||||
</para>
|
||||
<para>
|
||||
The meaning of an empty scope list is provider-dependent. An OAuth
|
||||
authorization server may choose to issue a token with "default scope",
|
||||
whatever that happens to be, or it may reject the token request
|
||||
entirely.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</para>
|
||||
</sect2>
|
||||
@ -10020,6 +10130,329 @@ void PQinitSSL(int do_ssl);
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="libpq-oauth">
|
||||
<title>OAuth Support</title>
|
||||
|
||||
<para>
|
||||
libpq implements support for the OAuth v2 Device Authorization client flow,
|
||||
documented in
|
||||
<ulink url="https://datatracker.ietf.org/doc/html/rfc8628">RFC 8628</ulink>,
|
||||
which it will attempt to use by default if the server
|
||||
<link linkend="auth-oauth">requests a bearer token</link> 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 <application>SSH</application>. Client applications may implement their own flows
|
||||
instead; see <xref linkend="libpq-oauth-authdata-hooks"/>.
|
||||
</para>
|
||||
<para>
|
||||
The builtin flow will, by default, print a URL to visit and a user code to
|
||||
enter there:
|
||||
<programlisting>
|
||||
$ psql 'dbname=postgres oauth_issuer=https://example.com oauth_client_id=...'
|
||||
Visit https://example.com/device and enter the code: ABCD-EFGH
|
||||
</programlisting>
|
||||
(This prompt may be
|
||||
<link linkend="libpq-oauth-authdata-prompt-oauth-device">customized</link>.)
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
For an OAuth client flow to be usable, the connection string must at minimum
|
||||
contain <xref linkend="libpq-connect-oauth-issuer"/> and
|
||||
<xref linkend="libpq-connect-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.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
The builtin Device Authorization flow is not currently supported on Windows.
|
||||
Custom client flows may still be implemented.
|
||||
</para>
|
||||
</note>
|
||||
|
||||
<sect2 id="libpq-oauth-authdata-hooks">
|
||||
<title>Authdata Hooks</title>
|
||||
|
||||
<para>
|
||||
The behavior of the OAuth flow may be modified or replaced by a client using
|
||||
the following hook API:
|
||||
|
||||
<variablelist>
|
||||
<varlistentry id="libpq-PQsetAuthDataHook">
|
||||
<term><function>PQsetAuthDataHook</function><indexterm><primary>PQsetAuthDataHook</primary></indexterm></term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Sets the <symbol>PGauthDataHook</symbol>, overriding
|
||||
<application>libpq</application>'s handling of one or more aspects of
|
||||
its OAuth client flow.
|
||||
<synopsis>
|
||||
void PQsetAuthDataHook(PQauthDataHook_type hook);
|
||||
</synopsis>
|
||||
If <replaceable>hook</replaceable> is <literal>NULL</literal>, the
|
||||
default handler will be reinstalled. Otherwise, the application passes
|
||||
a pointer to a callback function with the signature:
|
||||
<programlisting>
|
||||
int hook_fn(PGauthData type, PGconn *conn, void *data);
|
||||
</programlisting>
|
||||
which <application>libpq</application> will call when an action is
|
||||
required of the application. <replaceable>type</replaceable> describes
|
||||
the request being made, <replaceable>conn</replaceable> is the
|
||||
connection handle being authenticated, and <replaceable>data</replaceable>
|
||||
points to request-specific metadata. The contents of this pointer are
|
||||
determined by <replaceable>type</replaceable>; see
|
||||
<xref linkend="libpq-oauth-authdata-hooks-types"/> for the supported
|
||||
list.
|
||||
</para>
|
||||
<para>
|
||||
Hooks can be chained together to allow cooperative and/or fallback
|
||||
behavior. In general, a hook implementation should examine the incoming
|
||||
<replaceable>type</replaceable> (and, potentially, the request metadata
|
||||
and/or the settings for the particular <replaceable>conn</replaceable>
|
||||
in 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 via <function>PQgetAuthDataHook</function>).
|
||||
</para>
|
||||
<para>
|
||||
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.)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-PQgetAuthDataHook">
|
||||
<term><function>PQgetAuthDataHook</function><indexterm><primary>PQgetAuthDataHook</primary></indexterm></term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Retrieves the current value of <symbol>PGauthDataHook</symbol>.
|
||||
<synopsis>
|
||||
PQauthDataHook_type PQgetAuthDataHook(void);
|
||||
</synopsis>
|
||||
At initialization time (before the first call to
|
||||
<function>PQsetAuthDataHook</function>), this function will return
|
||||
<symbol>PQdefaultAuthDataHook</symbol>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
|
||||
<sect3 id="libpq-oauth-authdata-hooks-types">
|
||||
<title>Hook Types</title>
|
||||
<para>
|
||||
The following <symbol>PGauthData</symbol> types and their corresponding
|
||||
<replaceable>data</replaceable> structures are defined:
|
||||
|
||||
<variablelist>
|
||||
<varlistentry id="libpq-oauth-authdata-prompt-oauth-device">
|
||||
<term>
|
||||
<symbol>PQAUTHDATA_PROMPT_OAUTH_DEVICE</symbol>
|
||||
<indexterm><primary>PQAUTHDATA_PROMPT_OAUTH_DEVICE</primary></indexterm>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Replaces the default user prompt during the builtin device
|
||||
authorization client flow. <replaceable>data</replaceable> points to
|
||||
an instance of <symbol>PGpromptOAuthDevice</symbol>:
|
||||
<synopsis>
|
||||
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;
|
||||
</synopsis>
|
||||
</para>
|
||||
<para>
|
||||
The OAuth Device Authorization flow included in <application>libpq</application>
|
||||
requires the end user to visit a URL with a browser, then enter a code
|
||||
which permits <application>libpq</application> to connect to the server
|
||||
on their behalf. The default prompt simply prints the
|
||||
<literal>verification_uri</literal> and <literal>user_code</literal>
|
||||
on standard error. Replacement implementations may display this
|
||||
information using any preferred method, for example with a GUI.
|
||||
</para>
|
||||
<para>
|
||||
This callback is only invoked during the builtin device
|
||||
authorization flow. If the application installs a
|
||||
<link linkend="libpq-oauth-authdata-oauth-bearer-token">custom OAuth
|
||||
flow</link>, this authdata type will not be used.
|
||||
</para>
|
||||
<para>
|
||||
If a non-NULL <structfield>verification_uri_complete</structfield> is
|
||||
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
|
||||
<ulink url="https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1">RFC 8628</ulink>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-oauth-authdata-oauth-bearer-token">
|
||||
<term>
|
||||
<symbol>PQAUTHDATA_OAUTH_BEARER_TOKEN</symbol>
|
||||
<indexterm><primary>PQAUTHDATA_OAUTH_BEARER_TOKEN</primary></indexterm>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Replaces the entire OAuth flow with a custom implementation. 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.
|
||||
</para>
|
||||
<para>
|
||||
<replaceable>data</replaceable> points to an instance
|
||||
of <symbol>PGoauthBearerRequest</symbol>, which should be filled in
|
||||
by the implementation:
|
||||
<synopsis>
|
||||
typedef struct _PGoauthBearerRequest
|
||||
{
|
||||
/* Hook inputs (constant across all calls) */
|
||||
const char *const openid_configuration; /* OIDC discovery URL */
|
||||
const char *const 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;
|
||||
</synopsis>
|
||||
</para>
|
||||
<para>
|
||||
Two pieces of information are provided to the hook by
|
||||
<application>libpq</application>:
|
||||
<replaceable>openid_configuration</replaceable> contains the URL of an
|
||||
OAuth discovery document describing the authorization server's
|
||||
supported flows, and <replaceable>scope</replaceable> contains a
|
||||
(possibly empty) space-separated list of OAuth scopes which are
|
||||
required to access the server. Either or both may be
|
||||
<literal>NULL</literal> to 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.)
|
||||
</para>
|
||||
<para>
|
||||
The final output of the hook is <replaceable>token</replaceable>, which
|
||||
must point to a valid Bearer token for use on the connection. (This
|
||||
token should be issued by the
|
||||
<xref linkend="libpq-connect-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
|
||||
<application>libpq</application> is finished connecting; the hook
|
||||
should set a <replaceable>cleanup</replaceable> callback which will be
|
||||
called when <application>libpq</application> no longer requires it.
|
||||
</para>
|
||||
<para>
|
||||
If an implementation cannot immediately produce a
|
||||
<replaceable>token</replaceable> during the initial call to the hook,
|
||||
it should set the <replaceable>async</replaceable> callback to handle
|
||||
nonblocking communication with the authorization server.
|
||||
<footnote>
|
||||
<para>
|
||||
Performing blocking operations during the
|
||||
<symbol>PQAUTHDATA_OAUTH_BEARER_TOKEN</symbol> hook callback will
|
||||
interfere with nonblocking connection APIs such as
|
||||
<function>PQconnectPoll</function> and prevent concurrent connections
|
||||
from making progress. Applications which only ever use the
|
||||
synchronous connection primitives, such as
|
||||
<function>PQconnectdb</function>, may synchronously retrieve a token
|
||||
during the hook instead of implementing the
|
||||
<replaceable>async</replaceable> callback, but they will necessarily
|
||||
be limited to one connection at a time.
|
||||
</para>
|
||||
</footnote>
|
||||
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 either <symbol>PGRES_POLLING_READING</symbol> or
|
||||
<symbol>PGRES_POLLING_WRITING</symbol> after setting
|
||||
<literal>*pgsocket</literal> to 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 via
|
||||
<function>PQsocket()</function>.) Return <symbol>PGRES_POLLING_OK</symbol>
|
||||
after setting <replaceable>token</replaceable> when the flow is
|
||||
complete, or <symbol>PGRES_POLLING_FAILED</symbol> to indicate failure.
|
||||
</para>
|
||||
<para>
|
||||
Implementations may wish to store additional data for bookkeeping
|
||||
across calls to the <replaceable>async</replaceable> and
|
||||
<replaceable>cleanup</replaceable> callbacks. The
|
||||
<replaceable>user</replaceable> pointer is provided for this purpose;
|
||||
<application>libpq</application> will not touch its contents and the
|
||||
application may use it at its convenience. (Remember to free any
|
||||
allocations during token cleanup.)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
</sect3>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="libpq-oauth-debugging">
|
||||
<title>Debugging and Developer Settings</title>
|
||||
|
||||
<para>
|
||||
A "dangerous debugging mode" may be enabled by setting the environment
|
||||
variable <envar>PGOAUTHDEBUG=UNSAFE</envar>. 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:
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
permits the use of unencrypted HTTP during the OAuth provider exchange
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
allows the system's trusted CA list to be completely replaced using the
|
||||
<envar>PGOAUTHCAFILE</envar> environment variable
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
prints HTTP traffic (containing several critical secrets) to standard
|
||||
error during the OAuth flow
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
permits the use of zero-second retry intervals, which can cause the
|
||||
client to busy-loop and pointlessly consume CPU
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<warning>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
</warning>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
|
||||
<sect1 id="libpq-threading">
|
||||
<title>Behavior in Threaded Programs</title>
|
||||
@ -10092,6 +10525,18 @@ int PQisthreadsafe();
|
||||
<application>libpq</application> source code for a way to do cooperative
|
||||
locking between <application>libpq</application> and your application.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Similarly, if you are using <productname>Curl</productname> inside your application,
|
||||
<emphasis>and</emphasis> you do not already
|
||||
<ulink url="https://curl.se/libcurl/c/curl_global_init.html">initialize
|
||||
libcurl globally</ulink> before starting new threads, you will need to
|
||||
cooperatively lock (again via <function>PQregisterThreadLock</function>)
|
||||
around any code that may initialize libcurl. This restriction is lifted for
|
||||
more recent versions of <productname>Curl</productname> that are built to support thread-safe
|
||||
initialization; those builds can be identified by the advertisement of a
|
||||
<literal>threadsafe</literal> feature in their version metadata.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
|
||||
|
414
doc/src/sgml/oauth-validators.sgml
Normal file
414
doc/src/sgml/oauth-validators.sgml
Normal file
@ -0,0 +1,414 @@
|
||||
<!-- doc/src/sgml/oauth-validators.sgml -->
|
||||
|
||||
<chapter id="oauth-validators">
|
||||
<title>OAuth Validator Modules</title>
|
||||
<indexterm zone="oauth-validators">
|
||||
<primary>OAuth Validators</primary>
|
||||
</indexterm>
|
||||
<para>
|
||||
<productname>PostgreSQL</productname> provides infrastructure for creating
|
||||
custom modules to perform server-side validation of OAuth bearer tokens.
|
||||
Because OAuth implementations vary so wildly, and bearer token validation is
|
||||
heavily dependent on the issuing party, the server cannot check the token
|
||||
itself; validator modules provide the integration layer between the server
|
||||
and the OAuth provider in use.
|
||||
</para>
|
||||
<para>
|
||||
OAuth validator modules must at least consist of an initialization function
|
||||
(see <xref linkend="oauth-validator-init"/>) and the required callback for
|
||||
performing validation (see <xref linkend="oauth-validator-callback-validate"/>).
|
||||
</para>
|
||||
<warning>
|
||||
<para>
|
||||
Since a misbehaving validator might let unauthorized users into the database,
|
||||
correct implementation is crucial for server safety. See
|
||||
<xref linkend="oauth-validator-design"/> for design considerations.
|
||||
</para>
|
||||
</warning>
|
||||
|
||||
<sect1 id="oauth-validator-design">
|
||||
<title>Safely Designing a Validator Module</title>
|
||||
<warning>
|
||||
<para>
|
||||
Read and understand the entirety of this section before implementing a
|
||||
validator module. A malfunctioning validator is potentially worse than no
|
||||
authentication at all, both because of the false sense of security it
|
||||
provides, and because it may contribute to attacks against other pieces of
|
||||
an OAuth ecosystem.
|
||||
</para>
|
||||
</warning>
|
||||
|
||||
<sect2 id="oauth-validator-design-responsibilities">
|
||||
<title>Validator Responsibilities</title>
|
||||
<para>
|
||||
Although different modules may take very different approaches to token
|
||||
validation, implementations generally need to perform three separate
|
||||
actions:
|
||||
</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>Validate the Token</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The validator must first ensure that the presented token is in fact a
|
||||
valid Bearer token for use in client authentication. The correct way to
|
||||
do this depends on the provider, but it generally involves either
|
||||
cryptographic operations to prove that the token was created by a trusted
|
||||
party (offline validation), or the presentation of the token to that
|
||||
trusted party so that it can perform validation for you (online
|
||||
validation).
|
||||
</para>
|
||||
<para>
|
||||
Online validation, usually implemented via
|
||||
<ulink url="https://datatracker.ietf.org/doc/html/rfc7662">OAuth Token
|
||||
Introspection</ulink>, requires fewer steps of a validator module and
|
||||
allows central revocation of a token in the event that it is stolen
|
||||
or misissued. However, it does require the module to make at least one
|
||||
network call per authentication attempt (all of which must complete
|
||||
within the configured <xref linkend="guc-authentication-timeout"/>).
|
||||
Additionally, your provider may not provide introspection endpoints for
|
||||
use by external resource servers.
|
||||
</para>
|
||||
<para>
|
||||
Offline validation is much more involved, typically requiring a validator
|
||||
to maintain a list of trusted signing keys for a provider and then
|
||||
check the token's cryptographic signature along with its contents.
|
||||
Implementations must follow the provider's instructions to the letter,
|
||||
including any verification of issuer ("where is this token from?"),
|
||||
audience ("who is this token for?"), and validity period ("when can this
|
||||
token be used?"). Since there is no communication between the module and
|
||||
the provider, tokens cannot be centrally revoked using this method;
|
||||
offline validator implementations may wish to place restrictions on the
|
||||
maximum length of a token's validity period.
|
||||
</para>
|
||||
<para>
|
||||
If the token cannot be validated, the module should immediately fail.
|
||||
Further authentication/authorization is pointless if the bearer token
|
||||
wasn't issued by a trusted party.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Authorize the Client</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Next the validator must ensure that the end user has given the client
|
||||
permission to access the server on their behalf. This generally involves
|
||||
checking the scopes that have been assigned to the token, to make sure
|
||||
that they cover database access for the current HBA parameters.
|
||||
</para>
|
||||
<para>
|
||||
The purpose of this step is to prevent an OAuth client from obtaining a
|
||||
token under false pretenses. If the validator requires all tokens to
|
||||
carry scopes that cover database access, the provider should then loudly
|
||||
prompt the user to grant that access during the flow. This gives them the
|
||||
opportunity to reject the request if the client isn't supposed to be
|
||||
using their credentials to connect to databases.
|
||||
</para>
|
||||
<para>
|
||||
While it is possible to establish client authorization without explicit
|
||||
scopes by using out-of-band knowledge of the deployed architecture, doing
|
||||
so removes the user from the loop, which prevents them from catching
|
||||
deployment mistakes and allows any such mistakes to be exploited
|
||||
silently. Access to the database must be tightly restricted to only
|
||||
trusted clients
|
||||
<footnote>
|
||||
<para>
|
||||
That is, "trusted" in the sense that the OAuth client and the
|
||||
<productname>PostgreSQL</productname> server are controlled by the same
|
||||
entity. Notably, the Device Authorization client flow supported by
|
||||
libpq does not usually meet this bar, since it's designed for use by
|
||||
public/untrusted clients.
|
||||
</para>
|
||||
</footnote>
|
||||
if users are not prompted for additional scopes.
|
||||
</para>
|
||||
<para>
|
||||
Even if authorization fails, a module may choose to continue to pull
|
||||
authentication information from the token for use in auditing and
|
||||
debugging.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Authenticate the End User</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Finally, the validator should determine a user identifier for the token,
|
||||
either by asking the provider for this information or by extracting it
|
||||
from the token itself, and return that identifier to the server (which
|
||||
will then make a final authorization decision using the HBA
|
||||
configuration). This identifier will be available within the session via
|
||||
<link linkend="functions-info-session-table"><function>system_user</function></link>
|
||||
and recorded in the server logs if <xref linkend="guc-log-connections"/>
|
||||
is enabled.
|
||||
</para>
|
||||
<para>
|
||||
Different providers may record a variety of different authentication
|
||||
information for an end user, typically referred to as
|
||||
<emphasis>claims</emphasis>. Providers usually document which of these
|
||||
claims are trustworthy enough to use for authorization decisions and
|
||||
which are not. (For instance, it would probably not be wise to use an
|
||||
end user's full name as the identifier for authentication, since many
|
||||
providers allow users to change their display names arbitrarily.)
|
||||
Ultimately, the choice of which claim (or combination of claims) to use
|
||||
comes down to the provider implementation and application requirements.
|
||||
</para>
|
||||
<para>
|
||||
Note that anonymous/pseudonymous login is possible as well, by enabling
|
||||
usermap delegation; see
|
||||
<xref linkend="oauth-validator-design-usermap-delegation"/>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="oauth-validator-design-guidelines">
|
||||
<title>General Coding Guidelines</title>
|
||||
<para>
|
||||
Developers should keep the following in mind when implementing token
|
||||
validation:
|
||||
</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>Token Confidentiality</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Modules should not write tokens, or pieces of tokens, into the server
|
||||
log. This is true even if the module considers the token invalid; an
|
||||
attacker who confuses a client into communicating with the wrong provider
|
||||
should not be able to retrieve that (otherwise valid) token from the
|
||||
disk.
|
||||
</para>
|
||||
<para>
|
||||
Implementations that send tokens over the network (for example, to
|
||||
perform online token validation with a provider) must authenticate the
|
||||
peer and ensure that strong transport security is in use.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Logging</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Modules may use the same <link linkend="error-message-reporting">logging
|
||||
facilities</link> as standard extensions; however, the rules for emitting
|
||||
log entries to the client are subtly different during the authentication
|
||||
phase of the connection. Generally speaking, modules should log
|
||||
verification problems at the <symbol>COMMERROR</symbol> level and return
|
||||
normally, instead of using <symbol>ERROR</symbol>/<symbol>FATAL</symbol>
|
||||
to unwind the stack, to avoid leaking information to unauthenticated
|
||||
clients.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Interruptibility</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Modules must remain interruptible by signals so that the server can
|
||||
correctly handle authentication timeouts and shutdown signals from
|
||||
<application>pg_ctl</application>. For example, a module receiving
|
||||
<symbol>EINTR</symbol>/<symbol>EAGAIN</symbol> from a blocking call
|
||||
should call <function>CHECK_FOR_INTERRUPTS()</function> before retrying.
|
||||
The same should be done during any long-running loops. Failure to follow
|
||||
this guidance may result in unresponsive backend sessions.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Testing</term>
|
||||
<listitem>
|
||||
<para>
|
||||
The breadth of testing an OAuth system is well beyond the scope of this
|
||||
documentation, but at minimum, negative testing should be considered
|
||||
mandatory. It's trivial to design a module that lets authorized users in;
|
||||
the whole point of the system is to keep unauthorized users out.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Documentation</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Validator implementations should document the contents and format of the
|
||||
authenticated ID that is reported to the server for each end user, since
|
||||
DBAs may need to use this information to construct pg_ident maps. (For
|
||||
instance, is it an email address? an organizational ID number? a UUID?)
|
||||
They should also document whether or not it is safe to use the module in
|
||||
<symbol>delegate_ident_mapping=1</symbol> mode, and what additional
|
||||
configuration is required in order to do so.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="oauth-validator-design-usermap-delegation">
|
||||
<title>Authorizing Users (Usermap Delegation)</title>
|
||||
<para>
|
||||
The standard deliverable of a validation module is the user identifier,
|
||||
which the server will then compare to any configured
|
||||
<link linkend="auth-username-maps"><filename>pg_ident.conf</filename>
|
||||
mappings</link> and determine whether the end user is authorized to connect.
|
||||
However, OAuth is itself an authorization framework, and tokens may carry
|
||||
information about user privileges. For example, a token may be associated
|
||||
with the organizational groups that a user belongs to, or list the roles
|
||||
that a user may assume, and duplicating that knowledge into local usermaps
|
||||
for every server may not be desirable.
|
||||
</para>
|
||||
<para>
|
||||
To bypass username mapping entirely, and have the validator module assume
|
||||
the additional responsibility of authorizing user connections, the HBA may
|
||||
be configured with <xref linkend="auth-oauth-delegate-ident-mapping"/>.
|
||||
The module may then use token scopes or an equivalent method to decide
|
||||
whether the user is allowed to connect under their desired role. The user
|
||||
identifier will still be recorded by the server, but it plays no part in
|
||||
determining whether to continue the connection.
|
||||
</para>
|
||||
<para>
|
||||
Using this scheme, authentication itself is optional. As long as the module
|
||||
reports that the connection is authorized, login will continue even if there
|
||||
is no recorded user identifier at all. This makes it possible to implement
|
||||
anonymous or pseudonymous access to the database, where the third-party
|
||||
provider performs all necessary authentication but does not provide any
|
||||
user-identifying information to the server. (Some providers may create an
|
||||
anonymized ID number that can be recorded instead, for later auditing.)
|
||||
</para>
|
||||
<para>
|
||||
Usermap delegation provides the most architectural flexibility, but it turns
|
||||
the validator module into a single point of failure for connection
|
||||
authorization. Use with caution.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="oauth-validator-init">
|
||||
<title>Initialization Functions</title>
|
||||
<indexterm zone="oauth-validator-init">
|
||||
<primary>_PG_oauth_validator_module_init</primary>
|
||||
</indexterm>
|
||||
<para>
|
||||
OAuth validator modules are dynamically loaded from the shared
|
||||
libraries listed in <xref linkend="guc-oauth-validator-libraries"/>.
|
||||
Modules are loaded on demand when requested from a login in progress.
|
||||
The normal library search path is used to locate the library. To
|
||||
provide the validator callbacks and to indicate that the library is an OAuth
|
||||
validator module a function named
|
||||
<function>_PG_oauth_validator_module_init</function> must be provided. The
|
||||
return value of the function must be a pointer to a struct of type
|
||||
<structname>OAuthValidatorCallbacks</structname>, which contains a magic
|
||||
number and pointers to the module's token validation functions. The returned
|
||||
pointer must be of server lifetime, which is typically achieved by defining
|
||||
it as a <literal>static const</literal> variable in global scope.
|
||||
<programlisting>
|
||||
typedef struct OAuthValidatorCallbacks
|
||||
{
|
||||
uint32 magic; /* must be set to PG_OAUTH_VALIDATOR_MAGIC */
|
||||
|
||||
ValidatorStartupCB startup_cb;
|
||||
ValidatorShutdownCB shutdown_cb;
|
||||
ValidatorValidateCB validate_cb;
|
||||
} OAuthValidatorCallbacks;
|
||||
|
||||
typedef const OAuthValidatorCallbacks *(*OAuthValidatorModuleInit) (void);
|
||||
</programlisting>
|
||||
|
||||
Only the <function>validate_cb</function> callback is required, the others
|
||||
are optional.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="oauth-validator-callbacks">
|
||||
<title>OAuth Validator Callbacks</title>
|
||||
<para>
|
||||
OAuth validator modules implement their functionality by defining a set of
|
||||
callbacks. The server will call them as required to process the
|
||||
authentication request from the user.
|
||||
</para>
|
||||
|
||||
<sect2 id="oauth-validator-callback-startup">
|
||||
<title>Startup Callback</title>
|
||||
<para>
|
||||
The <function>startup_cb</function> callback is executed directly after
|
||||
loading the module. This callback can be used to set up local state and
|
||||
perform additional initialization if required. If the validator module
|
||||
has state it can use <structfield>state->private_data</structfield> to
|
||||
store it.
|
||||
|
||||
<programlisting>
|
||||
typedef void (*ValidatorStartupCB) (ValidatorModuleState *state);
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="oauth-validator-callback-validate">
|
||||
<title>Validate Callback</title>
|
||||
<para>
|
||||
The <function>validate_cb</function> callback is executed during the OAuth
|
||||
exchange when a user attempts to authenticate using OAuth. Any state set in
|
||||
previous calls will be available in <structfield>state->private_data</structfield>.
|
||||
|
||||
<programlisting>
|
||||
typedef bool (*ValidatorValidateCB) (const ValidatorModuleState *state,
|
||||
const char *token, const char *role,
|
||||
ValidatorModuleResult *result);
|
||||
</programlisting>
|
||||
|
||||
<replaceable>token</replaceable> will contain the bearer token to validate.
|
||||
<application>PostgreSQL</application> has ensured that the token is well-formed syntactically, but no
|
||||
other validation has been performed. <replaceable>role</replaceable> will
|
||||
contain the role the user has requested to log in as. The callback must
|
||||
set output parameters in the <literal>result</literal> struct, which is
|
||||
defined as below:
|
||||
|
||||
<programlisting>
|
||||
typedef struct ValidatorModuleResult
|
||||
{
|
||||
bool authorized;
|
||||
char *authn_id;
|
||||
} ValidatorModuleResult;
|
||||
</programlisting>
|
||||
|
||||
The connection will only proceed if the module sets
|
||||
<structfield>result->authorized</structfield> to <literal>true</literal>. To
|
||||
authenticate the user, the authenticated user name (as determined using the
|
||||
token) shall be palloc'd and returned in the <structfield>result->authn_id</structfield>
|
||||
field. Alternatively, <structfield>result->authn_id</structfield> may be set to
|
||||
NULL if the token is valid but the associated user identity cannot be
|
||||
determined.
|
||||
</para>
|
||||
<para>
|
||||
A validator may return <literal>false</literal> to signal an internal error,
|
||||
in which case any result parameters are ignored and the connection fails.
|
||||
Otherwise the validator should return <literal>true</literal> to indicate
|
||||
that it has processed the token and made an authorization decision.
|
||||
</para>
|
||||
<para>
|
||||
The behavior after <function>validate_cb</function> returns depends on the
|
||||
specific HBA setup. Normally, the <structfield>result->authn_id</structfield> user
|
||||
name must exactly match the role that the user is logging in as. (This
|
||||
behavior may be modified with a usermap.) But when authenticating against
|
||||
an HBA rule with <literal>delegate_ident_mapping</literal> turned on,
|
||||
<productname>PostgreSQL</productname> will not perform any checks on the value of
|
||||
<structfield>result->authn_id</structfield> at all; in this case it is up to the
|
||||
validator to ensure that the token carries enough privileges for the user to
|
||||
log in under the indicated <replaceable>role</replaceable>.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="oauth-validator-callback-shutdown">
|
||||
<title>Shutdown Callback</title>
|
||||
<para>
|
||||
The <function>shutdown_cb</function> callback is executed when the backend
|
||||
process associated with the connection exits. If the validator module has
|
||||
any allocated state, this callback should free it to avoid resource leaks.
|
||||
<programlisting>
|
||||
typedef void (*ValidatorShutdownCB) (ValidatorModuleState *state);
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
</chapter>
|
@ -229,6 +229,7 @@ break is not needed in a wider output rendering.
|
||||
&logicaldecoding;
|
||||
&replication-origins;
|
||||
&archive-modules;
|
||||
&oauth-validators;
|
||||
|
||||
</part>
|
||||
|
||||
|
@ -1688,11 +1688,11 @@ SELCT 1/0;<!-- this typo is intentional -->
|
||||
|
||||
<para>
|
||||
<firstterm>SASL</firstterm> is a framework for authentication in connection-oriented
|
||||
protocols. At the moment, <productname>PostgreSQL</productname> implements two SASL
|
||||
authentication mechanisms, SCRAM-SHA-256 and SCRAM-SHA-256-PLUS. More
|
||||
might be added in the future. The below steps illustrate how SASL
|
||||
authentication is performed in general, while the next subsection gives
|
||||
more details on SCRAM-SHA-256 and SCRAM-SHA-256-PLUS.
|
||||
protocols. At the moment, <productname>PostgreSQL</productname> implements three
|
||||
SASL authentication mechanisms: SCRAM-SHA-256, SCRAM-SHA-256-PLUS, and
|
||||
OAUTHBEARER. More might be added in the future. The below steps illustrate how SASL
|
||||
authentication is performed in general, while the next subsections give
|
||||
more details on particular mechanisms.
|
||||
</para>
|
||||
|
||||
<procedure>
|
||||
@ -1727,7 +1727,7 @@ SELCT 1/0;<!-- this typo is intentional -->
|
||||
<step id="sasl-auth-end">
|
||||
<para>
|
||||
Finally, when the authentication exchange is completed successfully, the
|
||||
server sends an AuthenticationSASLFinal message, followed
|
||||
server sends an optional AuthenticationSASLFinal message, followed
|
||||
immediately by an AuthenticationOk message. The AuthenticationSASLFinal
|
||||
contains additional server-to-client data, whose content is particular to the
|
||||
selected authentication mechanism. If the authentication mechanism doesn't
|
||||
@ -1746,9 +1746,9 @@ SELCT 1/0;<!-- this typo is intentional -->
|
||||
<title>SCRAM-SHA-256 Authentication</title>
|
||||
|
||||
<para>
|
||||
The implemented SASL mechanisms at the moment
|
||||
are <literal>SCRAM-SHA-256</literal> and its variant with channel
|
||||
binding <literal>SCRAM-SHA-256-PLUS</literal>. They are described in
|
||||
<literal>SCRAM-SHA-256</literal>, and its variant with channel
|
||||
binding <literal>SCRAM-SHA-256-PLUS</literal>, are password-based
|
||||
authentication mechanisms. They are described in
|
||||
detail in <ulink url="https://datatracker.ietf.org/doc/html/rfc7677">RFC 7677</ulink>
|
||||
and <ulink url="https://datatracker.ietf.org/doc/html/rfc5802">RFC 5802</ulink>.
|
||||
</para>
|
||||
@ -1850,6 +1850,121 @@ SELCT 1/0;<!-- this typo is intentional -->
|
||||
</step>
|
||||
</procedure>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="sasl-oauthbearer">
|
||||
<title>OAUTHBEARER Authentication</title>
|
||||
|
||||
<para>
|
||||
<literal>OAUTHBEARER</literal> is a token-based mechanism for federated
|
||||
authentication. It is described in detail in
|
||||
<ulink url="https://datatracker.ietf.org/doc/html/rfc7628">RFC 7628</ulink>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A typical exchange differs depending on whether or not the client already
|
||||
has a bearer token cached for the current user. If it does not, the exchange
|
||||
will take place over two connections: the first "discovery" connection to
|
||||
obtain OAuth metadata from the server, and the second connection to send
|
||||
the token after the client has obtained it. (libpq does not currently
|
||||
implement a caching method as part of its builtin flow, so it uses the
|
||||
two-connection exchange.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This mechanism is client-initiated, like SCRAM. The client initial response
|
||||
consists of the standard "GS2" header used by SCRAM, followed by a list of
|
||||
<literal>key=value</literal> pairs. The only key currently supported by
|
||||
the server is <literal>auth</literal>, which contains the bearer token.
|
||||
<literal>OAUTHBEARER</literal> additionally specifies three optional
|
||||
components of the client initial response (the <literal>authzid</literal> of
|
||||
the GS2 header, and the <structfield>host</structfield> and
|
||||
<structfield>port</structfield> keys) which are currently ignored by the
|
||||
server.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<literal>OAUTHBEARER</literal> does not support channel binding, and there
|
||||
is no "OAUTHBEARER-PLUS" mechanism. This mechanism does not make use of
|
||||
server data during a successful authentication, so the
|
||||
AuthenticationSASLFinal message is not used in the exchange.
|
||||
</para>
|
||||
|
||||
<procedure>
|
||||
<title>Example</title>
|
||||
<step>
|
||||
<para>
|
||||
During the first exchange, the server sends an AuthenticationSASL message
|
||||
with the <literal>OAUTHBEARER</literal> mechanism advertised.
|
||||
</para>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
<para>
|
||||
The client responds by sending a SASLInitialResponse message which
|
||||
indicates the <literal>OAUTHBEARER</literal> mechanism. Assuming the
|
||||
client does not already have a valid bearer token for the current user,
|
||||
the <structfield>auth</structfield> field is empty, indicating a discovery
|
||||
connection.
|
||||
</para>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
<para>
|
||||
Server sends an AuthenticationSASLContinue message containing an error
|
||||
<literal>status</literal> alongside a well-known URI and scopes that the
|
||||
client should use to conduct an OAuth flow.
|
||||
</para>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
<para>
|
||||
Client sends a SASLResponse message containing the empty set (a single
|
||||
<literal>0x01</literal> byte) to finish its half of the discovery
|
||||
exchange.
|
||||
</para>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
<para>
|
||||
Server sends an ErrorMessage to fail the first exchange.
|
||||
</para>
|
||||
<para>
|
||||
At this point, the client conducts one of many possible OAuth flows to
|
||||
obtain a bearer token, using any metadata that it has been configured with
|
||||
in addition to that provided by the server. (This description is left
|
||||
deliberately vague; <literal>OAUTHBEARER</literal> does not specify or
|
||||
mandate any particular method for obtaining a token.)
|
||||
</para>
|
||||
<para>
|
||||
Once it has a token, the client reconnects to the server for the final
|
||||
exchange:
|
||||
</para>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
<para>
|
||||
The server once again sends an AuthenticationSASL message with the
|
||||
<literal>OAUTHBEARER</literal> mechanism advertised.
|
||||
</para>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
<para>
|
||||
The client responds by sending a SASLInitialResponse message, but this
|
||||
time the <structfield>auth</structfield> field in the message contains the
|
||||
bearer token that was obtained during the client flow.
|
||||
</para>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
<para>
|
||||
The server validates the token according to the instructions of the
|
||||
token provider. If the client is authorized to connect, it sends an
|
||||
AuthenticationOk message to end the SASL exchange.
|
||||
</para>
|
||||
</step>
|
||||
</procedure>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="protocol-replication">
|
||||
|
@ -347,6 +347,16 @@ make check-world PG_TEST_EXTRA='kerberos ldap ssl load_balance libpq_encryption'
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>oauth</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Runs the test suite under <filename>src/test/modules/oauth_validator</filename>.
|
||||
This opens TCP/IP listen sockets for a test-server running HTTPS.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
Tests for features that are not supported by the current build
|
||||
|
Reference in New Issue
Block a user