postgresql/src/interfaces/libpq-oauth/README
Jacob Champion b0635bfda0 oauth: Move the builtin flow into a separate module
The additional packaging footprint of the OAuth Curl dependency, as well
as the existence of libcurl in the address space even if OAuth isn't
ever used by a client, has raised some concerns. Split off this
dependency into a separate loadable module called libpq-oauth.

When configured using --with-libcurl, libpq.so searches for this new
module via dlopen(). End users may choose not to install the libpq-oauth
module, in which case the default flow is disabled.

For static applications using libpq.a, the libpq-oauth staticlib is a
mandatory link-time dependency for --with-libcurl builds. libpq.pc has
been updated accordingly.

The default flow relies on some libpq internals. Some of these can be
safely duplicated (such as the SIGPIPE handlers), but others need to be
shared between libpq and libpq-oauth for thread-safety. To avoid
exporting these internals to all libpq clients forever, these
dependencies are instead injected from the libpq side via an
initialization function. This also lets libpq communicate the offsets of
PGconn struct members to libpq-oauth, so that we can function without
crashing if the module on the search path came from a different build of
Postgres. (A minor-version upgrade could swap the libpq-oauth module out
from under a long-running libpq client before it does its first load of
the OAuth flow.)

This ABI is considered "private". The module has no SONAME or version
symlinks, and it's named libpq-oauth-<major>.so to avoid mixing and
matching across Postgres versions. (Future improvements may promote this
"OAuth flow plugin" to a first-class concept, at which point we would
need a public API to replace this anyway.)

Additionally, NLS support for error messages in b3f0be788a was
incomplete, because the new error macros weren't being scanned by
xgettext. Fix that now.

Per request from Tom Lane and Bruce Momjian. Based on an initial patch
by Daniel Gustafsson, who also contributed docs changes. The "bare"
dlopen() concept came from Thomas Munro. Many people reviewed the design
and implementation; thank you!

Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Christoph Berg <myon@debian.org>
Reviewed-by: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Jelte Fennema-Nio <postgres@jeltef.nl>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Reviewed-by: Wolfgang Walther <walther@technowledgy.de>
Discussion: https://postgr.es/m/641687.1742360249%40sss.pgh.pa.us
2025-05-01 09:14:30 -07:00

57 lines
2.8 KiB
Text

libpq-oauth is an optional module implementing the Device Authorization flow for
OAuth clients (RFC 8628). It is maintained as its own shared library in order to
isolate its dependency on libcurl. (End users who don't want the Curl dependency
can simply choose not to install this module.)
If a connection string allows the use of OAuth, and the server asks for it, and
a libpq client has not installed its own custom OAuth flow, libpq will attempt
to delay-load this module using dlopen() and the following ABI. Failure to load
results in a failed connection.
= Load-Time ABI =
This module ABI is an internal implementation detail, so it's subject to change
across major releases; the name of the module (libpq-oauth-MAJOR) reflects this.
The module exports the following symbols:
- PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn);
- void pg_fe_cleanup_oauth_flow(PGconn *conn);
pg_fe_run_oauth_flow and pg_fe_cleanup_oauth_flow are implementations of
conn->async_auth and conn->cleanup_async_auth, respectively.
At the moment, pg_fe_run_oauth_flow() relies on libpq's pg_g_threadlock and
libpq_gettext(), which must be injected by libpq using this initialization
function before the flow is run:
- void libpq_oauth_init(pgthreadlock_t threadlock,
libpq_gettext_func gettext_impl,
conn_errorMessage_func errmsg_impl,
conn_oauth_client_id_func clientid_impl,
conn_oauth_client_secret_func clientsecret_impl,
conn_oauth_discovery_uri_func discoveryuri_impl,
conn_oauth_issuer_id_func issuerid_impl,
conn_oauth_scope_func scope_impl,
conn_sasl_state_func saslstate_impl,
set_conn_altsock_func setaltsock_impl,
set_conn_oauth_token_func settoken_impl);
It also relies on access to several members of the PGconn struct. Not only can
these change positions across minor versions, but the offsets aren't necessarily
stable within a single minor release (conn->errorMessage, for instance, can
change offsets depending on configure-time options). Therefore the necessary
accessors (named conn_*) and mutators (set_conn_*) are injected here. With this
approach, we can safely search the standard dlopen() paths (e.g. RPATH,
LD_LIBRARY_PATH, the SO cache) for an implementation module to use, even if that
module wasn't compiled at the same time as libpq -- which becomes especially
important during "live upgrade" situations where a running libpq application has
the libpq-oauth module updated out from under it before it's first loaded from
disk.
= Static Build =
The static library libpq.a does not perform any dynamic loading. If the builtin
flow is enabled, the application is expected to link against libpq-oauth.a
directly to provide the necessary symbols. (libpq.a and libpq-oauth.a must be
part of the same build. Unlike the dynamic module, there are no translation
shims provided.)