diff options
author | Daniel Gustafsson <dgustafsson@postgresql.org> | 2025-02-20 16:25:17 +0100 |
---|---|---|
committer | Daniel Gustafsson <dgustafsson@postgresql.org> | 2025-02-20 16:25:17 +0100 |
commit | b3f0be788afc17d2206e1ae1c731d8aeda1f2f59 (patch) | |
tree | 4935e9d745787830d57941771dd2e63b49236ae5 /src/interfaces/libpq/fe-connect.c | |
parent | 1fd1bd871012732e3c6c482667d2f2c56f1a9395 (diff) | |
download | postgresql-b3f0be788afc17d2206e1ae1c731d8aeda1f2f59.tar.gz postgresql-b3f0be788afc17d2206e1ae1c731d8aeda1f2f59.zip |
Add support for OAUTHBEARER SASL mechanism
This commit implements OAUTHBEARER, RFC 7628, and OAuth 2.0 Device
Authorization Grants, RFC 8628. In order to use this there is a
new pg_hba auth method called oauth. When speaking to a OAuth-
enabled server, it looks a bit like this:
$ psql 'host=example.org oauth_issuer=... oauth_client_id=...'
Visit https://oauth.example.org/login and enter the code: FPQ2-M4BG
Device authorization is currently the only supported flow so the
OAuth issuer must support that in order for users to authenticate.
Third-party clients may however extend this and provide their own
flows. The built-in device authorization flow is currently not
supported on Windows.
In order for validation to happen server side a new framework for
plugging in OAuth validation modules is added. As validation is
implementation specific, with no default specified in the standard,
PostgreSQL does not ship with one built-in. Each pg_hba entry can
specify a specific validator or be left blank for the validator
installed as default.
This adds a requirement on libcurl for the client side support,
which is optional to build, but the server side has no additional
build requirements. In order to run the tests, Python is required
as this adds a https server written in Python. Tests are gated
behind PG_TEST_EXTRA as they open ports.
This patch has been a multi-year project with many contributors
involved with reviews and in-depth discussions: Michael Paquier,
Heikki Linnakangas, Zhihong Yu, Mahendrakar Srinivasarao, Andrey
Chudnovsky and Stephen Frost to name a few. While Jacob Champion
is the main author there have been some levels of hacking by others.
Daniel Gustafsson contributed the validation module and various bits
and pieces; Thomas Munro wrote the client side support for kqueue.
Author: Jacob Champion <jacob.champion@enterprisedb.com>
Co-authored-by: Daniel Gustafsson <daniel@yesql.se>
Co-authored-by: Thomas Munro <thomas.munro@gmail.com>
Reviewed-by: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Reviewed-by: Antonin Houska <ah@cybertec.at>
Reviewed-by: Kashif Zeeshan <kashi.zeeshan@gmail.com>
Discussion: https://postgr.es/m/d1b467a78e0e36ed85a09adf979d04cf124a9d4b.camel@vmware.com
Diffstat (limited to 'src/interfaces/libpq/fe-connect.c')
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 48 |
1 files changed, 47 insertions, 1 deletions
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 85d1ca2864f..d5051f5e820 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -28,6 +28,7 @@ #include "common/scram-common.h" #include "common/string.h" #include "fe-auth.h" +#include "fe-auth-oauth.h" #include "libpq-fe.h" #include "libpq-int.h" #include "mb/pg_wchar.h" @@ -373,6 +374,23 @@ static const internalPQconninfoOption PQconninfoOptions[] = { {"scram_server_key", NULL, NULL, NULL, "SCRAM-Server-Key", "D", SCRAM_MAX_KEY_LEN * 2, offsetof(struct pg_conn, scram_server_key)}, + /* OAuth v2 */ + {"oauth_issuer", NULL, NULL, NULL, + "OAuth-Issuer", "", 40, + offsetof(struct pg_conn, oauth_issuer)}, + + {"oauth_client_id", NULL, NULL, NULL, + "OAuth-Client-ID", "", 40, + offsetof(struct pg_conn, oauth_client_id)}, + + {"oauth_client_secret", NULL, NULL, NULL, + "OAuth-Client-Secret", "", 40, + offsetof(struct pg_conn, oauth_client_secret)}, + + {"oauth_scope", NULL, NULL, NULL, + "OAuth-Scope", "", 15, + offsetof(struct pg_conn, oauth_scope)}, + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} @@ -399,6 +417,7 @@ static const PQEnvironmentOption EnvironmentOptions[] = static const pg_fe_sasl_mech *supported_sasl_mechs[] = { &pg_scram_mech, + &pg_oauth_mech, }; #define SASL_MECHANISM_COUNT lengthof(supported_sasl_mechs) @@ -655,6 +674,7 @@ pqDropServerData(PGconn *conn) conn->write_failed = false; free(conn->write_err_msg); conn->write_err_msg = NULL; + conn->oauth_want_retry = false; /* * Cancel connections need to retain their be_pid and be_key across @@ -1144,7 +1164,7 @@ static inline void fill_allowed_sasl_mechs(PGconn *conn) { /*--- - * We only support one mechanism at the moment, so rather than deal with a + * We only support two mechanisms at the moment, so rather than deal with a * linked list, conn->allowed_sasl_mechs is an array of static length. We * rely on the compile-time assertion here to keep us honest. * @@ -1519,6 +1539,10 @@ pqConnectOptions2(PGconn *conn) { mech = &pg_scram_mech; } + else if (strcmp(method, "oauth") == 0) + { + mech = &pg_oauth_mech; + } /* * Final group: meta-options. @@ -4111,7 +4135,19 @@ keep_going: /* We will come back to here until there is conn->inStart = conn->inCursor; if (res != STATUS_OK) + { + /* + * OAuth connections may perform two-step discovery, where + * the first connection is a dummy. + */ + if (conn->sasl == &pg_oauth_mech && conn->oauth_want_retry) + { + need_new_connection = true; + goto keep_going; + } + goto error_return; + } /* * Just make sure that any data sent by pg_fe_sendauth is @@ -4390,6 +4426,9 @@ keep_going: /* We will come back to here until there is } } + /* Don't hold onto any OAuth tokens longer than necessary. */ + pqClearOAuthToken(conn); + /* * For non cancel requests we can release the address list * now. For cancel requests we never actually resolve @@ -5002,6 +5041,12 @@ freePGconn(PGconn *conn) free(conn->load_balance_hosts); free(conn->scram_client_key); free(conn->scram_server_key); + free(conn->oauth_issuer); + free(conn->oauth_issuer_id); + free(conn->oauth_discovery_uri); + free(conn->oauth_client_id); + free(conn->oauth_client_secret); + free(conn->oauth_scope); termPQExpBuffer(&conn->errorMessage); termPQExpBuffer(&conn->workBuffer); @@ -5155,6 +5200,7 @@ pqClosePGconn(PGconn *conn) conn->asyncStatus = PGASYNC_IDLE; conn->xactStatus = PQTRANS_IDLE; conn->pipelineStatus = PQ_PIPELINE_OFF; + pqClearOAuthToken(conn); pqClearAsyncResult(conn); /* deallocate result */ pqClearConnErrorState(conn); |