From 8f4331cfbb099e4b641a5b31f55a05be969915df Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Wed, 18 Feb 2026 14:51:46 +0100 Subject: [PATCH 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 | 16 +++++++++++++++- src/interfaces/libpq-oauth/oauth-curl.c | 13 +++++++++---- src/interfaces/libpq/fe-auth-oauth-debug.c | 7 +++++++ src/interfaces/libpq/fe-auth-oauth.c | 18 +++++++++++------- src/interfaces/libpq/fe-auth-oauth.h | 1 + .../modules/oauth_validator/t/001_server.pl | 15 +++++++++------ 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 5d70cc2b261..6f29a4a7756 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -10559,6 +10559,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) @@ -10595,7 +10609,7 @@ PGOAUTHDEBUG=UNSAFE legacy format; enables all options Unsafe options (http, trace, - custom-ca) require the UNSAFE: prefix. + custom-ca, 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-oauth/oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c index ac8b4631d53..d9512ef17dd 100644 --- a/src/interfaces/libpq-oauth/oauth-curl.c +++ b/src/interfaces/libpq-oauth/oauth-curl.c @@ -2223,10 +2223,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/fe-auth-oauth-debug.c b/src/interfaces/libpq/fe-auth-oauth-debug.c index f65f069fed8..558b34da561 100644 --- a/src/interfaces/libpq/fe-auth-oauth-debug.c +++ b/src/interfaces/libpq/fe-auth-oauth-debug.c @@ -53,6 +53,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) { @@ -103,6 +109,7 @@ oauth_get_debug_flags(void) flags.http = true; flags.trace = true; flags.custom_ca = 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 5dff354c19b..b6d472a0330 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/interfaces/libpq/fe-auth-oauth.h b/src/interfaces/libpq/fe-auth-oauth.h index 272638ea359..918681f16a5 100644 --- a/src/interfaces/libpq/fe-auth-oauth.h +++ b/src/interfaces/libpq/fe-auth-oauth.h @@ -52,6 +52,7 @@ typedef struct oauth_debug_flags bool http; /* allow HTTP (unencrypted) connections */ bool trace; /* log HTTP traffic (exposes secrets) */ bool custom_ca; /* allow custom CA certificate file */ + bool issuer_mismatch; /* tolerate issuer mismatch */ /* SAFE features - allowed without UNSAFE: prefix */ bool fast_retry; /* allow zero-second retry intervals */ diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl index 6b649c0b06f..b587b51bb19 100644 --- a/src/test/modules/oauth_validator/t/001_server.pl +++ b/src/test/modules/oauth_validator/t/001_server.pl @@ -136,12 +136,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.43.0