From 82ea53a81a5e9f5e8b680ec6de849a48a7222463 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Wed, 20 Sep 2023 08:28:59 +0000 Subject: [PATCH v1] Allow background workers to bypass login check This adds a new flag value (BGWORKER_BYPASS_ROLELOGINCHECK) to the flags being used in BackgroundWorkerInitializeConnection() and BackgroundWorkerInitializeConnectionByOid(). This flag allows the background workers to bypass the login check done in InitializeSessionUserId(). --- doc/src/sgml/bgworker.sgml | 3 +- src/backend/bootstrap/bootstrap.c | 2 +- src/backend/postmaster/autovacuum.c | 4 +- src/backend/postmaster/postmaster.c | 4 +- src/backend/tcop/postgres.c | 2 +- src/backend/utils/init/miscinit.c | 4 +- src/backend/utils/init/postinit.c | 9 ++-- src/include/miscadmin.h | 5 +- src/include/postmaster/bgworker.h | 6 ++- .../modules/worker_spi/t/001_worker_spi.pl | 48 +++++++++++++++++++ src/test/modules/worker_spi/worker_spi.c | 25 +++++++++- 11 files changed, 95 insertions(+), 17 deletions(-) 6.9% doc/src/sgml/ 3.4% src/backend/bootstrap/ 10.6% src/backend/postmaster/ 16.8% src/backend/utils/init/ 7.2% src/include/postmaster/ 29.7% src/test/modules/worker_spi/t/ 21.1% src/test/modules/worker_spi/ 4.0% src/ diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml index 9ad1146ba0..7335c02ed8 100644 --- a/doc/src/sgml/bgworker.sgml +++ b/doc/src/sgml/bgworker.sgml @@ -200,7 +200,8 @@ typedef struct BackgroundWorker InvalidOid, the process will run as the superuser created during initdb. If BGWORKER_BYPASS_ALLOWCONN is specified as flags it is possible to bypass the restriction - to connect to databases not allowing user connections. + to connect to databases not allowing user connections. If BGWORKER_BYPASS_ROLELOGINCHECK + is specified as flags it is possible to bypass the login check to connect to databases. A background worker can only call one of these two functions, and only once. It is not possible to switch databases. diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 5810f8825e..d4b6425585 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -345,7 +345,7 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) if (pg_link_canary_is_frontend()) elog(ERROR, "backend is incorrectly linked to frontend functions"); - InitPostgres(NULL, InvalidOid, NULL, InvalidOid, false, false, NULL); + InitPostgres(NULL, InvalidOid, NULL, InvalidOid, false, 0, NULL); /* Initialize stuff for bootstrap-file processing */ for (i = 0; i < MAXATTR; i++) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index ae9be9b911..dde83e76fb 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -488,7 +488,7 @@ AutoVacLauncherMain(int argc, char *argv[]) /* Early initialization */ BaseInit(); - InitPostgres(NULL, InvalidOid, NULL, InvalidOid, false, false, NULL); + InitPostgres(NULL, InvalidOid, NULL, InvalidOid, false, 0, NULL); SetProcessingMode(NormalProcessing); @@ -1706,7 +1706,7 @@ AutoVacWorkerMain(int argc, char *argv[]) * Note: if we have selected a just-deleted database (due to using * stale stats info), we'll fail and exit here. */ - InitPostgres(NULL, dbid, NULL, InvalidOid, false, false, + InitPostgres(NULL, dbid, NULL, InvalidOid, false, 0, dbname); SetProcessingMode(NormalProcessing); set_ps_display(dbname); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 54e9bfb8c4..443b1c4725 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -5588,7 +5588,7 @@ BackgroundWorkerInitializeConnection(const char *dbname, const char *username, u InitPostgres(dbname, InvalidOid, /* database to connect to */ username, InvalidOid, /* role to connect as */ false, /* never honor session_preload_libraries */ - (flags & BGWORKER_BYPASS_ALLOWCONN) != 0, /* ignore datallowconn? */ + flags, NULL); /* no out_dbname */ /* it had better not gotten out of "init" mode yet */ @@ -5615,7 +5615,7 @@ BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, uint32 flags) InitPostgres(NULL, dboid, /* database to connect to */ NULL, useroid, /* role to connect as */ false, /* never honor session_preload_libraries */ - (flags & BGWORKER_BYPASS_ALLOWCONN) != 0, /* ignore datallowconn? */ + flags, NULL); /* no out_dbname */ /* it had better not gotten out of "init" mode yet */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 21b9763183..f5ef8f52a8 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -4210,7 +4210,7 @@ PostgresMain(const char *dbname, const char *username) InitPostgres(dbname, InvalidOid, /* database to connect to */ username, InvalidOid, /* role to connect as */ !am_walsender, /* honor session_preload_libraries? */ - false, /* don't ignore datallowconn */ + 0, NULL); /* no out_dbname */ /* diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 1e671c560c..182d666852 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -725,7 +725,7 @@ has_rolreplication(Oid roleid) * Initialize user identity during normal backend startup */ void -InitializeSessionUserId(const char *rolename, Oid roleid) +InitializeSessionUserId(const char *rolename, Oid roleid, bool bypass_login_check) { HeapTuple roleTup; Form_pg_authid rform; @@ -789,7 +789,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid) /* * Is role allowed to login at all? */ - if (!rform->rolcanlogin) + if (!bypass_login_check && !rform->rolcanlogin) ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role \"%s\" is not permitted to log in", diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index df4d15a50f..6ccedffd62 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -41,6 +41,7 @@ #include "miscadmin.h" #include "pgstat.h" #include "postmaster/autovacuum.h" +#include "postmaster/bgworker.h" #include "postmaster/postmaster.h" #include "replication/slot.h" #include "replication/walsender.h" @@ -718,10 +719,12 @@ void InitPostgres(const char *in_dbname, Oid dboid, const char *username, Oid useroid, bool load_session_libraries, - bool override_allow_connections, + uint32 flags, char *out_dbname) { bool bootstrap = IsBootstrapProcessingMode(); + bool override_allow_connections = (flags & BGWORKER_BYPASS_ALLOWCONN) != 0; + bool bypass_login_check = (flags & BGWORKER_BYPASS_ROLELOGINCHECK) != 0; bool am_superuser; char *fullpath; char dbname[NAMEDATALEN]; @@ -901,7 +904,7 @@ InitPostgres(const char *in_dbname, Oid dboid, } else { - InitializeSessionUserId(username, useroid); + InitializeSessionUserId(username, useroid, bypass_login_check); am_superuser = superuser(); } } @@ -910,7 +913,7 @@ InitPostgres(const char *in_dbname, Oid dboid, /* normal multiuser case */ Assert(MyProcPort != NULL); PerformAuthentication(MyProcPort); - InitializeSessionUserId(username, useroid); + InitializeSessionUserId(username, useroid, false); /* ensure that auth_method is actually valid, aka authn_id is not NULL */ if (MyClientConnectionInfo.authn_id) InitializeSystemUser(MyClientConnectionInfo.authn_id, diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 14bd574fc2..7e6ee415cb 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -364,7 +364,8 @@ extern bool InSecurityRestrictedOperation(void); extern bool InNoForceRLSOperation(void); extern void GetUserIdAndContext(Oid *userid, bool *sec_def_context); extern void SetUserIdAndContext(Oid userid, bool sec_def_context); -extern void InitializeSessionUserId(const char *rolename, Oid roleid); +extern void InitializeSessionUserId(const char *rolename, Oid roleid, + bool bypass_login_check); extern void InitializeSessionUserIdStandalone(void); extern void SetSessionAuthorization(Oid userid, bool is_superuser); extern Oid GetCurrentRoleId(void); @@ -468,7 +469,7 @@ extern void InitializeMaxBackends(void); extern void InitPostgres(const char *in_dbname, Oid dboid, const char *username, Oid useroid, bool load_session_libraries, - bool override_allow_connections, + uint32 flags, char *out_dbname); extern void BaseInit(void); diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h index 7815507e3d..d7a5c1a946 100644 --- a/src/include/postmaster/bgworker.h +++ b/src/include/postmaster/bgworker.h @@ -150,9 +150,11 @@ extern void BackgroundWorkerInitializeConnectionByOid(Oid dboid, Oid useroid, ui * Flags to BackgroundWorkerInitializeConnection et al * * - * Allow bypassing datallowconn restrictions when connecting to database + * Allow bypassing datallowconn restrictions and login check when connecting + * to database */ -#define BGWORKER_BYPASS_ALLOWCONN 1 +#define BGWORKER_BYPASS_ALLOWCONN 0x0001 +#define BGWORKER_BYPASS_ROLELOGINCHECK 0x0002 /* Block/unblock signals in a background worker process */ diff --git a/src/test/modules/worker_spi/t/001_worker_spi.pl b/src/test/modules/worker_spi/t/001_worker_spi.pl index 2965acd789..ff8b72f5b5 100644 --- a/src/test/modules/worker_spi/t/001_worker_spi.pl +++ b/src/test/modules/worker_spi/t/001_worker_spi.pl @@ -93,4 +93,52 @@ ok( $node->poll_query_until( 'dynamic bgworkers all launched' ) or die "Timed out while waiting for dynamic bgworkers to be launched"; +# Check BGWORKER_BYPASS_ROLELOGINCHECK behaves as expected. + +# First create a role without login. +$node->safe_psql( + 'postgres', qq[ + CREATE ROLE nologrole with nologin; + ALTER ROLE nologrole with superuser; +]); + +my $log_start = get_log_size($node); + +# Ask the background workers to connect with this role. +$node->append_conf( + 'postgresql.conf', q{ +worker_spi.role = 'nologrole' +}); +$node->restart; + +# An error message should be issued. +ok( $node->log_contains( + "role \"nologrole\" is not permitted to log in", $log_start), + "nologrole not allowed to connect if BGWORKER_BYPASS_ROLELOGINCHECK is not set" +); + + +$log_start = get_log_size($node); + +# Ask the background workers to connect with this role with the flag in place. +$node->append_conf( + 'postgresql.conf', q{ +worker_spi.role = 'nologrole' +worker_spi.bypass_login_check = true +}); +$node->restart; + +# An error message should not be issued. +ok( !$node->log_contains( + "role \"nologrole\" is not permitted to log in", $log_start), + "nologrole allowed to connect if BGWORKER_BYPASS_ROLELOGINCHECK is set"); + done_testing(); + +# return the size of logfile of $node in bytes +sub get_log_size +{ + my ($node) = @_; + + return (stat $node->logfile)[7]; +} diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c index 98f8d4194b..d2ca99f2c5 100644 --- a/src/test/modules/worker_spi/worker_spi.c +++ b/src/test/modules/worker_spi/worker_spi.c @@ -52,6 +52,8 @@ PGDLLEXPORT void worker_spi_main(Datum main_arg) pg_attribute_noreturn(); static int worker_spi_naptime = 10; static int worker_spi_total_workers = 2; static char *worker_spi_database = NULL; +static char *worker_spi_role = NULL; +static bool worker_spi_bypass_login_check = false; /* value cached, fetched from shared memory */ static uint32 worker_spi_wait_event_main = 0; @@ -152,7 +154,10 @@ worker_spi_main(Datum main_arg) BackgroundWorkerUnblockSignals(); /* Connect to our database */ - BackgroundWorkerInitializeConnection(worker_spi_database, NULL, 0); + if (worker_spi_bypass_login_check) + BackgroundWorkerInitializeConnection(worker_spi_database, worker_spi_role, BGWORKER_BYPASS_ROLELOGINCHECK); + else + BackgroundWorkerInitializeConnection(worker_spi_database, worker_spi_role, 0); elog(LOG, "%s initialized with %s.%s", MyBgworkerEntry->bgw_name, table->schema, table->name); @@ -316,6 +321,24 @@ _PG_init(void) 0, NULL, NULL, NULL); + DefineCustomStringVariable("worker_spi.role", + "Role to connect with.", + NULL, + &worker_spi_role, + NULL, + PGC_SIGHUP, + 0, + NULL, NULL, NULL); + + DefineCustomBoolVariable("worker_spi.bypass_login_check", + "Should BGW allows to connect with non login role.", + NULL, + &worker_spi_bypass_login_check, + false, + PGC_SIGHUP, + 0, + NULL, NULL, NULL); + if (!process_shared_preload_libraries_in_progress) return; -- 2.34.1