Refactor SSL test framework to support multiple TLS libraries

Started by Daniel Gustafssonalmost 5 years ago14 messages
#1Daniel Gustafsson
daniel@yesql.se
1 attachment(s)

In an attempt to slice off as much non-NSS specific changes as possible from
the larger libnss patch proposed in [0]/messages/by-id/FAB21FC8-0F62-434F-AA78-6BD9336D630A@yesql.se, the attached patch contains the ssl
test harness refactoring to support multiple TLS libraries.

The changes are mostly a refactoring to hide library specific setup in their
own modules, but also extend set_server_cert() to support password command
which cleans up the TAP tests from hands-on setup and teardown.

cheers ./daniel

[0]: /messages/by-id/FAB21FC8-0F62-434F-AA78-6BD9336D630A@yesql.se

Attachments:

0001-Refactor-library-specific-SSL-test-setup-into-separa.patchapplication/octet-stream; name=0001-Refactor-library-specific-SSL-test-setup-into-separa.patch; x-unix-mode=0644Download
From 73a9b2cd5ad2a166b1aeba66f0d3dd2cc6c0ff3e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 20 Jan 2021 17:55:21 +0100
Subject: [PATCH] Refactor library specific SSL test setup into separate module

This moves TLS library specific setup and teardown to its own classes
under SSL::Server. If another TLS library is supported then this will
make it easier to plug into the existing testing framework.

This changeset was extracted from the larger patchset for supporting
libnss as a TLS backend in PostgreSQL.
---
 src/test/ssl/t/001_ssltests.pl                |  55 ++--------
 src/test/ssl/t/002_scram.pl                   |   2 +-
 src/test/ssl/t/SSL/Backend/OpenSSL.pm         | 103 ++++++++++++++++++
 .../ssl/t/{SSLServer.pm => SSL/Server.pm}     |  70 +++++++++---
 4 files changed, 167 insertions(+), 63 deletions(-)
 create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
 rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (81%)

diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..8ae1e4e505 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,12 +4,10 @@ use PostgresNode;
 use TestLib;
 use Test::More;
 
-use File::Copy;
-
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_openssl} eq 'yes')
 {
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
 
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
-	"client",     "client-revoked",
-	"client-der", "client-encrypted-pem",
-	"client-encrypted-der");
-foreach my $key (@keys)
-{
-	copy("ssl/${key}.key", "ssl/${key}_tmp.key")
-	  or die
-	  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
-	chmod 0600, "ssl/${key}_tmp.key"
-	  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable.  We can't
-# necessarily rely on the file in the source tree having those
-# permissions.  Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
 #### Set up the server.
 
 note "setting up data directory";
@@ -72,32 +44,22 @@ $node->start;
 
 # Run this before we lock down access below.
 my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
 
 configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 	'trust');
 
 note "testing password-protected keys";
 
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+				   'server-password', 'echo wrongpassword');
 command_fails(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart fails with password-protected key file with wrong password');
 $node->_update_pid(0);
 
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
-
+set_server_cert($node, 'server-cn-only', 'root+client_ca',
+				'server-password', 'echo secret1');
 command_ok(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart succeeds with password-protected key file');
@@ -546,7 +508,4 @@ test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
 	qr/SSL error/, "intermediate client certificate is missing");
 
 # clean up
-foreach my $key (@keys)
-{
-	unlink("ssl/${key}_tmp.key");
-}
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a088f71a1a..dfbf781027 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,7 +11,7 @@ use File::Copy;
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_openssl} ne 'yes')
 {
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA       = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+	@keys = (
+		"client",     "client-revoked",
+		"client-der", "client-encrypted-pem",
+		"client-encrypted-der");
+}
+
+sub new
+{
+	my ($class) = @_;
+
+	my $self = { _library => 'OpenSSL' };
+
+	bless $self, $class;
+
+	return $self;
+}
+
+sub get_new_openssl_backend
+{
+	my $class = 'SSL::Backend::OpenSSL';
+
+	my $backend = $class->new();
+
+	return $backend;
+}
+
+sub init
+{
+	# The client's private key must not be world-readable, so take a copy
+	# of the key stored in the code tree and update its permissions.
+	#
+	# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+	# of the tests.
+	foreach my $key (@keys)
+	{
+		copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+		  or die
+		  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+		chmod 0600, "ssl/${key}_tmp.key"
+		  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+	}
+
+	# Also make a copy of that explicitly world-readable.  We can't
+	# necessarily rely on the file in the source tree having those
+	# permissions. Add it to @keys to include it in the final clean
+	# up phase.
+	copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+	  or die
+	  "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+	chmod 0644, "ssl/client_wrongperms_tmp.key"
+	  or die
+	  "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+	push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+	my $self     = $_[0];
+	my $certfile = $_[1];
+	my $cafile   = $_[2] || "root+client_ca";
+	my $keyfile  = $_[3] || $certfile;
+
+	my $sslconf =
+	    "ssl_ca_file='$cafile.crt'\n"
+	  . "ssl_cert_file='$certfile.crt'\n"
+	  . "ssl_key_file='$keyfile.key'\n"
+	  . "ssl_crl_file='root+client.crl'\n";
+
+	return $sslconf;
+}
+
+sub get_library
+{
+	my ($self) = @_;
+
+	return $self->{_library};
+}
+
+sub cleanup
+{
+	foreach my $key (@keys)
+	{
+		unlink("ssl/${key}_tmp.key");
+	}
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 81%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index f5987a003e..fc56e9f47a 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,19 +23,33 @@
 # explicitly because an invalid sslcert or sslrootcert, respectively,
 # causes those to be ignored.)
 
-package SSLServer;
+package SSL::Server;
 
 use strict;
 use warnings;
 use PostgresNode;
+use RecursiveCopy;
 use TestLib;
 use File::Basename;
 use File::Copy;
 use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+
+our ($openssl, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+	$backend = get_new_openssl_backend();
+	$openssl = 1;
+}
 
 use Exporter 'import';
 our @EXPORT = qw(
   configure_test_server_for_ssl
+  set_server_cert
   switch_server_cert
   test_connect_fails
   test_connect_ok
@@ -144,12 +158,15 @@ sub configure_test_server_for_ssl
 	close $sslconf;
 
 	# Copy all server certificates and keys, and client root cert, to the data dir
-	copy_files("ssl/server-*.crt", $pgdata);
-	copy_files("ssl/server-*.key", $pgdata);
-	chmod(0600, glob "$pgdata/server-*.key") or die $!;
-	copy_files("ssl/root+client_ca.crt", $pgdata);
-	copy_files("ssl/root_ca.crt",        $pgdata);
-	copy_files("ssl/root+client.crl",    $pgdata);
+	if (defined($openssl))
+	{
+		copy_files("ssl/server-*.crt", $pgdata);
+		copy_files("ssl/server-*.key", $pgdata);
+		chmod(0600, glob "$pgdata/server-*.key") or die $!;
+		copy_files("ssl/root+client_ca.crt", $pgdata);
+		copy_files("ssl/root_ca.crt",        $pgdata);
+		copy_files("ssl/root+client.crl",    $pgdata);
+	}
 
 	# Stop and restart server to load new listen_addresses.
 	$node->restart;
@@ -157,26 +174,51 @@ sub configure_test_server_for_ssl
 	# Change pg_hba after restart because hostssl requires ssl=on
 	configure_hba_for_ssl($node, $servercidr, $authmethod);
 
+	# Finally, perform backend specific configuration
+	$backend->init();
+
 	return;
 }
 
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
-sub switch_server_cert
+sub ssl_library
+{
+	return $backend->get_library();
+}
+
+sub cleanup
+{
+	$backend->cleanup();
+}
+
+# Change the configuration to use given server cert file,
+sub set_server_cert
 {
 	my $node     = $_[0];
 	my $certfile = $_[1];
 	my $cafile   = $_[2] || "root+client_ca";
+	my $keyfile  = $_[3] || '';
+	my $pwcmd    = $_[4] || '';
 	my $pgdata   = $node->data_dir;
 
+	$keyfile = $certfile if $keyfile eq '';
+
 	open my $sslconf, '>', "$pgdata/sslconfig.conf";
 	print $sslconf "ssl=on\n";
-	print $sslconf "ssl_ca_file='$cafile.crt'\n";
-	print $sslconf "ssl_cert_file='$certfile.crt'\n";
-	print $sslconf "ssl_key_file='$certfile.key'\n";
-	print $sslconf "ssl_crl_file='root+client.crl'\n";
+	print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+	print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+	  unless $pwcmd eq '';
 	close $sslconf;
+	return;
+}
 
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+# Takes the same arguments as set_server_cert, which it calls to do that
+# piece of the work.
+sub switch_server_cert
+{
+	my $node = $_[0];
+	set_server_cert(@_);
 	$node->restart;
 	return;
 }
-- 
2.21.1 (Apple Git-122.3)

#2Daniel Gustafsson
daniel@yesql.se
In reply to: Daniel Gustafsson (#1)
1 attachment(s)
Re: Refactor SSL test framework to support multiple TLS libraries

Attached is a v2 which addresses the comments raised on the main NSS thread, as
well as introduces named parameters for the server cert function to make the
test code easier to read.

--
Daniel Gustafsson https://vmware.com/

Attachments:

v2-0001-Refactor-SSL-testharness-for-multiple-library.patchapplication/octet-stream; name=v2-0001-Refactor-SSL-testharness-for-multiple-library.patch; x-unix-mode=0644Download
From e441a7c3cf1d807c998ce60c25f72a370d4f16af Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:34 +0100
Subject: [PATCH v2] Refactor SSL testharness for multiple library

The SSL testharness was fully tied to OpenSSL in the way the server was
set up and reconfigured. This refactors the SSLServer module into a SSL
library agnostic SSL/Server module which in turn use SSL/Backend/<lib>
modules for the implementation details.

No changes are done to the actual tests, this only change how setup and
teardown is performed.
---
 src/test/ssl/t/001_ssltests.pl                |  80 ++++------
 src/test/ssl/t/002_scram.pl                   |   8 +-
 src/test/ssl/t/SSL/Backend/OpenSSL.pm         | 139 ++++++++++++++++++
 .../ssl/t/{SSLServer.pm => SSL/Server.pm}     |  88 ++++++-----
 4 files changed, 210 insertions(+), 105 deletions(-)
 create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
 rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%)

diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index bfada03d3e..849df5dedc 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,12 +4,10 @@ use PostgresNode;
 use TestLib;
 use Test::More;
 
-use File::Copy;
-
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_ssl} ne 'openssl')
 {
@@ -32,32 +30,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 # Allocation of base connection string shared among multiple tests.
 my $common_connstr;
 
-# The client's private key must not be world-readable, so take a copy
-# of the key stored in the code tree and update its permissions.
-#
-# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
-# of the tests.
-my @keys = (
-	"client",     "client-revoked",
-	"client-der", "client-encrypted-pem",
-	"client-encrypted-der");
-foreach my $key (@keys)
-{
-	copy("ssl/${key}.key", "ssl/${key}_tmp.key")
-	  or die
-	  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
-	chmod 0600, "ssl/${key}_tmp.key"
-	  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
-}
-
-# Also make a copy of that explicitly world-readable.  We can't
-# necessarily rely on the file in the source tree having those
-# permissions.  Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
 #### Set up the server.
 
 note "setting up data directory";
@@ -72,31 +44,31 @@ $node->start;
 
 # Run this before we lock down access below.
 my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
 
 configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 	'trust');
 
 note "testing password-protected keys";
 
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
+switch_server_cert($node,
+	certfile => 'server-cn-only',
+	cafile => 'root+client_ca',
+	keyfile => 'server-password',
+	passphrase_cmd => 'echo wrongpassword',
+	restart => 'no' );
 
 command_fails(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
 	'restart fails with password-protected key file with wrong password');
 $node->_update_pid(0);
 
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+switch_server_cert($node,
+	certfile => 'server-cn-only',
+	cafile => 'root+client_ca',
+	keyfile => 'server-password',
+	passphrase_cmd => 'echo secret1',
+	restart => 'no');
 
 command_ok(
 	[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
@@ -129,7 +101,7 @@ command_ok(
 
 note "running client tests";
 
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only');
 
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -254,7 +226,7 @@ test_connect_fails(
 	"mismatch between host name and server certificate sslmode=verify-full");
 
 # Test Subject Alternative Names.
-switch_server_cert($node, 'server-multiple-alt-names');
+switch_server_cert($node, certfile => 'server-multiple-alt-names');
 
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
@@ -285,7 +257,7 @@ test_connect_fails(
 
 # Test certificate with a single Subject Alternative Name. (this gives a
 # slightly different error message, that's all)
-switch_server_cert($node, 'server-single-alt-name');
+switch_server_cert($node, certfile => 'server-single-alt-name');
 
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
@@ -309,7 +281,7 @@ test_connect_fails(
 
 # Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
 # should be ignored when the certificate has both.
-switch_server_cert($node, 'server-cn-and-alt-names');
+switch_server_cert($node, certfile => 'server-cn-and-alt-names');
 
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
@@ -330,7 +302,7 @@ test_connect_fails(
 
 # Finally, test a server certificate that has no CN or SANs. Of course, that's
 # not a very sensible certificate, but libpq should handle it gracefully.
-switch_server_cert($node, 'server-no-names');
+switch_server_cert($node, certfile => 'server-no-names');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
@@ -345,7 +317,7 @@ test_connect_fails(
 	"server certificate without CN or SANs sslmode=verify-full");
 
 # Test that the CRL works
-switch_server_cert($node, 'server-revoked');
+switch_server_cert($node, certfile => 'server-revoked');
 
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
@@ -405,6 +377,8 @@ test_connect_fails(
 ###
 ### Test certificate authorization.
 
+switch_server_cert($node, certfile => 'server-revoked');
+
 note "running server tests";
 
 $common_connstr =
@@ -552,7 +526,7 @@ test_connect_ok(
 );
 
 # intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
-switch_server_cert($node, 'server-cn-only', 'root_ca');
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca');
 $common_connstr =
   "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
@@ -564,7 +538,7 @@ test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
 	qr/SSL error/, "intermediate client certificate is missing");
 
 # test server-side CRL directory
-switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
+switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir');
 
 # revoked client cert
 test_connect_fails(
@@ -574,7 +548,5 @@ test_connect_fails(
 	"certificate authorization fails with revoked client cert with server-side CRL directory");
 
 # clean up
-foreach my $key (@keys)
-{
-	unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 410b9e910d..ceb2301642 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,11 +11,11 @@ use File::Copy;
 use FindBin;
 use lib $FindBin::RealBin;
 
-use SSLServer;
+use SSL::Server;
 
 if ($ENV{with_ssl} ne 'openssl')
 {
-	plan skip_all => 'OpenSSL not supported by this build';
+	plan skip_all => 'SSL not supported by this build';
 }
 
 # This is the hostname used to connect to the server.
@@ -47,7 +47,7 @@ $node->start;
 # Configure server for SSL connections, with password handling.
 configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
 	"scram-sha-256", "pass", "scram-sha-256");
-switch_server_cert($node, 'server-cn-only');
+switch_server_cert($node, certfile => 'server-cn-only');
 $ENV{PGPASSWORD} = "pass";
 $common_connstr =
   "dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
@@ -103,6 +103,6 @@ test_connect_fails(
 	"Cert authentication and channel_binding=require");
 
 # clean up
-unlink($client_tmp_key);
+SSL::Server::cleanup();
 
 done_testing($number_of_tests);
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
new file mode 100644
index 0000000000..0deb867759
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,139 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Basename;
+use File::Copy;
+
+our @ISA       = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend install_certificates);
+
+our (@keys);
+
+INIT
+{
+	@keys = (
+		"client",     "client-revoked",
+		"client-der", "client-encrypted-pem",
+		"client-encrypted-der");
+}
+
+sub new
+{
+	my ($class) = @_;
+
+	my $self = { _library => 'OpenSSL' };
+
+	bless $self, $class;
+
+	return $self;
+}
+
+sub get_new_openssl_backend
+{
+	my $class = 'SSL::Backend::OpenSSL';
+
+	my $backend = $class->new();
+
+	return $backend;
+}
+
+sub init
+{
+	# The client's private key must not be world-readable, so take a copy
+	# of the key stored in the code tree and update its permissions.
+	#
+	# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+	# of the tests.
+	foreach my $key (@keys)
+	{
+		copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+		  or die
+		  "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+		chmod 0600, "ssl/${key}_tmp.key"
+		  or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+	}
+
+	# Also make a copy of that explicitly world-readable.  We can't
+	# necessarily rely on the file in the source tree having those
+	# permissions. Add it to @keys to include it in the final clean
+	# up phase.
+	copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+	  or die
+	  "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+	chmod 0644, "ssl/client_wrongperms_tmp.key"
+	  or die
+	  "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+	push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+	my $self   = $_[0];
+	my $params = $_[1];
+
+	$params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
+	$params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
+	$params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
+
+	my $sslconf =
+	    "ssl_ca_file='$params->{cafile}.crt'\n"
+	  . "ssl_cert_file='$params->{certfile}.crt'\n"
+	  . "ssl_key_file='$params->{keyfile}.key'\n"
+	  . "ssl_crl_file='$params->{crlfile}'\n";
+	$sslconf .= "ssl_crl_dir='$params->{crldir}'\n" if defined $params->{crldir};
+
+	return $sslconf;
+}
+
+sub get_library
+{
+	my ($self) = @_;
+
+	return $self->{_library};
+}
+
+# Copy a set of files, taking into account wildcards
+sub __copy_files
+{
+	my $orig = shift;
+	my $dest = shift;
+
+	my @orig_files = glob $orig;
+	foreach my $orig_file (@orig_files)
+	{
+		my $base_file = basename($orig_file);
+		copy($orig_file, "$dest/$base_file")
+		  or die "Could not copy $orig_file to $dest";
+	}
+	return;
+}
+
+sub install_certificates
+{
+	my $self   = $_[0];
+	my $pgdata = $_[1];
+
+	# Copy all server certificates and keys, and client root cert, to the data dir
+	__copy_files("ssl/server-*.crt", $pgdata);
+	__copy_files("ssl/server-*.key", $pgdata);
+	chmod(0600, glob "$pgdata/server-*.key") or die $!;
+	__copy_files("ssl/root+client_ca.crt", $pgdata);
+	__copy_files("ssl/root_ca.crt",        $pgdata);
+	__copy_files("ssl/root+client.crl",    $pgdata);
+	mkdir("$pgdata/root+client-crldir");
+	__copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+}
+
+sub cleanup
+{
+	foreach my $key (@keys)
+	{
+		unlink("ssl/${key}_tmp.key");
+	}
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm
similarity index 78%
rename from src/test/ssl/t/SSLServer.pm
rename to src/test/ssl/t/SSL/Server.pm
index 5ec5e0dac8..66f0eaefe3 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -23,15 +23,27 @@
 # explicitly because an invalid sslcert or sslrootcert, respectively,
 # causes those to be ignored.)
 
-package SSLServer;
+package SSL::Server;
 
 use strict;
 use warnings;
 use PostgresNode;
+use RecursiveCopy;
 use TestLib;
-use File::Basename;
 use File::Copy;
 use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend install_certificates);
+
+our ($openssl, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_ssl} eq 'openssl')
+{
+	$backend = get_new_openssl_backend();
+	$openssl = 1;
+}
 
 use Exporter 'import';
 our @EXPORT = qw(
@@ -77,22 +89,6 @@ sub test_connect_fails
 	return;
 }
 
-# Copy a set of files, taking into account wildcards
-sub copy_files
-{
-	my $orig = shift;
-	my $dest = shift;
-
-	my @orig_files = glob $orig;
-	foreach my $orig_file (@orig_files)
-	{
-		my $base_file = basename($orig_file);
-		copy($orig_file, "$dest/$base_file")
-		  or die "Could not copy $orig_file to $dest";
-	}
-	return;
-}
-
 # serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
 # servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
 sub configure_test_server_for_ssl
@@ -143,15 +139,8 @@ sub configure_test_server_for_ssl
 	open my $sslconf, '>', "$pgdata/sslconfig.conf";
 	close $sslconf;
 
-	# Copy all server certificates and keys, and client root cert, to the data dir
-	copy_files("ssl/server-*.crt", $pgdata);
-	copy_files("ssl/server-*.key", $pgdata);
-	chmod(0600, glob "$pgdata/server-*.key") or die $!;
-	copy_files("ssl/root+client_ca.crt", $pgdata);
-	copy_files("ssl/root_ca.crt",        $pgdata);
-	copy_files("ssl/root+client.crl",    $pgdata);
-	mkdir("$pgdata/root+client-crldir");
-	copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
+	# install certificates and keys
+	$backend->install_certificates($pgdata);
 
 	# Stop and restart server to load new listen_addresses.
 	$node->restart;
@@ -159,36 +148,41 @@ sub configure_test_server_for_ssl
 	# Change pg_hba after restart because hostssl requires ssl=on
 	configure_hba_for_ssl($node, $servercidr, $authmethod);
 
+	# Finally, perform backend specific configuration
+	$backend->init();
+
 	return;
 }
 
-# Change the configuration to use given server cert file, and reload
-# the server so that the configuration takes effect.
+sub ssl_library
+{
+	return $backend->get_library();
+}
+
+sub cleanup
+{
+	$backend->cleanup();
+}
+
+# Change the configuration to use the given set of certificate, key, ca and
+# CRL, and potentially reload the configuration by restarting the server so
+# that the configuration takes effect.  Restarting is the default, passing
+# restart => 'no' opts out of it leaving the server running.
 sub switch_server_cert
 {
-	my $node     = $_[0];
-	my $certfile = $_[1];
-	my $cafile   = $_[2] || "root+client_ca";
-	my $crlfile  = "root+client.crl";
-	my $crldir;
-	my $pgdata   = $node->data_dir;
-
-	# defaults to use crl file
-	if (defined $_[3] || defined $_[4])
-	{
-		$crlfile = $_[3];
-		$crldir = $_[4];
-	}
+	my $node   = shift;
+	my %params = @_;
+	my $pgdata = $node->data_dir;
 
 	open my $sslconf, '>', "$pgdata/sslconfig.conf";
 	print $sslconf "ssl=on\n";
-	print $sslconf "ssl_ca_file='$cafile.crt'\n";
-	print $sslconf "ssl_cert_file='$certfile.crt'\n";
-	print $sslconf "ssl_key_file='$certfile.key'\n";
-	print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
-	print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
+	print $sslconf $backend->set_server_cert(\%params);
+	print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
+	  if defined $params{passphrase_cmd};
 	close $sslconf;
 
+	return if (defined($params{restart}) && $params{restart} eq 'no');
+
 	$node->restart;
 	return;
 }
-- 
2.21.1 (Apple Git-122.3)

#3Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Daniel Gustafsson (#2)
Re: Refactor SSL test framework to support multiple TLS libraries

On 2021-Mar-25, Daniel Gustafsson wrote:

Attached is a v2 which addresses the comments raised on the main NSS thread, as
well as introduces named parameters for the server cert function to make the
test code easier to read.

I don't like this patch. I think your SSL::Server::OpenSSL and
SSL::Server::NSS packages should be doing "use parent SSL::Server";
having SSL::Server grow a line to "use" its subclass
SSL::Server::OpenSSL and import its get_new_openssl_backend() method
seems to go against the grain.

--
�lvaro Herrera 39�49'30"S 73�17'W

#4Daniel Gustafsson
daniel@yesql.se
In reply to: Alvaro Herrera (#3)
Re: Refactor SSL test framework to support multiple TLS libraries

On 25 Mar 2021, at 00:26, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

On 2021-Mar-25, Daniel Gustafsson wrote:

Attached is a v2 which addresses the comments raised on the main NSS thread, as
well as introduces named parameters for the server cert function to make the
test code easier to read.

I don't like this patch. I think your SSL::Server::OpenSSL and
SSL::Server::NSS packages should be doing "use parent SSL::Server";
having SSL::Server grow a line to "use" its subclass
SSL::Server::OpenSSL and import its get_new_openssl_backend() method
seems to go against the grain.

I'm far from skilled at Perl module inheritance but that makes sense, I'll take
a stab at that after some sleep and coffee.

--
Daniel Gustafsson https://vmware.com/

#5Andrew Dunstan
andrew@dunslane.net
In reply to: Daniel Gustafsson (#4)
Re: Refactor SSL test framework to support multiple TLS libraries

On 3/24/21 7:49 PM, Daniel Gustafsson wrote:

On 25 Mar 2021, at 00:26, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

On 2021-Mar-25, Daniel Gustafsson wrote:

Attached is a v2 which addresses the comments raised on the main NSS thread, as
well as introduces named parameters for the server cert function to make the
test code easier to read.

I don't like this patch. I think your SSL::Server::OpenSSL and
SSL::Server::NSS packages should be doing "use parent SSL::Server";
having SSL::Server grow a line to "use" its subclass
SSL::Server::OpenSSL and import its get_new_openssl_backend() method
seems to go against the grain.

I'm far from skilled at Perl module inheritance but that makes sense, I'll take
a stab at that after some sleep and coffee.

The thing is that SSLServer isn't currently constructed in an OO
fashion. Typically, OO modules in perl don't export anything, and all
access is via the class (for the constructor or static methods) or
instances, as in

    my $instance = MyClass->new();
    $instance->mymethod();

In such a module you should not see lines using Exporter or defining
@Export.

So probably the first step in this process would be to recast SSLServer
as an OO type module, without subclassing it, and then create a subclass
along the lines Alvarro suggests.

If this is all strange to you, I can help a bit.

Incidentally, I'm not sure why we need to break SSLServer into
SSL::Server - are we expecting to create other children of the SSL
namespace?

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#6Michael Paquier
michael@paquier.xyz
In reply to: Andrew Dunstan (#5)
Re: Refactor SSL test framework to support multiple TLS libraries

On Thu, Mar 25, 2021 at 09:25:11AM -0400, Andrew Dunstan wrote:

The thing is that SSLServer isn't currently constructed in an OO
fashion. Typically, OO modules in perl don't export anything, and all
access is via the class (for the constructor or static methods) or
instances, as in

    my $instance = MyClass->new();
    $instance->mymethod();

In such a module you should not see lines using Exporter or defining
@Export.

So probably the first step in this process would be to recast SSLServer
as an OO type module, without subclassing it, and then create a subclass
along the lines Alvarro suggests.

It seems that it does not make sense to transform all the contents of
SSLServer to become an OO module. So it looks necessary to me to
split things, with one part being the OO module managing the server
configuration. So, first, we have some helper routines that should
not be within the module:
- copy_files()
- test_connect_fails()
- test_connect_ok()
The test_*() ones are just wrappers for psql able to use a customized
connection string. It seems to me that it would make sense to move
those two into PostgresNode::psql itself and extend it to be able to
handle custom connection strings? copy_files() is more generic than
that. Wouldn't it make sense to move that to TestLib.pm instead?

Second, the routines managing the server setup itself:
- a new() routine to create and register a node removing the
duplicated initialization setup in 001 and 002.
- switch_server_cert(), with a split on set_server_cert() as that
looks cleaner.
- configure_hba_for_ssl()
- install_certificates() (present inside Daniel's patch)
- Something to copy the keys from the tree.

Patch v2 from upthread does mostly that, but it seems to me that we
should integrate better with PostgresNode to manage the backend node,
no?

Incidentally, I'm not sure why we need to break SSLServer into
SSL::Server - are we expecting to create other children of the SSL
namespace?

Agreed.
--
Michael

#7Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#6)
1 attachment(s)
Re: Refactor SSL test framework to support multiple TLS libraries

On Tue, Mar 30, 2021 at 03:50:28PM +0900, Michael Paquier wrote:

The test_*() ones are just wrappers for psql able to use a customized
connection string. It seems to me that it would make sense to move
those two into PostgresNode::psql itself and extend it to be able to
handle custom connection strings?

Looking at this part, I think that this is a win in terms of future
changes for SSLServer.pm as it would become a facility only in charge
of managing the backend's SSL configuration. This has also the
advantage to make the error handling with psql more consistent with
the other tests.

So, attached is a patch to do this simplification. The bulk of the
changes is within the tests themselves to adapt to the merge of
$common_connstr and $connstr for the new routines of PostgresNode.pm,
and I have done things this way to ease the patch lookup. Thoughts?
--
Michael

Attachments:

ssl-tap-refactor-simplify.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1e96357d7e..403ef56d54 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1511,6 +1511,11 @@ the B<timed_out> parameter is also given.
 If B<timeout> is set and this parameter is given, the scalar it references
 is set to true if the psql call times out.
 
+=item connstr => B<value>
+
+If set, use this as the connection string for the connection to the
+backend.
+
 =item replication => B<value>
 
 If set, add B<replication=value> to the conninfo string.
@@ -1550,12 +1555,24 @@ sub psql
 	my $replication       = $params{replication};
 	my $timeout           = undef;
 	my $timeout_exception = 'psql timed out';
+
+	# Build the connection string.
+	my $psql_connstr;
+	if (defined $params{connstr})
+	{
+		$psql_connstr = $params{connstr};
+	}
+	else
+	{
+		$psql_connstr = $self->connstr($dbname);
+	}
+	$psql_connstr .= defined $replication ? " replication=$replication" : "";
+
 	my @psql_params       = (
 		'psql',
 		'-XAtq',
 		'-d',
-		$self->connstr($dbname)
-		  . (defined $replication ? " replication=$replication" : ""),
+		$psql_connstr,
 		'-f',
 		'-');
 
@@ -1849,6 +1866,54 @@ sub interactive_psql
 
 =pod
 
+=item $node->connect_ok($connstr, $testname)
+
+Attempt a connection to the given node, with a custom connection string.
+This is expected to succeed.
+
+=cut
+
+sub connect_ok
+{
+	local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+	my ($self, $connstr, $testname) = @_;
+
+	my ($ret, $stdout, $stderr) = $self->psql('postgres',
+		"SELECT \$\$connected with $connstr\$\$",
+		connstr => "$connstr", on_error_stop => 0);
+
+	ok($ret == 0, $testname);
+	return;
+}
+
+=pod
+
+=item $node->connect_fails($connstr, $testname, $expected_stderr)
+
+Attempt a connection to the given node, with a custom connection string.
+This is expected to fail.
+
+=cut
+
+sub connect_fails
+{
+	local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+	my ($self, $connstr, $expected_stderr, $testname) = @_;
+
+	my ($ret, $stdout, $stderr) = $self->psql('postgres',
+		"SELECT \$\$connected with $connstr\$\$",
+		connstr => "$connstr");
+
+	ok($ret != 0, $testname);
+
+	like($stderr, $expected_stderr, "$testname: matches");
+	return;
+}
+
+=pod
+
 =item $node->poll_query_until($dbname, $query [, $expected ])
 
 Run B<$query> repeatedly, until it returns the B<$expected> result
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index adaa1b4e9b..dc3b69cb46 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -135,102 +135,102 @@ $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 # The server should not accept non-SSL connections.
-test_connect_fails(
-	$common_connstr, "sslmode=disable",
+$node->connect_fails(
+	$common_connstr . " " . "sslmode=disable",
 	qr/\Qno pg_hba.conf entry\E/,
 	"server doesn't accept non-SSL connections");
 
 # Try without a root cert. In sslmode=require, this should work. In verify-ca
 # or verify-full mode it should fail.
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=invalid sslmode=require",
 	"connect without server root cert sslmode=require");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=invalid sslmode=verify-ca",
 	qr/root certificate file "invalid" does not exist/,
 	"connect without server root cert sslmode=verify-ca");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=invalid sslmode=verify-full",
 	qr/root certificate file "invalid" does not exist/,
 	"connect without server root cert sslmode=verify-full");
 
 # Try with wrong root cert, should fail. (We're using the client CA as the
 # root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
+$node->connect_fails($common_connstr . " " .
 	"sslrootcert=ssl/client_ca.crt sslmode=require",
 	qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
+$node->connect_fails($common_connstr . " " .
 	"sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
 	qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
+$node->connect_fails($common_connstr . " " .
 	"sslrootcert=ssl/client_ca.crt sslmode=verify-full",
 	qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
 
 # Try with just the server CA's cert. This fails because the root file
 # must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
+$node->connect_fails($common_connstr . " " .
 	"sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
 	qr/SSL error/, "connect with server CA cert, without root CA");
 
 # And finally, with the correct root cert.
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=require",
 	"connect with correct server CA cert file sslmode=require");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
 	"connect with correct server CA cert file sslmode=verify-ca");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
 	"connect with correct server CA cert file sslmode=verify-full");
 
 # Test with cert root file that contains two certificates. The client should
 # be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
 	"cert root file that contains two certificates, order 1");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
 	"cert root file that contains two certificates, order 2");
 
 # CRL tests
 
 # Invalid CRL filename is the same as no CRL, succeeds
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
 	"sslcrl option with invalid file name");
 
 # A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
 	qr/SSL error/,
 	"CRL belonging to a different CA");
 
 # The same for CRL directory
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
 	qr/SSL error/,
 	"directory CRL belonging to a different CA");
 
 # With the correct CRL, succeeds (this cert is not revoked)
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
 	"CRL with a non-revoked cert");
 
 # The same for CRL directory
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
 	"directory CRL with a non-revoked cert");
 
@@ -239,16 +239,16 @@ test_connect_ok(
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslmode=require host=wronghost.test",
 	"mismatch between host name and server certificate sslmode=require");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslmode=verify-ca host=wronghost.test",
 	"mismatch between host name and server certificate sslmode=verify-ca");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslmode=verify-full host=wronghost.test",
 	qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
 	"mismatch between host name and server certificate sslmode=verify-full");
@@ -259,26 +259,26 @@ switch_server_cert($node, 'server-multiple-alt-names');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
 
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"host=dns1.alt-name.pg-ssltest.test",
 	"host name matching with X.509 Subject Alternative Names 1");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"host=dns2.alt-name.pg-ssltest.test",
 	"host name matching with X.509 Subject Alternative Names 2");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"host=foo.wildcard.pg-ssltest.test",
 	"host name matching with X.509 Subject Alternative Names wildcard");
 
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"host=wronghost.alt-name.pg-ssltest.test",
 	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
 	"host name not matching with X.509 Subject Alternative Names");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"host=deep.subdomain.wildcard.pg-ssltest.test",
 	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
 	"host name not matching with X.509 Subject Alternative Names wildcard");
@@ -290,18 +290,18 @@ switch_server_cert($node, 'server-single-alt-name');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
 
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"host=single.alt-name.pg-ssltest.test",
 	"host name matching with a single X.509 Subject Alternative Name");
 
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"host=wronghost.alt-name.pg-ssltest.test",
 	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
 	"host name not matching with a single X.509 Subject Alternative Name");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"host=deep.subdomain.wildcard.pg-ssltest.test",
 	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
 	"host name not matching with a single X.509 Subject Alternative Name wildcard"
@@ -314,16 +314,16 @@ switch_server_cert($node, 'server-cn-and-alt-names');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
 
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"host=dns1.alt-name.pg-ssltest.test",
 	"certificate with both a CN and SANs 1");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"host=dns2.alt-name.pg-ssltest.test",
 	"certificate with both a CN and SANs 2");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"host=common-name.pg-ssltest.test",
 	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
 	"certificate with both a CN and SANs ignores CN");
@@ -334,12 +334,12 @@ switch_server_cert($node, 'server-no-names');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslmode=verify-ca host=common-name.pg-ssltest.test",
 	"server certificate without CN or SANs sslmode=verify-ca");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslmode=verify-full host=common-name.pg-ssltest.test",
 	qr/could not get server's host name from server certificate/,
 	"server certificate without CN or SANs sslmode=verify-full");
@@ -351,17 +351,17 @@ $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 # Without the CRL, succeeds. With it, fails.
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
 	"connects without client-side CRL");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
 	qr/SSL error/,
 	"does not connect with client-side CRL file");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
 	qr/SSL error/,
 	"does not connect with client-side CRL directory");
@@ -381,22 +381,22 @@ command_like(
 	'pg_stat_ssl view without client certificate');
 
 # Test min/max SSL protocol versions.
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
 	"connection success with correct range of TLS protocol versions");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
 	qr/invalid SSL protocol version range/,
 	"connection failure with incorrect range of TLS protocol versions");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
 	qr/invalid ssl_min_protocol_version value/,
 	"connection failure with an incorrect SSL protocol minimum bound");
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
 	qr/invalid ssl_max_protocol_version value/,
 	"connection failure with an incorrect SSL protocol maximum bound");
@@ -411,43 +411,43 @@ $common_connstr =
   "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
 
 # no client cert
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=invalid",
 	qr/connection requires a valid client certificate/,
 	"certificate authorization fails without client cert");
 
 # correct client cert in unencrypted PEM
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	"certificate authorization succeeds with correct client cert in PEM format"
 );
 
 # correct client cert in unencrypted DER
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
 	"certificate authorization succeeds with correct client cert in DER format"
 );
 
 # correct client cert in encrypted PEM
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
 	"certificate authorization succeeds with correct client cert in encrypted PEM format"
 );
 
 # correct client cert in encrypted DER
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
 	"certificate authorization succeeds with correct client cert in encrypted DER format"
 );
 
 # correct client cert in encrypted PEM with wrong password
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
 	qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
 	"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
@@ -457,8 +457,8 @@ test_connect_fails(
 # correct client cert using whole DN
 my $dn_connstr = "$common_connstr dbname=certdb_dn";
 
-test_connect_ok(
-	$dn_connstr,
+$node->connect_ok(
+	$dn_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
 	"certificate authorization succeeds with DN mapping"
 );
@@ -466,8 +466,8 @@ test_connect_ok(
 # same thing but with a regex
 $dn_connstr = "$common_connstr dbname=certdb_dn_re";
 
-test_connect_ok(
-	$dn_connstr,
+$node->connect_ok(
+	$dn_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
 	"certificate authorization succeeds with DN regex mapping"
 );
@@ -475,8 +475,8 @@ test_connect_ok(
 # same thing but using explicit CN
 $dn_connstr = "$common_connstr dbname=certdb_cn";
 
-test_connect_ok(
-	$dn_connstr,
+$node->connect_ok(
+	$dn_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
 	"certificate authorization succeeds with CN mapping"
 );
@@ -491,16 +491,16 @@ TODO:
 	todo_skip "Need Pty support", 4;
 
 	# correct client cert in encrypted PEM with empty password
-	test_connect_fails(
-		$common_connstr,
+	$node->connect_fails(
+		$common_connstr . " " .
 		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword=''",
 		qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!,
 		"certificate authorization fails with correct client cert and empty password in encrypted PEM format"
 	);
 
 	# correct client cert in encrypted PEM with no password
-	test_connect_fails(
-		$common_connstr,
+	$node->connect_fails(
+		$common_connstr . " " .
 		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key",
 		qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!,
 		"certificate authorization fails with correct client cert and no password in encrypted PEM format"
@@ -532,24 +532,24 @@ SKIP:
 {
 	skip "Permissions check not enforced on Windows", 2 if ($windows_os);
 
-	test_connect_fails(
-		$common_connstr,
+	$node->connect_fails(
+		$common_connstr . " " .
 		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
 		qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
 		"certificate authorization fails because of file permissions");
 }
 
 # client cert belonging to another user
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	qr/certificate authentication failed for user "anotheruser"/,
 	"certificate authorization fails with client cert belonging to another user"
 );
 
 # revoked client cert
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	qr/SSL error/,
 	"certificate authorization fails with revoked client cert");
@@ -560,14 +560,14 @@ test_connect_fails(
 $common_connstr =
   "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	"auth_option clientcert=verify-full succeeds with matching username and Common Name"
 );
 
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	qr/FATAL/,
 	"auth_option clientcert=verify-full fails with mismatching username and Common Name"
@@ -575,8 +575,8 @@ test_connect_fails(
 
 # Check that connecting with auth-optionverify-ca in pg_hba :
 # works, when username doesn't match Common Name
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name"
 );
@@ -586,19 +586,19 @@ switch_server_cert($node, 'server-cn-only', 'root_ca');
 $common_connstr =
   "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"sslmode=require sslcert=ssl/client+client_ca.crt",
 	"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
+$node->connect_fails($common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
 	qr/SSL error/, "intermediate client certificate is missing");
 
 # test server-side CRL directory
 switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
 
 # revoked client cert
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	qr/SSL error/,
 	"certificate authorization fails with revoked client cert with server-side CRL directory");
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 410b9e910d..60e32af7a4 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -53,38 +53,38 @@ $common_connstr =
   "dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
 
 # Default settings
-test_connect_ok($common_connstr, "user=ssltestuser",
+$node->connect_ok($common_connstr . " " . "user=ssltestuser",
 	"Basic SCRAM authentication with SSL");
 
 # Test channel_binding
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=ssltestuser channel_binding=invalid_value",
 	qr/invalid channel_binding value: "invalid_value"/,
 	"SCRAM with SSL and channel_binding=invalid_value");
-test_connect_ok(
-	$common_connstr,
+$node->connect_ok(
+	$common_connstr . " " .
 	"user=ssltestuser channel_binding=disable",
 	"SCRAM with SSL and channel_binding=disable");
 if ($supports_tls_server_end_point)
 {
-	test_connect_ok(
-		$common_connstr,
+	$node->connect_ok(
+		$common_connstr . " " .
 		"user=ssltestuser channel_binding=require",
 		"SCRAM with SSL and channel_binding=require");
 }
 else
 {
-	test_connect_fails(
-		$common_connstr,
+	$node->connect_fails(
+		$common_connstr . " " .
 		"user=ssltestuser channel_binding=require",
 		qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
 		"SCRAM with SSL and channel_binding=require");
 }
 
 # Now test when the user has an MD5-encrypted password; should fail
-test_connect_fails(
-	$common_connstr,
+$node->connect_fails(
+	$common_connstr . " " .
 	"user=md5testuser channel_binding=require",
 	qr/channel binding required but not supported by server's authentication request/,
 	"MD5 with SSL and channel_binding=require");
@@ -96,8 +96,8 @@ test_connect_fails(
 my $client_tmp_key = "ssl/client_scram_tmp.key";
 copy("ssl/client.key", $client_tmp_key);
 chmod 0600, $client_tmp_key;
-test_connect_fails(
-	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
+$node->connect_fails(
+	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR " .
 	"dbname=certdb user=ssltestuser channel_binding=require",
 	qr/channel binding required, but server authenticated client without channel binding/,
 	"Cert authentication and channel_binding=require");
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSLServer.pm
index c494c1ad1c..129a7154a9 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSLServer.pm
@@ -37,46 +37,8 @@ use Exporter 'import';
 our @EXPORT = qw(
   configure_test_server_for_ssl
   switch_server_cert
-  test_connect_fails
-  test_connect_ok
 );
 
-# Define a couple of helper functions to test connecting to the server.
-
-# The first argument is a base connection string to use for connection.
-# The second argument is a complementary connection string.
-sub test_connect_ok
-{
-	local $Test::Builder::Level = $Test::Builder::Level + 1;
-
-	my ($common_connstr, $connstr, $test_name) = @_;
-
-	my $cmd = [
-		'psql', '-X', '-A', '-t', '-c',
-		"SELECT \$\$connected with $connstr\$\$",
-		'-d', "$common_connstr $connstr"
-	];
-
-	command_ok($cmd, $test_name);
-	return;
-}
-
-sub test_connect_fails
-{
-	local $Test::Builder::Level = $Test::Builder::Level + 1;
-
-	my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
-
-	my $cmd = [
-		'psql', '-X', '-A', '-t', '-c',
-		"SELECT \$\$connected with $connstr\$\$",
-		'-d', "$common_connstr $connstr"
-	];
-
-	command_fails_like($cmd, $expected_stderr, $test_name);
-	return;
-}
-
 # Copy a set of files, taking into account wildcards
 sub copy_files
 {
#8Andrew Dunstan
andrew@dunslane.net
In reply to: Michael Paquier (#7)
Re: Refactor SSL test framework to support multiple TLS libraries

On 3/30/21 5:53 AM, Michael Paquier wrote:

On Tue, Mar 30, 2021 at 03:50:28PM +0900, Michael Paquier wrote:

The test_*() ones are just wrappers for psql able to use a customized
connection string. It seems to me that it would make sense to move
those two into PostgresNode::psql itself and extend it to be able to
handle custom connection strings?

Looking at this part, I think that this is a win in terms of future
changes for SSLServer.pm as it would become a facility only in charge
of managing the backend's SSL configuration. This has also the
advantage to make the error handling with psql more consistent with
the other tests.

So, attached is a patch to do this simplification. The bulk of the
changes is within the tests themselves to adapt to the merge of
$common_connstr and $connstr for the new routines of PostgresNode.pm,
and I have done things this way to ease the patch lookup. Thoughts?

Looks reasonable.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#9Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Michael Paquier (#7)
Re: Refactor SSL test framework to support multiple TLS libraries

On 2021-Mar-30, Michael Paquier wrote:

On Tue, Mar 30, 2021 at 03:50:28PM +0900, Michael Paquier wrote:

The test_*() ones are just wrappers for psql able to use a customized
connection string. It seems to me that it would make sense to move
those two into PostgresNode::psql itself and extend it to be able to
handle custom connection strings?

Looking at this part, I think that this is a win in terms of future
changes for SSLServer.pm as it would become a facility only in charge
of managing the backend's SSL configuration. This has also the
advantage to make the error handling with psql more consistent with
the other tests.

So, attached is a patch to do this simplification. The bulk of the
changes is within the tests themselves to adapt to the merge of
$common_connstr and $connstr for the new routines of PostgresNode.pm,
and I have done things this way to ease the patch lookup. Thoughts?

I agree this seems a win.

The only complain I have is that "the given node" is nonsensical in
PostgresNode. I suggest to delete the word "given". Also "This is
expected to fail with a message that matches the regular expression
$expected_stderr".

The POD doc for connect_fails uses order: ($connstr, $testname, $expected_stderr)
but the routine has:
+ my ($self, $connstr, $expected_stderr, $testname) = @_;

these should match.

(There's quite an inconsistency in the existing test code about
expected_stderr being a string or a regex; and some regexes are quite
generic: just qr/SSL error/. Not this patch responsibility to fix that.)

As I understand, our perlcriticrc no longer requires 'return' at the end
of routines (commit 0516f94d18c5), so you can omit that.

--
�lvaro Herrera Valdivia, Chile

#10Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#7)
Re: Refactor SSL test framework to support multiple TLS libraries

On 30 Mar 2021, at 11:53, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Mar 30, 2021 at 03:50:28PM +0900, Michael Paquier wrote:

The test_*() ones are just wrappers for psql able to use a customized
connection string. It seems to me that it would make sense to move
those two into PostgresNode::psql itself and extend it to be able to
handle custom connection strings?

Looking at this part, I think that this is a win in terms of future
changes for SSLServer.pm as it would become a facility only in charge
of managing the backend's SSL configuration. This has also the
advantage to make the error handling with psql more consistent with
the other tests.

So, attached is a patch to do this simplification. The bulk of the
changes is within the tests themselves to adapt to the merge of
$common_connstr and $connstr for the new routines of PostgresNode.pm,
and I have done things this way to ease the patch lookup. Thoughts?

LGTM with the findings that Alvaro reported.

+$node->connect_ok($common_connstr . " " . "user=ssltestuser",

This double concatenation could be a single concat, or just use scalar value
interpolation in the string to make it even more readable. As it isn't using
the same line broken pattern of the others the concat looks a bit weird as a
result.

Thanks for picking it up, as I have very limited time for hacking right now.

--
Daniel Gustafsson https://vmware.com/

#11Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Daniel Gustafsson (#10)
Re: Refactor SSL test framework to support multiple TLS libraries

On 2021-Mar-30, Daniel Gustafsson wrote:

+$node->connect_ok($common_connstr . " " . "user=ssltestuser",

This double concatenation could be a single concat, or just use scalar value
interpolation in the string to make it even more readable. As it isn't using
the same line broken pattern of the others the concat looks a bit weird as a
result.

+1 for using a single scalar.

--
�lvaro Herrera Valdivia, Chile

#12Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#11)
Re: Refactor SSL test framework to support multiple TLS libraries

On Tue, Mar 30, 2021 at 07:14:55PM -0300, Alvaro Herrera wrote:

On 2021-Mar-30, Daniel Gustafsson wrote:

This double concatenation could be a single concat, or just use scalar value
interpolation in the string to make it even more readable. As it isn't using
the same line broken pattern of the others the concat looks a bit weird as a
result.

+1 for using a single scalar.

Agreed. I posted things this way to make a lookup at the diffs easier
for the eye, but that was not intended for the final patch.
--
Michael

#13Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#9)
1 attachment(s)
Re: Refactor SSL test framework to support multiple TLS libraries

On Tue, Mar 30, 2021 at 12:15:07PM -0300, Alvaro Herrera wrote:

The only complain I have is that "the given node" is nonsensical in
PostgresNode. I suggest to delete the word "given". Also "This is
expected to fail with a message that matches the regular expression
$expected_stderr".

Your suggestions are better, indeed.

The POD doc for connect_fails uses order: ($connstr, $testname, $expected_stderr)
but the routine has:
+ my ($self, $connstr, $expected_stderr, $testname) = @_;

these should match.

Fixed.

(There's quite an inconsistency in the existing test code about
expected_stderr being a string or a regex; and some regexes are quite
generic: just qr/SSL error/. Not this patch responsibility to fix that.)

Jacob has just raised this as an issue for an integration with NLS,
because it may be possible that things fail with "SSL error" but a
different error pattern, causing false positives:
/messages/by-id/e0f0484a1815b26bb99ef9ddc7a110dfd6425931.camel@vmware.com

I agree that those matches should be much more picky. We may need to
be careful across all versions of OpenSSL supported though :/

As I understand, our perlcriticrc no longer requires 'return' at the end
of routines (commit 0516f94d18c5), so you can omit that.

Fixed. Thanks.

With all the comments addressed, with updates to use a single scalar
for all the connection strings and with a proper indentation, I finish
with the attached. Does that look fine?
--
Michael

Attachments:

ssl-tap-refactor-simplify-2.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1e96357d7e..d6e10544bb 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1511,6 +1511,11 @@ the B<timed_out> parameter is also given.
 If B<timeout> is set and this parameter is given, the scalar it references
 is set to true if the psql call times out.
 
+=item connstr => B<value>
+
+If set, use this as the connection string for the connection to the
+backend.
+
 =item replication => B<value>
 
 If set, add B<replication=value> to the conninfo string.
@@ -1550,14 +1555,20 @@ sub psql
 	my $replication       = $params{replication};
 	my $timeout           = undef;
 	my $timeout_exception = 'psql timed out';
-	my @psql_params       = (
-		'psql',
-		'-XAtq',
-		'-d',
-		$self->connstr($dbname)
-		  . (defined $replication ? " replication=$replication" : ""),
-		'-f',
-		'-');
+
+	# Build the connection string.
+	my $psql_connstr;
+	if (defined $params{connstr})
+	{
+		$psql_connstr = $params{connstr};
+	}
+	else
+	{
+		$psql_connstr = $self->connstr($dbname);
+	}
+	$psql_connstr .= defined $replication ? " replication=$replication" : "";
+
+	my @psql_params = ('psql', '-XAtq', '-d', $psql_connstr, '-f', '-');
 
 	# If the caller wants an array and hasn't passed stdout/stderr
 	# references, allocate temporary ones to capture them so we
@@ -1849,6 +1860,51 @@ sub interactive_psql
 
 =pod
 
+=item $node->connect_ok($connstr, $test_name)
+
+Attempt a connection with a custom connection string.  This is expected
+to succeed.
+
+=cut
+
+sub connect_ok
+{
+	local $Test::Builder::Level = $Test::Builder::Level + 1;
+	my ($self, $connstr, $test_name) = @_;
+	my ($ret,  $stdout,  $stderr)    = $self->psql(
+		'postgres',
+		"SELECT \$\$connected with $connstr\$\$",
+		connstr       => "$connstr",
+		on_error_stop => 0);
+
+	ok($ret == 0, $test_name);
+}
+
+=pod
+
+=item $node->connect_fails($connstr, $expected_stderr, $test_name)
+
+Attempt a connection with a custom connection string.  This is expected
+to fail with a message that matches the regular expression
+$expected_stderr.
+
+=cut
+
+sub connect_fails
+{
+	local $Test::Builder::Level = $Test::Builder::Level + 1;
+	my ($self, $connstr, $expected_stderr, $test_name) = @_;
+	my ($ret, $stdout, $stderr) = $self->psql(
+		'postgres',
+		"SELECT \$\$connected with $connstr\$\$",
+		connstr => "$connstr");
+
+	ok($ret != 0, $test_name);
+	like($stderr, $expected_stderr, "$test_name: matches");
+}
+
+=pod
+
 =item $node->poll_query_until($dbname, $query [, $expected ])
 
 Run B<$query> repeatedly, until it returns the B<$expected> result
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index adaa1b4e9b..b1a63f279c 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -135,103 +135,94 @@ $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 # The server should not accept non-SSL connections.
-test_connect_fails(
-	$common_connstr, "sslmode=disable",
+$node->connect_fails(
+	"$common_connstr sslmode=disable",
 	qr/\Qno pg_hba.conf entry\E/,
 	"server doesn't accept non-SSL connections");
 
 # Try without a root cert. In sslmode=require, this should work. In verify-ca
 # or verify-full mode it should fail.
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=invalid sslmode=require",
+$node->connect_ok(
+	"$common_connstr sslrootcert=invalid sslmode=require",
 	"connect without server root cert sslmode=require");
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=invalid sslmode=verify-ca",
+$node->connect_fails(
+	"$common_connstr sslrootcert=invalid sslmode=verify-ca",
 	qr/root certificate file "invalid" does not exist/,
 	"connect without server root cert sslmode=verify-ca");
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=invalid sslmode=verify-full",
+$node->connect_fails(
+	"$common_connstr sslrootcert=invalid sslmode=verify-full",
 	qr/root certificate file "invalid" does not exist/,
 	"connect without server root cert sslmode=verify-full");
 
 # Try with wrong root cert, should fail. (We're using the client CA as the
 # root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=require",
-	qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
-	qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/client_ca.crt sslmode=verify-full",
-	qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=require",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=require");
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=verify-ca");
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/client_ca.crt sslmode=verify-full",
+	qr/SSL error/,
+	"connect with wrong server root cert sslmode=verify-full");
 
 # Try with just the server CA's cert. This fails because the root file
 # must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
-	"sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
-	qr/SSL error/, "connect with server CA cert, without root CA");
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+	qr/SSL error/,
+	"connect with server CA cert, without root CA");
 
 # And finally, with the correct root cert.
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
 	"connect with correct server CA cert file sslmode=require");
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
 	"connect with correct server CA cert file sslmode=verify-ca");
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
 	"connect with correct server CA cert file sslmode=verify-full");
 
 # Test with cert root file that contains two certificates. The client should
 # be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
 	"cert root file that contains two certificates, order 1");
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
 	"cert root file that contains two certificates, order 2");
 
 # CRL tests
 
 # Invalid CRL filename is the same as no CRL, succeeds
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
 	"sslcrl option with invalid file name");
 
 # A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
 	qr/SSL error/,
 	"CRL belonging to a different CA");
 
 # The same for CRL directory
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/client-crldir",
 	qr/SSL error/,
 	"directory CRL belonging to a different CA");
 
 # With the correct CRL, succeeds (this cert is not revoked)
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
 	"CRL with a non-revoked cert");
 
 # The same for CRL directory
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
 	"directory CRL with a non-revoked cert");
 
 # Check that connecting with verify-full fails, when the hostname doesn't
@@ -239,17 +230,13 @@ test_connect_ok(
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
-	"sslmode=require host=wronghost.test",
+$node->connect_ok("$common_connstr sslmode=require host=wronghost.test",
 	"mismatch between host name and server certificate sslmode=require");
-test_connect_ok(
-	$common_connstr,
-	"sslmode=verify-ca host=wronghost.test",
+$node->connect_ok(
+	"$common_connstr sslmode=verify-ca host=wronghost.test",
 	"mismatch between host name and server certificate sslmode=verify-ca");
-test_connect_fails(
-	$common_connstr,
-	"sslmode=verify-full host=wronghost.test",
+$node->connect_fails(
+	"$common_connstr sslmode=verify-full host=wronghost.test",
 	qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
 	"mismatch between host name and server certificate sslmode=verify-full");
 
@@ -259,27 +246,21 @@ switch_server_cert($node, 'server-multiple-alt-names');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
 
-test_connect_ok(
-	$common_connstr,
-	"host=dns1.alt-name.pg-ssltest.test",
+$node->connect_ok(
+	"$common_connstr host=dns1.alt-name.pg-ssltest.test",
 	"host name matching with X.509 Subject Alternative Names 1");
-test_connect_ok(
-	$common_connstr,
-	"host=dns2.alt-name.pg-ssltest.test",
+$node->connect_ok(
+	"$common_connstr host=dns2.alt-name.pg-ssltest.test",
 	"host name matching with X.509 Subject Alternative Names 2");
-test_connect_ok(
-	$common_connstr,
-	"host=foo.wildcard.pg-ssltest.test",
+$node->connect_ok("$common_connstr host=foo.wildcard.pg-ssltest.test",
 	"host name matching with X.509 Subject Alternative Names wildcard");
 
-test_connect_fails(
-	$common_connstr,
-	"host=wronghost.alt-name.pg-ssltest.test",
+$node->connect_fails(
+	"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
 	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
 	"host name not matching with X.509 Subject Alternative Names");
-test_connect_fails(
-	$common_connstr,
-	"host=deep.subdomain.wildcard.pg-ssltest.test",
+$node->connect_fails(
+	"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
 	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
 	"host name not matching with X.509 Subject Alternative Names wildcard");
 
@@ -290,19 +271,16 @@ switch_server_cert($node, 'server-single-alt-name');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
 
-test_connect_ok(
-	$common_connstr,
-	"host=single.alt-name.pg-ssltest.test",
+$node->connect_ok(
+	"$common_connstr host=single.alt-name.pg-ssltest.test",
 	"host name matching with a single X.509 Subject Alternative Name");
 
-test_connect_fails(
-	$common_connstr,
-	"host=wronghost.alt-name.pg-ssltest.test",
+$node->connect_fails(
+	"$common_connstr host=wronghost.alt-name.pg-ssltest.test",
 	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
 	"host name not matching with a single X.509 Subject Alternative Name");
-test_connect_fails(
-	$common_connstr,
-	"host=deep.subdomain.wildcard.pg-ssltest.test",
+$node->connect_fails(
+	"$common_connstr host=deep.subdomain.wildcard.pg-ssltest.test",
 	qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
 	"host name not matching with a single X.509 Subject Alternative Name wildcard"
 );
@@ -314,17 +292,12 @@ switch_server_cert($node, 'server-cn-and-alt-names');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
 
-test_connect_ok(
-	$common_connstr,
-	"host=dns1.alt-name.pg-ssltest.test",
+$node->connect_ok("$common_connstr host=dns1.alt-name.pg-ssltest.test",
 	"certificate with both a CN and SANs 1");
-test_connect_ok(
-	$common_connstr,
-	"host=dns2.alt-name.pg-ssltest.test",
+$node->connect_ok("$common_connstr host=dns2.alt-name.pg-ssltest.test",
 	"certificate with both a CN and SANs 2");
-test_connect_fails(
-	$common_connstr,
-	"host=common-name.pg-ssltest.test",
+$node->connect_fails(
+	"$common_connstr host=common-name.pg-ssltest.test",
 	qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/,
 	"certificate with both a CN and SANs ignores CN");
 
@@ -334,13 +307,12 @@ switch_server_cert($node, 'server-no-names');
 $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
-	"sslmode=verify-ca host=common-name.pg-ssltest.test",
+$node->connect_ok(
+	"$common_connstr sslmode=verify-ca host=common-name.pg-ssltest.test",
 	"server certificate without CN or SANs sslmode=verify-ca");
-test_connect_fails(
-	$common_connstr,
-	"sslmode=verify-full host=common-name.pg-ssltest.test",
+$node->connect_fails(
+	$common_connstr . " "
+	  . "sslmode=verify-full host=common-name.pg-ssltest.test",
 	qr/could not get server's host name from server certificate/,
 	"server certificate without CN or SANs sslmode=verify-full");
 
@@ -351,18 +323,15 @@ $common_connstr =
   "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 # Without the CRL, succeeds. With it, fails.
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
 	"connects without client-side CRL");
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
 	qr/SSL error/,
 	"does not connect with client-side CRL file");
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrldir=ssl/root+server-crldir",
 	qr/SSL error/,
 	"does not connect with client-side CRL directory");
 
@@ -381,23 +350,19 @@ command_like(
 	'pg_stat_ssl view without client certificate');
 
 # Test min/max SSL protocol versions.
-test_connect_ok(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2",
 	"connection success with correct range of TLS protocol versions");
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1",
 	qr/invalid SSL protocol version range/,
 	"connection failure with incorrect range of TLS protocol versions");
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls",
 	qr/invalid ssl_min_protocol_version value/,
 	"connection failure with an incorrect SSL protocol minimum bound");
-test_connect_fails(
-	$common_connstr,
-	"sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
+$node->connect_fails(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls",
 	qr/invalid ssl_max_protocol_version value/,
 	"connection failure with an incorrect SSL protocol maximum bound");
 
@@ -411,44 +376,38 @@ $common_connstr =
   "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR";
 
 # no client cert
-test_connect_fails(
-	$common_connstr,
-	"user=ssltestuser sslcert=invalid",
+$node->connect_fails(
+	"$common_connstr user=ssltestuser sslcert=invalid",
 	qr/connection requires a valid client certificate/,
 	"certificate authorization fails without client cert");
 
 # correct client cert in unencrypted PEM
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+$node->connect_ok(
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	"certificate authorization succeeds with correct client cert in PEM format"
 );
 
 # correct client cert in unencrypted DER
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+$node->connect_ok(
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
 	"certificate authorization succeeds with correct client cert in DER format"
 );
 
 # correct client cert in encrypted PEM
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+$node->connect_ok(
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
 	"certificate authorization succeeds with correct client cert in encrypted PEM format"
 );
 
 # correct client cert in encrypted DER
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+$node->connect_ok(
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
 	"certificate authorization succeeds with correct client cert in encrypted DER format"
 );
 
 # correct client cert in encrypted PEM with wrong password
-test_connect_fails(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+$node->connect_fails(
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
 	qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
 	"certificate authorization fails with correct client cert and wrong password in encrypted PEM format"
 );
@@ -457,29 +416,23 @@ test_connect_fails(
 # correct client cert using whole DN
 my $dn_connstr = "$common_connstr dbname=certdb_dn";
 
-test_connect_ok(
-	$dn_connstr,
-	"user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
-	"certificate authorization succeeds with DN mapping"
-);
+$node->connect_ok(
+	"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+	"certificate authorization succeeds with DN mapping");
 
 # same thing but with a regex
 $dn_connstr = "$common_connstr dbname=certdb_dn_re";
 
-test_connect_ok(
-	$dn_connstr,
-	"user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
-	"certificate authorization succeeds with DN regex mapping"
-);
+$node->connect_ok(
+	"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+	"certificate authorization succeeds with DN regex mapping");
 
 # same thing but using explicit CN
 $dn_connstr = "$common_connstr dbname=certdb_cn";
 
-test_connect_ok(
-	$dn_connstr,
-	"user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
-	"certificate authorization succeeds with CN mapping"
-);
+$node->connect_ok(
+	"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
+	"certificate authorization succeeds with CN mapping");
 
 
 
@@ -491,17 +444,15 @@ TODO:
 	todo_skip "Need Pty support", 4;
 
 	# correct client cert in encrypted PEM with empty password
-	test_connect_fails(
-		$common_connstr,
-		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword=''",
+	$node->connect_fails(
+		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword=''",
 		qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!,
 		"certificate authorization fails with correct client cert and empty password in encrypted PEM format"
 	);
 
 	# correct client cert in encrypted PEM with no password
-	test_connect_fails(
-		$common_connstr,
-		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key",
+	$node->connect_fails(
+		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key",
 		qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!,
 		"certificate authorization fails with correct client cert and no password in encrypted PEM format"
 	);
@@ -532,25 +483,22 @@ SKIP:
 {
 	skip "Permissions check not enforced on Windows", 2 if ($windows_os);
 
-	test_connect_fails(
-		$common_connstr,
-		"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
+	$node->connect_fails(
+		"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
 		qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
 		"certificate authorization fails because of file permissions");
 }
 
 # client cert belonging to another user
-test_connect_fails(
-	$common_connstr,
-	"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+$node->connect_fails(
+	"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	qr/certificate authentication failed for user "anotheruser"/,
 	"certificate authorization fails with client cert belonging to another user"
 );
 
 # revoked client cert
-test_connect_fails(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+$node->connect_fails(
+	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	qr/SSL error/,
 	"certificate authorization fails with revoked client cert");
 
@@ -560,24 +508,21 @@ test_connect_fails(
 $common_connstr =
   "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+$node->connect_ok(
+	"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	"auth_option clientcert=verify-full succeeds with matching username and Common Name"
 );
 
-test_connect_fails(
-	$common_connstr,
-	"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+$node->connect_fails(
+	"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	qr/FATAL/,
 	"auth_option clientcert=verify-full fails with mismatching username and Common Name"
 );
 
 # Check that connecting with auth-optionverify-ca in pg_hba :
 # works, when username doesn't match Common Name
-test_connect_ok(
-	$common_connstr,
-	"user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
+$node->connect_ok(
+	"$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
 	"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name"
 );
 
@@ -586,20 +531,19 @@ switch_server_cert($node, 'server-cn-only', 'root_ca');
 $common_connstr =
   "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
 
-test_connect_ok(
-	$common_connstr,
-	"sslmode=require sslcert=ssl/client+client_ca.crt",
+$node->connect_ok(
+	"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
 	"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
+$node->connect_fails(
+	$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
 	qr/SSL error/, "intermediate client certificate is missing");
 
 # test server-side CRL directory
 switch_server_cert($node, 'server-cn-only', undef, undef, 'root+client-crldir');
 
 # revoked client cert
-test_connect_fails(
-	$common_connstr,
-	"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+$node->connect_fails(
+	"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
 	qr/SSL error/,
 	"certificate authorization fails with revoked client cert with server-side CRL directory");
 
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 410b9e910d..e31650b931 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -53,39 +53,34 @@ $common_connstr =
   "dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR";
 
 # Default settings
-test_connect_ok($common_connstr, "user=ssltestuser",
+$node->connect_ok(
+	"$common_connstr user=ssltestuser",
 	"Basic SCRAM authentication with SSL");
 
 # Test channel_binding
-test_connect_fails(
-	$common_connstr,
-	"user=ssltestuser channel_binding=invalid_value",
+$node->connect_fails(
+	"$common_connstr user=ssltestuser channel_binding=invalid_value",
 	qr/invalid channel_binding value: "invalid_value"/,
 	"SCRAM with SSL and channel_binding=invalid_value");
-test_connect_ok(
-	$common_connstr,
-	"user=ssltestuser channel_binding=disable",
+$node->connect_ok("$common_connstr user=ssltestuser channel_binding=disable",
 	"SCRAM with SSL and channel_binding=disable");
 if ($supports_tls_server_end_point)
 {
-	test_connect_ok(
-		$common_connstr,
-		"user=ssltestuser channel_binding=require",
+	$node->connect_ok(
+		"$common_connstr user=ssltestuser channel_binding=require",
 		"SCRAM with SSL and channel_binding=require");
 }
 else
 {
-	test_connect_fails(
-		$common_connstr,
-		"user=ssltestuser channel_binding=require",
+	$node->connect_fails(
+		"$common_connstr user=ssltestuser channel_binding=require",
 		qr/channel binding is required, but server did not offer an authentication method that supports channel binding/,
 		"SCRAM with SSL and channel_binding=require");
 }
 
 # Now test when the user has an MD5-encrypted password; should fail
-test_connect_fails(
-	$common_connstr,
-	"user=md5testuser channel_binding=require",
+$node->connect_fails(
+	"$common_connstr user=md5testuser channel_binding=require",
 	qr/channel binding required but not supported by server's authentication request/,
 	"MD5 with SSL and channel_binding=require");
 
@@ -96,9 +91,8 @@ test_connect_fails(
 my $client_tmp_key = "ssl/client_scram_tmp.key";
 copy("ssl/client.key", $client_tmp_key);
 chmod 0600, $client_tmp_key;
-test_connect_fails(
-	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR",
-	"dbname=certdb user=ssltestuser channel_binding=require",
+$node->connect_fails(
+	"sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=certdb user=ssltestuser channel_binding=require",
 	qr/channel binding required, but server authenticated client without channel binding/,
 	"Cert authentication and channel_binding=require");
 
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSLServer.pm
index c494c1ad1c..129a7154a9 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSLServer.pm
@@ -37,46 +37,8 @@ use Exporter 'import';
 our @EXPORT = qw(
   configure_test_server_for_ssl
   switch_server_cert
-  test_connect_fails
-  test_connect_ok
 );
 
-# Define a couple of helper functions to test connecting to the server.
-
-# The first argument is a base connection string to use for connection.
-# The second argument is a complementary connection string.
-sub test_connect_ok
-{
-	local $Test::Builder::Level = $Test::Builder::Level + 1;
-
-	my ($common_connstr, $connstr, $test_name) = @_;
-
-	my $cmd = [
-		'psql', '-X', '-A', '-t', '-c',
-		"SELECT \$\$connected with $connstr\$\$",
-		'-d', "$common_connstr $connstr"
-	];
-
-	command_ok($cmd, $test_name);
-	return;
-}
-
-sub test_connect_fails
-{
-	local $Test::Builder::Level = $Test::Builder::Level + 1;
-
-	my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
-
-	my $cmd = [
-		'psql', '-X', '-A', '-t', '-c',
-		"SELECT \$\$connected with $connstr\$\$",
-		'-d', "$common_connstr $connstr"
-	];
-
-	command_fails_like($cmd, $expected_stderr, $test_name);
-	return;
-}
-
 # Copy a set of files, taking into account wildcards
 sub copy_files
 {
#14Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#13)
Re: Refactor SSL test framework to support multiple TLS libraries

On Wed, Mar 31, 2021 at 10:43:00AM +0900, Michael Paquier wrote:

Jacob has just raised this as an issue for an integration with NLS,
because it may be possible that things fail with "SSL error" but a
different error pattern, causing false positives:
/messages/by-id/e0f0484a1815b26bb99ef9ddc7a110dfd6425931.camel@vmware.com

I agree that those matches should be much more picky. We may need to
be careful across all versions of OpenSSL supported though :/

As I got my eyes on that, I am going to begin a new thread with a patch.

With all the comments addressed, with updates to use a single scalar
for all the connection strings and with a proper indentation, I finish
with the attached. Does that look fine?

Hearing nothing, I have applied this cleanup patch. I am not sure if
I will be able to tackle the remaining issues, aka switching
SSLServer.pm to become an OO module and plug OpenSSL-specific things
on top of that.
--
Michael