From bb4aec953d7fecbe5786d70204b63a42ef97c831 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Thu, 15 Oct 2026 17:36:54 +0000 Subject: [PATCH v1 1/3] ssl: Don't disable SSL for all hosts when one SNI host fails to load On Windows and in EXEC_BACKEND builds every backend reloads the SSL configuration, with no previous one to fall back on. If a single pg_hosts.conf entry failed to load there, the whole configuration was discarded and the backend ran without SSL for every host. Fix by keeping the failed entry without an SSL context in child processes and failing the handshake only for connections selecting it. Also document that SSL_passphrase_cmd_reload must be enabled on Windows, like commit 0f4f45772c5 did for the GUC. Author: Zsolt Parragi --- doc/src/sgml/runtime.sgml | 10 +++++++++ src/backend/libpq/be-secure-openssl.c | 24 ++++++++++++++++++++- src/test/ssl/t/004_sni.pl | 31 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index d9984910cc4..071e82a83bc 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2643,6 +2643,16 @@ openssl x509 -req -in server.csr -text -days 365 \ off. + + On Windows, all connections will + perform a configuration reload due to the different process model of that + platform, so SSL_passphrase_cmd_reload must be + set to on for every entry which has an + SSL_passphrase_cmd. If an entry fails to load + at backend start, SSL connections for the hosts of that entry will fail, + while the other entries remain usable. + + hostname can be either the literal hostname for the connection, /no_sni/, or *. diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 11f5a483b36..fbd4f1173bc 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -167,6 +167,7 @@ be_tls_init(bool isServerStart) int ssl_ver_max = -1; host_cache_hash *host_cache = NULL; bool hasWarned = false; + int nloaded = 0; /* * Since we don't know which host we're using until the ClientHello is @@ -331,10 +332,20 @@ be_tls_init(bool isServerStart) * Create the SSL context only once the entry has been accepted * and added to new_hosts, as the cleanup callback can only free * contexts of entries it can reach from there. + * + * An EXEC_BACKEND child has no previous configuration to fall + * back on, so rather than leaving the backend without SSL for all + * hosts we keep a failed entry without a context. Connections + * selecting it will fail the handshake. */ - if (!init_host_context(host, isServerStart, &hasWarned)) + if (init_host_context(host, isServerStart, &hasWarned)) + nloaded++; + else if (!IsUnderPostmaster) goto error; } + + if (nloaded == 0) + goto error; } /* @@ -2098,6 +2109,17 @@ sni_clienthello_cb(SSL *ssl, int *al, void *arg) } found: + if (install_config->ssl_ctx == NULL) + { + *al = SSL_AD_INTERNAL_ERROR; + ereport(COMMERROR, + errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("SSL configuration for host could not be loaded, terminating connection"), + errcontext("line %d of configuration file \"%s\"", + install_config->linenumber, install_config->sourcefile)); + return SSL_CLIENT_HELLO_ERROR; + } + if (!ssl_update_ssl(ssl, install_config)) { *al = SSL_AD_INTERNAL_ERROR; diff --git a/src/test/ssl/t/004_sni.pl b/src/test/ssl/t/004_sni.pl index 635e637bdd3..48ee6b455b6 100644 --- a/src/test/ssl/t/004_sni.pl +++ b/src/test/ssl/t/004_sni.pl @@ -504,6 +504,37 @@ SKIP: 'log reload failure due to passphrase command reloading'); } +# A host whose key cannot be loaded in an EXEC_BACKEND child must only affect +# connections to that host. +ok(unlink($node->data_dir . '/pg_hosts.conf')); +$node->append_conf('pg_hosts.conf', + 'localhost server-cn-only.crt server-cn-only.key'); +$node->append_conf('pg_hosts.conf', + 'example.org server-cn-only.crt server-password.key root+client_ca.crt "echo secret1" off' +); +$node->restart; +$node_loglocation = -s $node->logfile; + +$node->connect_ok( + "$connstr sslrootcert=ssl/root+server_ca.crt sslmode=require host=localhost", + "pg_hosts.conf: connect to host next to a passphrase host without reload"); + +if ($windows_os || $exec_backend =~ /on/) +{ + $node->connect_fails( + "$connstr sslrootcert=ssl/root+server_ca.crt sslmode=require host=example.org", + "pg_hosts.conf: connect to passphrase host without reload fails", + expected_stderr => qr/internal error/); + $node->wait_for_log(qr/SSL configuration for host could not be loaded/, + $node_loglocation); +} +else +{ + $node->connect_ok( + "$connstr sslrootcert=ssl/root+server_ca.crt sslmode=require host=example.org", + "pg_hosts.conf: connect to passphrase host without reload"); +} + # Configure with only non-SNI connections allowed ok(unlink($node->data_dir . '/pg_hosts.conf')); $node->append_conf('pg_hosts.conf', -- 2.56.0