Skip to content

feat: Add stats_tls_certificates and stats_global tables for TLS certificate metadata and tracking#5456

Merged
renecannao merged 10 commits intov3.0from
copilot/add-ssl-tls-certificate-stats-table
Mar 22, 2026
Merged

feat: Add stats_tls_certificates and stats_global tables for TLS certificate metadata and tracking#5456
renecannao merged 10 commits intov3.0from
copilot/add-ssl-tls-certificate-stats-table

Conversation

Copy link
Contributor

Copilot AI commented Mar 18, 2026

No way currently exists to inspect which SSL/TLS certificates ProxySQL has loaded, their validity periods, or reload history — without accessing the filesystem directly.

New table: stats_tls_certificates

Populated at query time (every query, no caching) by reading certs from disk via OpenSSL. days_until_expiry is always fresh.

SELECT * FROM stats_tls_certificates;
-- cert_type | file_path | subject_cn | issuer_cn | serial_number | not_before | not_after | days_until_expiry | sha256_fingerprint | loaded_at
-- server    | /var/lib/proxysql/proxysql-cert.pem | ProxySQL_Auto_Generated_Server_Certificate | ... | 3287 | ... | 1705315800
-- ca        | /var/lib/proxysql/proxysql-ca.pem   | ProxySQL_Auto_Generated_CA_Certificate     | ... | 3287 | ... | 1705315800

New table: stats_global

A new in-memory-only table for ProxySQL-wide metrics that are not specific to MySQL or PgSQL. TLS tracking variables are placed here rather than stats_mysql_global, which is reserved for MySQL-specific metrics.

Variable Description
TLS_Load_Count Times TLS has been loaded/reloaded
TLS_Last_Load_Timestamp Unix timestamp of last load
TLS_Last_Load_Result NONE / SUCCESS / FAILED
TLS_Server_Cert_File Path to server certificate
TLS_CA_Cert_File Path to CA certificate
TLS_Key_File Path to private key
SELECT * FROM stats.stats_global WHERE Variable_Name LIKE 'TLS%';

Implementation

  • include/ProxySQL_Admin_Tables_Definitions.h: DDL for stats_tls_certificates and new stats_global tables
  • include/proxysql_glovars.hpp + lib/ProxySQL_GloVars.cpp: Six new global struct fields (tls_load_count, tls_last_load_timestamp, tls_last_load_ok, tls_cert_file, tls_ca_file, tls_key_file); initialized in constructor
  • src/proxy_tls.cpp: ProxySQL_create_or_load_TLS() populates tracking fields on success; file paths stored as strdup copies (freed before reassignment); reload path updates counters inside existing ssl_mutex lock
  • lib/Admin_Bootstrap.cpp: Both tables registered via insert_into_tables_defs
  • lib/ProxySQL_Admin_Stats.cpp: stats___tls_certificates() reads certs at query time using PEM_read_bio_X509, extracts metadata via OpenSSL (ASN1_TIME_diff, X509_digest, X509_NAME_get_text_by_NID); new stats___global() populates TLS tracking variables (removed from stats___mysql_global()); ASN1_TIME_diff return value is checked
  • lib/ProxySQL_Admin.cpp: Query detection (strstr) and refresh dispatch wired in GenericRefreshStatistics() for both tables
  • include/proxysql_admin.h: Declarations for stats___tls_certificates() and stats___global()
  • test/tap/tests/test_tls_stats-t.cpp: TAP test with 29 assertions covering presence and validity of all TLS variables in stats_global, correct structure and content of stats_tls_certificates, counter/timestamp updates after PROXYSQL RELOAD TLS, and absence of TLS variables from stats_mysql_global
Original prompt

This section details on the original issue you should resolve

<issue_title>Feature Request: Add stats table to track loaded SSL/TLS certificates metadata</issue_title>
<issue_description>## Summary

ProxySQL loads server-side SSL certificates/keys during startup and when PROXYSQL RELOAD TLS is executed. Currently, there is no way to query which certificates are loaded, their validity periods, or when they were last reloaded. This feature request proposes adding a new stats table to expose SSL certificate metadata.

Current Behavior

Certificate Loading Process

SSL certificates are loaded in src/proxy_tls.cpp:

  1. Bootstrap (startup): ProxySQL_create_or_load_TLS(true, msg) is called from src/main.cpp:416
  2. Runtime reload: PROXYSQL RELOAD TLS command triggers ProxySQL_create_or_load_TLS(false, msg) from lib/Admin_Handler.cpp:988

The three expected files are:

  • proxysql-key.pem - Private key
  • proxysql-cert.pem - Server certificate
  • proxysql-ca.pem - CA certificate

File paths are constructed from GloVars.datadir and stored in static variables:

// src/proxy_tls.cpp lines 44-46
static char *ssl_key_fp = NULL;
static char *ssl_cert_fp = NULL;
static char *ssl_ca_fp = NULL;

Current Tracking (Minimal)

The only SSL-related metric currently tracked is in lib/ProxySQL_Admin_Stats.cpp:616:

sqlite3_global_stats_row_step(statsdb, row_stmt, "OpenSSL_Version_Num", OpenSSL_version_num());

No certificate metadata (subject, issuer, validity dates, etc.) is captured or exposed.

Logging Only

Current logging provides minimal information:

  • src/proxy_tls.cpp:286: "No SSL keys/certificates found in datadir (%s). Generating new keys/certificates."
  • src/proxy_tls.cpp:321: "SSL keys/certificates found in datadir (%s): loading them."
  • Error messages when files are missing or invalid

Problem Statement

Operators cannot:

  1. Verify which certificate is currently loaded (important after PROXYSQL RELOAD TLS)
  2. Check certificate expiration dates without accessing the filesystem
  3. Confirm the certificate subject/issuer matches expectations
  4. Audit when certificates were last reloaded
  5. Monitor certificate expiration proactively
  6. Verify the file path from which certificates were loaded

Proposed Solution

New Stats Table: stats_tls_certificates

Create a new table in the stats database to expose certificate metadata:

CREATE TABLE stats_tls_certificates (
    cert_type VARCHAR NOT NULL PRIMARY KEY,  -- 'server', 'ca'
    file_path VARCHAR NOT NULL,              -- Full path to the certificate file
    subject_cn VARCHAR,                      -- Common Name from subject
    issuer_cn VARCHAR,                       -- Common Name from issuer
    serial_number VARCHAR,                   -- Certificate serial number (hex)
    not_before VARCHAR,                      -- Validity start (ISO 8601)
    not_after VARCHAR,                       -- Validity end (ISO 8601)
    days_until_expiry INT,                   -- Days until certificate expires (negative if expired)
    sha256_fingerprint VARCHAR,              -- SHA-256 fingerprint for identification
    loaded_at INT                            -- Unix timestamp when certificate was loaded
)

Example Output

SELECT * FROM stats_tls_certificates;
cert_type file_path subject_cn issuer_cn serial_number not_before not_after days_until_expiry sha256_fingerprint loaded_at
server /var/lib/proxysql/proxysql-cert.pem ProxySQL_Auto_Generated_Server_Certificate ProxySQL_Auto_Generated_CA_Certificate 67C3A2B1 2024-01-15T10:30:00Z 2034-01-13T10:30:00Z 3287 A1B2C3D4... 1705315800
ca /var/lib/proxysql/proxysql-ca.pem ProxySQL_Auto_Generated_CA_Certificate ProxySQL_Auto_Generated_CA_Certificate 67C3A2B1 2024-01-15T10:30:00Z 2034-01-13T10:30:00Z 3287 E5F6G7H8... 1705315800

Additional Global Stats

Add the following to stats_mysql_global:

Variable_Name Variable_Value Description
TLS_Load_Count 5 Number of times TLS has been loaded/reloaded
TLS_Last_Load_Timestamp 1705315800 Unix timestamp of last TLS load
TLS_Last_Load_Result SUCCESS Result of last TLS load operation
TLS_Server_Cert_File /var/lib/proxysql/proxysql-cert.pem Path to server certificate
TLS_CA_Cert_File /var/lib/proxysql/proxysql-ca.pem Path to CA certificate
TLS_Key_File /var/lib/proxysql/proxysql-key.pem Path to private key

Implementation Details

Runtime Table Refresh

The table must be repopulated at runtime every time it is queried, following the pattern used by other stats tables. This ensures:

  • days_until_expiry is always current (calculat...

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added TLS certificate statistics collection and reporting. The new metrics table tracks certificate metadata including subject/issuer information, validity dates, days until expiry, and SHA-256 fingerprints.
    • Introduced TLS load monitoring with operation count, timestamp, and success status tracking for better visibility into certificate configuration and reload events.

Copilot AI and others added 2 commits March 18, 2026 15:57
- Add STATS_SQLITE_TABLE_TLS_CERTIFICATES definition with cert_type, file_path,
  subject_cn, issuer_cn, serial_number, not_before, not_after, days_until_expiry,
  sha256_fingerprint, and loaded_at columns
- Add TLS tracking fields to GloVars.global: tls_load_count, tls_last_load_timestamp,
  tls_last_load_ok, tls_cert_file, tls_ca_file, tls_key_file
- Update ProxySQL_create_or_load_TLS() to populate tracking fields on success
- Register stats_tls_certificates table in Admin_Bootstrap
- Add stats___tls_certificates() function that reads certs from disk at query time,
  extracts metadata (CN, serial, validity dates, SHA-256 fingerprint, days_until_expiry)
  and populates the table
- Add TLS variables to stats___mysql_global(): TLS_Load_Count, TLS_Last_Load_Timestamp,
  TLS_Last_Load_Result, TLS_Server_Cert_File, TLS_CA_Cert_File, TLS_Key_File
- Wire up query detection and refresh in GenericRefreshStatistics()

Co-authored-by: renecannao <3645227+renecannao@users.noreply.github.com>
…ASN1_TIME_diff return, refactor dup cert loading, larger CN buffer

Co-authored-by: renecannao <3645227+renecannao@users.noreply.github.com>
Copilot AI changed the title [WIP] Add stats table to track loaded SSL/TLS certificates metadata feat: Add stats_tls_certificates table and TLS tracking metrics to stats_mysql_global Mar 18, 2026
Copilot AI requested a review from renecannao March 18, 2026 16:04
@renecannao
Copy link
Contributor

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ad6ad4a-3d68-4dcb-b2cb-5f7a0940d6e8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes implement a new runtime-populated stats_tls_certificates stats table exposing loaded SSL/TLS certificate metadata. Global TLS tracking fields monitor load counts, timestamps, and load success status. OpenSSL helpers extract certificate metadata (subject/issuer CNs, serial numbers, validity dates in ISO-8601, days until expiry, SHA-256 fingerprints) from PEM files, with the table repopulated on each query to reflect current certificate states and changes after PROXYSQL RELOAD TLS.

Changes

Cohort / File(s) Summary
Table Definition & Admin Method Declaration
include/ProxySQL_Admin_Tables_Definitions.h, include/proxysql_admin.h
Added STATS_SQLITE_TABLE_TLS_CERTIFICATES macro defining the stats table schema with columns for cert type, file path, X509 metadata, fingerprints, and load timestamp; declared stats___tls_certificates() public method.
Global State for TLS Tracking
include/proxysql_glovars.hpp, lib/ProxySQL_GloVars.cpp
Added global state fields to track TLS load activity: tls_load_count, tls_last_load_timestamp, tls_last_load_ok, and file path pointers (tls_cert_file, tls_ca_file, tls_key_file); initialized to safe defaults in constructor.
Admin Infrastructure
lib/Admin_Bootstrap.cpp, lib/ProxySQL_Admin.cpp
Registered stats_tls_certificates table definition at bootstrap; extended GenericRefreshStatistics() to recognize and dispatch stats_tls_certificates command.
Stats Implementation with OpenSSL Integration
lib/ProxySQL_Admin_Stats.cpp
Implemented stats___tls_certificates() function with OpenSSL helpers to extract X509 metadata (CN, serial, dates, fingerprint); extended stats___mysql_global() to expose TLS load metrics and file paths; thread-safe access via ssl_mutex.
TLS Loading Metadata Capture
src/proxy_tls.cpp
Updated TLS load/reload logic to snapshot file paths, increment tls_load_count, record tls_last_load_timestamp, and set tls_last_load_ok status for stats consumption.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Query as Admin Query
    participant Dispatch as GenericRefreshStatistics
    participant Stats as stats___tls_certificates()
    participant OpenSSL as OpenSSL Helpers
    participant DB as stats_tls_certificates Table
    
    Client->>Query: SELECT * FROM stats_tls_certificates
    Query->>Dispatch: GenericRefreshStatistics(stats_tls_certificates)
    Dispatch->>Stats: stats___tls_certificates()
    Stats->>Stats: Lock ssl_mutex
    Stats->>Stats: Clear table, snapshot cert paths & timestamp
    Stats->>OpenSSL: Load PEM certificates from disk
    OpenSSL->>OpenSSL: Parse X509 objects<br/>(CN, serial, dates, fingerprint)
    OpenSSL-->>Stats: Extracted metadata
    Stats->>DB: INSERT certificate rows<br/>(cert_type, file_path, subject_cn,<br/>issuer_cn, serial, not_before,<br/>not_after, days_until_expiry,<br/>sha256_fingerprint, loaded_at)
    Stats->>Stats: Unlock ssl_mutex, commit
    DB-->>Client: Return stats_tls_certificates rows
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Hoppy news, my friends so fine!
TLS certs now tracked in time,
OpenSSL helpers extract with care,
SHA-256 fingerprints laid bare!
Days till expiry, dates divine,
Stats refreshed—a rabbit's design! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding a stats_tls_certificates table and TLS tracking metrics to stats_mysql_global.
Linked Issues check ✅ Passed All primary objectives from issue #5411 are implemented: stats_tls_certificates table with required columns, runtime repopulation, X509 metadata extraction via OpenSSL, TLS tracking globals, thread-safe ssl_mutex access, and stats_mysql_global integration.
Out of Scope Changes check ✅ Passed All changes directly support the requirements in issue #5411; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copilot/add-ssl-tls-certificate-stats-table

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
lib/ProxySQL_GloVars.cpp (1)

88-173: Missing destructor cleanup for TLS file path pointers.

The destructor frees many global struct members (e.g., gr_bootstrap_uri, gr_bootstrap_ssl_ca, etc.) but does not free the newly added tls_cert_file, tls_ca_file, and tls_key_file pointers which are allocated via strdup() in src/proxy_tls.cpp.

If this is intentional (similar to checksums_values members which avoid cleanup due to concurrent access during shutdown), please add a comment explaining the design decision. Otherwise, add cleanup:

♻️ Proposed fix to add cleanup
 	if (global.gr_bootstrap_ssl_mode) {
 		free(global.gr_bootstrap_ssl_mode);
 		global.gr_bootstrap_ssl_mode = nullptr;
 	}
+	// TLS certificate file paths
+	if (global.tls_cert_file) {
+		free(global.tls_cert_file);
+		global.tls_cert_file = nullptr;
+	}
+	if (global.tls_ca_file) {
+		free(global.tls_ca_file);
+		global.tls_ca_file = nullptr;
+	}
+	if (global.tls_key_file) {
+		free(global.tls_key_file);
+		global.tls_key_file = nullptr;
+	}
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ProxySQL_GloVars.cpp` around lines 88 - 173, The destructor
ProxySQL_GlobalVariables::~ProxySQL_GlobalVariables currently frees many
global.* strings but omits freeing the TLS pointers (global.tls_cert_file,
global.tls_ca_file, global.tls_key_file) that are strdup'd in src/proxy_tls.cpp;
either add the same pattern as other globals (if (global.tls_cert_file) {
free(global.tls_cert_file); global.tls_cert_file = nullptr; } and likewise for
tls_ca_file and tls_key_file) or, if omission is intentional, add a clear
comment near the checksums in_shutdown block explaining why these TLS pointers
are not freed during shutdown to avoid race/free issues.
lib/ProxySQL_Admin_Stats.cpp (1)

2966-3011: Clarify: loaded_at may be inconsistent with displayed certificate metadata.

The implementation reads certificate metadata from disk at query time (which ensures days_until_expiry is current), but uses tls_last_load_timestamp for loaded_at. If the certificate file on disk has been updated but PROXYSQL RELOAD TLS hasn't been executed yet, the displayed metadata (subject_cn, not_after, fingerprint, etc.) will reflect the new file while loaded_at still shows the old load timestamp.

Consider either:

  1. Documenting this behavior (metadata from disk, timestamp from last load)
  2. Reading from in-memory X509 objects stored during TLS load (if persisted) to ensure consistency

Otherwise the implementation is thread-safe with proper mutex usage and correct memory management.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ProxySQL_Admin_Stats.cpp` around lines 2966 - 3011, The
stats___tls_certificates function can show certificate metadata read from disk
while using GloVars.global.tls_last_load_timestamp for loaded_at, causing
inconsistent displays when files changed but RELOAD TLS wasn't run; fix by
either (A) making loaded_at reflect the actual file read time by replacing use
of tls_last_load_timestamp with the file's mtime or the time you read the file
inside process_cert, or (B) ensuring consistency by reading the in-memory X509
objects and their associated load timestamps held under the ssl_mutex (use the
same in-memory structures populated at TLS load rather than re-reading disk) —
update stats___tls_certificates and the process_cert logic to obtain both the
X509 and the correct loaded_at from the same source (refer to
GloVars.global.tls_cert_file/tls_ca_file,
GloVars.global.tls_last_load_timestamp, process_cert lambda, and
insert_tls_cert_row) or alternatively document the current behavior clearly if
you choose not to change code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@include/proxysql_glovars.hpp`:
- Around line 147-149: The new char* members tls_cert_file, tls_ca_file, and
tls_key_file are strdup()'d elsewhere but not freed in the ProxySQL_GloVars
destructor; add cleanup in the destructor (same pattern as gr_bootstrap_ssl_*
cleanup) to check each pointer, call free() and set the pointer to nullptr for
tls_cert_file, tls_ca_file and tls_key_file to avoid memory leaks (locate the
destructor in ProxySQL_GloVars.cpp and mirror the existing free+nullptr
pattern).

In `@src/proxy_tls.cpp`:
- Around line 439-442: The TLS reload success branch currently increments
GloVars.global.tls_load_count and sets GloVars.global.tls_last_load_timestamp
and GloVars.global.tls_last_load_ok = true, but the failure branches do not set
tls_last_load_ok = false; update each TLS reload failure path to (under the same
ssl_mutex) increment tls_load_count, set tls_last_load_timestamp = time(NULL)
and set tls_last_load_ok = false so TLS_Last_Load_Result correctly reflects
FAILED; look for the reload-related branches that log failures and update those
branches to mirror the success updates (using GloVars.global.tls_load_count,
GloVars.global.tls_last_load_timestamp, and GloVars.global.tls_last_load_ok) and
ensure this is done inside the ssl_mutex-protected section.

---

Nitpick comments:
In `@lib/ProxySQL_Admin_Stats.cpp`:
- Around line 2966-3011: The stats___tls_certificates function can show
certificate metadata read from disk while using
GloVars.global.tls_last_load_timestamp for loaded_at, causing inconsistent
displays when files changed but RELOAD TLS wasn't run; fix by either (A) making
loaded_at reflect the actual file read time by replacing use of
tls_last_load_timestamp with the file's mtime or the time you read the file
inside process_cert, or (B) ensuring consistency by reading the in-memory X509
objects and their associated load timestamps held under the ssl_mutex (use the
same in-memory structures populated at TLS load rather than re-reading disk) —
update stats___tls_certificates and the process_cert logic to obtain both the
X509 and the correct loaded_at from the same source (refer to
GloVars.global.tls_cert_file/tls_ca_file,
GloVars.global.tls_last_load_timestamp, process_cert lambda, and
insert_tls_cert_row) or alternatively document the current behavior clearly if
you choose not to change code.

In `@lib/ProxySQL_GloVars.cpp`:
- Around line 88-173: The destructor
ProxySQL_GlobalVariables::~ProxySQL_GlobalVariables currently frees many
global.* strings but omits freeing the TLS pointers (global.tls_cert_file,
global.tls_ca_file, global.tls_key_file) that are strdup'd in src/proxy_tls.cpp;
either add the same pattern as other globals (if (global.tls_cert_file) {
free(global.tls_cert_file); global.tls_cert_file = nullptr; } and likewise for
tls_ca_file and tls_key_file) or, if omission is intentional, add a clear
comment near the checksums in_shutdown block explaining why these TLS pointers
are not freed during shutdown to avoid race/free issues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7547e27b-e848-46ad-83be-ac5073add834

📥 Commits

Reviewing files that changed from the base of the PR and between e61e30d and 0357862.

📒 Files selected for processing (8)
  • include/ProxySQL_Admin_Tables_Definitions.h
  • include/proxysql_admin.h
  • include/proxysql_glovars.hpp
  • lib/Admin_Bootstrap.cpp
  • lib/ProxySQL_Admin.cpp
  • lib/ProxySQL_Admin_Stats.cpp
  • lib/ProxySQL_GloVars.cpp
  • src/proxy_tls.cpp

Comment on lines +147 to +149
char *tls_cert_file;
char *tls_ca_file;
char *tls_key_file;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if the destructor frees the TLS file path pointers
rg -n "tls_cert_file|tls_ca_file|tls_key_file" lib/ProxySQL_GloVars.cpp

Repository: sysown/proxysql

Length of output: 157


🏁 Script executed:

# Check context around lines 252-254 in lib/ProxySQL_GloVars.cpp
sed -n '240,270p' lib/ProxySQL_GloVars.cpp

Repository: sysown/proxysql

Length of output: 1820


🏁 Script executed:

# Search for the destructor of ProxySQL_GlobalVariables
rg -n "~ProxySQL_GlobalVariables|ProxySQL_GlobalVariables::~ProxySQL_GlobalVariables" lib/ProxySQL_GloVars.cpp

Repository: sysown/proxysql

Length of output: 117


🏁 Script executed:

# Read the destructor implementation
sed -n '88,150p' lib/ProxySQL_GloVars.cpp

Repository: sysown/proxysql

Length of output: 2637


🏁 Script executed:

# Continue reading the destructor to find TLS pointer cleanup
sed -n '150,200p' lib/ProxySQL_GloVars.cpp

Repository: sysown/proxysql

Length of output: 1400


🏁 Script executed:

# Verify that TLS pointers are allocated with strdup() in proxy_tls.cpp
rg -n "tls_cert_file|tls_ca_file|tls_key_file" src/proxy_tls.cpp | head -20

Repository: sysown/proxysql

Length of output: 409


🏁 Script executed:

# Check the context around the TLS allocation code in proxy_tls.cpp
sed -n '400,425p' src/proxy_tls.cpp

Repository: sysown/proxysql

Length of output: 1629


Add destructor cleanup for TLS file path pointers.

The new char* pointers (tls_cert_file, tls_ca_file, tls_key_file) are allocated via strdup() in src/proxy_tls.cpp (lines 412, 414, 416), but the destructor in lib/ProxySQL_GloVars.cpp does not free them. Add cleanup following the existing pattern used for other dynamically allocated pointers (e.g., gr_bootstrap_ssl_* pointers at lines 144-172):

if (global.tls_cert_file) {
    free(global.tls_cert_file);
    global.tls_cert_file = nullptr;
}
if (global.tls_ca_file) {
    free(global.tls_ca_file);
    global.tls_ca_file = nullptr;
}
if (global.tls_key_file) {
    free(global.tls_key_file);
    global.tls_key_file = nullptr;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/proxysql_glovars.hpp` around lines 147 - 149, The new char* members
tls_cert_file, tls_ca_file, and tls_key_file are strdup()'d elsewhere but not
freed in the ProxySQL_GloVars destructor; add cleanup in the destructor (same
pattern as gr_bootstrap_ssl_* cleanup) to check each pointer, call free() and
set the pointer to nullptr for tls_cert_file, tls_ca_file and tls_key_file to
avoid memory leaks (locate the destructor in ProxySQL_GloVars.cpp and mirror the
existing free+nullptr pattern).

Comment on lines +439 to +442
// Update TLS tracking fields for stats table (under ssl_mutex)
GloVars.global.tls_load_count++;
GloVars.global.tls_last_load_timestamp = time(NULL);
GloVars.global.tls_last_load_ok = true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing tls_last_load_ok = false on reload failure paths.

When TLS reload fails (lines 444-472), tls_last_load_ok is not updated to false. Per PR objectives, TLS_Last_Load_Result should reflect NONE/SUCCESS/FAILED. After a failed reload, the stats will still show "SUCCESS" from the previous bootstrap, which is misleading.

Consider updating tls_last_load_ok = false on failure paths to accurately reflect the last reload attempt status:

🐛 Proposed fix for reload failure tracking
 							} else {
 								proxy_error("Failed to load location of CA certificates for verification\n");
 								msg = "Unable to load CA certificates location for verification";
+								std::lock_guard<std::mutex> lock(GloVars.global.ssl_mutex);
+								GloVars.global.tls_last_load_ok = false;
 								ret = 1;
 							}
 						} else {
 							proxy_error("Private key does not match the public certificate\n");
 							msg = "Private key does not match the public certificate";
+							std::lock_guard<std::mutex> lock(GloVars.global.ssl_mutex);
+							GloVars.global.tls_last_load_ok = false;
 							ret = 1;
 						}

Apply similar changes to other failure branches (lines 454-459, 460-465, 466-472).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/proxy_tls.cpp` around lines 439 - 442, The TLS reload success branch
currently increments GloVars.global.tls_load_count and sets
GloVars.global.tls_last_load_timestamp and GloVars.global.tls_last_load_ok =
true, but the failure branches do not set tls_last_load_ok = false; update each
TLS reload failure path to (under the same ssl_mutex) increment tls_load_count,
set tls_last_load_timestamp = time(NULL) and set tls_last_load_ok = false so
TLS_Last_Load_Result correctly reflects FAILED; look for the reload-related
branches that log failures and update those branches to mirror the success
updates (using GloVars.global.tls_load_count,
GloVars.global.tls_last_load_timestamp, and GloVars.global.tls_last_load_ok) and
ensure this is done inside the ssl_mutex-protected section.

@renecannao
Copy link
Contributor

@copilot : this feature is not strictly related to mysql , thus status variables shouldn't go to stats_mysql_global .
We abused table stats_mysql_global storing metrics not related to mysql.
Perhaps we can introduce a new table stats_global , and use for metrics that are not related to either mysql or pgsql .
Create table stats_global (in memory only) and make sure it is populated correctly wih TLS metrics.

Also, add plenty of unit/tap tests , and ensure that the code is well documented.

…mysql_global; add TAP tests

- Add STATS_SQLITE_TABLE_GLOBAL definition (stats_global table for non-MySQL/PgSQL metrics)
- Register stats_global in Admin_Bootstrap.cpp
- Add stats___global() implementation and declaration; document with Doxygen comment
- Remove TLS_* variables from stats___mysql_global() - they were misplaced there
- Move all TLS tracking metrics to stats___global() under ssl_mutex
- Wire up stats_global query detection and refresh in GenericRefreshStatistics()
- Add TAP test test_tls_stats-t.cpp:
  - Verifies stats_global contains all 6 TLS tracking variables
  - Checks value ranges and validity of each TLS variable
  - Verifies stats_tls_certificates has 2 rows (server + ca) with correct fields
  - Verifies TLS_Load_Count increments and TLS_Last_Load_Timestamp increases after PROXYSQL RELOAD TLS
  - Confirms TLS variables are absent from stats_mysql_global

Co-authored-by: renecannao <3645227+renecannao@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot : this feature is not strictly related to mysql , thus status variables shouldn't go to stats_mysql_global .
We abused table stats_mysql_global storing metrics not rel...

Done in commit e274862:

  • New stats_global table added (in-memory only) for metrics not tied to MySQL or PgSQL. Definition added to ProxySQL_Admin_Tables_Definitions.h, registered in Admin_Bootstrap.cpp, and backed by stats___global() in ProxySQL_Admin_Stats.cpp.
  • TLS metrics removed from stats_mysql_globalTLS_Load_Count, TLS_Last_Load_Timestamp, TLS_Last_Load_Result, TLS_Server_Cert_File, TLS_CA_Cert_File, TLS_Key_File now live exclusively in stats.stats_global.
  • TAP test test_tls_stats-t.cpp added with 29 assertions covering:
    • All 6 TLS variables present in stats_global with valid values
    • stats_tls_certificates has 2 rows (server and ca) with non-empty fields and reasonable days_until_expiry
    • PROXYSQL RELOAD TLS increments TLS_Load_Count and increases TLS_Last_Load_Timestamp
    • TLS variables are absent from stats_mysql_global

Copilot AI changed the title feat: Add stats_tls_certificates table and TLS tracking metrics to stats_mysql_global feat: Add stats_tls_certificates and stats_global tables for TLS certificate metadata and tracking Mar 20, 2026
@renecannao renecannao marked this pull request as ready for review March 20, 2026 22:05
Copilot AI review requested due to automatic review settings March 20, 2026 22:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new admin stats surfaces for TLS certificate introspection and TLS load/reload tracking, enabling operators to query certificate metadata and reload history without direct filesystem access.

Changes:

  • Introduces stats.stats_tls_certificates (query-time OpenSSL parsing) to expose cert metadata and expiry info.
  • Introduces stats.stats_global to hold ProxySQL-wide TLS tracking variables (moved out of stats_mysql_global).
  • Adds TAP coverage to validate TLS variables, table shape/content, and behavior after PROXYSQL RELOAD TLS.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
include/ProxySQL_Admin_Tables_Definitions.h Adds DDL for new stats_global and stats_tls_certificates tables.
include/proxysql_glovars.hpp Adds TLS tracking fields to global state.
lib/ProxySQL_GloVars.cpp Initializes newly added TLS tracking fields.
src/proxy_tls.cpp Updates TLS load/reload path to populate tracking fields.
lib/Admin_Bootstrap.cpp Registers new stats tables in bootstrap table definitions.
lib/ProxySQL_Admin.cpp Wires query detection + refresh dispatch for the new stats tables.
include/proxysql_admin.h Declares new stats refresh functions.
lib/ProxySQL_Admin_Stats.cpp Implements stats___global() and stats___tls_certificates() with OpenSSL parsing helpers.
test/tap/tests/test_tls_stats-t.cpp Adds a TAP test for both new stats tables and reload behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +439 to +442
// Update TLS tracking fields for stats table (under ssl_mutex)
GloVars.global.tls_load_count++;
GloVars.global.tls_last_load_timestamp = time(NULL);
GloVars.global.tls_last_load_ok = true;
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLS tracking fields (tls_load_count/tls_last_load_timestamp/tls_last_load_ok) are only updated on the success path. If PROXYSQL RELOAD TLS fails in any of the later else branches, tls_last_load_ok can remain true from a previous success and TLS_Last_Load_Result will incorrectly report SUCCESS. Consider setting tls_last_load_ok=false (and updating the timestamp/result) under ssl_mutex on every failure path as well.

Copilot uses AI. Check for mistakes.
*
* Currently tracked variables:
* - TLS_Load_Count : Number of times TLS has been loaded or reloaded.
* - TLS_Last_Load_Timestamp : Unix timestamp of the most recent successful TLS load.
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says TLS_Last_Load_Timestamp is the timestamp of the most recent successful TLS load, but TLS_Last_Load_Result can be FAILED and the code currently reads a single tls_last_load_timestamp value. Either update the implementation to track both “last attempt” and “last successful” timestamps, or adjust the documentation/variable meaning so timestamp+result are consistent.

Suggested change
* - TLS_Last_Load_Timestamp : Unix timestamp of the most recent successful TLS load.
* - TLS_Last_Load_Timestamp : Unix timestamp of the most recent TLS load attempt.

Copilot uses AI. Check for mistakes.
Comment on lines +3022 to +3033
// Helper lambda: read a PEM cert from file and insert a row into stats_tls_certificates
auto process_cert = [&](const char *cert_type, char *file_path) {
if (!file_path) return;
BIO *bio = BIO_new_file(file_path, "r");
if (bio) {
X509 *cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
BIO_free_all(bio);
if (cert) {
insert_tls_cert_row(statsdb, stmt, cert_type, file_path, cert, loaded_at);
X509_free(cert);
}
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stats___tls_certificates() only inserts a row when the cert file can be opened and parsed. If the file is missing/unreadable/invalid, the table silently ends up missing the expected cert_type row, which can break monitoring assumptions (and even your own test expects exactly 2 rows). Consider always inserting both server and ca rows (with file_path/loaded_at populated and other fields empty/default) and optionally surface read/parse failures via an additional column or by setting metadata fields empty but still present.

Suggested change
// Helper lambda: read a PEM cert from file and insert a row into stats_tls_certificates
auto process_cert = [&](const char *cert_type, char *file_path) {
if (!file_path) return;
BIO *bio = BIO_new_file(file_path, "r");
if (bio) {
X509 *cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
BIO_free_all(bio);
if (cert) {
insert_tls_cert_row(statsdb, stmt, cert_type, file_path, cert, loaded_at);
X509_free(cert);
}
}
// Helper lambda: read a PEM cert from file and insert a row into stats_tls_certificates.
// Always inserts a row for the given cert_type, even if the file is missing/unreadable/invalid.
auto process_cert = [&](const char *cert_type, char *file_path) {
X509 *cert = nullptr;
if (file_path) {
BIO *bio = BIO_new_file(file_path, "r");
if (bio) {
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
BIO_free_all(bio);
}
}
// Insert a row regardless of whether the certificate was successfully loaded.
insert_tls_cert_row(statsdb, stmt, cert_type, file_path, cert, loaded_at);
if (cert) {
X509_free(cert);
}

Copilot uses AI. Check for mistakes.
Comment on lines +272 to +306
if (mysql_query(admin, "PROXYSQL RELOAD TLS")) {
diag("PROXYSQL RELOAD TLS failed: %s", mysql_error(admin));
}
mysql_free_result(mysql_store_result(admin));

auto global_stats_after = query_key_value(admin,
"SELECT Variable_Name, Variable_Value FROM stats.stats_global");

int tls_load_count_after = 0;
if (global_stats_after.count("TLS_Load_Count"))
tls_load_count_after = std::stoi(global_stats_after["TLS_Load_Count"]);
ok(tls_load_count_after == tls_load_count + 1,
"stats_global: TLS_Load_Count incremented after RELOAD TLS (%d -> %d)",
tls_load_count, tls_load_count_after);

long long tls_ts_after = 0;
if (global_stats_after.count("TLS_Last_Load_Timestamp"))
tls_ts_after = std::stoll(global_stats_after["TLS_Last_Load_Timestamp"]);
ok(tls_ts_after > tls_ts,
"stats_global: TLS_Last_Load_Timestamp increased after RELOAD TLS (%lld -> %lld)",
tls_ts, tls_ts_after);

string tls_result_after = global_stats_after.count("TLS_Last_Load_Result") ?
global_stats_after["TLS_Last_Load_Result"] : "";
ok(tls_result_after == "SUCCESS",
"stats_global: TLS_Last_Load_Result='SUCCESS' after RELOAD TLS (got '%s')",
tls_result_after.c_str());

// stats_tls_certificates rows still present after reload
auto cert_rows_after = query_rows(admin,
"SELECT cert_type, file_path, sha256_fingerprint FROM stats.stats_tls_certificates",
{"cert_type", "file_path", "sha256_fingerprint"});
ok(cert_rows_after.size() == 2,
"stats_tls_certificates: still has 2 rows after RELOAD TLS (got %zu)",
cert_rows_after.size());
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test logs an error if PROXYSQL RELOAD TLS fails but then continues and performs assertions that assume reload succeeded. This can produce noisy, misleading failures; consider turning the reload into an ok(mysql_query(...)==0, ...) assertion (and bail/skip dependent checks if it fails).

Suggested change
if (mysql_query(admin, "PROXYSQL RELOAD TLS")) {
diag("PROXYSQL RELOAD TLS failed: %s", mysql_error(admin));
}
mysql_free_result(mysql_store_result(admin));
auto global_stats_after = query_key_value(admin,
"SELECT Variable_Name, Variable_Value FROM stats.stats_global");
int tls_load_count_after = 0;
if (global_stats_after.count("TLS_Load_Count"))
tls_load_count_after = std::stoi(global_stats_after["TLS_Load_Count"]);
ok(tls_load_count_after == tls_load_count + 1,
"stats_global: TLS_Load_Count incremented after RELOAD TLS (%d -> %d)",
tls_load_count, tls_load_count_after);
long long tls_ts_after = 0;
if (global_stats_after.count("TLS_Last_Load_Timestamp"))
tls_ts_after = std::stoll(global_stats_after["TLS_Last_Load_Timestamp"]);
ok(tls_ts_after > tls_ts,
"stats_global: TLS_Last_Load_Timestamp increased after RELOAD TLS (%lld -> %lld)",
tls_ts, tls_ts_after);
string tls_result_after = global_stats_after.count("TLS_Last_Load_Result") ?
global_stats_after["TLS_Last_Load_Result"] : "";
ok(tls_result_after == "SUCCESS",
"stats_global: TLS_Last_Load_Result='SUCCESS' after RELOAD TLS (got '%s')",
tls_result_after.c_str());
// stats_tls_certificates rows still present after reload
auto cert_rows_after = query_rows(admin,
"SELECT cert_type, file_path, sha256_fingerprint FROM stats.stats_tls_certificates",
{"cert_type", "file_path", "sha256_fingerprint"});
ok(cert_rows_after.size() == 2,
"stats_tls_certificates: still has 2 rows after RELOAD TLS (got %zu)",
cert_rows_after.size());
int reload_rc = mysql_query(admin, "PROXYSQL RELOAD TLS");
ok(reload_rc == 0, "PROXYSQL RELOAD TLS succeeds");
if (reload_rc != 0) {
diag("PROXYSQL RELOAD TLS failed: %s", mysql_error(admin));
} else {
MYSQL_RES* reload_res = mysql_store_result(admin);
if (reload_res != nullptr) {
mysql_free_result(reload_res);
}
auto global_stats_after = query_key_value(admin,
"SELECT Variable_Name, Variable_Value FROM stats.stats_global");
int tls_load_count_after = 0;
if (global_stats_after.count("TLS_Load_Count"))
tls_load_count_after = std::stoi(global_stats_after["TLS_Load_Count"]);
ok(tls_load_count_after == tls_load_count + 1,
"stats_global: TLS_Load_Count incremented after RELOAD TLS (%d -> %d)",
tls_load_count, tls_load_count_after);
long long tls_ts_after = 0;
if (global_stats_after.count("TLS_Last_Load_Timestamp"))
tls_ts_after = std::stoll(global_stats_after["TLS_Last_Load_Timestamp"]);
ok(tls_ts_after > tls_ts,
"stats_global: TLS_Last_Load_Timestamp increased after RELOAD TLS (%lld -> %lld)",
tls_ts, tls_ts_after);
string tls_result_after = global_stats_after.count("TLS_Last_Load_Result") ?
global_stats_after["TLS_Last_Load_Result"] : "";
ok(tls_result_after == "SUCCESS",
"stats_global: TLS_Last_Load_Result='SUCCESS' after RELOAD TLS (got '%s')",
tls_result_after.c_str());
// stats_tls_certificates rows still present after reload
auto cert_rows_after = query_rows(admin,
"SELECT cert_type, file_path, sha256_fingerprint FROM stats.stats_tls_certificates",
{"cert_type", "file_path", "sha256_fingerprint"});
ok(cert_rows_after.size() == 2,
"stats_tls_certificates: still has 2 rows after RELOAD TLS (got %zu)",
cert_rows_after.size());
}

Copilot uses AI. Check for mistakes.
Comment on lines +220 to +230
// Check non-empty required fields for server cert
if (!server_row.empty()) {
ok(!server_row["file_path"].empty(),
"stats_tls_certificates: server file_path is non-empty ('%s')",
server_row["file_path"].c_str());
ok(!server_row["sha256_fingerprint"].empty(),
"stats_tls_certificates: server sha256_fingerprint is non-empty");
int server_days = std::stoi(server_row["days_until_expiry"]);
ok(server_days > -MAX_CERT_DAYS && server_days < MAX_CERT_DAYS,
"stats_tls_certificates: server days_until_expiry=%d is reasonable",
server_days);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment says the test verifies non-empty subject_cn, issuer_cn, serial_number, not_before, and not_after, but the assertions currently only check file_path, sha256_fingerprint, days_until_expiry, and loaded_at. Either add assertions for the remaining columns or adjust the documented expectations so the test matches what it actually validates.

Copilot uses AI. Check for mistakes.
renecannao and others added 5 commits March 20, 2026 23:50
- lib/ProxySQL_GloVars.cpp: Free tls_cert_file, tls_ca_file, and tls_key_file in ProxySQL_GlobalVariables destructor.
- src/proxy_tls.cpp: Added std::lock_guard during initial bootstrap of TLS variables to prevent race conditions.
- src/proxy_tls.cpp: Ensure TLS file paths are updated in GloVars during PROXYSQL RELOAD TLS to keep stats table consistent with runtime configuration.
1. stats_global strstr match: add exclusion guards for stats_mysql_global
   and stats_pgsql_global to prevent spurious stats___global() calls and
   unnecessary ssl_mutex contention on every MySQL/PgSQL global stats query.

2. TLS reload failure tracking: set tls_last_load_ok=false and update
   tls_load_count/tls_last_load_timestamp on non-bootstrap reload failures.
   Previously, a failed PROXYSQL RELOAD TLS would leave stale SUCCESS status
   in stats_global.
…g collision

stats_global is a substring of stats_mysql_global and stats_pgsql_global,
causing the strstr-based dispatch to match incorrectly. Exclusion guards
don't work either because a query referencing both tables would suppress
the stats_proxysql_global refresh.

Renaming to stats_proxysql_global follows the existing naming convention
(stats_mysql_global, stats_pgsql_global) and eliminates the substring
collision entirely.
@renecannao renecannao force-pushed the copilot/add-ssl-tls-certificate-stats-table branch from ac97aa7 to a616b34 Compare March 22, 2026 15:39
…ertificate-stats-table

# Conflicts:
#	test/tap/groups/groups.json
@renecannao renecannao merged commit cb8d0aa into v3.0 Mar 22, 2026
3 of 5 checks passed
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add stats table to track loaded SSL/TLS certificates metadata

3 participants