Skip to content

Commit

Permalink
Added check for pg_checkpoint role presence (#807)
Browse files Browse the repository at this point in the history
* Added check for pg_checkpoint role presence

This commit provides the needed infrastructure in `repmgr` so if the `repmgr` database
user is a member of the `pg_checkpoint` role, and inherits its privileges, there is no 
need for such a user to be a superuser.

Co-authored-by: Martín Marqués <[email protected]>
  • Loading branch information
RealGreenDragon and martinmarques authored Sep 11, 2024
1 parent b4a0938 commit f69485c
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 9 deletions.
50 changes: 49 additions & 1 deletion dbutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,51 @@ get_wal_receiver_pid(PGconn *conn)
/* =============================== */


/*
* Determine if the user associated with the current connection can execute CHECKPOINT command.
* User must be a supersuer or a member of the pg_checkpoint default role (available from PostgreSQL 15).
*/
bool
can_execute_checkpoint(PGconn *conn)
{
PQExpBufferData query;
PGresult *res;
bool has_pg_checkpoint_role = false;

/* superusers can do anything, no role check needed */
if (is_superuser_connection(conn, NULL) == true)
return true;

/* pg_checkpoint available from PostgreSQL 15 */
if (PQserverVersion(conn) < 150000)
return false;

initPQExpBuffer(&query);
appendPQExpBufferStr(&query,
" SELECT pg_catalog.pg_has_role('pg_checkpoint','USAGE') ");

res = PQexec(conn, query.data);

if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
log_db_error(conn, query.data,
_("can_execute_checkpoint(): unable to query user roles"));
}
else
{
has_pg_checkpoint_role = atobool(PQgetvalue(res, 0, 0));
}
termPQExpBuffer(&query);
PQclear(res);

return has_pg_checkpoint_role;
}


/*
* Determine if the user associated with the current connection
* has sufficient permissions to use pg_promote function
*/
bool
can_execute_pg_promote(PGconn *conn)
{
Expand Down Expand Up @@ -2492,7 +2537,10 @@ get_repmgr_extension_status(PGconn *conn, t_extension_versions *extversions)
/* node management functions */
/* ========================= */

/* assumes superuser connection */
/*
* Assumes the connection can execute CHECKPOINT command.
* A check can be executed via 'can_execute_checkpoint' function.
*/
void
checkpoint(PGconn *conn)
{
Expand Down
1 change: 1 addition & 0 deletions dbutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ TimeLineHistoryEntry *get_timeline_history(PGconn *repl_conn, TimeLineID tli);
pid_t get_wal_receiver_pid(PGconn *conn);

/* user/role information functions */
bool can_execute_checkpoint(PGconn *conn);
bool can_execute_pg_promote(PGconn *conn);
bool can_disable_walsender(PGconn *conn);
bool connection_has_pg_monitor_role(PGconn *conn, const char *subrole);
Expand Down
6 changes: 6 additions & 0 deletions doc/configuration-permissions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@
Alternatively the meta-role <varname>pg_monitor</varname> can be granted, which includes membership
of the above predefined roles.
</para>
<para>
PostgreSQL 15 introduced the <varname>pg_checkpoint</varname> predefined role which allows a
non-superuser &repmgr; database user to perform a CHECKPOINT command.
</para>
<para>
Membership of these roles can be granted with e.g. <command>GRANT pg_read_all_stats TO repmgr</command>.
</para>
Expand Down Expand Up @@ -148,6 +152,8 @@
<link linkend="repmgr-standby-switchover">repmgr standby switchover</link>. This can only
be executed by a superuser; if the &repmgr; user is not a superuser,
the <option>-S</option>/<option>--superuser</option> should be used.
From PostgreSQL 15 the <varname>pg_checkpoint</varname> predefined role removes the need of
superuser permissions to perform <command>CHECKPOINT</command> command.
</simpara>
<simpara>
If &repmgr; is not able to execute <command>CHECKPOINT</command>,
Expand Down
3 changes: 2 additions & 1 deletion doc/repmgr-node-service.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
</para>
<para>
Note that a superuser connection is required to be able to execute the
<command>CHECKPOINT</command> command.
<command>CHECKPOINT</command> command. From PostgreSQL 15 the <varname>pg_checkpoint</varname>
predefined role removes the need for superuser permissions to perform <command>CHECKPOINT</command> command.
</para>
</listitem>
</varlistentry>
Expand Down
3 changes: 2 additions & 1 deletion doc/repmgr-standby-switchover.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@
<para>
Note that <command>CHECKPOINT</command> requires database superuser permissions to execute.
If the <literal>repmgr</literal> user is not a superuser, the name of a superuser should be
provided with the <option>-S</option>/<option>--superuser</option> option.
provided with the <option>-S</option>/<option>--superuser</option> option. From PostgreSQL 15 the <varname>pg_checkpoint</varname>
predefined role removes the need for superuser permissions to perform <command>CHECKPOINT</command> command.
</para>
<para>
If &repmgr; is unable to execute the <command>CHECKPOINT</command> command, the switchover
Expand Down
15 changes: 11 additions & 4 deletions repmgr-action-node.c
Original file line number Diff line number Diff line change
Expand Up @@ -2365,18 +2365,25 @@ do_node_service(void)
conn = establish_db_connection_by_params(&source_conninfo, true);
}

if (is_superuser_connection(conn, NULL) == false)
if (can_execute_checkpoint(conn) == false)
{
if (runtime_options.dry_run == true)
{
log_warning(_("a CHECKPOINT would be issued here but no superuser connection is available"));
log_warning(_("a CHECKPOINT would be issued here but no authorized connection is available"));
}
else
{
log_warning(_("a superuser connection is required to issue a CHECKPOINT"));
log_warning(_("an authorized connection is required to issue a CHECKPOINT"));
}

log_hint(_("provide a superuser with -S/--superuser"));
if (PQserverVersion(conn) >= 150000)
{
log_hint(_("provide a superuser with -S/--superuser or grant pg_checkpoint role to repmgr user"));
}
else
{
log_hint(_("provide a superuser with -S/--superuser"));
}
}
else
{
Expand Down
13 changes: 11 additions & 2 deletions repmgr-action-standby.c
Original file line number Diff line number Diff line change
Expand Up @@ -5288,7 +5288,7 @@ do_standby_switchover(void)
checkpoint_conn = superuser_conn;
}

if (is_superuser_connection(checkpoint_conn, NULL) == true)
if (can_execute_checkpoint(checkpoint_conn) == true)
{
log_notice(_("issuing CHECKPOINT on node \"%s\" (ID: %i) "),
config_file_options.node_name,
Expand All @@ -5297,7 +5297,16 @@ do_standby_switchover(void)
}
else
{
log_warning(_("no superuser connection available, unable to issue CHECKPOINT"));
log_warning(_("no authorized connection available, unable to issue CHECKPOINT"));

if (PQserverVersion(conn) >= 150000)
{
log_hint(_("provide a superuser with -S/--superuser or grant pg_checkpoint role to repmgr user"));
}
else
{
log_hint(_("provide a superuser with -S/--superuser"));
}
}
}

Expand Down

0 comments on commit f69485c

Please sign in to comment.