Merge pull request #5567 from sysown/v3.0_pgsql_sslkeylog_5281

Add SSL/TLS traffic decryption for PostgreSQL backend connections
fix/ci-workflow-run-chain-pr-sha
René Cannaò 1 month ago committed by GitHub
commit 1a8e4ed918
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

1
deps/Makefile vendored

@ -370,6 +370,7 @@ postgresql/postgresql/src/interfaces/libpq/libpq.a:
cd postgresql/postgresql && patch -p0 < ../fmt_err_msg.patch
cd postgresql/postgresql && patch -p0 < ../bind_fmt_text.patch
cd postgresql/postgresql && patch -p0 < ../pqsendpipelinesync.patch
cd postgresql/postgresql && patch -p0 < ../sslkeylogfile.patch
ifeq ($(UNAME_S),Darwin)
cd postgresql/postgresql && LDFLAGS="-L$$(brew --prefix icu4c)/lib" CPPFLAGS="-I$$(brew --prefix icu4c)/include" PKG_CONFIG_PATH="$$(brew --prefix icu4c)/lib/pkgconfig:$$PKG_CONFIG_PATH" DYLD_LIBRARY_PATH="$(SSL_LDIR):$$DYLD_LIBRARY_PATH" ./configure --with-ssl=openssl --with-includes="$(SSL_IDIR)" --with-libraries="$(SSL_LDIR)" --without-readline --with-icu
else

@ -0,0 +1,63 @@
diff -ruN ../tmp/src/interfaces/libpq/fe-secure-openssl.c ./src/interfaces/libpq/fe-secure-openssl.c
--- ../tmp/src/interfaces/libpq/fe-secure-openssl.c 2025-08-11 21:06:43.000000000 +0000
+++ ./src/interfaces/libpq/fe-secure-openssl.c 2026-04-03 00:00:00.000000000 +0000
@@ -97,6 +97,8 @@
static PQsslKeyPassHook_OpenSSL_type PQsslKeyPassHook = NULL;
static int ssl_protocol_version_to_openssl(const char *protocol);
+
+static PQsslKeyLogCallback_type PQsslKeyLogCB = NULL;
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -972,6 +974,10 @@
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ /* Set SSL keylog callback if configured (for TLS traffic decryption) */
+ if (PQsslKeyLogCB)
+ SSL_CTX_set_keylog_callback(SSL_context, (void(*)(const SSL*, const char*))PQsslKeyLogCB);
+
/* Set the minimum and maximum protocol versions if necessary */
if (conn->ssl_min_protocol_version &&
strlen(conn->ssl_min_protocol_version) != 0)
@@ -1758,6 +1764,24 @@
return NULL;
}
+/*
+ * SSL Key Log callback support
+ *
+ * Global callback for writing TLS secrets to a keylog file.
+ * Follows the same pattern as PQsslKeyPassHook.
+ */
+PQsslKeyLogCallback_type
+PQgetSSLKeyLogCallback(void)
+{
+ return PQsslKeyLogCB;
+}
+
+void
+PQsetSSLKeyLogCallback(PQsslKeyLogCallback_type cb)
+{
+ PQsslKeyLogCB = cb;
+}
+
const char *const *
PQsslAttributeNames(PGconn *conn)
{
diff -ruN ../tmp/src/interfaces/libpq/libpq-fe.h ./src/interfaces/libpq/libpq-fe.h
--- ../tmp/src/interfaces/libpq/libpq-fe.h 2025-08-11 21:06:43.000000000 +0000
+++ ./src/interfaces/libpq/libpq-fe.h 2026-04-03 00:00:00.000000000 +0000
@@ -669,6 +669,11 @@
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* Support for SSL key log callback (TLS traffic decryption) */
+typedef void (*PQsslKeyLogCallback_type)(const void *ssl, const char *line);
+extern PQsslKeyLogCallback_type PQgetSSLKeyLogCallback(void);
+extern void PQsetSSLKeyLogCallback(PQsslKeyLogCallback_type cb);
+
#ifdef __cplusplus
}
#endif

@ -8,8 +8,8 @@ SSL/TLS key logging is a debugging feature that allows ProxySQL to write TLS enc
This feature is primarily useful for:
- **Debugging TLS connection issues** between clients and ProxySQL
- **Analyzing encrypted traffic** without modifying application code
- **Debugging TLS connection issues** between clients and ProxySQL, or between ProxySQL and MySQL/PostgreSQL backends
- **Analyzing encrypted backend traffic** to MySQL and PostgreSQL servers without modifying application code
- **Troubleshooting TLS handshake problems**
- **Performance analysis** of TLS connections
- **Security auditing** of TLS configurations
@ -222,11 +222,16 @@ In production environments, you typically don't run Wireshark directly on the se
On the ProxySQL server, capture network traffic to a pcap file:
```bash
# Capture on the interface ProxySQL is listening on (e.g., eth0)
# Replace 6033 with your ProxySQL MySQL port
# Capture MySQL frontend traffic (client → ProxySQL)
sudo tcpdump -i eth0 -w /tmp/proxysql_debug.pcap port 6033
# Or capture traffic between specific hosts
# Capture PgSQL frontend traffic (client → ProxySQL)
sudo tcpdump -i eth0 -w /tmp/proxysql_debug.pcap port 6133
# Capture PgSQL backend traffic (ProxySQL → PostgreSQL server)
sudo tcpdump -i eth0 -w /tmp/proxysql_debug.pcap port 5432
# Capture traffic between specific hosts
sudo tcpdump -i eth0 -w /tmp/proxysql_debug.pcap host client_ip and host proxysql_ip
# Run for a specific duration
@ -271,6 +276,9 @@ On your analysis system with Wireshark installed:
# Show only MySQL packets
mysql
# Show only PostgreSQL packets
pgsql
# Show TLS handshake
tls.handshake.type == 1
@ -407,11 +415,21 @@ mysql_variables=
**Solutions:**
1. Verify TLS is actually being used:
```sql
-- Check if connections are using TLS
-- Check MySQL backend connections
SELECT * FROM stats_mysql_connection_pool;
-- Check PgSQL backend connections
SELECT * FROM stats_pgsql_connection_pool;
```
2. For PgSQL backends, ensure `use_ssl=1` is set on the servers:
```sql
SELECT hostgroup_id, hostname, port, use_ssl FROM pgsql_servers;
```
3. Keylog entries are only written during **new** SSL handshakes. Existing pooled connections won't generate entries. To force new handshakes, reload servers:
```sql
LOAD PGSQL SERVERS TO RUNTIME;
```
2. Make sure clients are connecting with SSL/TLS
3. Check that `admin-ssl_keylog_file` is loaded into runtime:
4. Make sure clients are connecting with SSL/TLS
5. Check that `admin-ssl_keylog_file` is loaded into runtime:
```sql
LOAD ADMIN VARIABLES TO RUNTIME;
```
@ -476,9 +494,26 @@ sudo tcpdump -i eth0 -w /tmp/capture.pcap port 6033
---
## Supported Connection Types
When `admin-ssl_keylog_file` is configured, TLS secrets are captured from **all** SSL/TLS connection types:
| Connection Type | Direction | Protocol |
|----------------|-----------|----------|
| Frontend (client) | Client → ProxySQL | MySQL |
| Frontend (client) | Client → ProxySQL | PostgreSQL |
| Backend | ProxySQL → MySQL server | MySQL |
| Backend | ProxySQL → PostgreSQL server | PostgreSQL |
| Monitor | ProxySQL → MySQL server | MySQL |
| Monitor | ProxySQL → PostgreSQL server | PostgreSQL |
| Cluster | ProxySQL → ProxySQL peer | MySQL |
No additional configuration is needed per connection type — the single `admin-ssl_keylog_file` variable enables logging for all types.
---
## Additional Resources
- **Developer Documentation:** See `ssl_keylog_developer_guide.md` for implementation details
- **NSS Key Log Format:** https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
- **Wireshark TLS Decryption:** https://wiki.wireshark.org/TLS
- **tshark Manual:** `man tshark` or https://www.wireshark.org/docs/man-pages/tshark.html

@ -103,4 +103,19 @@ void proxysql_keylog_attach_callback(SSL_CTX* ssl_ctx);
*/
void proxysql_keylog_write_line_callback(const SSL* ssl, const char* line);
/**
* @brief Register the ProxySQL keylog callback with libpq
*
* Sets the global SSL keylog callback in libpq so that all PostgreSQL
* backend connections (regular, monitor, kill, harvester) will write
* TLS secrets to the keylog file.
*
* Must be called once at ProxySQL startup after proxysql_keylog_init().
*
* Thread-safety: Safe (should only be called during single-threaded initialization)
*
* @see PQsetSSLKeyLogCallback
*/
void proxysql_keylog_set_pgsql_callback();
#endif // __PROXYSQL_SSLKEYLOG_H

@ -487,6 +487,7 @@ void ProxySQL_GlobalVariables::process_opts_pre() {
init_coredump_struct();
proxysql_keylog_init();
proxysql_keylog_set_pgsql_callback();
};
void ProxySQL_GlobalVariables::process_opts_post() {

@ -23,6 +23,7 @@
*/
#include "proxysql_sslkeylog.h"
#include "libpq-fe.h"
// NSS Key Log Format reference:
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
@ -229,3 +230,19 @@ void proxysql_keylog_attach_callback(SSL_CTX* ssl_ctx) {
SSL_CTX_set_keylog_callback(ssl_ctx, proxysql_keylog_write_line_callback);
}
}
/**
* @brief Register the ProxySQL keylog callback with libpq
*
* Sets the global SSL keylog callback in libpq using PQsetSSLKeyLogCallback().
* This ensures all PostgreSQL backend connections write TLS secrets to the
* keylog file, matching the behavior already implemented for MySQL backends
* via MARIADB_OPT_SSL_KEYLOG_CALLBACK.
*
* The callback is set globally (not per-connection) because libpq creates
* a new SSL_CTX per connection internally. A global callback ensures coverage
* for both async (PQconnectStart) and sync (PQconnectdb) connection paths.
*/
void proxysql_keylog_set_pgsql_callback() {
PQsetSSLKeyLogCallback((PQsslKeyLogCallback_type)proxysql_keylog_write_line_callback);
}

@ -146,6 +146,7 @@
"pgsql-reg_test_5415_copy_error_recovery-t" : [ "legacy-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-set_parameter_validation_test-t" : [ "legacy-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"pgsql-set_statement_test-t" : [ "legacy-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-ssl_keylog-t" : [ "legacy-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-test_malformed_packet-t" : [ "legacy-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-transaction_state_comprehensive-t" : [ "legacy-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-transaction_variable_state_tracking-t" : [ "legacy-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,458 @@
/**
* @file pgsql-ssl_keylog-t.cpp
* @brief Integration test for PgSQL backend SSL keylog support.
*
* @details Validates that TLS secrets from PostgreSQL backend SSL connections
* are written to the keylog file when admin-ssl_keylog_file is set.
*
* ProxySQL patches libpq with a global SSL keylog callback
* (PQsetSSLKeyLogCallback). When admin-ssl_keylog_file is set, ALL
* TLS connections write secrets to the keylog file in NSS Key Log Format.
*
* Test cases:
* 1. Basic keylog creation set admin-ssl_keylog_file, make PgSQL SSL connection,
* verify file is non-empty
* 2. NSS format validation parse keylog lines, verify regex match for NSS Key Log Format
* 3. TLS version label check verify keylog contains TLS 1.2 or 1.3 secret labels
* 4. Concurrent connections 8 threads making PgSQL SSL connections, verify no corruption
* 5. Disable keylog set empty, make new connections, verify no new lines
* 6. Log rotation PROXYSQL FLUSH LOGS, verify new secrets appended
* 7. Monitor SSL keylog enable use_ssl=1, wait for monitor, verify keylog entries
* 8. Non-SSL no keylog use_ssl=0, verify no new entries
* 9. Invalid keylog path set /nonexistent/dir/file, verify graceful handling
*
* Test infrastructure: docker-pgsql16-single (PgSQL backend with SSL enabled)
* Admin connection: PgSQL protocol on pgsql_admin_host:pgsql_admin_port
*/
#include <unistd.h>
#include <string>
#include <sstream>
#include <fstream>
#include <thread>
#include <vector>
#include <atomic>
#include <regex>
#include <cstdio>
#include <cstring>
#include "libpq-fe.h"
#include "command_line.h"
#include "tap.h"
#include "utils.h"
CommandLine cl;
using PGConnPtr = std::unique_ptr<PGconn, decltype(&PQfinish)>;
static const char* KEYLOG_PATH = "/tmp/pgsql_ssl_keylog_test.log";
// ============================================================
// Helper: open PgSQL admin connection
// ============================================================
static PGConnPtr open_admin() {
std::stringstream ss;
ss << "host=" << cl.pgsql_admin_host
<< " port=" << cl.pgsql_admin_port
<< " user=" << cl.admin_username
<< " password=" << cl.admin_password
<< " sslmode=disable";
PGconn* conn = PQconnectdb(ss.str().c_str());
if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Admin connection failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return PGConnPtr(nullptr, &PQfinish);
}
return PGConnPtr(conn, &PQfinish);
}
// ============================================================
// Helper: run admin SQL, return true on success
// ============================================================
static bool admin_exec(PGconn* admin, const char* sql) {
PGresult* res = PQexec(admin, sql);
ExecStatusType st = PQresultStatus(res);
bool ok = (st == PGRES_COMMAND_OK || st == PGRES_TUPLES_OK);
if (!ok) {
diag("Admin query failed: '%s' err='%s'", sql, PQerrorMessage(admin));
}
PQclear(res);
return ok;
}
// ============================================================
// Helper: run admin SQL and return first column of first row
// ============================================================
static std::string admin_query_value(PGconn* admin, const char* sql) {
std::string result;
PGresult* res = PQexec(admin, sql);
if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) > 0) {
const char* val = PQgetvalue(res, 0, 0);
if (val) result = val;
} else {
diag("Admin query failed: '%s' err='%s'", sql, PQerrorMessage(admin));
}
PQclear(res);
return result;
}
// ============================================================
// Helper: set admin-ssl_keylog_file and load to runtime
// ============================================================
static bool set_keylog_file(PGconn* admin, const char* path) {
std::string sql = "SET admin-ssl_keylog_file='";
if (path) sql += path;
sql += "'";
if (!admin_exec(admin, sql.c_str())) return false;
return admin_exec(admin, "LOAD ADMIN VARIABLES TO RUNTIME");
}
// ============================================================
// Helper: make a PgSQL backend connection with SSL
// ============================================================
static PGConnPtr make_pgsql_ssl_conn() {
std::stringstream ss;
ss << "host=" << cl.pgsql_host
<< " port=" << cl.pgsql_port
<< " user=" << cl.pgsql_root_username
<< " password=" << cl.pgsql_root_password
<< " sslmode=require";
PGconn* conn = PQconnectdb(ss.str().c_str());
return PGConnPtr(conn, &PQfinish);
}
// ============================================================
// Helper: make a PgSQL backend connection without SSL
// ============================================================
static PGConnPtr make_pgsql_nossl_conn() {
std::stringstream ss;
ss << "host=" << cl.pgsql_host
<< " port=" << cl.pgsql_port
<< " user=" << cl.pgsql_root_username
<< " password=" << cl.pgsql_root_password
<< " sslmode=disable";
PGconn* conn = PQconnectdb(ss.str().c_str());
return PGConnPtr(conn, &PQfinish);
}
// ============================================================
// Helper: count non-empty lines in a file
// ============================================================
static long count_file_lines(const char* path) {
std::ifstream f(path);
if (!f.is_open()) return -1;
long n = 0;
std::string line;
while (std::getline(f, line)) {
if (!line.empty()) ++n;
}
return n;
}
// ============================================================
// Helper: get file size in bytes, returns -1 if file doesn't exist
// ============================================================
static long file_size(const char* path) {
std::ifstream f(path, std::ios::ate | std::ios::binary);
if (!f.is_open()) return -1;
return (long)f.tellg();
}
// ============================================================
// Helper: check whether any line in the file matches a regex
// ============================================================
static bool file_has_regex_match(const char* path, const std::regex& re) {
std::ifstream f(path);
if (!f.is_open()) return false;
std::string line;
while (std::getline(f, line)) {
if (std::regex_search(line, re)) return true;
}
return false;
}
// ============================================================
// Helper: set pgsql_servers use_ssl and reload
// ============================================================
static bool set_pgsql_use_ssl(PGconn* admin, int value) {
std::string sql = "UPDATE pgsql_servers SET use_ssl=" + std::to_string(value);
if (!admin_exec(admin, sql.c_str())) return false;
return admin_exec(admin, "LOAD PGSQL SERVERS TO RUNTIME");
}
// ============================================================
// Helper: get pgsql-monitor_connect_interval (ms)
// ============================================================
static long get_monitor_interval(PGconn* admin) {
std::string v = admin_query_value(admin,
"SELECT Variable_Value FROM global_variables "
"WHERE Variable_Name='pgsql-monitor_connect_interval'");
return v.empty() ? 1000 : atol(v.c_str());
}
int main(int argc, char** argv) {
plan(9);
if (cl.getEnv()) {
diag("Failed to get required environment variables");
return exit_status();
}
// Remove any leftover keylog file from a prior run
::remove(KEYLOG_PATH);
// Open PgSQL admin connection
auto admin_conn = open_admin();
if (!admin_conn) {
BAIL_OUT("Admin connection failed — cannot continue");
return exit_status();
}
PGconn* admin = admin_conn.get();
diag("PgSQL admin connected to %s:%d", cl.pgsql_admin_host, cl.pgsql_admin_port);
// Save original keylog file setting so we can restore it at the end
std::string original_keylog = admin_query_value(admin,
"SELECT Variable_Value FROM global_variables "
"WHERE Variable_Name='admin-ssl_keylog_file'");
diag("Original admin-ssl_keylog_file='%s'", original_keylog.c_str());
// ================================================================
// Test 1: Basic keylog creation
// ================================================================
diag("---- Test 1: Basic keylog creation ----");
bool set_ok = set_keylog_file(admin, KEYLOG_PATH);
diag("Set admin-ssl_keylog_file='%s' result=%s", KEYLOG_PATH, set_ok ? "ok" : "FAIL");
if (set_ok) {
auto conn1 = make_pgsql_ssl_conn();
if (PQstatus(conn1.get()) != CONNECTION_OK) {
diag("PgSQL SSL connection failed: %s", PQerrorMessage(conn1.get()));
}
usleep(100000);
}
long sz = file_size(KEYLOG_PATH);
diag("Keylog file size after SSL PgSQL connection: %ld bytes", sz);
ok(set_ok && sz > 0,
"Test 1: Keylog file is non-empty after PgSQL SSL connection (size=%ld)", sz);
// ================================================================
// Test 2: NSS format validation
// ================================================================
diag("---- Test 2: NSS Key Log Format validation ----");
std::regex nss_re(R"(^[A-Z_]+ [0-9a-fA-F]{64} [0-9a-fA-F]+$)");
bool has_valid_line = file_has_regex_match(KEYLOG_PATH, nss_re);
ok(has_valid_line,
"Test 2: Keylog file contains valid NSS Key Log Format lines");
// ================================================================
// Test 3: TLS version label check
// ================================================================
diag("---- Test 3: TLS 1.2/1.3 secret label check ----");
std::regex tls_label_re(
R"(^(CLIENT_RANDOM|CLIENT_HANDSHAKE_TRAFFIC_SECRET|SERVER_HANDSHAKE_TRAFFIC_SECRET|)"
R"(CLIENT_TRAFFIC_SECRET_\d+|SERVER_TRAFFIC_SECRET_\d+) )");
bool has_tls_label = file_has_regex_match(KEYLOG_PATH, tls_label_re);
ok(has_tls_label,
"Test 3: Keylog contains TLS 1.2 CLIENT_RANDOM or TLS 1.3 traffic secret labels");
// ================================================================
// Test 4: Concurrent connections — no corruption
// ================================================================
diag("---- Test 4: Concurrent SSL connections ----");
long lines_before = count_file_lines(KEYLOG_PATH);
diag("Lines before concurrent test: %ld", lines_before);
const int NUM_THREADS = 8;
std::atomic<int> conn_ok_count{0};
std::vector<std::thread> threads;
threads.reserve(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; ++i) {
threads.emplace_back([&conn_ok_count]() {
auto c = make_pgsql_ssl_conn();
if (PQstatus(c.get()) == CONNECTION_OK) {
++conn_ok_count;
}
});
}
for (auto& t : threads) t.join();
usleep(200000);
long lines_after = count_file_lines(KEYLOG_PATH);
diag("Lines after concurrent test: %ld (conn_ok=%d)", lines_after, (int)conn_ok_count);
bool all_valid = true;
{
std::ifstream f(KEYLOG_PATH);
std::string line;
while (std::getline(f, line)) {
if (line.empty()) continue;
if (!std::regex_search(line, nss_re)) {
diag("Corrupt/unexpected line: '%s'", line.c_str());
all_valid = false;
}
}
}
ok(all_valid && lines_after > lines_before,
"Test 4: Concurrent SSL connections produced valid keylog lines without corruption "
"(before=%ld after=%ld)", lines_before, lines_after);
// ================================================================
// Test 5: Disable keylog — no new lines after clearing
// ================================================================
diag("---- Test 5: Disable keylog ----");
set_keylog_file(admin, "");
usleep(50000);
long lines_disabled = count_file_lines(KEYLOG_PATH);
diag("Lines before disable connection: %ld", lines_disabled);
for (int i = 0; i < 3; ++i) {
auto c = make_pgsql_ssl_conn();
(void)c;
}
usleep(200000);
long lines_after_disable = count_file_lines(KEYLOG_PATH);
diag("Lines after connections with keylog disabled: %ld", lines_after_disable);
ok(lines_after_disable == lines_disabled,
"Test 5: No new keylog lines written after disabling keylog "
"(before=%ld after=%ld)", lines_disabled, lines_after_disable);
// ================================================================
// Test 6: Log rotation — PROXYSQL FLUSH LOGS appends new secrets
// ================================================================
diag("---- Test 6: Log rotation via PROXYSQL FLUSH LOGS ----");
set_keylog_file(admin, KEYLOG_PATH);
usleep(50000);
{
auto c = make_pgsql_ssl_conn();
(void)c;
}
usleep(100000);
long lines_pre_flush = count_file_lines(KEYLOG_PATH);
diag("Lines before PROXYSQL FLUSH LOGS: %ld", lines_pre_flush);
bool flush_ok = admin_exec(admin, "PROXYSQL FLUSH LOGS");
diag("PROXYSQL FLUSH LOGS result=%s", flush_ok ? "ok" : "FAIL");
{
auto c = make_pgsql_ssl_conn();
(void)c;
}
usleep(100000);
long lines_post_flush = count_file_lines(KEYLOG_PATH);
diag("Lines after PROXYSQL FLUSH LOGS + new connection: %ld", lines_post_flush);
ok(flush_ok && lines_post_flush > lines_pre_flush,
"Test 6: New keylog secrets appended after PROXYSQL FLUSH LOGS "
"(before=%ld after=%ld)", lines_pre_flush, lines_post_flush);
// ================================================================
// Test 7: Monitor SSL keylog — enable use_ssl, wait, verify entries
// ================================================================
diag("---- Test 7: Monitor SSL keylog ----");
::remove(KEYLOG_PATH);
set_keylog_file(admin, KEYLOG_PATH);
usleep(50000);
long lines_before_monitor = count_file_lines(KEYLOG_PATH);
diag("Lines before enabling monitor SSL: %ld", lines_before_monitor);
long monitor_interval_ms = get_monitor_interval(admin);
diag("pgsql-monitor_connect_interval=%ld ms", monitor_interval_ms);
bool use_ssl_ok = set_pgsql_use_ssl(admin, 1);
diag("Set pgsql_servers use_ssl=1 result=%s", use_ssl_ok ? "ok" : "FAIL");
useconds_t wait_us = (useconds_t)(monitor_interval_ms * 2 * 1000);
if (wait_us > 10000000) wait_us = 10000000;
diag("Waiting %u us for monitor cycles...", wait_us);
usleep(wait_us);
long lines_after_monitor = count_file_lines(KEYLOG_PATH);
diag("Lines after monitor SSL cycles: %ld", lines_after_monitor);
ok(use_ssl_ok && lines_after_monitor > lines_before_monitor,
"Test 7: Monitor SSL connections write keylog entries "
"(before=%ld after=%ld)", lines_before_monitor, lines_after_monitor);
set_pgsql_use_ssl(admin, 0);
usleep(50000);
// ================================================================
// Test 8: Non-SSL PgSQL connection — no keylog entries added
// ================================================================
diag("---- Test 8: Non-SSL connection produces no keylog entries ----");
admin_exec(admin, "SET pgsql-monitor_enabled='false'");
admin_exec(admin, "LOAD PGSQL VARIABLES TO RUNTIME");
usleep(500000);
::remove(KEYLOG_PATH);
set_keylog_file(admin, KEYLOG_PATH);
usleep(50000);
long lines_nossl_before = count_file_lines(KEYLOG_PATH);
diag("Lines before non-SSL connections: %ld", lines_nossl_before);
for (int i = 0; i < 3; ++i) {
auto c = make_pgsql_nossl_conn();
if (PQstatus(c.get()) != CONNECTION_OK) {
diag("Non-SSL PgSQL connection failed (may be expected): %s",
PQerrorMessage(c.get()));
}
}
usleep(200000);
long lines_nossl_after = count_file_lines(KEYLOG_PATH);
diag("Lines after non-SSL connections: %ld", lines_nossl_after);
ok(lines_nossl_after == lines_nossl_before,
"Test 8: Non-SSL PgSQL connections do not add keylog entries "
"(before=%ld after=%ld)", lines_nossl_before, lines_nossl_after);
admin_exec(admin, "SET pgsql-monitor_enabled='true'");
admin_exec(admin, "LOAD PGSQL VARIABLES TO RUNTIME");
// ================================================================
// Test 9: Invalid keylog path — graceful handling
// ================================================================
diag("---- Test 9: Invalid keylog path ----");
const char* bad_path = "/nonexistent/dir/proxysql_keylog.log";
set_keylog_file(admin, bad_path);
{
auto c = make_pgsql_ssl_conn();
(void)c;
}
usleep(100000);
long bad_sz = file_size(bad_path);
diag("bad_path size=%ld (expected -1)", bad_sz);
ok(bad_sz < 0,
"Test 9: Invalid keylog path handled gracefully — file not created at bad path");
// ================================================================
// Cleanup
// ================================================================
diag("---- Cleanup ----");
set_keylog_file(admin, original_keylog.empty() ? "" : original_keylog.c_str());
if (::remove(KEYLOG_PATH) == 0) {
diag("Removed keylog file: %s", KEYLOG_PATH);
}
return exit_status();
}

@ -14,9 +14,10 @@
* Compiled only when PROXYSQLGENAI=1 (auto-detected from libproxysql.a).
*/
#include "tap.h"
#ifdef PROXYSQLGENAI
#include "tap.h"
#include "test_globals.h"
#include "test_init.h"
#include "proxysql.h"

Loading…
Cancel
Save