Merge pull request #5237 from sysown/v3.0_pgsql-monitor-sslsupport-5205

Add SSL support for backend connections in PGSQL monitor
pull/5254/head^2
René Cannaò 4 months ago committed by GitHub
commit 2667540fcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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<table_def_t> tables_defs_monitor {

@ -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<SQLite3_result> fetch_hgm_srvs_conf(PgSQL_HostGroups_Manager* hgm, co
vector<mon_srv_t> ext_srvs(const unique_ptr<SQLite3_result>& srvs_info) {
vector<mon_srv_t> srvs {};
srvs.reserve(srvs_info->rows.size());
for (const auto& row : srvs_info->rows) {
srvs.push_back({
string { row->fields[0] },
static_cast<uint16_t>(std::atoi(row->fields[1])),
static_cast<bool>(std::atoi(row->fields[2]))
static_cast<bool>(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;
}
@ -624,6 +637,13 @@ pair<short,bool> 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) {
@ -870,18 +890,44 @@ pair<bool,pgsql_conn_t> 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) {

@ -4425,6 +4425,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";

@ -239,5 +239,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" ]
}

@ -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 <unistd.h>
#include <string>
#include <sstream>
#include <chrono>
#include <thread>
#include "libpq-fe.h"
#include "command_line.h"
#include "tap.h"
#include "utils.h"
CommandLine cl;
using PGConnPtr = std::unique_ptr<PGconn, decltype(&PQfinish)>;
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();
}
Loading…
Cancel
Save