diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index f75d555ae5..f2cd7377d1 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -17,7 +17,7 @@ if (!$use_unix_sockets) } else { - plan tests => 22; + plan tests => 23; } @@ -35,47 +35,28 @@ sub reset_pg_hba return; } -# Test access for a single role, useful to wrap all tests into one. +# Test access for a single role, useful to wrap all tests into one. Extra +# named parameters are passed to connect_ok/fails as-is. sub test_role { - my $node = shift; - my $role = shift; - my $method = shift; - my $expected_res = shift; - my $expected_log_msg = shift; - my $status_string = 'failed'; - + my ($node, $role, $method, $expected_res, %params) = @_; + my $status_string = 'failed'; $status_string = 'success' if ($expected_res eq 0); local $Test::Builder::Level = $Test::Builder::Level + 1; - my $res = - $node->psql('postgres', undef, extra_params => [ '-U', $role, '-w' ]); - my $testname = - "authentication $status_string for method $method, role $role"; - is($res, $expected_res, $testname); + my $connstr = "user=$role"; + my $testname = "authentication $status_string for method $method, role $role"; - # Check if any log generated by authentication matches or not. - if ($expected_log_msg ne '') + if ($expected_res eq 0) { - my $log_contents = slurp_file($node->logfile); - if ($res == 0) - { - like($log_contents, $expected_log_msg, - "$testname: logs matching"); - } - else - { - unlike($log_contents, $expected_log_msg, - "$testname: logs not matching"); - } - - # Clean up any existing contents in the node's log file so as - # future tests don't step on each other's generated contents. - truncate $node->logfile, 0; + $node->connect_ok($connstr, $testname, %params, extra_params => [ '-w' ]); + } + else + { + # Currently, we don't check the error message, just the code. + $node->connect_fails($connstr, undef, $testname, %params, extra_params => [ '-w' ]); } - - return; } # Initialize primary node @@ -94,47 +75,41 @@ $node->safe_psql('postgres', ); $ENV{"PGPASSWORD"} = 'pass'; -# For "trust" method, all users should be able to connect. +# For "trust" method, all users should be able to connect. These users are not +# considered to be authenticated. reset_pg_hba($node, 'trust'); -test_role($node, 'scram_role', 'trust', 0); -test_role($node, 'md5_role', 'trust', 0); - -# Particular case here, the logs should have generated nothing related -# to authenticated users. -my $log_contents = slurp_file($node->logfile); -unlike( - $log_contents, - qr/connection authenticated:/, - "trust method does not authenticate users"); -truncate $node->logfile, 0; +test_role($node, 'scram_role', 'trust', 0, + log_unlike => [ qr/connection authenticated:/ ]); +test_role($node, 'md5_role', 'trust', 0, + log_unlike => [ qr/connection authenticated:/ ]); # For plain "password" method, all users should also be able to connect. reset_pg_hba($node, 'password'); test_role($node, 'scram_role', 'password', 0, - qr/connection authenticated: identity="scram_role" method=password/); + log_like => [ qr/connection authenticated: identity="scram_role" method=password/ ]); test_role($node, 'md5_role', 'password', 0, - qr/connection authenticated: identity="md5_role" method=password/); + log_like => [ qr/connection authenticated: identity="md5_role" method=password/ ]); # For "scram-sha-256" method, user "scram_role" should be able to connect. reset_pg_hba($node, 'scram-sha-256'); test_role($node, 'scram_role', 'scram-sha-256', 0, - qr/connection authenticated: identity="scram_role" method=scram-sha-256/); + log_like => [ qr/connection authenticated: identity="scram_role" method=scram-sha-256/ ]); test_role($node, 'md5_role', 'scram-sha-256', 2, - qr/connection authenticated: identity="md5_role"/); + log_unlike => [ qr/connection authenticated:/ ]); # Test that bad passwords are rejected. $ENV{"PGPASSWORD"} = 'badpass'; test_role($node, 'scram_role', 'scram-sha-256', 2, - qr/connection authenticated:/); + log_unlike => [ qr/connection authenticated:/ ]); $ENV{"PGPASSWORD"} = 'pass'; # For "md5" method, all users should be able to connect (SCRAM # authentication will be performed for the user with a SCRAM secret.) reset_pg_hba($node, 'md5'); test_role($node, 'scram_role', 'md5', 0, - qr/connection authenticated: identity="scram_role" method=md5/); + log_like => [ qr/connection authenticated: identity="scram_role" method=md5/ ]); test_role($node, 'md5_role', 'md5', 0, - qr/connection authenticated: identity="md5_role" method=md5/); + log_like => [ qr/connection authenticated: identity="md5_role" method=md5/ ]); # Tests for channel binding without SSL. # Using the password authentication method; channel binding can't work diff --git a/src/test/authentication/t/002_saslprep.pl b/src/test/authentication/t/002_saslprep.pl index 0aaab090ec..33e305e16c 100644 --- a/src/test/authentication/t/002_saslprep.pl +++ b/src/test/authentication/t/002_saslprep.pl @@ -41,12 +41,19 @@ sub test_login $status_string = 'success' if ($expected_res eq 0); + my $connstr = "user=$role"; + my $testname = "authentication $status_string for role $role with password $password"; + $ENV{"PGPASSWORD"} = $password; - my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role ]); - is($res, $expected_res, - "authentication $status_string for role $role with password $password" - ); - return; + if ($expected_res eq 0) + { + $node->connect_ok($connstr, $testname); + } + else + { + # Currently, we don't check the error message, just the code. + $node->connect_fails($connstr, undef, $testname); + } } # Initialize primary node. Force UTF-8 encoding, so that we can use non-ASCII diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl index d970cf3186..5ca2eb0694 100644 --- a/src/test/kerberos/t/001_auth.pl +++ b/src/test/kerberos/t/001_auth.pl @@ -20,7 +20,7 @@ use Time::HiRes qw(usleep); if ($ENV{with_gssapi} eq 'yes') { - plan tests => 38; + plan tests => 46; } else { @@ -186,40 +186,35 @@ sub test_access @expect_log_msgs) = @_; # need to connect over TCP/IP for Kerberos - my ($res, $stdoutres, $stderrres) = $node->psql( - 'postgres', - "$query", - extra_params => [ - '-XAtd', - $node->connstr('postgres') - . " host=$host hostaddr=$hostaddr $gssencmode", - '-U', - $role - ]); - - # If we get a query result back, it should be true. - if ($res == $expected_res and $res eq 0) + my $connstr = $node->connstr('postgres') . + " user=$role host=$host hostaddr=$hostaddr $gssencmode"; + + my %params = ( + query => $query, + extra_params => [ '-XAt' ], + ); + + if (@expect_log_msgs) { - is($stdoutres, "t", $test_name); + # Match every message literally. + my @regexes = map { qr/\Q$_\E/ } @expect_log_msgs; + + $params{log_like} = \@regexes; + } + + if ($expected_res eq 0) + { + my $stdoutres; + $params{stdout} = \$stdoutres; + + $node->connect_ok($connstr, $test_name, %params); + + is($stdoutres, "t", "$test_name: query result is true"); } else { - is($res, $expected_res, $test_name); + $node->connect_fails($connstr, undef, $test_name, %params); } - - # Verify specified log messages are logged in the log file. - while (my $expect_log_msg = shift @expect_log_msgs) - { - my $first_logfile = slurp_file($node->logfile); - - like($first_logfile, qr/\Q$expect_log_msg\E/, - 'found expected log file content'); - } - - # Clean up any existing contents in the node's log file so as - # future tests don't step on each other's generated contents. - truncate $node->logfile, 0; - return; } # As above, but test for an arbitrary query result. @@ -228,18 +223,18 @@ sub test_query my ($node, $role, $query, $expected, $gssencmode, $test_name) = @_; # need to connect over TCP/IP for Kerberos - my ($res, $stdoutres, $stderrres) = $node->psql( - 'postgres', - "$query", - extra_params => [ - '-XAtd', - $node->connstr('postgres') - . " host=$host hostaddr=$hostaddr $gssencmode", - '-U', - $role - ]); + my $connstr = $node->connstr('postgres') . + " user=$role host=$host hostaddr=$hostaddr $gssencmode"; + + my ($stdoutres, $stderrres); + + $node->connect_ok($connstr, $test_name, + query => $query, + extra_params => [ '-XAt' ], + stdout => \$stdoutres, + stderr => \$stderrres, + ); - is($res, 0, $test_name); like($stdoutres, $expected, $test_name); is($stderrres, "", $test_name); return; @@ -346,7 +341,7 @@ test_access( "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)" ); test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=disable', - 'fails with GSS encryption disabled and hostgssenc hba', ''); + 'fails with GSS encryption disabled and hostgssenc hba'); unlink($node->data_dir . '/pg_hba.conf'); $node->append_conf('pg_hba.conf', diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl index e98c8fe965..bf3cf4a658 100644 --- a/src/test/ldap/t/001_auth.pl +++ b/src/test/ldap/t/001_auth.pl @@ -163,33 +163,20 @@ note "running tests"; sub test_access { - my ($node, $role, $expected_res, $test_name, $expected_log_msg) = @_; + my ($node, $role, $expected_res, $test_name, %params) = @_; + my $connstr = "user=$role"; - my $res = - $node->psql('postgres', undef, - extra_params => [ '-U', $role, '-c', 'SELECT 1' ]); - is($res, $expected_res, $test_name); + local $Test::Builder::Level = $Test::Builder::Level + 1; - # Check if any log generated by authentication matches or not. - if ($expected_log_msg ne '') + if ($expected_res eq 0) { - my $log_contents = slurp_file($node->logfile); - if ($res == 0) - { - like($log_contents, $expected_log_msg, - "$test_name: logs matching"); - } - else - { - unlike($log_contents, $expected_log_msg, - "$test_name: logs not matching"); - } - - # Clean up any existing contents in the node's log file so as - # future tests don't step on each other's generated contents. - truncate $node->logfile, 0; + $node->connect_ok($connstr, $test_name, %params); + } + else + { + # Currently, we don't check the error message, just the code. + $node->connect_fails($connstr, undef, $test_name, %params); } - return; } note "simple bind"; @@ -201,18 +188,16 @@ $node->append_conf('pg_hba.conf', $node->restart; $ENV{"PGPASSWORD"} = 'wrong'; -test_access( - $node, 'test0', 2, +test_access($node, 'test0', 2, 'simple bind authentication fails if user not found in LDAP', - qr/connection authenticated:/); -test_access( - $node, 'test1', 2, + log_unlike => [ qr/connection authenticated:/ ]); +test_access($node, 'test1', 2, 'simple bind authentication fails with wrong password', - qr/connection authenticated:/); + log_unlike => [ qr/connection authenticated:/ ]); $ENV{"PGPASSWORD"} = 'secret1'; test_access($node, 'test1', 0, 'simple bind authentication succeeds', - qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/ + log_like => [ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/ ], ); note "search+bind"; diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index d6e10544bb..2afe225267 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -1860,47 +1860,157 @@ sub interactive_psql =pod -=item $node->connect_ok($connstr, $test_name) +=item $node->connect_ok($connstr, $test_name [, %params ]) Attempt a connection with a custom connection string. This is expected to succeed. +=over + +=item query => 'SELECT some_query;' + +By default, a query based on the connection string is performed after the +connection succeeds, to identify it in the server logs. If B is set, it +will be used instead. This can be used with the B parameter (see +C) to capture a query result. + +=item log_like => [ qr/required message/ ] + +If given, it must be an array reference containing a list of regular expressions +to match against the server log, using C. + +=item log_unlike => [ qr/prohibited message/ ] + +If given, it must be an array reference containing a list of regular expressions +that must NOT match against the server log. They will be passed to +C. + +=back + +Any additional named parameters are passed to C as-is. + =cut sub connect_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; - my ($self, $connstr, $test_name) = @_; + my ($self, $connstr, $test_name, %params) = @_; + + my $query = "SELECT \$\$connected with $connstr\$\$"; + if (exists $params{query}) + { + $query = delete $params{query}; + } + + my (@log_like, @log_unlike); + if (exists $params{log_like}) + { + @log_like = @{ delete $params{log_like} }; + } + if (exists $params{log_unlike}) + { + @log_unlike = @{ delete $params{log_unlike} }; + } + + if (@log_like or @log_unlike) + { + # Don't let previous log entries match for this connection. + truncate $self->logfile, 0; + } + my ($ret, $stdout, $stderr) = $self->psql( 'postgres', - "SELECT \$\$connected with $connstr\$\$", + $query, + %params, connstr => "$connstr", on_error_stop => 0); - ok($ret == 0, $test_name); + is($ret, 0, $test_name); + + if (@log_like or @log_unlike) + { + my $log_contents = TestLib::slurp_file($self->logfile); + + while (my $regex = shift @log_like) + { + like($log_contents, $regex, "$test_name: log matches"); + } + while (my $regex = shift @log_unlike) + { + unlike($log_contents, $regex, "$test_name: log does not match"); + } + } } =pod -=item $node->connect_fails($connstr, $expected_stderr, $test_name) +=item $node->connect_fails($connstr, $expected_stderr, $test_name [, %params ]) Attempt a connection with a custom connection string. This is expected to fail with a message that matches the regular expression $expected_stderr. +=over + +=item log_like => [ qr/required message/ ] + +=item log_unlike => [ qr/prohibited message/ ] + +See C, above. + +=back + +Any additional named parameters are passed to C. + =cut sub connect_fails { local $Test::Builder::Level = $Test::Builder::Level + 1; - my ($self, $connstr, $expected_stderr, $test_name) = @_; + my ($self, $connstr, $expected_stderr, $test_name, %params) = @_; + + my (@log_like, @log_unlike); + if (exists $params{log_like}) + { + @log_like = @{ delete $params{log_like} }; + } + if (exists $params{log_unlike}) + { + @log_unlike = @{ delete $params{log_unlike} }; + } + + if (@log_like or @log_unlike) + { + # Don't let previous log entries match for this connection. + truncate $self->logfile, 0; + } + my ($ret, $stdout, $stderr) = $self->psql( 'postgres', "SELECT \$\$connected with $connstr\$\$", + %params, connstr => "$connstr"); - ok($ret != 0, $test_name); - like($stderr, $expected_stderr, "$test_name: matches"); + isnt($ret, 0, $test_name); + + if (defined $expected_stderr) + { + like($stderr, $expected_stderr, "$test_name: stderr matches"); + } + + if (@log_like or @log_unlike) + { + my $log_contents = TestLib::slurp_file($self->logfile); + + while (my $regex = shift @log_like) + { + like($log_contents, $regex, "$test_name: log matches"); + } + while (my $regex = shift @log_unlike) + { + unlike($log_contents, $regex, "$test_name: log does not match"); + } + } } =pod diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index e20aff6b55..943703fc44 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -17,33 +17,7 @@ if ($ENV{with_ssl} ne 'openssl') } else { - plan tests => 108; -} - -# Check for matching patterns in the logs of a node. This should -# be used after a connection attempt. $res indicates if the pattern -# should match or not. -sub check_auth_logs -{ - my $node = shift; - my $expected_log_msg = shift; - my $test_name = shift; - my $res = shift; - - my $log_contents = slurp_file($node->logfile); - - if ($res == 0) - { - like($log_contents, $expected_log_msg, $test_name); - } - else - { - unlike($log_contents, $expected_log_msg, $test_name); - } - - # Clean up any existing contents in the node's log file so as - # future tests don't step on each other's generated contents. - truncate $node->logfile, 0; + plan tests => 110; } #### Some configuration @@ -444,13 +418,8 @@ my $dn_connstr = "$common_connstr dbname=certdb_dn"; $node->connect_ok( "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key", - "certificate authorization succeeds with DN mapping"); - -check_auth_logs( - $node, - qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/, - "certificate authorization with DN mapping logged", - 0); + "certificate authorization succeeds with DN mapping", + log_like => [ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/ ]); # same thing but with a regex $dn_connstr = "$common_connstr dbname=certdb_dn_re"; @@ -464,13 +433,9 @@ $dn_connstr = "$common_connstr dbname=certdb_cn"; $node->connect_ok( "$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key", - "certificate authorization succeeds with CN mapping"); - -check_auth_logs( - $node, - qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/, - "certificate authorization with CN mapping logged with DN info", - 0); + "certificate authorization succeeds with CN mapping", + # the full DN should still be used as the authenticated identity + log_like => [ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/ ]); @@ -531,24 +496,19 @@ SKIP: $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" + "certificate authorization fails with client cert belonging to another user", + # certificate authentication should be logged even on failure + log_like => [ qr/connection authenticated: identity="CN=ssltestuser" method=cert/ ], ); - -check_auth_logs( - $node, - qr/connection authenticated: identity="CN=ssltestuser" method=cert/, - "certificate authorization logged even on failure", 0); # revoked client cert $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"); - -check_auth_logs( - $node, - qr/connection authenticated:/, - "certificate authentication with revoked client cert not logged", 1); + "certificate authorization fails with revoked client cert", + # revoked certificates should not authenticate the user + log_unlike => [ qr/connection authenticated:/ ], +); # Check that connecting with auth-option verify-full in pg_hba: # works, iff username matches Common Name @@ -558,31 +518,28 @@ $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" + "auth_option clientcert=verify-full succeeds with matching username and Common Name", + # verify-full does not provide authentication + log_unlike => [ qr/connection authenticated:/ ], ); $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" + "auth_option clientcert=verify-full fails with mismatching username and Common Name", + # verify-full does not provide authentication + log_unlike => [ qr/connection authenticated:/ ], ); # Check that connecting with auth-optionverify-ca in pg_hba : # works, when username doesn't match Common Name -# Clean up once the logs to have a clean check state. -truncate $node->logfile, 0; $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" + "auth_option clientcert=verify-ca succeeds with mismatching username and Common Name", + # verify-full does not provide authentication + log_unlike => [ qr/connection authenticated:/ ], ); -# None of the above connections to verifydb should have resulted in -# authentication. -check_auth_logs( - $node, - qr/connection authenticated:/, - "trust auth method does not set authenticated identity", 1); - # 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 9f1b080d45..d98f6319fa 100644 --- a/src/test/ssl/t/002_scram.pl +++ b/src/test/ssl/t/002_scram.pl @@ -97,20 +97,10 @@ $node->connect_fails( "Cert authentication and channel_binding=require"); # Certificate verification at the connection level should still work fine. -# Truncate once the logs, to ensure that we check what is generated for this -# specific connection. -truncate $node->logfile, 0; -test_connect_ok( - "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR", - "dbname=verifydb user=ssltestuser channel_binding=require", - "SCRAM with clientcert=verify-full and channel_binding=require"); - -my $log_contents = slurp_file($node->logfile); -like( - $log_contents, - qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/, - "SCRAM with clientcert=verify-full sets the username as the authenticated identity" -); +$node->connect_ok( + "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser channel_binding=require", + "SCRAM with clientcert=verify-full and channel_binding=require", + log_like => [ qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/ ]); # clean up unlink($client_tmp_key);