Hello! On Thu, 2026-03-19 at 20:15 +0000, Zsolt Parragi wrote: > Thanks, v5 looks good! > > (One documentation comment I missed previously: the oauth_ca_file > connection parameter should also be documented, but that's just the > same documentation repeated at one more place)
Good point! attached with the new doc and updated reviewers list (sorry Jacob I forgot you the first time) Regards! -- Jonathan Gonzalez V. EDB: https://www.enterprisedb.com
From 32f3f1163a061c3a512e05dfe55e7c643e73fcf8 Mon Sep 17 00:00:00 2001 From: "Jonathan Gonzalez V." <[email protected]> Date: Wed, 29 Oct 2025 16:54:42 +0100 Subject: [PATCH v6 1/1] libpq-oauth: allow changing the CA when not in debug mode Allowing to set a CA enables users environment like companies with internal CA or developers working on their own local system while using a self-signed CA and don't need to see all the debug messages while testing inside an internal environment. Reviewed-by: Jacob Champion <[email protected]> Reviewed-by: Zsolt Parragi <[email protected]> Signed-off-by: Jonathan Gonzalez V. <[email protected]> --- doc/src/sgml/libpq.sgml | 33 ++++++++++++--- src/interfaces/libpq-oauth/oauth-curl.c | 27 +++++++------ src/interfaces/libpq/fe-connect.c | 5 +++ src/interfaces/libpq/libpq-int.h | 1 + .../modules/oauth_validator/t/001_server.pl | 40 ++++++++++++++++++- .../modules/oauth_validator/t/OAuth/Server.pm | 2 +- 6 files changed, 86 insertions(+), 22 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 6db823808fc..cb836abc978 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2585,6 +2585,16 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-oauth-ca-file" xreflabel="oauth_ca_file"> + <term><literal>oauth_ca_file</literal></term> + <listitem> + <para> + Allows to specify the path to a CA file that will be used by the client + to verify the certificate from the OAuth server side. + </para> + </listitem> + </varlistentry> + </variablelist> </para> </sect2> @@ -10620,12 +10630,6 @@ typedef struct permits the use of unencrypted HTTP during the OAuth provider exchange </para> </listitem> - <listitem> - <para> - allows the system's trusted CA list to be completely replaced using the - <envar>PGOAUTHCAFILE</envar> environment variable - </para> - </listitem> <listitem> <para> prints HTTP traffic (containing several critical secrets) to standard @@ -10647,6 +10651,23 @@ typedef struct </para> </warning> </sect2> + <sect2 id="libpq-oauth-environment"> + <title>Environment variables</title> + <para> + The behavior of the OAuth calls may be affected by the following variables: + <variablelist> + <varlistentry> + <term><envar>PGOAUTHCAFILE</envar></term> + <listitem> + <para> + Allows to specify the path to a CA file that will be used by the client + to verify the certificate from the OAuth server side. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </sect2> </sect1> diff --git a/src/interfaces/libpq-oauth/oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c index 052ecd32da2..561875d6db1 100644 --- a/src/interfaces/libpq-oauth/oauth-curl.c +++ b/src/interfaces/libpq-oauth/oauth-curl.c @@ -17,6 +17,7 @@ #include <curl/curl.h> #include <math.h> +#include <string.h> #include <unistd.h> #if defined(HAVE_SYS_EPOLL_H) @@ -216,6 +217,7 @@ struct async_ctx /* relevant connection options cached from the PGconn */ char *client_id; /* oauth_client_id */ char *client_secret; /* oauth_client_secret (may be NULL) */ + char *ca_file; /* oauth_ca_file */ /* options cached from the PGoauthBearerRequest (we don't own these) */ const char *discovery_uri; @@ -336,6 +338,7 @@ free_async_ctx(struct async_ctx *actx) free(actx->client_id); free(actx->client_secret); + free(actx->ca_file); free(actx); } @@ -1834,20 +1837,12 @@ setup_curl_handles(struct async_ctx *actx) } /* - * If we're in debug mode, allow the developer to change the trusted CA - * list. For now, this is not something we expose outside of the UNSAFE - * mode, because it's not clear that it's useful in production: both libpq - * and the user's browser must trust the same authorization servers for - * the flow to work at all, so any changes to the roots are likely to be - * done system-wide. + * Allow to set the CA even if we're not in debug mode, this would make it + * easy to work on environments where the CA could be internal and + * available on every system, like big companies with airgap systems. */ - if (actx->debugging) - { - const char *env; - - if ((env = getenv("PGOAUTHCAFILE")) != NULL) - CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false); - } + if (actx->ca_file != NULL) + CHECK_SETOPT(actx, CURLOPT_CAINFO, actx->ca_file, return false); /* * Suppress the Accept header to make our request as minimal as possible. @@ -3125,6 +3120,12 @@ pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request) if (!actx->client_secret) goto oom; } + else if (strcmp(opt->keyword, "oauth_ca_file") == 0) + { + actx->ca_file = strdup(opt->val); + if (!actx->ca_file) + goto oom; + } } PQconninfoFree(conninfo); diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index db9b4c8edbf..4f3af722881 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -413,6 +413,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "OAuth-Scope", "", 15, offsetof(struct pg_conn, oauth_scope)}, + {"oauth_ca_file", "PGOAUTHCAFILE", NULL, NULL, + "OAuth-CA-File", "", 64, + offsetof(struct pg_conn, oauth_ca_file)}, + {"sslkeylogfile", NULL, NULL, NULL, "SSL-Key-Log-File", "D", 64, offsetof(struct pg_conn, sslkeylogfile)}, @@ -5158,6 +5162,7 @@ freePGconn(PGconn *conn) free(conn->oauth_discovery_uri); free(conn->oauth_client_id); free(conn->oauth_client_secret); + free(conn->oauth_ca_file); free(conn->oauth_scope); /* Note that conn->Pfdebug is not ours to close or free */ free(conn->events); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index bd7eb59f5f8..1f1fb89e02f 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -444,6 +444,7 @@ struct pg_conn char *oauth_client_secret; /* client secret */ char *oauth_scope; /* access token scope */ char *oauth_token; /* access token */ + char *oauth_ca_file; /* CA file path */ bool oauth_want_retry; /* should we retry on failure? */ /* Optional file to write trace info to */ diff --git a/src/test/modules/oauth_validator/t/001_server.pl b/src/test/modules/oauth_validator/t/001_server.pl index cdad2ae8011..b66d99dd4bb 100644 --- a/src/test/modules/oauth_validator/t/001_server.pl +++ b/src/test/modules/oauth_validator/t/001_server.pl @@ -137,10 +137,46 @@ $node->connect_fails( expected_stderr => qr/failed to fetch OpenID discovery document:.*peer certificate/i); -# Now we can use our alternative CA. -$ENV{PGOAUTHCAFILE} = "$ENV{cert_dir}/root+server_ca.crt"; +# Make sure that PGOAUTHDEBUG is not required to specify the certificate +delete $ENV{PGOAUTHDEBUG}; +# The alternative CA path to use during the tests +my $alternative_ca = "$ENV{cert_dir}/root+server_ca.crt"; + +# Make sure we can use oauth_ca_file option to specify the alternative CA path my $user = "test"; +$node->connect_ok( + "user=$user dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0635 oauth_ca_file=$alternative_ca", + "connect as test", + expected_stderr => + qr@Visit https://example\.com/ and enter the code: postgresuser@, + log_like => [ + qr/oauth_validator: token="9243959234", role="$user"/, + qr/oauth_validator: issuer="\Q$issuer\E", scope="openid postgres"/, + qr/connection authenticated: identity="test" method=oauth/, + qr/connection authorized/, + ]); + +# Make sure that we can use the environment variable without the PGOAUTHDEBUG +# and use it for the rest of the tests +$ENV{PGOAUTHCAFILE} = $alternative_ca; + +$node->connect_ok( + "user=$user dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0635", + "connect as test", + expected_stderr => + qr@Visit https://example\.com/ and enter the code: postgresuser@, + log_like => [ + qr/oauth_validator: token="9243959234", role="$user"/, + qr/oauth_validator: issuer="\Q$issuer\E", scope="openid postgres"/, + qr/connection authenticated: identity="test" method=oauth/, + qr/connection authorized/, + ]); + +# Enable PGOAUTHDEBUG=UNSAFE to have the proper count later with the `[libpq] total number of polls` messages +$ENV{PGOAUTHDEBUG} = "UNSAFE"; + +$user = "test"; $node->connect_ok( "user=$user dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0635", "connect as test", diff --git a/src/test/modules/oauth_validator/t/OAuth/Server.pm b/src/test/modules/oauth_validator/t/OAuth/Server.pm index d923d4c5eb2..62a29c283df 100644 --- a/src/test/modules/oauth_validator/t/OAuth/Server.pm +++ b/src/test/modules/oauth_validator/t/OAuth/Server.pm @@ -28,7 +28,7 @@ daemon implemented in t/oauth_server.py. (Python has a fairly usable HTTP server in its standard library, so the implementation was ported from Perl.) This authorization server serves HTTPS on 127.0.0.1 (IPv4 only). libpq will need -to set PGOAUTHDEBUG=UNSAFE and PGOAUTHCAFILE with the right CA. +to set PGOAUTHCAFILE with the right CA. =cut -- 2.51.0
signature.asc
Description: This is a digitally signed message part
