From d13dce61a7e7c00d937f4268064ea489d33c6f31 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Thu, 15 Oct 2026 17:36:54 +0000 Subject: [PATCH v1 3/3] ssl: Reject keys which cannot be loaded in EXEC_BACKEND children Do the same as the previous commit for postgresql.conf with ssl_sni off: a passphrase-protected key without ssl_passphrase_command_supports_reload now prevents server start in EXEC_BACKEND builds, rather than leaving every backend without SSL. Commit 0f4f45772c5 only documented this requirement. Installations using their own TLS init hook are not affected. Author: Zsolt Parragi --- doc/src/sgml/config.sgml | 3 ++- doc/src/sgml/runtime.sgml | 5 +++-- src/backend/libpq/be-secure-openssl.c | 13 +++++++++++- src/test/ssl/t/001_ssltests.pl | 30 +++++++++++++++------------ 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index e537b3b0043..411b03fb389 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1758,7 +1758,8 @@ include_dir 'conf.d' This parameter must be set to on when running on Windows since all connections will perform a configuration reload due to the different process model - of that platform. + of that platform. If the private key requires a passphrase and this + parameter is off, the server will refuse to start. This parameter can only be set in the postgresql.conf diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 2c396427f59..37ff32237c6 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2286,8 +2286,9 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 Using a passphrase by default disables the ability to change the server's SSL configuration without a server restart, but see . - Furthermore, passphrase-protected private keys cannot be used at all - on Windows. + On Windows, passphrase-protected + private keys can only be used with a passphrase command which supports + reloading, otherwise the server will refuse to start. diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 866de73005c..948a0cff3c8 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -776,7 +776,10 @@ init_host_context(HostsLine *host, bool isServerStart, bool *hasWarned) { if (dummy_ssl_passwd_cb_called && isServerStart) { - /* Only reachable in EXEC_BACKEND builds, see above */ + /* + * Only reachable in EXEC_BACKEND builds, see above and + * default_openssl_tls_init(). + */ ereport(FATAL, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase", @@ -2610,7 +2613,15 @@ host_cache_pointer(const char *key) static void default_openssl_tls_init(SSL_CTX *context, bool isServerStart) { + /* + * In EXEC_BACKEND builds every child process loads the configuration + * again, see init_host_context(). + */ +#ifdef EXEC_BACKEND + if (false) +#else if (isServerStart) +#endif { if (ssl_passphrase_command[0]) { diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index cb7c2a06193..7dae786198b 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -88,13 +88,15 @@ $ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR, note "testing password-protected keys"; # Test a passphrase command which fails to unlock the private key, the server -# should not start at all. +# should not start at all. Enable reloading so that the command is executed +# at server start in EXEC_BACKEND builds as well. switch_server_cert( $node, certfile => 'server-cn-only', cafile => 'root+client_ca', keyfile => 'server-password', passphrase_cmd => 'echo wrongpassword', + passphrase_cmd_reload => 'on', restart => 'no'); $result = $node->restart( @@ -107,8 +109,8 @@ is($result, 0, # which doesn't support reloading. Unlocking the private key will fail when # reloading and the already existing SSL context will remain in place, with # connections still accepted. EXEC_BACKEND builds will reload the SSL context -# on each backend startup, so command reloading must be enabled or else -# connections will fail. +# on each backend startup, so command reloading must be enabled or else the +# server refuses to start. switch_server_cert( $node, certfile => 'server-cn-only', @@ -121,26 +123,28 @@ switch_server_cert( $result = $node->restart( fail_ok => 1, log_unlike => qr/could not load private key file/); -is($result, 1, 'restart succeeds with password-protected key file'); +my $log_start; if ($exec_backend =~ /on/) { - $node->connect_fails( - "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require", - "connect with correct server CA cert file sslmode=require", - expected_stderr => qr/\Qserver does not support SSL\E/); + is($result, 0, + 'restart fails with password-protected key file without passphrase command reloading' + ); + $log_start = $node->wait_for_log( + qr/cannot be reloaded because it requires a passphrase/); } else { + is($result, 1, 'restart succeeds with password-protected key file'); $node->connect_ok( "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require", "connect with correct server CA cert file sslmode=require"); -} -# Reloading should fail since we cannot execute the passphrase command -$node->reload(); -my $log_start = $node->wait_for_log( - qr/cannot be reloaded because it requires a passphrase/); + # Reloading should fail since we cannot execute the passphrase command + $node->reload(); + $log_start = $node->wait_for_log( + qr/cannot be reloaded because it requires a passphrase/); +} # Test a passphrase command which successfully unlocks the private key, and # which can be reloaded. The server should start and connections be accepted. -- 2.56.0