diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index 9fac0689ee..b5fb15f794 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -10,6 +10,7 @@ use FindBin; use lib $FindBin::RealBin; use SSLServer; +use LogCollector; if ($ENV{with_ssl} ne 'openssl') { @@ -454,8 +455,7 @@ test_connect_fails( ); -my $log = $node->rotate_logfile(); -$node->restart; +my $log = LogCollector->new($node); # correct client cert using whole DN my $dn_connstr = "$common_connstr dbname=certdb_dn"; @@ -466,16 +466,10 @@ test_connect_ok( "certificate authorization succeeds with DN mapping" ); -$node->stop('fast'); -my $log_contents = slurp_file($log); - -like( - $log_contents, +$log->like( qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/, "authenticated DNs are logged"); -$node->start; - # same thing but with a regex $dn_connstr = "$common_connstr dbname=certdb_dn_re"; @@ -485,8 +479,7 @@ test_connect_ok( "certificate authorization succeeds with DN regex mapping" ); -$log = $node->rotate_logfile(); -$node->restart; +$log->rotate; # same thing but using explicit CN $dn_connstr = "$common_connstr dbname=certdb_cn"; @@ -497,17 +490,10 @@ test_connect_ok( "certificate authorization succeeds with CN mapping" ); -$node->stop('fast'); -$log_contents = slurp_file($log); - -like( - $log_contents, +$log->like( qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/, "full authenticated DNs are logged even in CN mapping mode"); -$node->start; - - TODO: { @@ -565,8 +551,7 @@ SKIP: "certificate authorization fails because of file permissions"); } -$log = $node->rotate_logfile(); -$node->restart; +$log->rotate; # client cert belonging to another user test_connect_fails( @@ -576,17 +561,10 @@ test_connect_fails( "certificate authorization fails with client cert belonging to another user" ); -$node->stop('fast'); -$log_contents = slurp_file($log); - -like( - $log_contents, +$log->like( qr/connection authenticated: identity="CN=ssltestuser" method=cert/, "cert authentication succeeds even if authorization fails"); -$log = $node->rotate_logfile(); -$node->restart; - # revoked client cert test_connect_fails( $common_connstr, @@ -594,25 +572,16 @@ test_connect_fails( qr/SSL error/, "certificate authorization fails with revoked client cert"); -$node->stop('fast'); -$log_contents = slurp_file($log); - -unlike( - $log_contents, +$log->unlike( qr/connection authenticated:/, "revoked certs do not authenticate users"); -$node->start; - # Check that connecting with auth-option verify-full in pg_hba: # works, iff username matches Common Name # fails, iff username doesn't match Common Name. $common_connstr = "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR"; -$log = $node->rotate_logfile(); -$node->restart; - test_connect_ok( $common_connstr, "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key", @@ -634,18 +603,12 @@ test_connect_ok( "auth_option clientcert=verify-ca succeeds with mismatching username and Common Name" ); -$node->stop('fast'); -$log_contents = slurp_file($log); - # None of the above connections to verifydb should have resulted in # authentication. -unlike( - $log_contents, +$log->unlike( qr/connection authenticated:/, "trust auth method does not set authenticated identity"); -$node->start; - # 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'); $common_connstr = diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl index c15b9c405b..81aaa0b02d 100644 --- a/src/test/ssl/t/002_scram.pl +++ b/src/test/ssl/t/002_scram.pl @@ -12,6 +12,7 @@ use FindBin; use lib $FindBin::RealBin; use SSLServer; +use LogCollector; if ($ENV{with_ssl} ne 'openssl') { @@ -102,8 +103,7 @@ test_connect_fails( qr/channel binding required, but server authenticated client without channel binding/, "Cert authentication and channel_binding=require"); -my $log = $node->rotate_logfile(); -$node->restart; +my $log = LogCollector->new($node); # Certificate verification at the connection level should still work fine. test_connect_ok( @@ -111,16 +111,10 @@ test_connect_ok( "dbname=verifydb user=ssltestuser channel_binding=require", "SCRAM with clientcert=verify-full and channel_binding=require"); -$node->stop('fast'); -my $log_contents = slurp_file($log); - -like( - $log_contents, +$log->like( qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/, "SCRAM with clientcert=verify-full sets the username as the authenticated identity"); -$node->start; - # clean up unlink($client_tmp_key); diff --git a/src/test/ssl/t/LogCollector.pm b/src/test/ssl/t/LogCollector.pm new file mode 100644 index 0000000000..ce0c4063f2 --- /dev/null +++ b/src/test/ssl/t/LogCollector.pm @@ -0,0 +1,84 @@ +# This module defines a class to help match log entries to specific connections +# or queries. + +package LogCollector; + +use strict; +use warnings; +use TestLib; +use Test::More (); + +use Exporter 'import'; +our @EXPORT = qw( + start_log_capture +); + +# Wrap a new collector around $node. +# +# The node's current logfile will be saved, then rotated, so that future log +# entries are written to a new logfile. Whenever the rotate() method is called +# on the collector, all entries from the new logfile will be appended to the old +# logfile, and then the new logfile will be truncated. This gives a clean slate +# to the next tests. +sub new +{ + my ($class, $node) = @_; + + my $self = { + _node => $node, + _orig_log => $node->logfile, + _log => $node->rotate_logfile, + }; + bless $self, $class; + + $node->restart; # start writing to the new log + return $self; +} + +# Returns the contents of the current (new) logfile. These will be written to +# the old logfile when rotate() is called. +sub contents +{ + my $self = shift; + + return slurp_file($self->{_log}); +} + +# Writes the current contents of the new logfile to the old logfile, then +# truncates the new logfile. +sub rotate +{ + my $self = shift; + my $log_contents = $self->contents; + + append_to_file($self->{_orig_log}, $log_contents); + truncate $self->{_log}, 0; +} + +# Calls Test::More::like on the current logfile contents, then rotates the logs +# so that future calls to like() or unlike() will not match the contents that +# have already been checked. +# +# To avoid the auto-rotation behavior (e.g. if you want to perform multiple +# checks on the same log contents), use contents() instead and perform a manual +# rotate() after the checks are complete. +sub like +{ + my ($self, $expected, $name) = @_; + + Test::More::like($self->contents, $expected, $name); + + $self->rotate; +} + +# The opposite of like(), above. +sub unlike +{ + my ($self, $expected, $name) = @_; + + Test::More::unlike($self->contents, $expected, $name); + + $self->rotate; +} + +1;