From fb5ce720f31747997e5cb753dbe2b3e125388e85 Mon Sep 17 00:00:00 2001 From: Ian Barwick Date: Fri, 6 Mar 2020 12:50:23 +0900 Subject: [PATCH] standby promote: fall back to "pg_ctl promote" if necessary From PostgreSQL 12, the SQL-level function "pg_promote()" can be used to promote a PostgreSQL instance, however usage is restricted to superusers and users to whom explicit execution permission for this function has been granted. Therefore, if execution permission is not available, fall back to "pg_ctl promote". --- HISTORY | 2 + dbutils.c | 37 ++++++++++++++++ dbutils.h | 1 + doc/appendix-release-notes.xml | 10 +++++ doc/repmgr-standby-promote.xml | 24 ++++++----- repmgr-action-standby.c | 79 +++++++++++++++++++++------------- 6 files changed, 111 insertions(+), 42 deletions(-) diff --git a/HISTORY b/HISTORY index 36f33f48..9f2897e7 100644 --- a/HISTORY +++ b/HISTORY @@ -12,6 +12,8 @@ repmgr: add replication configuration file ownership check to "standby switchover" (Ian) repmgr: consolidate replication connection code (Ian) + repmgr: check permissions for "pg_promote()" and fall back to pg_ctl + if necessary (Ian) 5.0 2019-10-15 general: add PostgreSQL 12 support (Ian) diff --git a/dbutils.c b/dbutils.c index 273dcadb..ded07784 100644 --- a/dbutils.c +++ b/dbutils.c @@ -1723,6 +1723,43 @@ get_timeline_history(PGconn *repl_conn, TimeLineID tli) /* user/role information functions */ /* =============================== */ + +bool +can_execute_pg_promote(PGconn *conn) +{ + PQExpBufferData query; + PGresult *res; + bool has_pg_promote= false; + + /* pg_promote() available from PostgreSQL 12 */ + if(PQserverVersion(conn) < 120000) + return false; + + initPQExpBuffer(&query); + appendPQExpBufferStr(&query, + " SELECT pg_catalog.has_function_privilege( " + " CURRENT_USER, " + " 'pg_catalog.pg_promote(bool,int)', " + " 'execute' " + " )"); + + res = PQexec(conn, query.data); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_db_error(conn, query.data, + _("can_execute_pg_promote(): unable to query user function privilege")); + } + else + { + has_pg_promote = atobool(PQgetvalue(res, 0, 0)); + } + termPQExpBuffer(&query); + + return has_pg_promote; +} + + bool connection_has_pg_settings(PGconn *conn) { diff --git a/dbutils.h b/dbutils.h index ad1f86c6..4923252b 100644 --- a/dbutils.h +++ b/dbutils.h @@ -441,6 +441,7 @@ uint64 system_identifier(PGconn *conn); TimeLineHistoryEntry *get_timeline_history(PGconn *repl_conn, TimeLineID tli); /* user/role information functions */ +bool can_execute_pg_promote(PGconn *conn); bool connection_has_pg_settings(PGconn *conn); bool is_replication_role(PGconn *conn, char *rolname); bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo); diff --git a/doc/appendix-release-notes.xml b/doc/appendix-release-notes.xml index 0f557683..1eefcdeb 100644 --- a/doc/appendix-release-notes.xml +++ b/doc/appendix-release-notes.xml @@ -88,6 +88,16 @@ + + + repmgr standby promote + will check if the repmgr user has permission to execute + pg_promote() and fall back to pg_ctl promote if + necessary. + + + + Fix situation where replication connections were not created correctly, which diff --git a/doc/repmgr-standby-promote.xml b/doc/repmgr-standby-promote.xml index 72be9595..4a26a3a2 100644 --- a/doc/repmgr-standby-promote.xml +++ b/doc/repmgr-standby-promote.xml @@ -106,20 +106,22 @@ User permission requirements pg_promote() (PostgreSQL 12) - From PostgreSQL 12, &repmgr; uses the pg_promote() function to promote a standby - to primary. + From PostgreSQL 12, &repmgr; will attempt to use the pg_promote() function + to promote a standby to primary. - By default, execution of pg_promote() is restricted to superusers. - If the repmgr use is not a superuser, execution permission for this - function must be granted with e.g.: - + By default, execution of pg_promote() is restricted to superusers. + If the repmgr user does not have permission to execute + pg_promote(), &repmgr; will fall back to using pg_ctl promote. + + + + If the repmgr user is not a superuser, execution permission for this + function can be granted with e.g.: + GRANT EXECUTE ON FUNCTION pg_catalog.pg_promote TO repmgr - - - A future &repmgr; release will relax this restriction by falling back to - pg_ctl promote, as used for pre-PostgreSQL 12 versions. - + + diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c index 000b9070..201ba433 100644 --- a/repmgr-action-standby.c +++ b/repmgr-action-standby.c @@ -2476,45 +2476,62 @@ _do_standby_promote_internal(PGconn *conn) * option so we can't be sure when or if the promotion completes. For now * we'll poll the server until the default timeout (60 seconds) * - * For PostgreSQL 12+, use the pg_promote() function - note this is - * experimental + * For PostgreSQL 12+, use the pg_promote() function, unless one of + * "service_promote_command" or "use_pg_ctl_promote" is set. */ - log_notice(_("promoting standby to primary")); - - if (PQserverVersion(conn) >= 120000) { - log_detail(_("promoting server \"%s\" (ID: %i) using pg_promote()"), - local_node_record.node_name, - local_node_record.node_id); + bool use_pg_promote = false; - /* - * We'll check for promotion success ourselves, but will abort - * if some unrecoverable error prevented the function from being - * executed. - */ - if (!promote_standby(conn, false, 0)) + + if (PQserverVersion(conn) >= 120000) { - log_error(_("unable to promote server from standby to primary")); - exit(ERR_PROMOTION_FAIL); + use_pg_promote = true; + + if (can_execute_pg_promote(conn) == false) + { + use_pg_promote = false; + log_info(_("user \"%s\" does not have permission to execute \"pg_promote()\", falling back to \"pg_ctl promote\""), + PQuser(conn)); + } } - } - else - { - char script[MAXLEN]; - int r; - get_server_action(ACTION_PROMOTE, script, (char *) data_dir); + log_notice(_("promoting standby to primary")); - log_detail(_("promoting server \"%s\" (ID: %i) using \"%s\""), - local_node_record.node_name, - local_node_record.node_id, - script); - - r = system(script); - if (r != 0) + if (use_pg_promote == true) { - log_error(_("unable to promote server from standby to primary")); - exit(ERR_PROMOTION_FAIL); + log_detail(_("promoting server \"%s\" (ID: %i) using pg_promote()"), + local_node_record.node_name, + local_node_record.node_id); + + /* + * We'll check for promotion success ourselves, but will abort + * if some unrecoverable error prevented the function from being + * executed. + */ + if (!promote_standby(conn, false, 0)) + { + log_error(_("unable to promote server from standby to primary")); + exit(ERR_PROMOTION_FAIL); + } + } + else + { + char script[MAXLEN]; + int r; + + get_server_action(ACTION_PROMOTE, script, (char *) data_dir); + + log_detail(_("promoting server \"%s\" (ID: %i) using \"%s\""), + local_node_record.node_name, + local_node_record.node_id, + script); + + r = system(script); + if (r != 0) + { + log_error(_("unable to promote server from standby to primary")); + exit(ERR_PROMOTION_FAIL); + } } }