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); + } } }