From 7205f424a2a3a1826635cb73faaabcc62290bc5e Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Wed, 3 Dec 2025 17:16:32 +0500 Subject: [PATCH 1/4] Add SSL support for backend connections in PGSQL monitor --- lib/PgSQL_Monitor.cpp | 61 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/lib/PgSQL_Monitor.cpp b/lib/PgSQL_Monitor.cpp index 4db02cf77a..f465cf3625 100644 --- a/lib/PgSQL_Monitor.cpp +++ b/lib/PgSQL_Monitor.cpp @@ -266,6 +266,13 @@ struct mon_srv_t { string addr; uint16_t port; bool ssl; + struct ssl_opts_t { + string ssl_p2s_key; + string ssl_p2s_cert; + string ssl_p2s_ca; + string ssl_p2s_crl; + string ssl_p2s_crlpath; + } ssl_opt; }; struct mon_user_t { @@ -353,15 +360,21 @@ unique_ptr fetch_hgm_srvs_conf(PgSQL_HostGroups_Manager* hgm, co vector ext_srvs(const unique_ptr& srvs_info) { vector srvs {}; - + srvs.reserve(srvs_info->rows.size()); for (const auto& row : srvs_info->rows) { srvs.push_back({ string { row->fields[0] }, static_cast(std::atoi(row->fields[1])), - static_cast(std::atoi(row->fields[2])) + static_cast(std::atoi(row->fields[2])), + mon_srv_t::ssl_opts_t { + string { pgsql_thread___ssl_p2s_key ? pgsql_thread___ssl_p2s_key : ""}, + string { pgsql_thread___ssl_p2s_cert ? pgsql_thread___ssl_p2s_cert : "" }, + string { pgsql_thread___ssl_p2s_ca ? pgsql_thread___ssl_p2s_ca : "" }, + string { pgsql_thread___ssl_p2s_crl ? pgsql_thread___ssl_p2s_crl : "" }, + string { pgsql_thread___ssl_p2s_crlpath ? pgsql_thread___ssl_p2s_crlpath : ""} + } }); } - return srvs; } @@ -870,18 +883,44 @@ pair get_task_conn(conn_pool_t& conn_pool, task_st_t& task_st } } +static void append_conninfo_param(std::ostringstream& conninfo, const std::string& key, const std::string& val) { + if (val.empty()) return; + + std::string escaped_val; + escaped_val.reserve(val.length() * 2); // Reserve maximum possible size + + for (char c : val) { + if (c == '\'' || c == '\\') { + escaped_val.push_back('\\'); + } + escaped_val.push_back(c); + } + + conninfo << key << "='" << escaped_val << "' "; +} + string build_conn_str(const task_st_t& task_st) { const mon_srv_t& srv_info { task_st.op_st.srv_info }; const mon_user_t& user_info { task_st.op_st.user_info }; - return string { - "host='" + srv_info.addr + "' " - + "port='" + std::to_string(srv_info.port) + "' " - + "user='" + user_info.user + "' " - + "password='" + user_info.pass + "' " - + "dbname='" + user_info.dbname + "' " - + "application_name=ProxySQL-Monitor" - }; + std::ostringstream conninfo; + append_conninfo_param(conninfo, "user", user_info.user); // username + append_conninfo_param(conninfo, "password", user_info.pass); // password + append_conninfo_param(conninfo, "dbname", user_info.dbname); // dbname + append_conninfo_param(conninfo, "host", srv_info.addr); // backend address + conninfo << "port=" << srv_info.port << " "; // backend port + conninfo << "application_name=ProxySQL-Monitor "; // application name + if (srv_info.ssl) { + conninfo << "sslmode='require' "; // SSL required + append_conninfo_param(conninfo, "sslkey", srv_info.ssl_opt.ssl_p2s_key); + append_conninfo_param(conninfo, "sslcert", srv_info.ssl_opt.ssl_p2s_cert); + append_conninfo_param(conninfo, "sslrootcert", srv_info.ssl_opt.ssl_p2s_ca); + append_conninfo_param(conninfo, "sslcrl", srv_info.ssl_opt.ssl_p2s_crl); + append_conninfo_param(conninfo, "sslcrldir", srv_info.ssl_opt.ssl_p2s_crlpath); + } else { + conninfo << "sslmode='disable' "; // not supporting SSL + } + return conninfo.str(); } pgsql_conn_t create_new_conn(task_st_t& task_st) { From fae283cf7ee4a4fe3e38611182f4891c55d22d64 Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Thu, 11 Dec 2025 01:31:32 +0500 Subject: [PATCH 2/4] Add SSL and non-SSL connection OK metrics for PostgreSQL monitor connections Adds two new metrics, ssl_connections_OK and non_ssl_connections_OK, to improve visibility into PostgreSQL monitor connection status. --- include/PgSQL_Monitor.hpp | 2 ++ lib/PgSQL_Monitor.cpp | 7 +++++++ lib/PgSQL_Thread.cpp | 12 ++++++++++++ 3 files changed, 21 insertions(+) diff --git a/include/PgSQL_Monitor.hpp b/include/PgSQL_Monitor.hpp index bd5a3b7b78..70b6281db0 100644 --- a/include/PgSQL_Monitor.hpp +++ b/include/PgSQL_Monitor.hpp @@ -39,6 +39,8 @@ struct PgSQL_Monitor { uint64_t ping_check_OK { 0 }; uint64_t readonly_check_ERR { 0 }; uint64_t readonly_check_OK { 0 }; + uint64_t ssl_connections_OK { 0 }; + uint64_t non_ssl_connections_OK { 0 }; /////////////////////////////////////////////////////////////////////////// std::vector tables_defs_monitor { diff --git a/lib/PgSQL_Monitor.cpp b/lib/PgSQL_Monitor.cpp index f465cf3625..8088abc513 100644 --- a/lib/PgSQL_Monitor.cpp +++ b/lib/PgSQL_Monitor.cpp @@ -637,6 +637,13 @@ pair handle_async_connect_cont(state_t& st, short revent) { case PGRES_POLLING_OK: pgconn.state = ASYNC_ST::ASYNC_CONNECT_END; + // connection successful, update SSL stats + if (PQsslInUse(pgconn.conn)) { + __sync_fetch_and_add(&GloPgMon->ssl_connections_OK, 1); + } else { + __sync_fetch_and_add(&GloPgMon->non_ssl_connections_OK, 1); + } + if (st.task.type == task_type_t::connect) { st.task.end = monotonic_time(); } else if (st.task.type == task_type_t::ping) { diff --git a/lib/PgSQL_Thread.cpp b/lib/PgSQL_Thread.cpp index d1084d557d..3e45ce974e 100644 --- a/lib/PgSQL_Thread.cpp +++ b/lib/PgSQL_Thread.cpp @@ -4482,6 +4482,18 @@ SQLite3_result* PgSQL_Threads_Handler::SQL3_GlobalStatus(bool _memory) { pta[1] = buf; result->add_row(pta); } + { + pta[0] = (char*)"PgSQL_Monitor_ssl_connections_OK"; + sprintf(buf, "%lu", GloPgMon->ssl_connections_OK); + pta[1] = buf; + result->add_row(pta); + } + { + pta[0] = (char*)"PgSQL_Monitor_non_ssl_connections_OK"; + sprintf(buf, "%lu", GloPgMon->non_ssl_connections_OK); + pta[1] = buf; + result->add_row(pta); + } /* { pta[0] = (char*)"MySQL_Monitor_replication_lag_check_OK"; From 0e7b5e2ba82ca0bc49b31e717ff982aa91cacbee Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Thu, 11 Dec 2025 01:34:02 +0500 Subject: [PATCH 3/4] Added TAP test --- .../pgsql-monitor_ssl_connections_test-t.cpp | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 test/tap/tests/pgsql-monitor_ssl_connections_test-t.cpp diff --git a/test/tap/tests/pgsql-monitor_ssl_connections_test-t.cpp b/test/tap/tests/pgsql-monitor_ssl_connections_test-t.cpp new file mode 100644 index 0000000000..48d70f8c01 --- /dev/null +++ b/test/tap/tests/pgsql-monitor_ssl_connections_test-t.cpp @@ -0,0 +1,191 @@ +/** + * @file pgsql-monitor_ssl_connections_test-t.cpp + * @brief Intention: validate that ProxySQL's PostgreSQL monitor correctly establishes SSL and non-SSL + * connections depending on server configuration. The test runs in two phases: first with `use_ssl=1` + * to ensure only SSL connection counters increase, and then with `use_ssl=0` to ensure only non-SSL + * counters increase. + */ + +#include +#include +#include +#include +#include +#include "libpq-fe.h" +#include "command_line.h" +#include "tap.h" +#include "utils.h" + +CommandLine cl; + +using PGConnPtr = std::unique_ptr; + +enum ConnType { + ADMIN, + BACKEND +}; + +PGConnPtr createNewConnection(ConnType conn_type, const std::string& options = "", bool with_ssl = false) { + + const char* host = (conn_type == BACKEND) ? cl.pgsql_host : cl.pgsql_admin_host; + int port = (conn_type == BACKEND) ? cl.pgsql_port : cl.pgsql_admin_port; + const char* username = (conn_type == BACKEND) ? cl.pgsql_root_username : cl.admin_username; + const char* password = (conn_type == BACKEND) ? cl.pgsql_root_password : cl.admin_password; + + std::stringstream ss; + + ss << "host=" << host << " port=" << port; + ss << " user=" << username << " password=" << password; + ss << (with_ssl ? " sslmode=require" : " sslmode=disable"); + + if (options.empty() == false) { + ss << " options='" << options << "'"; + } + + PGconn* conn = PQconnectdb(ss.str().c_str()); + if (PQstatus(conn) != CONNECTION_OK) { + fprintf(stderr, "Connection failed to '%s': %s", (conn_type == BACKEND ? "Backend" : "Admin"), PQerrorMessage(conn)); + PQfinish(conn); + return PGConnPtr(nullptr, &PQfinish); + } + return PGConnPtr(conn, &PQfinish); +} + +static long getMonitorValue(PGConnPtr& admin, const char* varname) { + std::stringstream q; + q << "SELECT Variable_Value FROM stats_pgsql_global " + "WHERE Variable_Name='" << varname << "';"; + + PGresult* res = PQexec(admin.get(), q.str().c_str()); + if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) { + PQclear(res); + return -1; + } + long v = atol(PQgetvalue(res, 0, 0)); + PQclear(res); + return v; +} + +static long getConnectInterval(PGConnPtr& admin) { + PGresult* res = PQexec(admin.get(), + "SELECT Variable_Value FROM global_variables WHERE Variable_Name='pgsql-monitor_connect_interval';" + ); + if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) { + PQclear(res); + return 1000; // default fallback + } + long v = atol(PQgetvalue(res, 0, 0)); + PQclear(res); + return v; +} + +static bool setUseSSL(PGConnPtr& admin, int value) { + std::stringstream q; + q << "UPDATE pgsql_servers SET use_ssl=" << value << ";"; + PGresult* res = PQexec(admin.get(), q.str().c_str()); + bool ok = PQresultStatus(res) == PGRES_COMMAND_OK; + PQclear(res); + + PGresult* load = PQexec(admin.get(), "LOAD PGSQL SERVERS TO RUNTIME;"); + bool ok2 = PQresultStatus(load) == PGRES_COMMAND_OK; + PQclear(load); + usleep(10000); + return ok && ok2; +} + +static bool setConnectInterval(PGConnPtr& admin, int value) { + std::stringstream q; + q << "SET pgsql-monitor_connect_interval=" << value << ";"; + PGresult* res = PQexec(admin.get(), q.str().c_str()); + bool ok = PQresultStatus(res) == PGRES_COMMAND_OK; + PQclear(res); + + PGresult* load = PQexec(admin.get(), "LOAD PGSQL VARIABLES TO RUNTIME;"); + bool ok2 = PQresultStatus(load) == PGRES_COMMAND_OK; + PQclear(load); + usleep(10000); + return ok && ok2; +} + +int main(int argc, char** argv) { + + plan(7); + + if (cl.getEnv()) + return exit_status(); + + // ----------------------------------------- + // Connect to ADMIN + // ----------------------------------------- + auto admin = createNewConnection(ADMIN); + ok(admin != nullptr, "ADMIN connection created"); + + long original_connect_interval_ms = getConnectInterval(admin); + diag("Original pgsql-monitor_connect_interval = %ld ms", original_connect_interval_ms); + + setConnectInterval(admin, 2000); // set to 2 second for faster test + usleep(original_connect_interval_ms * 1000); // microseconds + + long connect_interval_ms = getConnectInterval(admin); + diag("Updated pgsql-monitor_connect_interval = %ld ms", connect_interval_ms); + + // ############################################################### + // PHASE 1: TEST SSL (use_ssl = 1) + // ############################################################### + diag("---- PHASE 1: Checking SSL monitoring ----"); + + ok(setUseSSL(admin, 1), "Set pgsql_server -> use_ssl = 1"); + + long initial_ssl = getMonitorValue(admin, "PgSQL_Monitor_ssl_connections_OK"); + long initial_non = getMonitorValue(admin, "PgSQL_Monitor_non_ssl_connections_OK"); + + diag("Initial SSL OK: %ld", initial_ssl); + diag("Initial NON-SSL OK: %ld", initial_non); + + usleep((connect_interval_ms * 2) * 1000); // microseconds + + long after_ssl = getMonitorValue(admin, "PgSQL_Monitor_ssl_connections_OK"); + long after_non = getMonitorValue(admin, "PgSQL_Monitor_non_ssl_connections_OK"); + + diag("After SSL mode -> SSL OK: %ld", after_ssl); + diag("After SSL mode -> NON-SSL OK: %ld", after_non); + + ok(after_ssl > initial_ssl, + "SSL monitoring increased when use_ssl=1"); + + ok(after_non == initial_non, + "NON-SSL monitoring unchanged when use_ssl=1"); + + // ############################################################### + // PHASE 2: TEST NON-SSL (use_ssl = 0) + // ############################################################### + diag("---- PHASE 2: Checking NON-SSL monitoring ----"); + + ok(setUseSSL(admin, 0), "Set pgsql_server -> use_ssl = 0"); + + long initial_ssl2 = getMonitorValue(admin, "PgSQL_Monitor_ssl_connections_OK"); + long initial_non2 = getMonitorValue(admin, "PgSQL_Monitor_non_ssl_connections_OK"); + + diag("Initial SSL OK (phase2): %ld", initial_ssl2); + diag("Initial NON-SSL OK (phase2): %ld", initial_non2); + + usleep((connect_interval_ms * 2) * 1000); // microseconds + + long after_ssl2 = getMonitorValue(admin, "PgSQL_Monitor_ssl_connections_OK"); + long after_non2 = getMonitorValue(admin, "PgSQL_Monitor_non_ssl_connections_OK"); + + diag("After NON-SSL mode -> SSL OK: %ld", after_ssl2); + diag("After NON-SSL mode -> NON-SSL OK: %ld", after_non2); + + ok(after_non2 > initial_non2, + "NON-SSL monitoring increased when use_ssl=0"); + + ok(after_ssl2 == initial_ssl2, + "SSL monitoring unchanged when use_ssl=0"); + + diag("SSL + NON-SSL monitoring test completed successfully"); + + setConnectInterval(admin, original_connect_interval_ms); // reset to original value + + return exit_status(); +} From d1b003a0e453817947effa346c824e88a9bc5f09 Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Thu, 11 Dec 2025 11:15:57 +0500 Subject: [PATCH 4/4] Added TAP test to groups.json --- test/tap/groups/groups.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index b95ebb3f84..fdeae35793 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -234,5 +234,6 @@ "test_ssl_fast_forward-2_libmysql-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], "test_ssl_fast_forward-3_libmariadb-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], "test_ssl_fast_forward-3_libmysql-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], - "test_ignore_min_gtid-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ] + "test_ignore_min_gtid-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], + "pgsql-monitor_ssl_connections_test-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ] }