
From 1b275fec48762c34cdf9ce158c88956a3ebb0372 Mon Sep 17 00:00:00 2001
From: Bob Ross <bob.ross.19821@gmail.com>
Date: Wed, 25 Mar 2026 05:55:29 -0400
Subject: [PATCH] feat: reload SSL certificates on SIGHUP without restart
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Allow Pgpool-II to pick up rotated TLS certificates (and any change to
SSL-related configuration) when receiving SIGHUP (i.e. systemctl reload
pgpool2), matching the behavior PostgreSQL has had since PostgreSQL 12.

Problem
-------
All SSL configuration parameters (ssl_cert, ssl_key, ssl_ca_cert,
ssl_ciphers, etc.) were declared CFGCXT_INIT, meaning they were silently
ignored when pool_get_config() was called under CFGCXT_RELOAD.
Furthermore, SSL_ServerSide_init() was only called once at startup in
main.c and never again, so the in-memory SSL_CTX was never refreshed.

Fix
---
1. src/main/pgpool_main.c
   - Include utils/pool_ssl.h.
   - In reload_config(), call SSL_ServerSide_init() (guarded by
     #ifdef USE_SSL) *before* kill_all_children(SIGHUP).  The function
     already replaces SSL_frontend_context atomically: it frees the old
     SSL_CTX only after a new one has been created successfully, so a
     failed reload leaves the existing context intact.

2. src/protocol/child.c
   - In check_config_reload(), call SSL_ServerSide_init() (guarded by
     #ifdef USE_SSL) so each worker child also refreshes its own copy of
     the SSL context for subsequent new connections.  In-flight TLS
     sessions are unaffected because they hold a direct reference to the
     SSL object, not to SSL_frontend_context.

3. src/config/pool_config_variables.c
   - Change CFGCXT_INIT -> CFGCXT_RELOAD for:
       ssl_prefer_server_ciphers, ssl_cert, ssl_key, ssl_ca_cert,
       ssl_ca_cert_dir, ssl_crl_file, ssl_ciphers, ssl_ecdh_curve,
       ssl_dh_params_file, ssl_passphrase_command.
   - The 'ssl' boolean (master enable flag) is intentionally left as
     CFGCXT_INIT because dynamically enabling SSL at runtime is a
     larger, separate concern.

Usage after this change
-----------------------
Standard in-place certificate rotation (cert-manager, ACME, manual
openssl refresh at the same path):

    # replace /etc/pgpool/server.{crt,key} with new files
    systemctl reload pgpool2
    # or: pgpool -f /etc/pgpool/pgpool.conf reload

New connections will use the new certificates after workers process 
the reload signal. Existing connections are not interrupted.

Switching to a different certificate path also works: update
pgpool.conf then reload — the new paths are now accepted in
CFGCXT_RELOAD context.
---
 src/config/pool_config_variables.c | 20 ++++++++++----------
 src/main/pgpool_main.c             | 17 +++++++++++++++++
 src/protocol/child.c               | 11 +++++++++++
 3 files changed, 38 insertions(+), 10 deletions(-)

diff --git a/src/config/pool_config_variables.c b/src/config/pool_config_variables.c
index ce13c42f6..ca40c341a 100644
--- a/src/config/pool_config_variables.c
+++ b/src/config/pool_config_variables.c
@@ -705,7 +705,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},

 	{
-		{"ssl_prefer_server_ciphers", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_prefer_server_ciphers", CFGCXT_RELOAD, SSL_CONFIG,
 			"Use server's SSL cipher preferences, rather than the client's",
 			CONFIG_VAR_TYPE_BOOL, false, 0
 		},
@@ -1271,7 +1271,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_cert", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_cert", CFGCXT_RELOAD, SSL_CONFIG,
 			"SSL public certificate file.",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1281,7 +1281,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_key", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_key", CFGCXT_RELOAD, SSL_CONFIG,
 			"SSL private key file.",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1291,7 +1291,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_ca_cert", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_ca_cert", CFGCXT_RELOAD, SSL_CONFIG,
 			"Single PEM format file containing CA root certificate(s).",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1301,7 +1301,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_ca_cert_dir", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_ca_cert_dir", CFGCXT_RELOAD, SSL_CONFIG,
 			"Directory containing CA root certificate(s).",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1311,7 +1311,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_crl_file", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_crl_file", CFGCXT_RELOAD, SSL_CONFIG,
 			"SSL certificate revocation list file",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1321,7 +1321,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_ciphers", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_ciphers", CFGCXT_RELOAD, SSL_CONFIG,
 			"Allowed SSL ciphers.",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1331,7 +1331,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_ecdh_curve", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_ecdh_curve", CFGCXT_RELOAD, SSL_CONFIG,
 			"The curve to use in ECDH key exchange.",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1341,7 +1341,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_dh_params_file", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_dh_params_file", CFGCXT_RELOAD, SSL_CONFIG,
 			"Path to the Diffie-Hellman parameters contained file",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
@@ -1351,7 +1351,7 @@ static struct config_string ConfigureNamesString[] =
 	},

 	{
-		{"ssl_passphrase_command", CFGCXT_INIT, SSL_CONFIG,
+		{"ssl_passphrase_command", CFGCXT_RELOAD, SSL_CONFIG,
 			"Path to the Diffie-Hellman parameters contained file",
 			CONFIG_VAR_TYPE_STRING, false, 0
 		},
diff --git a/src/main/pgpool_main.c b/src/main/pgpool_main.c
index bf7c452e2..0a9c92826 100644
--- a/src/main/pgpool_main.c
+++ b/src/main/pgpool_main.c
@@ -61,6 +61,7 @@
 #include "watchdog/wd_lifecheck.h"
 #include "watchdog/watchdog.h"
 #include "pcp/pcp_worker.h"
+#include "utils/pool_ssl.h"
 #include <grp.h>

 /*
@@ -3489,6 +3490,22 @@ reload_config(void)
 	if (pool_config->enable_pool_hba)
 		load_hba(hba_file);

+#ifdef USE_SSL
+	/*
+	 * If SSL is enabled, re-initialize the SSL context so that new
+	 * connections pick up rotated certificates without requiring a restart.
+	 * SSL_ServerSide_init() is safe to call repeatedly: it frees and replaces
+	 * the existing SSL_CTX only on success, leaving the old context intact on
+	 * failure.
+	 */
+	if (pool_config->ssl)
+	{
+		ereport(LOG,
+				(errmsg("reload SSL certificates.")));
+		SSL_ServerSide_init();
+	}
+#endif							/* USE_SSL */
+
 	kill_all_children(SIGHUP);
 }

diff --git a/src/protocol/child.c b/src/protocol/child.c
index c34f05728..713bfe28f 100644
--- a/src/protocol/child.c
+++ b/src/protocol/child.c
@@ -1796,6 +1796,17 @@ check_config_reload(void)
 		if (strcmp("", pool_config->pool_passwd))
 			pool_reopen_passwd_file();

+#ifdef USE_SSL
+		/*
+		 * Re-initialize the frontend SSL context so this child process
+		 * serves new connections with any rotated certificates without a
+		 * restart.  In-flight TLS sessions are unaffected; they hold a
+		 * direct reference to the old SSL object.
+		 */
+		if (pool_config->ssl)
+			SSL_ServerSide_init();
+#endif						/* USE_SSL */
+
 		got_sighup = 0;
 	}
 }
--
2.52.0
