diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6db823808fc..a48d3161495 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2585,6 +2585,23 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
+
+ oauth_ca_file
+
+
+ The name of a file containing one or more SSL certificate authority
+ (CA) certificates, which will be used to verify the
+ identity of the authorization server and its endpoints. By default, the
+ Curl system certificate bundle is used.
+
+
+ This parameter does not affect verification of the
+ PostgreSQL server certificate; see
+ instead.
+
+
+
+
@@ -9422,6 +9439,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
linkend="libpq-connect-max-protocol-version"/> connection parameter.
+
+
+
+
+ PGOAUTHCAFILE
+
+ PGOAUTHCAFILE behaves the same as the connection parameter.
+
+
@@ -10608,6 +10635,13 @@ typedef struct
Debugging and Developer Settings
+
+ While developing against a local authorization server, it may be helpful to
+ make use of the connection
+ parameter (or the equivalent PGOAUTHCAFILE environment
+ variable) in the client application.
+
+
A "dangerous debugging mode" may be enabled by setting the environment
variable PGOAUTHDEBUG=UNSAFE. This functionality is provided
@@ -10620,12 +10654,6 @@ typedef struct
permits the use of unencrypted HTTP during the OAuth provider exchange
-
-
- allows the system's trusted CA list to be completely replaced using the
- PGOAUTHCAFILE environment variable
-
-
prints HTTP traffic (containing several critical secrets) to standard
diff --git a/src/interfaces/libpq-oauth/oauth-curl.c b/src/interfaces/libpq-oauth/oauth-curl.c
index 052ecd32da2..3baede1b2e7 100644
--- a/src/interfaces/libpq-oauth/oauth-curl.c
+++ b/src/interfaces/libpq-oauth/oauth-curl.c
@@ -216,6 +216,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 +337,7 @@ free_async_ctx(struct async_ctx *actx)
free(actx->client_id);
free(actx->client_secret);
+ free(actx->ca_file);
free(actx);
}
@@ -1833,21 +1835,9 @@ setup_curl_handles(struct async_ctx *actx)
CHECK_SETOPT(actx, popt, protos, return false);
}
- /*
- * 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.
- */
- if (actx->debugging)
- {
- const char *env;
-
- if ((env = getenv("PGOAUTHCAFILE")) != NULL)
- CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
- }
+ /* Allow the user to change the trusted CA list. */
+ 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 +3115,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..4272d386e64 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..23de98290c9 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..9e4dba8c924 100644
--- a/src/test/modules/oauth_validator/t/001_server.pl
+++ b/src/test/modules/oauth_validator/t/001_server.pl
@@ -71,9 +71,9 @@ END
$? = $exit_code;
}
-# To test against HTTPS with our custom CA, we need to enable PGOAUTHDEBUG and
-# PGOAUTHCAFILE. But first, check to make sure the client refuses HTTP and
-# untrusted HTTPS connections by default.
+# To test against HTTPS with our custom CA, we'll set PGOAUTHCAFILE. But first,
+# check to make sure the client refuses HTTP and untrusted HTTPS connections by
+# default.
my $port = $webserver->port();
my $issuer = "http://127.0.0.1:$port";
@@ -119,28 +119,46 @@ is( $contents,
3|oauth|\{issuer=$issuer/param,"scope=openid postgres",validator=validator\}},
"pg_hba_file_rules recreates OAuth HBA settings");
-# Make sure PGOAUTHDEBUG=UNSAFE doesn't disable certificate verification.
-$ENV{PGOAUTHDEBUG} = "UNSAFE";
+{
+ # Make sure PGOAUTHDEBUG=UNSAFE doesn't disable certificate verification.
+ local $ENV{PGOAUTHDEBUG} = "UNSAFE";
-$node->connect_fails(
- "user=test dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0635",
- "HTTPS trusts only system CA roots by default",
- # Note that the latter half of this error message comes from Curl, which has
- # had a few variants since 7.61:
- #
- # - SSL peer certificate or SSH remote key was not OK
- # - Peer certificate cannot be authenticated with given CA certificates
- # - Issuer check against peer certificate failed
- #
- # Key off of the "peer certificate" portion, since that seems to have
- # remained constant over a long period of time.
- 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";
+ $node->connect_fails(
+ "user=test dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0635",
+ "HTTPS trusts only system CA roots by default",
+ # Note that the latter half of this error message comes from Curl, which
+ # has had a few variants since 7.61:
+ #
+ # - SSL peer certificate or SSH remote key was not OK
+ # - Peer certificate cannot be authenticated with given CA certificates
+ # - Issuer check against peer certificate failed
+ #
+ # Key off of the "peer certificate" portion, since that seems to have
+ # remained constant over a long period of time.
+ expected_stderr =>
+ qr/failed to fetch OpenID discovery document:.*peer certificate/i);
+}
+my $alternative_ca = "$ENV{cert_dir}/root+server_ca.crt";
my $user = "test";
+
+# Make sure we can use oauth_ca_file option to specify the alternative CA path
+$node->connect_ok(
+ "user=$user dbname=postgres oauth_issuer=$issuer oauth_client_id=f02c6361-0635 oauth_ca_file=$alternative_ca",
+ "connect as test (oauth_ca_file)",
+ 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 PGOAUTHDEBUG, and
+# then 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",
@@ -153,6 +171,9 @@ $node->connect_ok(
qr/connection authorized/,
]);
+# Enable PGOAUTHDEBUG for all remaining tests.
+$ENV{PGOAUTHDEBUG} = "UNSAFE";
+
# The /alternate issuer uses slightly different parameters, along with an
# OAuth-style discovery document.
$user = "testalt";
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