v49-0002-v48-fixup-patches-Add-OAUTHBEARER-SASL-mechanism.patch
application/octet-stream
Filename: v49-0002-v48-fixup-patches-Add-OAUTHBEARER-SASL-mechanism.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v49-0002
Subject: v48 fixup patches! Add OAUTHBEARER SASL mechanism
| File | + | − |
|---|---|---|
| doc/src/sgml/libpq.sgml | 39 | 1 |
| doc/src/sgml/oauth-validators.sgml | 21 | 10 |
| src/backend/libpq/auth-oauth.c | 17 | 3 |
| src/include/libpq/oauth.h | 47 | 1 |
| src/interfaces/libpq/fe-auth-oauth-curl.c | 13 | 1 |
| src/test/modules/oauth_validator/fail_validator.c | 10 | 5 |
| src/test/modules/oauth_validator/t/001_server.pl | 10 | 1 |
| src/test/modules/oauth_validator/validator.c | 18 | 10 |
From e723c2040405237155eeca58cab675866763b876 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Fri, 7 Feb 2025 14:23:40 -0800
Subject: [PATCH v49 2/4] v48 fixup patches! Add OAUTHBEARER SASL mechanism
---
doc/src/sgml/libpq.sgml | 40 +++++++++++++++-
doc/src/sgml/oauth-validators.sgml | 31 ++++++++----
src/backend/libpq/auth-oauth.c | 20 ++++++--
src/include/libpq/oauth.h | 48 ++++++++++++++++++-
src/interfaces/libpq/fe-auth-oauth-curl.c | 14 +++++-
.../modules/oauth_validator/fail_validator.c | 15 ++++--
.../modules/oauth_validator/t/001_server.pl | 11 ++++-
src/test/modules/oauth_validator/validator.c | 28 +++++++----
8 files changed, 175 insertions(+), 32 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index a51355e238f..b2abae8deee 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -10133,8 +10133,46 @@ void PQinitSSL(int do_ssl);
<title>OAuth Support</title>
<para>
- TODO
+ 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 SSH. 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>.)
+ You will then log into your OAuth provider, which will ask whether you want
+ to allow libpq and the server to perform actions on your behalf. It is always
+ a good idea to carefully review the URL and permissions displayed, to ensure
+ they match your expectations, before continuing. Do not give permissions 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>
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index d0bca9196d9..eb8c4431c2d 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -41,7 +41,9 @@
<sect2 id="oauth-validator-design-responsibilities">
<title>Validator Responsibilities</title>
<para>
- TODO
+ Although different modules may take very different approaches to token
+ validation, implementations generally need to perform three separate
+ actions:
</para>
<variablelist>
<varlistentry>
@@ -121,6 +123,11 @@
</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>
@@ -290,13 +297,15 @@
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 pointers to
- the module's token validation functions. The returned
+ <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;
@@ -341,14 +350,16 @@ typedef void (*ValidatorStartupCB) (ValidatorModuleState *state);
previous calls will be available in <structfield>state->private_data</structfield>.
<programlisting>
-typedef ValidatorModuleResult *(*ValidatorValidateCB) (ValidatorModuleState *state, const char *token, const char *role);
+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.
The server 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
- return a palloc'd <literal>ValidatorModuleResult</literal> struct, which is
+ set output parameters in the <literal>result</literal> struct, which is
defined as below:
<programlisting>
@@ -368,17 +379,17 @@ typedef struct ValidatorModuleResult
determined.
</para>
<para>
- The caller assumes ownership of the returned memory allocation, the
- validator module should not in any way access the memory after it has been
- returned. A validator may instead return NULL to signal an internal
- error.
+ 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>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>trust_validator_authz</literal> turned on, the
+ an HBA rule with <literal>delegate_ident_mapping</literal> turned on, the
server will not perform any checks on the value of
<structfield>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
diff --git a/src/backend/libpq/auth-oauth.c b/src/backend/libpq/auth-oauth.c
index aa16977c643..e2b5d1ed913 100644
--- a/src/backend/libpq/auth-oauth.c
+++ b/src/backend/libpq/auth-oauth.c
@@ -656,9 +656,9 @@ validate(Port *port, const char *auth)
errmsg("validation of OAuth token requested without a validator loaded"));
/* Call the validation function from the validator module */
- ret = ValidatorCallbacks->validate_cb(validator_module_state,
- token, port->user_name);
- if (ret == NULL)
+ ret = palloc0(sizeof(ValidatorModuleResult));
+ if (!ValidatorCallbacks->validate_cb(validator_module_state, token,
+ port->user_name, ret))
{
ereport(LOG, errmsg("internal error in OAuth validator module"));
return false;
@@ -756,8 +756,22 @@ load_validator_library(const char *libname)
ValidatorCallbacks = (*validator_init) ();
Assert(ValidatorCallbacks);
+ /*
+ * Check the magic number, to protect against break-glass scenarios where
+ * the ABI must change within a major version. load_external_function()
+ * already checks for compatibility across major versions.
+ */
+ if (ValidatorCallbacks->magic != PG_OAUTH_VALIDATOR_MAGIC)
+ ereport(ERROR,
+ errmsg("%s module \"%s\": magic number mismatch",
+ "OAuth validator", libname),
+ errdetail("Server has magic number 0x%08X, module has 0x%08X.",
+ PG_OAUTH_VALIDATOR_MAGIC, ValidatorCallbacks->magic));
+
/* Allocate memory for validator library private state data */
validator_module_state = (ValidatorModuleState *) palloc0(sizeof(ValidatorModuleState));
+ validator_module_state->sversion = PG_VERSION_NUM;
+
if (ValidatorCallbacks->startup_cb != NULL)
ValidatorCallbacks->startup_cb(validator_module_state);
diff --git a/src/include/libpq/oauth.h b/src/include/libpq/oauth.h
index 4fcdda74305..7e249613e10 100644
--- a/src/include/libpq/oauth.h
+++ b/src/include/libpq/oauth.h
@@ -20,26 +20,72 @@ extern PGDLLIMPORT char *oauth_validator_libraries_string;
typedef struct ValidatorModuleState
{
+ /* Holds the server's PG_VERSION_NUM. Reserved for future extensibility. */
+ int sversion;
+
+ /*
+ * Private data pointer for use by a validator module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
void *private_data;
} ValidatorModuleState;
typedef struct ValidatorModuleResult
{
+ /*
+ * Should be set to true if the token carries sufficient permissions for
+ * the bearer to connect.
+ */
bool authorized;
+
+ /*
+ * If the token authenticates the user, this should be set to a palloc'd
+ * string containing the SYSTEM_USER to use for HBA mapping. Consider
+ * setting this even if result->authorized is false so that DBAs may use
+ * the logs to match end users to token failures.
+ *
+ * This is required if the module is not configured for ident mapping
+ * delegation. See the validator module documentation for details.
+ */
char *authn_id;
} ValidatorModuleResult;
+/*
+ * Validator module callbacks
+ *
+ * These callback functions should be defined by validator modules and returned
+ * via _PG_oauth_validator_module_init(). ValidatorValidateCB is the only
+ * required callback. For more information about the purpose of each callback,
+ * refer to the OAuth validator modules documentation.
+ */
typedef void (*ValidatorStartupCB) (ValidatorModuleState *state);
typedef void (*ValidatorShutdownCB) (ValidatorModuleState *state);
-typedef ValidatorModuleResult *(*ValidatorValidateCB) (ValidatorModuleState *state, const char *token, const char *role);
+typedef bool (*ValidatorValidateCB) (const ValidatorModuleState *state,
+ const char *token, const char *role,
+ ValidatorModuleResult *result);
+
+/*
+ * Identifies the compiled ABI version of the validator module. Since the server
+ * already enforces the PG_MODULE_MAGIC number for modules across major
+ * versions, this is reserved for emergency use within a stable release line.
+ * May it never need to change.
+ */
+#define PG_OAUTH_VALIDATOR_MAGIC 0x20250207
typedef struct OAuthValidatorCallbacks
{
+ uint32 magic; /* must be set to PG_OAUTH_VALIDATOR_MAGIC */
+
ValidatorStartupCB startup_cb;
ValidatorShutdownCB shutdown_cb;
ValidatorValidateCB validate_cb;
} OAuthValidatorCallbacks;
+/*
+ * Type of the shared library symbol _PG_oauth_validator_module_init that is
+ * looked up when loading a validator module.
+ */
typedef const OAuthValidatorCallbacks *(*OAuthValidatorModuleInit) (void);
extern PGDLLEXPORT const OAuthValidatorCallbacks *_PG_oauth_validator_module_init(void);
diff --git a/src/interfaces/libpq/fe-auth-oauth-curl.c b/src/interfaces/libpq/fe-auth-oauth-curl.c
index 2179bb89800..74323de309a 100644
--- a/src/interfaces/libpq/fe-auth-oauth-curl.c
+++ b/src/interfaces/libpq/fe-auth-oauth-curl.c
@@ -32,7 +32,19 @@
#include "libpq-int.h"
#include "mb/pg_wchar.h"
-#define MAX_OAUTH_RESPONSE_SIZE (1024 * 1024)
+/*
+ * It's generally prudent to set a maximum response size to buffer in memory,
+ * but it's less clear what size to choose. The biggest of our expected
+ * responses is the server metadata JSON, which will only continue to grow in
+ * size; the number of IANA-registered parameters in that document is up to 78
+ * as of February 2025.
+ *
+ * Even if every single parameter were to take up 2k on average (a previously
+ * common limit on the size of a URL), 256k gives us 128 parameter values before
+ * we give up. (That's almost certainly complete overkill in practice; 2-4k
+ * appears to be common among popular providers at the moment.)
+ */
+#define MAX_OAUTH_RESPONSE_SIZE (256 * 1024)
/*
* Parsed JSON Representations
diff --git a/src/test/modules/oauth_validator/fail_validator.c b/src/test/modules/oauth_validator/fail_validator.c
index f77a3e115c6..7b1e69518d9 100644
--- a/src/test/modules/oauth_validator/fail_validator.c
+++ b/src/test/modules/oauth_validator/fail_validator.c
@@ -19,12 +19,15 @@
PG_MODULE_MAGIC;
-static ValidatorModuleResult *fail_token(ValidatorModuleState *state,
- const char *token,
- const char *role);
+static bool fail_token(const ValidatorModuleState *state,
+ const char *token,
+ const char *role,
+ ValidatorModuleResult *result);
/* Callback implementations (we only need the main one) */
static const OAuthValidatorCallbacks validator_callbacks = {
+ PG_OAUTH_VALIDATOR_MAGIC,
+
.validate_cb = fail_token,
};
@@ -34,8 +37,10 @@ _PG_oauth_validator_module_init(void)
return &validator_callbacks;
}
-static ValidatorModuleResult *
-fail_token(ValidatorModuleState *state, const char *token, const char *role)
+static bool
+fail_token(const ValidatorModuleState *state,
+ const char *token, const char *role,
+ ValidatorModuleResult *res)
{
elog(FATAL, "fail_validator: sentinel error");
pg_unreachable();
diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl
index f0b918390fd..d2dda62a2d4 100644
--- a/src/test/modules/oauth_validator/t/001_server.pl
+++ b/src/test/modules/oauth_validator/t/001_server.pl
@@ -14,12 +14,18 @@ use MIME::Base64 qw(encode_base64);
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
+use Config;
use FindBin;
use lib $FindBin::RealBin;
use OAuth::Server;
+if ($Config{osname} eq 'MSWin32')
+{
+ plan skip_all => 'OAuth server-side tests are not supported on Windows';
+}
+
if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\boauth\b/)
{
plan skip_all =>
@@ -402,7 +408,10 @@ note "running '" . join("' '", @cmd) . "'";
my ($stdout, $stderr) = run_command(\@cmd);
like($stdout, qr/connection succeeded/, "stress-async: stdout matches");
-unlike($stderr, qr/connection to database failed/, "stress-async: stderr matches");
+unlike(
+ $stderr,
+ qr/connection to database failed/,
+ "stress-async: stderr matches");
#
# This section of tests reconfigures the validator module itself, rather than
diff --git a/src/test/modules/oauth_validator/validator.c b/src/test/modules/oauth_validator/validator.c
index ef9bbb2866f..e218f5c8902 100644
--- a/src/test/modules/oauth_validator/validator.c
+++ b/src/test/modules/oauth_validator/validator.c
@@ -23,12 +23,15 @@ PG_MODULE_MAGIC;
static void validator_startup(ValidatorModuleState *state);
static void validator_shutdown(ValidatorModuleState *state);
-static ValidatorModuleResult *validate_token(ValidatorModuleState *state,
- const char *token,
- const char *role);
+static bool validate_token(const ValidatorModuleState *state,
+ const char *token,
+ const char *role,
+ ValidatorModuleResult *result);
/* Callback implementations (exercise all three) */
static const OAuthValidatorCallbacks validator_callbacks = {
+ PG_OAUTH_VALIDATOR_MAGIC,
+
.startup_cb = validator_startup,
.shutdown_cb = validator_shutdown,
.validate_cb = validate_token
@@ -89,6 +92,13 @@ _PG_oauth_validator_module_init(void)
static void
validator_startup(ValidatorModuleState *state)
{
+ /*
+ * Make sure the server is correctly setting sversion. (Real modules
+ * should not do this; it would defeat upgrade compatibility.)
+ */
+ if (state->sversion != PG_VERSION_NUM)
+ elog(ERROR, "oauth_validator: sversion set to %d", state->sversion);
+
state->private_data = PRIVATE_COOKIE;
}
@@ -108,18 +118,16 @@ validator_shutdown(ValidatorModuleState *state)
* Validator implementation. Logs the incoming data and authorizes the token by
* default; the behavior can be modified via the module's GUC settings.
*/
-static ValidatorModuleResult *
-validate_token(ValidatorModuleState *state, const char *token, const char *role)
+static bool
+validate_token(const ValidatorModuleState *state,
+ const char *token, const char *role,
+ ValidatorModuleResult *res)
{
- ValidatorModuleResult *res;
-
/* Check to make sure our private state still exists. */
if (state->private_data != PRIVATE_COOKIE)
elog(ERROR, "oauth_validator: private state cookie changed to %p in validate",
state->private_data);
- res = palloc(sizeof(ValidatorModuleResult));
-
elog(LOG, "oauth_validator: token=\"%s\", role=\"%s\"", token, role);
elog(LOG, "oauth_validator: issuer=\"%s\", scope=\"%s\"",
MyProcPort->hba->oauth_issuer,
@@ -131,5 +139,5 @@ validate_token(ValidatorModuleState *state, const char *token, const char *role)
else
res->authn_id = pstrdup(role);
- return res;
+ return true;
}
--
2.39.3 (Apple Git-146)