is_downstream_node_attached(): avoid false negative

If the provided connection does not have sufficient permission to read
"pg_stat_replication.state", and there is an entry for the node in
"pg_stat_replication", assume it's connected. Finer-grained detection
requires additional user permissions, nothing we can do about that.
This commit is contained in:
Ian Barwick
2020-09-01 14:28:40 +09:00
parent 13e7c679cd
commit 8d57d7e001
2 changed files with 82 additions and 16 deletions

View File

@@ -1903,6 +1903,62 @@ connection_has_pg_settings(PGconn *conn)
} }
/*
* Determine if the user associated with the current connection is
* a member of the "pg_monitor" default role, or optionally one
* of its three constituent "subroles".
*/
bool
connection_has_pg_monitor_role(PGconn *conn, const char *subrole)
{
PQExpBufferData query;
PGresult *res;
bool has_pg_monitor_role = false;
/* superusers can read anything, no role check needed */
if (is_superuser_connection(conn, NULL) == true)
return true;
/* pg_monitor and associated "subroles" introduced in PostgreSQL 10 */
if (PQserverVersion(conn) < 100000)
return false;
initPQExpBuffer(&query);
appendPQExpBufferStr(&query,
" SELECT CASE "
" WHEN pg_catalog.pg_has_role('pg_monitor','MEMBER') "
" THEN TRUE ");
if (subrole != NULL)
{
appendPQExpBuffer(&query,
" WHEN pg_catalog.pg_has_role('%s','MEMBER') "
" THEN TRUE ",
subrole);
}
appendPQExpBufferStr(&query,
" ELSE FALSE "
" END AS has_pg_monitor");
res = PQexec(conn, query.data);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
log_db_error(conn, query.data,
_("connection_has_pg_monitor_role(): unable to query user roles"));
}
else
{
has_pg_monitor_role = atobool(PQgetvalue(res, 0, 0));
}
termPQExpBuffer(&query);
PQclear(res);
return has_pg_monitor_role;
}
bool bool
is_replication_role(PGconn *conn, char *rolname) is_replication_role(PGconn *conn, char *rolname)
{ {
@@ -5772,7 +5828,6 @@ is_downstream_node_attached(PGconn *conn, char *node_name, char **node_state)
{ {
PQExpBufferData query; PQExpBufferData query;
PGresult *res = NULL; PGresult *res = NULL;
const char *state = NULL;
initPQExpBuffer(&query); initPQExpBuffer(&query);
@@ -5824,26 +5879,36 @@ is_downstream_node_attached(PGconn *conn, char *node_name, char **node_state)
} }
/* /*
* If the connec * If the connection is not a superuser or member of pg_read_all_stats, we
* won't be able to retrieve the "state" column, so we'll assume
* the node is attached.
*/ */
state = PQgetvalue(res, 0, 1); if (connection_has_pg_monitor_role(conn, "pg_read_all_stats"))
if (node_state != NULL)
{ {
*node_state = palloc0(strlen(state) + 1); const char *state = PQgetvalue(res, 0, 1);
strncpy(*node_state, state, strlen(state));
if (node_state != NULL)
{
*node_state = palloc0(strlen(state) + 1);
strncpy(*node_state, state, strlen(state));
}
if (strcmp(state, "streaming") != 0)
{
log_warning(_("node \"%s\" attached in state \"%s\""),
node_name,
state);
PQclear(res);
return NODE_NOT_ATTACHED;
}
} }
else if (node_state != NULL)
if (strcmp(state, "streaming") != 0)
{ {
log_warning(_("node \"%s\" attached in state \"%s\""), *node_state = palloc0(1);
node_name, *node_state[0] = '\0';
state);
PQclear(res);
return NODE_NOT_ATTACHED;
} }
PQclear(res); PQclear(res);

View File

@@ -454,6 +454,7 @@ TimeLineHistoryEntry *get_timeline_history(PGconn *repl_conn, TimeLineID tli);
/* user/role information functions */ /* user/role information functions */
bool can_execute_pg_promote(PGconn *conn); bool can_execute_pg_promote(PGconn *conn);
bool connection_has_pg_settings(PGconn *conn); bool connection_has_pg_settings(PGconn *conn);
bool connection_has_pg_monitor_role(PGconn *conn, const char *subrole);
bool is_replication_role(PGconn *conn, char *rolname); bool is_replication_role(PGconn *conn, char *rolname);
bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo); bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo);