public inbox for [email protected]
help / color / mirror / Atom feedFrom: Zsolt Parragi <[email protected]>
To: VASUKI M <[email protected]>
Cc: Jacob Champion <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Cc: [email protected]
Cc: Robert Haas <[email protected]>
Cc: [email protected]
Subject: Re: Custom oauth validator options
Date: Thu, 18 Dec 2025 09:08:10 +0000
Message-ID: <CAN4CZFN9RMF_79kx75SkQZezd91DocUzz89bJeBJrMO=uNuG2w@mail.gmail.com> (raw)
In-Reply-To: <CAE2r8H439jg+e5gXJpNNMoroe4CfWauDRfUBZC_9NUNTOhqzBQ@mail.gmail.com>
References: <CAN4CZFM3b8u5uNNNsY6XCya257u+Dofms3su9f11iMCxvCacag@mail.gmail.com>
<CAE2r8H55geNFtECuFunpgn0LJK2+rntGuTeqNr6mP7gGhWFRbA@mail.gmail.com>
<CAN4CZFP_2fe2-18wUoXDZodV8suVe9o++pv=hP8KxxvWkmCx7A@mail.gmail.com>
<CAOYmi+kMuA7t9ao6rWZ=5kn_Zmd7qtwOay_ocEBXwkzKWbefhQ@mail.gmail.com>
<CAE2r8H439jg+e5gXJpNNMoroe4CfWauDRfUBZC_9NUNTOhqzBQ@mail.gmail.com>
> Personally I would go with either (a) or (c), and I was planning to
> clean up / improve / share my (c) patch as a second attempt for this
> thread, if it didn't receive any replies. I can still do that, so that
> we have multiple test implementations.
I attached the patch. It modifies one of the existing oauth_validator
tests to showcase how it works, but in theory it isn't dependent on
oauth. It however requires shared_preload_libraries (that is common
for all options), maybe oauth_validator_libraries could imply that?
Attachments:
[application/octet-stream] 0001-Adding-hooks-to-HBA-parsing-option-c.patch (9.0K, 2-0001-Adding-hooks-to-HBA-parsing-option-c.patch)
download | inline diff:
From 296acdb1551db523009ce7201daa03ef3e33f182 Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <[email protected]>
Date: Thu, 18 Dec 2025 08:25:46 +0000
Subject: [PATCH] Adding hooks to HBA parsing, option c
This commit showcases how we can add a generic, non oauth dependent hook
to parsing hba entries, and also adds a simple test to the existing
oauth_validator test suite.
---
src/backend/libpq/hba.c | 34 ++++++++----
src/include/libpq/hba.h | 31 +++++++++++
.../modules/oauth_validator/t/002_client.pl | 51 ++++++++++++++++++
src/test/modules/oauth_validator/validator.c | 53 +++++++++++++++++++
4 files changed, 160 insertions(+), 9 deletions(-)
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 4c259f58d77..409801ec6d8 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -125,6 +125,11 @@ static const char *const UserAuthName[] =
StaticAssertDecl(lengthof(UserAuthName) == USER_AUTH_LAST + 1,
"UserAuthName[] must match the UserAuth enum");
+/*
+ * Hook for plugins to extend pg_hba.conf option parsing.
+ */
+hba_parse_option_hook_type hba_parse_option_hook = NULL;
+
static List *tokenize_expand_file(List *tokens, const char *outer_filename,
const char *inc_filename, int elevel,
@@ -2507,15 +2512,26 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
}
else
{
- ereport(elevel,
- (errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("unrecognized authentication option name: \"%s\"",
- name),
- errcontext("line %d of configuration file \"%s\"",
- line_num, file_name)));
- *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
- name);
- return false;
+ bool handled = false;
+
+ if (hba_parse_option_hook)
+ {
+ handled = (*hba_parse_option_hook) (name, val, hbaline,
+ elevel, err_msg);
+ }
+
+ if (!handled)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unrecognized authentication option name: \"%s\"",
+ name),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+ name);
+ return false;
+ }
}
return true;
}
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 7b93ba4a709..bd4658c72c8 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -172,6 +172,37 @@ typedef struct TokenizedAuthLine
/* avoid including libpq/libpq-be.h here */
typedef struct Port Port;
+/*
+ * Hook for plugins to extend pg_hba.conf option parsing.
+ *
+ * This hook is called by parse_hba_auth_opt() when it encounters an option
+ * name that it doesn't recognize. Plugins can use this to parse custom
+ * authentication.
+ *
+ * Parameters:
+ * name - The option name being parsed (e.g., "custom_option")
+ * val - The option value (may be NULL for boolean-style options)
+ * hbaline - The HbaLine structure being populated. Plugins should not
+ * modify standard fields, but can use this to check auth_method,
+ * conntype, etc. to validate option applicability.
+ * elevel - Error level for reporting (LOG, ERROR, etc.)
+ * err_msg - Output parameter for error messages. Set this to a palloc'd
+ * string if returning false due to a validation error.
+ *
+ * Return value:
+ * true - The hook recognized and successfully handled this option.
+ * false - The hook doesn't recognize this option, or encountered an error.
+ * If an error occurred, the hook should set *err_msg and/or call
+ * ereport().
+ */
+typedef bool (*hba_parse_option_hook_type) (const char *name,
+ const char *val,
+ HbaLine *hbaline,
+ int elevel,
+ char **err_msg);
+
+extern PGDLLIMPORT hba_parse_option_hook_type hba_parse_option_hook;
+
extern bool load_hba(void);
extern bool load_ident(void);
extern const char *hba_authname(UserAuth auth_method);
diff --git a/src/test/modules/oauth_validator/t/002_client.pl b/src/test/modules/oauth_validator/t/002_client.pl
index e6c91fc911c..6576d6e41e0 100644
--- a/src/test/modules/oauth_validator/t/002_client.pl
+++ b/src/test/modules/oauth_validator/t/002_client.pl
@@ -29,6 +29,8 @@ $node->init;
$node->append_conf('postgresql.conf', "log_connections = all\n");
$node->append_conf('postgresql.conf',
"oauth_validator_libraries = 'validator'\n");
+$node->append_conf('postgresql.conf',
+ "shared_preload_libraries = 'validator'\n");
# Needed to inspect postmaster log after connection failure:
$node->append_conf('postgresql.conf', "log_min_messages = debug2");
$node->start;
@@ -115,6 +117,55 @@ test(
expected_stdout => qr/connection succeeded/,
log_like => [qr/oauth_validator: token="my-token", role="$user"/]);
+# Test custom HBA option parsing hook
+my $log_start_custom = -s $node->logfile;
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf(
+ 'pg_hba.conf', qq{
+local all test oauth issuer="$issuer" scope="$scope" test_custom_claim="my_custom_value"
+});
+$node->reload;
+$node->wait_for_log(qr/reloading configuration files/, $log_start_custom);
+
+$node->wait_for_log(
+ qr/oauth_validator: parsed custom HBA option test_custom_claim="my_custom_value"/,
+ $log_start_custom);
+
+test(
+ "custom HBA option is parsed and used",
+ flags => [
+ "--token", "test-token",
+ "--expected-uri", "$issuer/.well-known/openid-configuration",
+ "--expected-scope", $scope,
+ ],
+ expected_stdout => qr/connection succeeded/,
+ log_like => [qr/oauth_validator: custom_claim="my_custom_value"/]);
+
+# Test that unknown HBA options still fail
+my $log_start_unknown = -s $node->logfile;
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf(
+ 'pg_hba.conf', qq{
+local all test oauth issuer="$issuer" scope="$scope" unknown_option="value"
+});
+$node->reload;
+$node->wait_for_log(qr/reloading configuration files/, $log_start_unknown);
+
+# Check that the server logged the error about the unknown option
+$node->wait_for_log(
+ qr/unrecognized authentication option name: "unknown_option"/,
+ $log_start_unknown);
+pass("unknown HBA option is rejected");
+
+# Restore working configuration
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf(
+ 'pg_hba.conf', qq{
+local all test oauth issuer="$issuer" scope="$scope"
+});
+$node->reload;
+$node->wait_for_log(qr/reloading configuration files/);
+
if ($ENV{with_libcurl} ne 'yes')
{
# libpq should help users out if no OAuth support is built in.
diff --git a/src/test/modules/oauth_validator/validator.c b/src/test/modules/oauth_validator/validator.c
index 42b69646fbb..0115f228cea 100644
--- a/src/test/modules/oauth_validator/validator.c
+++ b/src/test/modules/oauth_validator/validator.c
@@ -14,6 +14,7 @@
#include "postgres.h"
#include "fmgr.h"
+#include "libpq/hba.h"
#include "libpq/oauth.h"
#include "miscadmin.h"
#include "utils/guc.h"
@@ -41,6 +42,50 @@ static const OAuthValidatorCallbacks validator_callbacks = {
static char *authn_id = NULL;
static bool authorize_tokens = true;
+static char *custom_claim = NULL;
+
+static hba_parse_option_hook_type prev_hba_parse_option_hook = NULL;
+
+static bool
+validator_hba_parse_option(const char *name, const char *val,
+ HbaLine *hbaline, int elevel, char **err_msg)
+{
+ int line_num = hbaline->linenumber;
+ char *file_name = hbaline->sourcefile;
+
+ if (prev_hba_parse_option_hook)
+ {
+ if ((*prev_hba_parse_option_hook) (name, val, hbaline,
+ elevel, err_msg))
+ return true;
+ }
+
+ if (strcmp(name, "test_custom_claim") == 0)
+ {
+ if (val == NULL || val[0] == '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("test_custom_claim requires a value"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = pstrdup("test_custom_claim requires a value");
+ return false;
+ }
+
+ if (custom_claim)
+ pfree(custom_claim);
+ custom_claim = pstrdup(val);
+
+ elog(LOG, "oauth_validator: parsed custom HBA option test_custom_claim=\"%s\"",
+ custom_claim);
+
+ return true;
+ }
+
+ return false;
+}
+
/*---
* Extension entry point. Sets up GUCs for use by tests:
*
@@ -55,6 +100,8 @@ static bool authorize_tokens = true;
void
_PG_init(void)
{
+ elog(LOG, "oauth_validator: _PG_init() called, installing HBA parse option hook");
+
DefineCustomStringVariable("oauth_validator.authn_id",
"Authenticated identity to use for future connections",
NULL,
@@ -73,6 +120,9 @@ _PG_init(void)
NULL, NULL, NULL);
MarkGUCPrefixReserved("oauth_validator");
+
+ prev_hba_parse_option_hook = hba_parse_option_hook;
+ hba_parse_option_hook = validator_hba_parse_option;
}
/*
@@ -133,6 +183,9 @@ validate_token(const ValidatorModuleState *state,
MyProcPort->hba->oauth_issuer,
MyProcPort->hba->oauth_scope);
+ if (custom_claim)
+ elog(LOG, "oauth_validator: custom_claim=\"%s\"", custom_claim);
+
res->authorized = authorize_tokens;
if (authn_id)
res->authn_id = pstrdup(authn_id);
--
2.43.0
view thread (24+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Custom oauth validator options
In-Reply-To: <CAN4CZFN9RMF_79kx75SkQZezd91DocUzz89bJeBJrMO=uNuG2w@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox