diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 666900f5d0f..6e9f7f466df 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2649,6 +2649,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..d02bc2d6ad2 100644 --- a/src/test/ssl/t/004_sni.pl +++ b/src/test/ssl/t/004_sni.pl @@ -504,6 +504,38 @@ 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',