From 933f6432f8743287977e9da99670c582b91275ae Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Wed, 18 Feb 2026 14:51:46 +0100 Subject: [PATCH v2 2/2] Add new PGOAUTHDEBUG option: issuer-mismatch This new unsafe option allows to connection to proceed if the issuer configured on the server and client mismatch, allowing to write mismatched-issuer tests for validators. Validators should test scenarios like this, as the wire allows this situation, but previously libpq/psql prevented it, making writing tests for this more difficult. --- doc/src/sgml/libpq.sgml | 18 ++++++++++++++++-- src/interfaces/libpq/fe-auth-oauth.h | 1 + src/interfaces/libpq-oauth/oauth-curl.c | 13 +++++++++---- src/interfaces/libpq-oauth/test-oauth-curl.c | 1 + src/interfaces/libpq/fe-auth-oauth-debug.c | 7 +++++++ src/interfaces/libpq/fe-auth-oauth.c | 18 +++++++++++------- .../modules/oauth_validator/t/001_server.pl | 15 +++++++++------ 7 files changed, 54 insertions(+), 19 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 2e5fb9011e9..172f8138546 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -10683,6 +10683,20 @@ PGOAUTHDEBUG=UNSAFE legacy format; enables all options + + issuer-mismatch (unsafe) + + + Tolerates a mismatch between the client's configured + oauth_issuer and the issuer found in the server's + discovery document. This disables the mix-up attack protection from + RFC 9207 and should only be used in development or testing environments + where the server's issuer identifier does not match the client + configuration. + + + + fast-retry (safe) @@ -10718,8 +10732,8 @@ PGOAUTHDEBUG=UNSAFE legacy format; enables all options - Unsafe options (http, trace) - require the UNSAFE: prefix. + Unsafe options (http, trace, + issuer-mismatch) require the UNSAFE: prefix. If unsafe options are specified without this prefix, a warning is printed to standard error and that option is ignored. Other valid options in the list continue to work. Safe options (fast-retry, diff --git a/src/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h index fde5c30c013..acc678f91cf 100644 --- a/src/interfaces/libpq/fe-auth-oauth.h +++ b/src/interfaces/libpq/fe-auth-oauth.h @@ -47,6 +47,7 @@ typedef struct oauth_debug_flags /* UNSAFE features - require UNSAFE: prefix */ bool http; /* allow HTTP (unencrypted) connections */ bool trace; /* log HTTP traffic (exposes secrets) */ + bool issuer_mismatch; /* tolerate issuer mismatch */ /* SAFE features - allowed without UNSAFE: prefix */ bool fast_retry; /* allow zero-second retry intervals */ diff --git a/src/interfaces/libpq-oauth/oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c index 564d76cf063..5c18702da62 100644 --- a/src/interfaces/libpq-oauth/oauth-curl.c +++ b/src/interfaces/libpq-oauth/oauth-curl.c @@ -2249,10 +2249,15 @@ check_issuer(struct async_ctx *actx, PGconn *conn) */ if (strcmp(oauth_issuer_id, provider->issuer) != 0) { - actx_error(actx, - "the issuer identifier (%s) does not match oauth_issuer (%s)", - provider->issuer, oauth_issuer_id); - return false; + if (!actx->debug_flags.issuer_mismatch) + { + actx_error(actx, + "the issuer identifier (%s) does not match oauth_issuer (%s)", + provider->issuer, oauth_issuer_id); + return false; + } + + return true; } return true; diff --git a/src/interfaces/libpq-oauth/test-oauth-curl.c b/src/interfaces/libpq-oauth/test-oauth-curl.c index 06815be9a0a..776eaaafc8d 100644 --- a/src/interfaces/libpq-oauth/test-oauth-curl.c +++ b/src/interfaces/libpq-oauth/test-oauth-curl.c @@ -91,6 +91,7 @@ init_test_actx(void) actx->timerfd = -1; actx->debug_flags.http = true; actx->debug_flags.trace = true; + actx->debug_flags.issuer_mismatch = true; actx->debug_flags.fast_retry = true; actx->debug_flags.poll_counts = true; actx->debug_flags.print_plugin_errors = true; diff --git a/src/interfaces/libpq/fe-auth-oauth-debug.c b/src/interfaces/libpq/fe-auth-oauth-debug.c index f9a1b1f195f..309286e253f 100644 --- a/src/interfaces/libpq/fe-auth-oauth-debug.c +++ b/src/interfaces/libpq/fe-auth-oauth-debug.c @@ -47,6 +47,12 @@ parse_debug_option(const char *option, oauth_debug_flags *flags, bool *is_unsafe *is_unsafe = true; return true; } + else if (strcmp(option, "issuer-mismatch") == 0) + { + flags->issuer_mismatch = true; + *is_unsafe = true; + return true; + } /* Safe options */ else if (strcmp(option, "fast-retry") == 0) { @@ -96,6 +102,7 @@ oauth_get_debug_flags(void) { flags.http = true; flags.trace = true; + flags.issuer_mismatch = true; flags.fast_retry = true; flags.poll_counts = true; flags.print_plugin_errors = true; diff --git a/src/interfaces/libpq/fe-auth-oauth.c b/src/interfaces/libpq/fe-auth-oauth.c index 4bfe31b03cb..41340530f3e 100644 --- a/src/interfaces/libpq/fe-auth-oauth.c +++ b/src/interfaces/libpq/fe-auth-oauth.c @@ -606,13 +606,16 @@ handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen) if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0) { - libpq_append_conn_error(conn, - "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)", - ctx.discovery_uri, discovery_issuer, - conn->oauth_issuer_id); + if (!oauth_get_debug_flags().issuer_mismatch) + { + libpq_append_conn_error(conn, + "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)", + ctx.discovery_uri, discovery_issuer, + conn->oauth_issuer_id); - free(discovery_issuer); - goto cleanup; + free(discovery_issuer); + goto cleanup; + } } free(discovery_issuer); @@ -625,7 +628,8 @@ handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen) else { /* This must match the URI we'd previously determined. */ - if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0) + if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0 + && !oauth_get_debug_flags().issuer_mismatch) { libpq_append_conn_error(conn, "server's discovery document has moved to %s (previous location was %s)", diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl index 9e4dba8c924..abe47154529 100644 --- a/src/test/modules/oauth_validator/t/001_server.pl +++ b/src/test/modules/oauth_validator/t/001_server.pl @@ -190,12 +190,15 @@ $node->connect_ok( ]); # The issuer linked by the server must match the client's oauth_issuer setting. -$node->connect_fails( - "user=$user dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0636", - "oauth_issuer must match discovery", - expected_stderr => - qr@server's discovery document at \Q$issuer/.well-known/oauth-authorization-server/alternate\E \(issuer "\Q$issuer/alternate\E"\) is incompatible with oauth_issuer \(\Q$issuer\E\)@ -); +{ + local $ENV{PGOAUTHDEBUG} = "UNSAFE:http"; + $node->connect_fails( + "user=$user dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0636", + "oauth_issuer must match discovery", + expected_stderr => + qr@server's discovery document at \Q$issuer/.well-known/oauth-authorization-server/alternate\E \(issuer "\Q$issuer/alternate\E"\) is incompatible with oauth_issuer \(\Q$issuer\E\)@ + ); +} # Test require_auth settings against OAUTHBEARER. my @cases = ( -- 2.34.1