From 65dfe9d167e925cd8892dedb51dde29f69b7388d Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Fri, 12 Apr 2024 19:52:37 +0300 Subject: Move libpq encryption negotiation tests The test targets libpq's options, so 'src/test/interfaces/libpq/t' is a more natural place for it. While doing this, I noticed that I had missed adding the libpq_encryption subdir to the Makefile. That's why this commit only needs to remove it from the meson.build file. Per Peter Eisentraut's suggestion. Discussion: https://www.postgresql.org/message-id/09d4bf5d-d0fa-4c66-a1d7-5ec757609646@eisentraut.org --- src/interfaces/libpq/Makefile | 2 +- src/interfaces/libpq/meson.build | 7 +- src/interfaces/libpq/t/005_negotiate_encryption.pl | 724 +++++++++++++++++++++ src/test/libpq_encryption/Makefile | 25 - src/test/libpq_encryption/README | 31 - src/test/libpq_encryption/meson.build | 18 - .../libpq_encryption/t/001_negotiate_encryption.pl | 724 --------------------- src/test/meson.build | 1 - 8 files changed, 731 insertions(+), 801 deletions(-) create mode 100644 src/interfaces/libpq/t/005_negotiate_encryption.pl delete mode 100644 src/test/libpq_encryption/Makefile delete mode 100644 src/test/libpq_encryption/README delete mode 100644 src/test/libpq_encryption/meson.build delete mode 100644 src/test/libpq_encryption/t/001_negotiate_encryption.pl (limited to 'src') diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index fe2af575c5d..b36a7657648 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -13,7 +13,7 @@ subdir = src/interfaces/libpq top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -export with_ssl +export with_ssl with_gssapi with_krb_srvnam PGFILEDESC = "PostgreSQL Access Library" diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build index be6fadaea23..ed2a4048d18 100644 --- a/src/interfaces/libpq/meson.build +++ b/src/interfaces/libpq/meson.build @@ -118,8 +118,13 @@ tests += { 't/002_api.pl', 't/003_load_balance_host_list.pl', 't/004_load_balance_dns.pl', + 't/005_negotiate_encryption.pl', ], - 'env': {'with_ssl': ssl_library}, + 'env': { + 'with_ssl': ssl_library, + 'with_gssapi': gssapi.found() ? 'yes' : 'no', + 'with_krb_srvnam': 'postgres', + }, }, } diff --git a/src/interfaces/libpq/t/005_negotiate_encryption.pl b/src/interfaces/libpq/t/005_negotiate_encryption.pl new file mode 100644 index 00000000000..b369289ef1d --- /dev/null +++ b/src/interfaces/libpq/t/005_negotiate_encryption.pl @@ -0,0 +1,724 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# OVERVIEW +# -------- +# +# Test negotiation of SSL and GSSAPI encryption +# +# We test all combinations of: +# +# - all the libpq client options that affect the protocol negotiations +# (gssencmode, sslmode, sslnegotiation) +# - server accepting or rejecting the authentication due to +# pg_hba.conf entries +# - SSL and GSS enabled/disabled in the server +# +# That's a lot of combinations, so we use a table-driven approach. +# Each combination is represented by a line in a table. The line lists +# the options specifying the test case, and an expected outcome. The +# expected outcome includes whether the connection succeeds or fails, +# and whether it uses SSL, GSS or no encryption. It also includes a +# condensed trace of what steps were taken during the negotiation. +# That can catch cases like useless retries, or if the encryption +# methods are attempted in wrong order, even when it doesn't affect +# the final outcome. +# +# TEST TABLE FORMAT +# ----------------- +# +# Example of the test table format: +# +# # USER GSSENCMODE SSLMODE EVENTS -> OUTCOME +# testuser disable allow connect, authok -> plain +# . . prefer connect, sslaccept, authok -> ssl +# testuser require * connect, gssreject -> fail +# +# USER, GSSENCMODE and SSLMODE fields are the libpq 'user', +# 'gssencmode' and 'sslmode' options used in the test. As a shorthand, +# a single dot ('.') can be used in the USER, GSSENCMODE, and SSLMODE +# fields, to indicate "same as on previous line". A '*' can be used +# as a wildcard; it is expanded to mean all possible values of that +# field. +# +# The EVENTS field is a condensed trace of expected steps during the +# negotiation: +# +# connect: a TCP connection was established +# reconnect: TCP connection was disconnected, and a new one was established +# sslaccept: client requested SSL encryption and server accepted it +# sslreject: client requested SSL encryption but server rejected it +# gssaccept: client requested GSSAPI encryption and server accepted it +# gssreject: client requested GSSAPI encryption but server rejected it +# authok: client sent startup packet and authentication was performed successfully +# authfail: client sent startup packet but server rejected the authentication +# +# The event trace can be used to verify that the client negotiated the +# connection properly in more detail than just by looking at the +# outcome. For example, if the client opens spurious extra TCP +# connections, that would show up in the EVENTS. +# +# The OUTCOME field indicates the expected result of the test: +# +# plain: an unencrypted connection was established +# ssl: SSL connection was established +# gss: GSSAPI encrypted connection was established +# fail: the connection attempt failed +# +# Empty lines are ignored. '#' can be used to mark the rest of the +# line as a comment. + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Kerberos; +use File::Basename; +use File::Copy; +use Test::More; + +if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\blibpq_encryption\b/) +{ + plan skip_all => + 'Potentially unsafe test libpq_encryption not enabled in PG_TEST_EXTRA'; +} + +my $ssl_supported = $ENV{with_ssl} eq 'openssl'; +my $gss_supported = $ENV{with_gssapi} eq 'yes'; + +### +### Prepare test server for GSSAPI and SSL authentication, with a few +### different test users and helper functions. We don't actually +### enable SSL and kerberos in the server yet, we will do that later. +### + +my $host = 'enc-test-localhost.postgresql.example.com'; +my $hostaddr = '127.0.0.1'; +my $servercidr = '127.0.0.1/32'; + +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init; +$node->append_conf( + 'postgresql.conf', qq{ +listen_addresses = '$hostaddr' + +# Capturing the EVENTS that occur during tests requires these settings +log_connections = on +log_disconnections = on +trace_connection_negotiation = on +lc_messages = 'C' +}); +my $pgdata = $node->data_dir; + +my $dbname = 'postgres'; +my $username = 'enctest'; +my $application = '001_negotiate_encryption.pl'; + +my $gssuser_password = 'secret1'; + +my $krb; + +if ($gss_supported != 0) +{ + note "setting up Kerberos"; + + my $realm = 'EXAMPLE.COM'; + $krb = PostgreSQL::Test::Kerberos->new($host, $hostaddr, $realm); + $node->append_conf('postgresql.conf', "krb_server_keyfile = '$krb->{keytab}'\n"); +} + +if ($ssl_supported != 0) +{ + my $certdir = dirname(__FILE__) . "/../../../test/ssl/ssl"; + + copy "$certdir/server-cn-only.crt", "$pgdata/server.crt" + || die "copying server.crt: $!"; + copy "$certdir/server-cn-only.key", "$pgdata/server.key" + || die "copying server.key: $!"; + chmod(0600, "$pgdata/server.key") + or die "failed to change permissions on server keys: $!"; + + # Start with SSL disabled. + $node->append_conf('postgresql.conf', "ssl = off\n"); +} + +$node->start; + +$node->safe_psql('postgres', 'CREATE USER localuser;'); +$node->safe_psql('postgres', 'CREATE USER testuser;'); +$node->safe_psql('postgres', 'CREATE USER ssluser;'); +$node->safe_psql('postgres', 'CREATE USER nossluser;'); +$node->safe_psql('postgres', 'CREATE USER gssuser;'); +$node->safe_psql('postgres', 'CREATE USER nogssuser;'); + +my $unixdir = $node->safe_psql('postgres', 'SHOW unix_socket_directories;'); +chomp($unixdir); + +# Helper function that returns the encryption method in use in the +# connection. +$node->safe_psql('postgres', q{ +CREATE FUNCTION current_enc() RETURNS text LANGUAGE plpgsql AS $$ +DECLARE + ssl_in_use bool; + gss_in_use bool; +BEGIN + ssl_in_use = (SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid()); + gss_in_use = (SELECT encrypted FROM pg_stat_gssapi WHERE pid = pg_backend_pid()); + + raise log 'ssl % gss %', ssl_in_use, gss_in_use; + + IF ssl_in_use AND gss_in_use THEN + RETURN 'ssl+gss'; -- shouldn't happen + ELSIF ssl_in_use THEN + RETURN 'ssl'; + ELSIF gss_in_use THEN + RETURN 'gss'; + ELSE + RETURN 'plain'; + END IF; +END; +$$; +}); + +# Only accept SSL connections from $servercidr. Our tests don't depend on this +# but seems best to keep it as narrow as possible for security reasons. +open my $hba, '>', "$pgdata/pg_hba.conf" or die $!; +print $hba qq{ +# TYPE DATABASE USER ADDRESS METHOD OPTIONS +local postgres localuser trust +host postgres testuser $servercidr trust +hostnossl postgres nossluser $servercidr trust +hostnogssenc postgres nogssuser $servercidr trust +}; + +print $hba qq{ +hostssl postgres ssluser $servercidr trust +} if ($ssl_supported != 0); + +print $hba qq{ +hostgssenc postgres gssuser $servercidr trust +} if ($gss_supported != 0); +close $hba; +$node->reload; + +# Ok, all prepared. Run the tests. + +my @all_test_users = ('testuser', 'ssluser', 'nossluser', 'gssuser', 'nogssuser'); +my @all_gssencmodes = ('disable', 'prefer', 'require'); +my @all_sslmodes = ('disable', 'allow', 'prefer', 'require'); +my @all_sslnegotiations = ('postgres', 'direct', 'requiredirect'); + +my $server_config = { + server_ssl => 0, + server_gss => 0, +}; + +### +### Run tests with GSS and SSL disabled in the server +### +my $test_table; +if ($ssl_supported) { + $test_table = q{ +# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME +testuser disable disable * connect, authok -> plain +. . allow * connect, authok -> plain +. . prefer postgres connect, sslreject, authok -> plain +. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain +. . . requiredirect connect, directsslreject, reconnect, authok -> plain +. . require postgres connect, sslreject -> fail +. . . direct connect, directsslreject, reconnect, sslreject -> fail +. . . requiredirect connect, directsslreject -> fail +. prefer disable * connect, authok -> plain +. . allow * connect, authok -> plain +. . prefer postgres connect, sslreject, authok -> plain +. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain +. . . requiredirect connect, directsslreject, reconnect, authok -> plain +. . require postgres connect, sslreject -> fail +. . . direct connect, directsslreject, reconnect, sslreject -> fail +. . . requiredirect connect, directsslreject -> fail + }; +} else { + # Compiled without SSL support + $test_table = q{ +# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME +testuser disable disable postgres connect, authok -> plain +. . allow postgres connect, authok -> plain +. . prefer postgres connect, authok -> plain +. prefer disable postgres connect, authok -> plain +. . allow postgres connect, authok -> plain +. . prefer postgres connect, authok -> plain + +# Without SSL support, sslmode=require and sslnegotiation=direct/requiredirect +# are not accepted at all. +. * require * - -> fail +. * * direct - -> fail +. * * requiredirect - -> fail + }; +} + +# All attempts with gssencmode=require fail without connecting because +# no credential cache has been configured in the client. (Or if GSS +# support is not compiled in, they will fail because of that.) +$test_table .= q{ +testuser require * * - -> fail +}; + +note("Running tests with SSL and GSS disabled in the server"); +test_matrix($node, $server_config, + ['testuser'], \@all_gssencmodes, \@all_sslmodes, \@all_sslnegotiations, + parse_table($test_table)); + + +### +### Run tests with GSS disabled and SSL enabled in the server +### +SKIP: +{ + skip "SSL not supported by this build" if $ssl_supported == 0; + + $test_table = q{ +# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME +testuser disable disable * connect, authok -> plain +. . allow * connect, authok -> plain +. . prefer postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. . require postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +ssluser . disable * connect, authfail -> fail +. . allow postgres connect, authfail, reconnect, sslaccept, authok -> ssl +. . . direct connect, authfail, reconnect, directsslaccept, authok -> ssl +. . . requiredirect connect, authfail, reconnect, directsslaccept, authok -> ssl +. . prefer postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. . require postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +nossluser . disable * connect, authok -> plain +. . allow postgres connect, authok -> plain +. . . direct connect, authok -> plain +. . . requiredirect connect, authok -> plain +. . prefer postgres connect, sslaccept, authfail, reconnect, authok -> plain +. . . direct connect, directsslaccept, authfail, reconnect, authok -> plain +. . . requiredirect connect, directsslaccept, authfail, reconnect, authok -> plain +. . require postgres connect, sslaccept, authfail -> fail +. . require direct connect, directsslaccept, authfail -> fail +. . require requiredirect connect, directsslaccept, authfail -> fail +}; + + # Enable SSL in the server + $node->adjust_conf('postgresql.conf', 'ssl', 'on'); + $node->reload; + $server_config->{server_ssl} = 1; + + note("Running tests with SSL enabled in server"); + test_matrix($node, $server_config, + ['testuser', 'ssluser', 'nossluser'], + ['disable'], \@all_sslmodes, \@all_sslnegotiations, + parse_table($test_table)); + + # Disable SSL again + $node->adjust_conf('postgresql.conf', 'ssl', 'off'); + $node->reload; + $server_config->{server_ssl} = 0; +} + +### +### Run tests with GSS enabled, SSL disabled in the server +### +SKIP: +{ + skip "GSSAPI/Kerberos not supported by this build" if $gss_supported == 0; + + $krb->create_principal('gssuser', $gssuser_password); + $krb->create_ticket('gssuser', $gssuser_password); + $server_config->{server_gss} = 1; + + $test_table = q{ +# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME +testuser disable disable * connect, authok -> plain +. . allow * connect, authok -> plain +. . prefer postgres connect, sslreject, authok -> plain +. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain +. . . requiredirect connect, directsslreject, reconnect, authok -> plain +. . require postgres connect, sslreject -> fail +. . . direct connect, directsslreject, reconnect, sslreject -> fail +. . . requiredirect connect, directsslreject -> fail +. prefer * * connect, gssaccept, authok -> gss +. require * * connect, gssaccept, authok -> gss + +gssuser disable disable * connect, authfail -> fail +. . allow postgres connect, authfail, reconnect, sslreject -> fail +. . . direct connect, authfail, reconnect, directsslreject, reconnect, sslreject -> fail +. . . requiredirect connect, authfail, reconnect, directsslreject -> fail +. . prefer postgres connect, sslreject, authfail -> fail +. . . direct connect, directsslreject, reconnect, sslreject, authfail -> fail +. . . requiredirect connect, directsslreject, reconnect, authfail -> fail +. . require postgres connect, sslreject -> fail +. . . direct connect, directsslreject, reconnect, sslreject -> fail +. . . requiredirect connect, directsslreject -> fail +. prefer * * connect, gssaccept, authok -> gss +. require * * connect, gssaccept, authok -> gss + +nogssuser disable disable * connect, authok -> plain +. . allow postgres connect, authok -> plain +. . . direct connect, authok -> plain +. . . requiredirect connect, authok -> plain +. . prefer postgres connect, sslreject, authok -> plain +. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain +. . . requiredirect connect, directsslreject, reconnect, authok -> plain +. . require postgres connect, sslreject -> fail +. . . direct connect, directsslreject, reconnect, sslreject -> fail +. . . requiredirect connect, directsslreject -> fail +. prefer disable * connect, gssaccept, authfail, reconnect, authok -> plain +. . allow postgres connect, gssaccept, authfail, reconnect, authok -> plain +. . . direct connect, gssaccept, authfail, reconnect, authok -> plain +. . . requiredirect connect, gssaccept, authfail, reconnect, authok -> plain +. . prefer postgres connect, gssaccept, authfail, reconnect, sslreject, authok -> plain +. . . direct connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject, authok -> plain +. . . requiredirect connect, gssaccept, authfail, reconnect, directsslreject, reconnect, authok -> plain +. . require postgres connect, gssaccept, authfail, reconnect, sslreject -> fail +. . . direct connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject -> fail +. . . requiredirect connect, gssaccept, authfail, reconnect, directsslreject -> fail +. require disable * connect, gssaccept, authfail -> fail +. . allow * connect, gssaccept, authfail -> fail +. . prefer * connect, gssaccept, authfail -> fail +. . require * connect, gssaccept, authfail -> fail # If both GSSAPI and sslmode are required, and GSS is not available -> fail + }; + + # The expected events and outcomes above assume that SSL support + # is enabled. When libpq is compiled without SSL support, all + # attempts to connect with sslmode=require or + # sslnegotition=direct/requiredirectwould fail immediately without + # even connecting to the server. Skip those, because we tested + # them earlier already. + my ($sslmodes, $sslnegotiations); + if ($ssl_supported != 0) { + ($sslmodes, $sslnegotiations) = (\@all_sslmodes, \@all_sslnegotiations); + } else { + ($sslmodes, $sslnegotiations) = (['disable'], ['postgres']); + } + + note("Running tests with GSS enabled in server"); + test_matrix($node, $server_config, + ['testuser', 'gssuser', 'nogssuser'], + \@all_gssencmodes, $sslmodes, $sslnegotiations, + parse_table($test_table)); +} + +### +### Tests with both GSS and SSL enabled in the server +### +SKIP: +{ + skip "GSSAPI/Kerberos or SSL not supported by this build" unless ($ssl_supported && $gss_supported); + + # Sanity check that GSSAPI is still enabled from previous test. + connect_test($node, 'user=testuser gssencmode=prefer sslmode=prefer', 'connect, gssaccept, authok -> gss'); + + # Enable SSL + $node->adjust_conf('postgresql.conf', 'ssl', 'on'); + $node->reload; + $server_config->{server_ssl} = 1; + + $test_table = q{ +# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME +testuser disable disable * connect, authok -> plain +. . allow * connect, authok -> plain +. . prefer postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. . require postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. prefer disable * connect, gssaccept, authok -> gss +. . allow * connect, gssaccept, authok -> gss +. . prefer * connect, gssaccept, authok -> gss +. . require * connect, gssaccept, authok -> gss # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require +. require disable * connect, gssaccept, authok -> gss +. . allow * connect, gssaccept, authok -> gss +. . prefer * connect, gssaccept, authok -> gss +. . require * connect, gssaccept, authok -> gss # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require + +gssuser disable disable * connect, authfail -> fail +. . allow postgres connect, authfail, reconnect, sslaccept, authfail -> fail +. . . direct connect, authfail, reconnect, directsslaccept, authfail -> fail +. . . requiredirect connect, authfail, reconnect, directsslaccept, authfail -> fail +. . prefer postgres connect, sslaccept, authfail, reconnect, authfail -> fail +. . . direct connect, directsslaccept, authfail, reconnect, authfail -> fail +. . . requiredirect connect, directsslaccept, authfail, reconnect, authfail -> fail +. . require postgres connect, sslaccept, authfail -> fail +. . . direct connect, directsslaccept, authfail -> fail +. . . requiredirect connect, directsslaccept, authfail -> fail +. prefer disable * connect, gssaccept, authok -> gss +. . allow * connect, gssaccept, authok -> gss +. . prefer * connect, gssaccept, authok -> gss +. . require * connect, gssaccept, authok -> gss # GSS is chosen over SSL, even though sslmode=require +. require disable * connect, gssaccept, authok -> gss +. . allow * connect, gssaccept, authok -> gss +. . prefer * connect, gssaccept, authok -> gss +. . require * connect, gssaccept, authok -> gss # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require + +ssluser disable disable * connect, authfail -> fail +. . allow postgres connect, authfail, reconnect, sslaccept, authok -> ssl +. . . direct connect, authfail, reconnect, directsslaccept, authok -> ssl +. . . requiredirect connect, authfail, reconnect, directsslaccept, authok -> ssl +. . prefer postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. . require postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. prefer disable * connect, gssaccept, authfail, reconnect, authfail -> fail +. . allow postgres connect, gssaccept, authfail, reconnect, authfail, reconnect, sslaccept, authok -> ssl +. . . direct connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl +. . . requiredirect connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl +. . prefer postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl +. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. . require postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl +. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. require disable * connect, gssaccept, authfail -> fail +. . allow * connect, gssaccept, authfail -> fail +. . prefer * connect, gssaccept, authfail -> fail +. . require * connect, gssaccept, authfail -> fail # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required + +nogssuser disable disable * connect, authok -> plain +. . allow postgres connect, authok -> plain +. . . direct connect, authok -> plain +. . . requiredirect connect, authok -> plain +. . prefer postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. . require postgres connect, sslaccept, authok -> ssl +. . . direct connect, directsslaccept, authok -> ssl +. . . requiredirect connect, directsslaccept, authok -> ssl +. prefer disable * connect, gssaccept, authfail, reconnect, authok -> plain +. . allow * connect, gssaccept, authfail, reconnect, authok -> plain +. . prefer postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl +. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. . require postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl +. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl +. require disable * connect, gssaccept, authfail -> fail +. . allow * connect, gssaccept, authfail -> fail +. . prefer * connect, gssaccept, authfail -> fail +. . require * connect, gssaccept, authfail -> fail # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required + +nossluser disable disable * connect, authok -> plain +. . allow * connect, authok -> plain +. . prefer postgres connect, sslaccept, authfail, reconnect, authok -> plain +. . . direct connect, directsslaccept, authfail, reconnect, authok -> plain +. . . requiredirect connect, directsslaccept, authfail, reconnect, authok -> plain +. . require postgres connect, sslaccept, authfail -> fail +. . . direct connect, directsslaccept, authfail -> fail +. . . requiredirect connect, directsslaccept, authfail -> fail +. prefer * * connect, gssaccept, authok -> gss +. require * * connect, gssaccept, authok -> gss + }; + + note("Running tests with both GSS and SSL enabled in server"); + test_matrix($node, $server_config, + ['testuser', 'gssuser', 'ssluser', 'nogssuser', 'nossluser'], + \@all_gssencmodes, \@all_sslmodes, \@all_sslnegotiations, + parse_table($test_table)); +} + +### +### Test negotiation over unix domain sockets. +### +SKIP: +{ + skip "Unix domain sockets not supported" unless ($unixdir ne ""); + + # libpq doesn't attempt SSL or GSSAPI over Unix domain + # sockets. The server would reject them too. + connect_test($node, "user=localuser gssencmode=prefer sslmode=prefer host=$unixdir", 'connect, authok -> plain'); + connect_test($node, "user=localuser gssencmode=require sslmode=prefer host=$unixdir", '- -> fail'); +} + +done_testing(); + + +### Helper functions + +# Test the cube of parameters: user, gssencmode, sslmode, and sslnegotitation +sub test_matrix +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($pg_node, $node_conf, + $test_users, $gssencmodes, $sslmodes, $sslnegotiations, %expected) = @_; + + foreach my $test_user (@{$test_users}) + { + foreach my $gssencmode (@{$gssencmodes}) + { + foreach my $client_mode (@{$sslmodes}) + { + # sslnegotiation only makes a difference if SSL is enabled. This saves a few combinations. + my ($key, $expected_events); + foreach my $negotiation (@{$sslnegotiations}) + { + $key = "$test_user $gssencmode $client_mode $negotiation"; + $expected_events = $expected{$key}; + if (!defined($expected_events)) { + $expected_events = ""; + } + connect_test($pg_node, "user=$test_user gssencmode=$gssencmode sslmode=$client_mode sslnegotiation=$negotiation", $expected_events); + } + } + } + } +} + +# Try to establish a connection to the server using libpq. Verify the +# negotiation events and outcome. +sub connect_test +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($node, $connstr, $expected_events_and_outcome) = @_; + + my $test_name = " '$connstr' -> $expected_events_and_outcome"; + + my $connstr_full = ""; + $connstr_full .= "dbname=postgres " unless $connstr =~ m/dbname=/; + $connstr_full .= "host=$host hostaddr=$hostaddr " unless $connstr =~ m/host=/; + $connstr_full .= $connstr; + + # Get the current size of the logfile before running the test. + # After the test, we can then check just the new lines that have + # appeared. (This is the same approach that the $node->log_contains + # function uses). + my $log_location = -s $node->logfile; + + # XXX: Pass command with -c, because I saw intermittent test + # failures like this: + # + # ack Broken pipe: write( 13, 'SELECT current_enc()' ) at /usr/local/lib/perl5/site_perl/IPC/Run/IO.pm line 550. + # + # I think that happens if the connection fails before we write the + # query to its stdin. This test gets a lot of connection failures + # on purpose. + my ($ret, $stdout, $stderr) = $node->psql( + 'postgres', + '', + extra_params => ['-w', '-c', 'SELECT current_enc()'], + connstr => "$connstr_full", + on_error_stop => 0); + + my $outcome = $ret == 0 ? $stdout : 'fail'; + + # Parse the EVENTS from the log file. + my $log_contents = + PostgreSQL::Test::Utils::slurp_file($node->logfile, $log_location); + my @events = parse_log_events($log_contents); + + # Check that the events and outcome match the expected events and + # outcome + my $events_and_outcome = join(', ', @events) . " -> $outcome"; + is($events_and_outcome, $expected_events_and_outcome, $test_name) or diag("$stderr"); +} + +# Parse a test table. See comment at top of the file for the format. +sub parse_table +{ + my ($table) = @_; + my @lines = split /\n/, $table; + + my %expected; + + my ($user, $gssencmode, $sslmode, $sslnegotiation); + foreach my $line (@lines) { + + # Trim comments + $line =~ s/#.*$//; + + # Trim whitespace at beginning and end + $line =~ s/^\s+//; + $line =~ s/\s+$//; + + # Ignore empty lines (includes comment-only lines) + next if $line eq ''; + + $line =~ m/^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)\s*->\s*(\S+)\s*$/ or die "could not parse line \"$line\""; + $user = $1 unless $1 eq "."; + $gssencmode = $2 unless $2 eq "."; + $sslmode = $3 unless $3 eq "."; + $sslnegotiation = $4 unless $4 eq "."; + + # Normalize the whitespace in the "EVENTS -> OUTCOME" part + my @events = split /,\s*/, $5; + my $outcome = $6; + my $events_str = join(', ', @events); + $events_str =~ s/\s+$//; # trim whitespace + my $events_and_outcome = "$events_str -> $outcome"; + + my %expanded = expand_expected_line($user, $gssencmode, $sslmode, $sslnegotiation, $events_and_outcome); + %expected = (%expected, %expanded); + } + return %expected; +} + +# Expand wildcards on a test table line +sub expand_expected_line +{ + my ($user, $gssencmode, $sslmode, $sslnegotiation, $expected) = @_; + + my %result; + if ($user eq '*') { + foreach my $x (@all_test_users) { + %result = (%result, expand_expected_line($x, $gssencmode, $sslmode, $sslnegotiation, $expected)); + } + } elsif ($gssencmode eq '*') { + foreach my $x (@all_gssencmodes) { + %result = (%result, expand_expected_line($user, $x, $sslmode, $sslnegotiation, $expected)); + } + } elsif ($sslmode eq '*') { + foreach my $x (@all_sslmodes) { + %result = (%result, expand_expected_line($user, $gssencmode, $x, $sslnegotiation, $expected)); + } + } elsif ($sslnegotiation eq '*') { + foreach my $x (@all_sslnegotiations) { + %result = (%result, expand_expected_line($user, $gssencmode, $sslmode, $x, $expected)); + } + } else { + $result{"$user $gssencmode $sslmode $sslnegotiation"} = $expected; + } + return %result; +} + +# Scrape the server log for the negotiation events that match the +# EVENTS field of the test tables. +sub parse_log_events +{ + my ($log_contents) = (@_); + + my @events = (); + + my @lines = split /\n/, $log_contents; + foreach my $line (@lines) { + push @events, "reconnect" if $line =~ /connection received/ && scalar(@events) > 0; + push @events, "connect" if $line =~ /connection received/ && scalar(@events) == 0; + push @events, "sslaccept" if $line =~ /SSLRequest accepted/; + push @events, "sslreject" if $line =~ /SSLRequest rejected/; + push @events, "directsslaccept" if $line =~ /direct SSL connection accepted/; + push @events, "directsslreject" if $line =~ /direct SSL connection rejected/; + push @events, "gssaccept" if $line =~ /GSSENCRequest accepted/; + push @events, "gssreject" if $line =~ /GSSENCRequest rejected/; + push @events, "authfail" if $line =~ /no pg_hba.conf entry/; + push @events, "authok" if $line =~ /connection authenticated/; + } + + # No events at all is represented by "-" + if (scalar @events == 0) { + push @events, "-" + } + + return @events; +} diff --git a/src/test/libpq_encryption/Makefile b/src/test/libpq_encryption/Makefile deleted file mode 100644 index 3ad3da7031b..00000000000 --- a/src/test/libpq_encryption/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -#------------------------------------------------------------------------- -# -# Makefile for src/test/libpq_encryption -# -# Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group -# Portions Copyright (c) 1994, Regents of the University of California -# -# src/test/libpq_encryption/Makefile -# -#------------------------------------------------------------------------- - -subdir = src/test/libpq_encryption -top_builddir = ../../.. -include $(top_builddir)/src/Makefile.global - -export with_ssl with_gssapi with_krb_srvnam - -check: - $(prove_check) - -installcheck: - $(prove_installcheck) - -clean distclean: - rm -rf tmp_check diff --git a/src/test/libpq_encryption/README b/src/test/libpq_encryption/README deleted file mode 100644 index 8ceb1945277..00000000000 --- a/src/test/libpq_encryption/README +++ /dev/null @@ -1,31 +0,0 @@ -src/test/libpq_encryption/README - -Tests for negotiating network encryption method -=============================================== - -This directory contains a test suite for the libpq options to -negotiate encryption with the server. This requires reconfiguring a -test server, enabling/disabling SSL and GSSAPI, and is therefore kept -separate and not run by default. - -CAUTION: The test server run by this test is configured to listen for TCP -connections on localhost. Any user on the same host is able to log in to the -test server while the tests are running. Do not run this suite on a multi-user -system where you don't trust all local users! Also, this test suite creates a -KDC server that listens for TCP/IP connections on localhost without any real -access control. - -Running the tests -================= - -NOTE: You must have given the --enable-tap-tests argument to configure. - -Run - make check PG_TEST_EXTRA=libpq_encryption - -You can use "make installcheck" if you previously did "make install". -In that case, the code in the installation tree is tested. With -"make check", a temporary installation tree is built from the current -sources and then tested. - -See src/test/perl/README for more info about running these tests. diff --git a/src/test/libpq_encryption/meson.build b/src/test/libpq_encryption/meson.build deleted file mode 100644 index ac1db10d742..00000000000 --- a/src/test/libpq_encryption/meson.build +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2022-2024, PostgreSQL Global Development Group - -tests += { - 'name': 'libpq_encryption', - 'sd': meson.current_source_dir(), - 'bd': meson.current_build_dir(), - 'tap': { - 'tests': [ - 't/001_negotiate_encryption.pl', - ], - 'env': { - 'with_ssl': ssl_library, - 'OPENSSL': openssl.found() ? openssl.path() : '', - 'with_gssapi': gssapi.found() ? 'yes' : 'no', - 'with_krb_srvnam': 'postgres', - }, - }, -} diff --git a/src/test/libpq_encryption/t/001_negotiate_encryption.pl b/src/test/libpq_encryption/t/001_negotiate_encryption.pl deleted file mode 100644 index d07d9498bbc..00000000000 --- a/src/test/libpq_encryption/t/001_negotiate_encryption.pl +++ /dev/null @@ -1,724 +0,0 @@ - -# Copyright (c) 2021-2024, PostgreSQL Global Development Group - -# OVERVIEW -# -------- -# -# Test negotiation of SSL and GSSAPI encryption -# -# We test all combinations of: -# -# - all the libpq client options that affect the protocol negotiations -# (gssencmode, sslmode, sslnegotiation) -# - server accepting or rejecting the authentication due to -# pg_hba.conf entries -# - SSL and GSS enabled/disabled in the server -# -# That's a lot of combinations, so we use a table-driven approach. -# Each combination is represented by a line in a table. The line lists -# the options specifying the test case, and an expected outcome. The -# expected outcome includes whether the connection succeeds or fails, -# and whether it uses SSL, GSS or no encryption. It also includes a -# condensed trace of what steps were taken during the negotiation. -# That can catch cases like useless retries, or if the encryption -# methods are attempted in wrong order, even when it doesn't affect -# the final outcome. -# -# TEST TABLE FORMAT -# ----------------- -# -# Example of the test table format: -# -# # USER GSSENCMODE SSLMODE EVENTS -> OUTCOME -# testuser disable allow connect, authok -> plain -# . . prefer connect, sslaccept, authok -> ssl -# testuser require * connect, gssreject -> fail -# -# USER, GSSENCMODE and SSLMODE fields are the libpq 'user', -# 'gssencmode' and 'sslmode' options used in the test. As a shorthand, -# a single dot ('.') can be used in the USER, GSSENCMODE, and SSLMODE -# fields, to indicate "same as on previous line". A '*' can be used -# as a wildcard; it is expanded to mean all possible values of that -# field. -# -# The EVENTS field is a condensed trace of expected steps during the -# negotiation: -# -# connect: a TCP connection was established -# reconnect: TCP connection was disconnected, and a new one was established -# sslaccept: client requested SSL encryption and server accepted it -# sslreject: client requested SSL encryption but server rejected it -# gssaccept: client requested GSSAPI encryption and server accepted it -# gssreject: client requested GSSAPI encryption but server rejected it -# authok: client sent startup packet and authentication was performed successfully -# authfail: client sent startup packet but server rejected the authentication -# -# The event trace can be used to verify that the client negotiated the -# connection properly in more detail than just by looking at the -# outcome. For example, if the client opens spurious extra TCP -# connections, that would show up in the EVENTS. -# -# The OUTCOME field indicates the expected result of the test: -# -# plain: an unencrypted connection was established -# ssl: SSL connection was established -# gss: GSSAPI encrypted connection was established -# fail: the connection attempt failed -# -# Empty lines are ignored. '#' can be used to mark the rest of the -# line as a comment. - -use strict; -use warnings FATAL => 'all'; -use PostgreSQL::Test::Utils; -use PostgreSQL::Test::Cluster; -use PostgreSQL::Test::Kerberos; -use File::Basename; -use File::Copy; -use Test::More; - -if (!$ENV{PG_TEST_EXTRA} || $ENV{PG_TEST_EXTRA} !~ /\blibpq_encryption\b/) -{ - plan skip_all => - 'Potentially unsafe test libpq_encryption not enabled in PG_TEST_EXTRA'; -} - -my $ssl_supported = $ENV{with_ssl} eq 'openssl'; -my $gss_supported = $ENV{with_gssapi} eq 'yes'; - -### -### Prepare test server for GSSAPI and SSL authentication, with a few -### different test users and helper functions. We don't actually -### enable SSL and kerberos in the server yet, we will do that later. -### - -my $host = 'enc-test-localhost.postgresql.example.com'; -my $hostaddr = '127.0.0.1'; -my $servercidr = '127.0.0.1/32'; - -my $node = PostgreSQL::Test::Cluster->new('node'); -$node->init; -$node->append_conf( - 'postgresql.conf', qq{ -listen_addresses = '$hostaddr' - -# Capturing the EVENTS that occur during tests requires these settings -log_connections = on -log_disconnections = on -trace_connection_negotiation = on -lc_messages = 'C' -}); -my $pgdata = $node->data_dir; - -my $dbname = 'postgres'; -my $username = 'enctest'; -my $application = '001_negotiate_encryption.pl'; - -my $gssuser_password = 'secret1'; - -my $krb; - -if ($gss_supported != 0) -{ - note "setting up Kerberos"; - - my $realm = 'EXAMPLE.COM'; - $krb = PostgreSQL::Test::Kerberos->new($host, $hostaddr, $realm); - $node->append_conf('postgresql.conf', "krb_server_keyfile = '$krb->{keytab}'\n"); -} - -if ($ssl_supported != 0) -{ - my $certdir = dirname(__FILE__) . "/../../ssl/ssl"; - - copy "$certdir/server-cn-only.crt", "$pgdata/server.crt" - || die "copying server.crt: $!"; - copy "$certdir/server-cn-only.key", "$pgdata/server.key" - || die "copying server.key: $!"; - chmod(0600, "$pgdata/server.key") - or die "failed to change permissions on server keys: $!"; - - # Start with SSL disabled. - $node->append_conf('postgresql.conf', "ssl = off\n"); -} - -$node->start; - -$node->safe_psql('postgres', 'CREATE USER localuser;'); -$node->safe_psql('postgres', 'CREATE USER testuser;'); -$node->safe_psql('postgres', 'CREATE USER ssluser;'); -$node->safe_psql('postgres', 'CREATE USER nossluser;'); -$node->safe_psql('postgres', 'CREATE USER gssuser;'); -$node->safe_psql('postgres', 'CREATE USER nogssuser;'); - -my $unixdir = $node->safe_psql('postgres', 'SHOW unix_socket_directories;'); -chomp($unixdir); - -# Helper function that returns the encryption method in use in the -# connection. -$node->safe_psql('postgres', q{ -CREATE FUNCTION current_enc() RETURNS text LANGUAGE plpgsql AS $$ -DECLARE - ssl_in_use bool; - gss_in_use bool; -BEGIN - ssl_in_use = (SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid()); - gss_in_use = (SELECT encrypted FROM pg_stat_gssapi WHERE pid = pg_backend_pid()); - - raise log 'ssl % gss %', ssl_in_use, gss_in_use; - - IF ssl_in_use AND gss_in_use THEN - RETURN 'ssl+gss'; -- shouldn't happen - ELSIF ssl_in_use THEN - RETURN 'ssl'; - ELSIF gss_in_use THEN - RETURN 'gss'; - ELSE - RETURN 'plain'; - END IF; -END; -$$; -}); - -# Only accept SSL connections from $servercidr. Our tests don't depend on this -# but seems best to keep it as narrow as possible for security reasons. -open my $hba, '>', "$pgdata/pg_hba.conf" or die $!; -print $hba qq{ -# TYPE DATABASE USER ADDRESS METHOD OPTIONS -local postgres localuser trust -host postgres testuser $servercidr trust -hostnossl postgres nossluser $servercidr trust -hostnogssenc postgres nogssuser $servercidr trust -}; - -print $hba qq{ -hostssl postgres ssluser $servercidr trust -} if ($ssl_supported != 0); - -print $hba qq{ -hostgssenc postgres gssuser $servercidr trust -} if ($gss_supported != 0); -close $hba; -$node->reload; - -# Ok, all prepared. Run the tests. - -my @all_test_users = ('testuser', 'ssluser', 'nossluser', 'gssuser', 'nogssuser'); -my @all_gssencmodes = ('disable', 'prefer', 'require'); -my @all_sslmodes = ('disable', 'allow', 'prefer', 'require'); -my @all_sslnegotiations = ('postgres', 'direct', 'requiredirect'); - -my $server_config = { - server_ssl => 0, - server_gss => 0, -}; - -### -### Run tests with GSS and SSL disabled in the server -### -my $test_table; -if ($ssl_supported) { - $test_table = q{ -# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME -testuser disable disable * connect, authok -> plain -. . allow * connect, authok -> plain -. . prefer postgres connect, sslreject, authok -> plain -. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain -. . . requiredirect connect, directsslreject, reconnect, authok -> plain -. . require postgres connect, sslreject -> fail -. . . direct connect, directsslreject, reconnect, sslreject -> fail -. . . requiredirect connect, directsslreject -> fail -. prefer disable * connect, authok -> plain -. . allow * connect, authok -> plain -. . prefer postgres connect, sslreject, authok -> plain -. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain -. . . requiredirect connect, directsslreject, reconnect, authok -> plain -. . require postgres connect, sslreject -> fail -. . . direct connect, directsslreject, reconnect, sslreject -> fail -. . . requiredirect connect, directsslreject -> fail - }; -} else { - # Compiled without SSL support - $test_table = q{ -# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME -testuser disable disable postgres connect, authok -> plain -. . allow postgres connect, authok -> plain -. . prefer postgres connect, authok -> plain -. prefer disable postgres connect, authok -> plain -. . allow postgres connect, authok -> plain -. . prefer postgres connect, authok -> plain - -# Without SSL support, sslmode=require and sslnegotiation=direct/requiredirect -# are not accepted at all. -. * require * - -> fail -. * * direct - -> fail -. * * requiredirect - -> fail - }; -} - -# All attempts with gssencmode=require fail without connecting because -# no credential cache has been configured in the client. (Or if GSS -# support is not compiled in, they will fail because of that.) -$test_table .= q{ -testuser require * * - -> fail -}; - -note("Running tests with SSL and GSS disabled in the server"); -test_matrix($node, $server_config, - ['testuser'], \@all_gssencmodes, \@all_sslmodes, \@all_sslnegotiations, - parse_table($test_table)); - - -### -### Run tests with GSS disabled and SSL enabled in the server -### -SKIP: -{ - skip "SSL not supported by this build" if $ssl_supported == 0; - - $test_table = q{ -# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME -testuser disable disable * connect, authok -> plain -. . allow * connect, authok -> plain -. . prefer postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. . require postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -ssluser . disable * connect, authfail -> fail -. . allow postgres connect, authfail, reconnect, sslaccept, authok -> ssl -. . . direct connect, authfail, reconnect, directsslaccept, authok -> ssl -. . . requiredirect connect, authfail, reconnect, directsslaccept, authok -> ssl -. . prefer postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. . require postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -nossluser . disable * connect, authok -> plain -. . allow postgres connect, authok -> plain -. . . direct connect, authok -> plain -. . . requiredirect connect, authok -> plain -. . prefer postgres connect, sslaccept, authfail, reconnect, authok -> plain -. . . direct connect, directsslaccept, authfail, reconnect, authok -> plain -. . . requiredirect connect, directsslaccept, authfail, reconnect, authok -> plain -. . require postgres connect, sslaccept, authfail -> fail -. . require direct connect, directsslaccept, authfail -> fail -. . require requiredirect connect, directsslaccept, authfail -> fail -}; - - # Enable SSL in the server - $node->adjust_conf('postgresql.conf', 'ssl', 'on'); - $node->reload; - $server_config->{server_ssl} = 1; - - note("Running tests with SSL enabled in server"); - test_matrix($node, $server_config, - ['testuser', 'ssluser', 'nossluser'], - ['disable'], \@all_sslmodes, \@all_sslnegotiations, - parse_table($test_table)); - - # Disable SSL again - $node->adjust_conf('postgresql.conf', 'ssl', 'off'); - $node->reload; - $server_config->{server_ssl} = 0; -} - -### -### Run tests with GSS enabled, SSL disabled in the server -### -SKIP: -{ - skip "GSSAPI/Kerberos not supported by this build" if $gss_supported == 0; - - $krb->create_principal('gssuser', $gssuser_password); - $krb->create_ticket('gssuser', $gssuser_password); - $server_config->{server_gss} = 1; - - $test_table = q{ -# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME -testuser disable disable * connect, authok -> plain -. . allow * connect, authok -> plain -. . prefer postgres connect, sslreject, authok -> plain -. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain -. . . requiredirect connect, directsslreject, reconnect, authok -> plain -. . require postgres connect, sslreject -> fail -. . . direct connect, directsslreject, reconnect, sslreject -> fail -. . . requiredirect connect, directsslreject -> fail -. prefer * * connect, gssaccept, authok -> gss -. require * * connect, gssaccept, authok -> gss - -gssuser disable disable * connect, authfail -> fail -. . allow postgres connect, authfail, reconnect, sslreject -> fail -. . . direct connect, authfail, reconnect, directsslreject, reconnect, sslreject -> fail -. . . requiredirect connect, authfail, reconnect, directsslreject -> fail -. . prefer postgres connect, sslreject, authfail -> fail -. . . direct connect, directsslreject, reconnect, sslreject, authfail -> fail -. . . requiredirect connect, directsslreject, reconnect, authfail -> fail -. . require postgres connect, sslreject -> fail -. . . direct connect, directsslreject, reconnect, sslreject -> fail -. . . requiredirect connect, directsslreject -> fail -. prefer * * connect, gssaccept, authok -> gss -. require * * connect, gssaccept, authok -> gss - -nogssuser disable disable * connect, authok -> plain -. . allow postgres connect, authok -> plain -. . . direct connect, authok -> plain -. . . requiredirect connect, authok -> plain -. . prefer postgres connect, sslreject, authok -> plain -. . . direct connect, directsslreject, reconnect, sslreject, authok -> plain -. . . requiredirect connect, directsslreject, reconnect, authok -> plain -. . require postgres connect, sslreject -> fail -. . . direct connect, directsslreject, reconnect, sslreject -> fail -. . . requiredirect connect, directsslreject -> fail -. prefer disable * connect, gssaccept, authfail, reconnect, authok -> plain -. . allow postgres connect, gssaccept, authfail, reconnect, authok -> plain -. . . direct connect, gssaccept, authfail, reconnect, authok -> plain -. . . requiredirect connect, gssaccept, authfail, reconnect, authok -> plain -. . prefer postgres connect, gssaccept, authfail, reconnect, sslreject, authok -> plain -. . . direct connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject, authok -> plain -. . . requiredirect connect, gssaccept, authfail, reconnect, directsslreject, reconnect, authok -> plain -. . require postgres connect, gssaccept, authfail, reconnect, sslreject -> fail -. . . direct connect, gssaccept, authfail, reconnect, directsslreject, reconnect, sslreject -> fail -. . . requiredirect connect, gssaccept, authfail, reconnect, directsslreject -> fail -. require disable * connect, gssaccept, authfail -> fail -. . allow * connect, gssaccept, authfail -> fail -. . prefer * connect, gssaccept, authfail -> fail -. . require * connect, gssaccept, authfail -> fail # If both GSSAPI and sslmode are required, and GSS is not available -> fail - }; - - # The expected events and outcomes above assume that SSL support - # is enabled. When libpq is compiled without SSL support, all - # attempts to connect with sslmode=require or - # sslnegotition=direct/requiredirectwould fail immediately without - # even connecting to the server. Skip those, because we tested - # them earlier already. - my ($sslmodes, $sslnegotiations); - if ($ssl_supported != 0) { - ($sslmodes, $sslnegotiations) = (\@all_sslmodes, \@all_sslnegotiations); - } else { - ($sslmodes, $sslnegotiations) = (['disable'], ['postgres']); - } - - note("Running tests with GSS enabled in server"); - test_matrix($node, $server_config, - ['testuser', 'gssuser', 'nogssuser'], - \@all_gssencmodes, $sslmodes, $sslnegotiations, - parse_table($test_table)); -} - -### -### Tests with both GSS and SSL enabled in the server -### -SKIP: -{ - skip "GSSAPI/Kerberos or SSL not supported by this build" unless ($ssl_supported && $gss_supported); - - # Sanity check that GSSAPI is still enabled from previous test. - connect_test($node, 'user=testuser gssencmode=prefer sslmode=prefer', 'connect, gssaccept, authok -> gss'); - - # Enable SSL - $node->adjust_conf('postgresql.conf', 'ssl', 'on'); - $node->reload; - $server_config->{server_ssl} = 1; - - $test_table = q{ -# USER GSSENCMODE SSLMODE SSLNEGOTIATION EVENTS -> OUTCOME -testuser disable disable * connect, authok -> plain -. . allow * connect, authok -> plain -. . prefer postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. . require postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. prefer disable * connect, gssaccept, authok -> gss -. . allow * connect, gssaccept, authok -> gss -. . prefer * connect, gssaccept, authok -> gss -. . require * connect, gssaccept, authok -> gss # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require -. require disable * connect, gssaccept, authok -> gss -. . allow * connect, gssaccept, authok -> gss -. . prefer * connect, gssaccept, authok -> gss -. . require * connect, gssaccept, authok -> gss # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require - -gssuser disable disable * connect, authfail -> fail -. . allow postgres connect, authfail, reconnect, sslaccept, authfail -> fail -. . . direct connect, authfail, reconnect, directsslaccept, authfail -> fail -. . . requiredirect connect, authfail, reconnect, directsslaccept, authfail -> fail -. . prefer postgres connect, sslaccept, authfail, reconnect, authfail -> fail -. . . direct connect, directsslaccept, authfail, reconnect, authfail -> fail -. . . requiredirect connect, directsslaccept, authfail, reconnect, authfail -> fail -. . require postgres connect, sslaccept, authfail -> fail -. . . direct connect, directsslaccept, authfail -> fail -. . . requiredirect connect, directsslaccept, authfail -> fail -. prefer disable * connect, gssaccept, authok -> gss -. . allow * connect, gssaccept, authok -> gss -. . prefer * connect, gssaccept, authok -> gss -. . require * connect, gssaccept, authok -> gss # GSS is chosen over SSL, even though sslmode=require -. require disable * connect, gssaccept, authok -> gss -. . allow * connect, gssaccept, authok -> gss -. . prefer * connect, gssaccept, authok -> gss -. . require * connect, gssaccept, authok -> gss # If both GSS and SSL is possible, GSS is chosen over SSL, even if sslmode=require - -ssluser disable disable * connect, authfail -> fail -. . allow postgres connect, authfail, reconnect, sslaccept, authok -> ssl -. . . direct connect, authfail, reconnect, directsslaccept, authok -> ssl -. . . requiredirect connect, authfail, reconnect, directsslaccept, authok -> ssl -. . prefer postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. . require postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. prefer disable * connect, gssaccept, authfail, reconnect, authfail -> fail -. . allow postgres connect, gssaccept, authfail, reconnect, authfail, reconnect, sslaccept, authok -> ssl -. . . direct connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl -. . . requiredirect connect, gssaccept, authfail, reconnect, authfail, reconnect, directsslaccept, authok -> ssl -. . prefer postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl -. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. . require postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl -. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. require disable * connect, gssaccept, authfail -> fail -. . allow * connect, gssaccept, authfail -> fail -. . prefer * connect, gssaccept, authfail -> fail -. . require * connect, gssaccept, authfail -> fail # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required - -nogssuser disable disable * connect, authok -> plain -. . allow postgres connect, authok -> plain -. . . direct connect, authok -> plain -. . . requiredirect connect, authok -> plain -. . prefer postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. . require postgres connect, sslaccept, authok -> ssl -. . . direct connect, directsslaccept, authok -> ssl -. . . requiredirect connect, directsslaccept, authok -> ssl -. prefer disable * connect, gssaccept, authfail, reconnect, authok -> plain -. . allow * connect, gssaccept, authfail, reconnect, authok -> plain -. . prefer postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl -. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. . require postgres connect, gssaccept, authfail, reconnect, sslaccept, authok -> ssl -. . . direct connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. . . requiredirect connect, gssaccept, authfail, reconnect, directsslaccept, authok -> ssl -. require disable * connect, gssaccept, authfail -> fail -. . allow * connect, gssaccept, authfail -> fail -. . prefer * connect, gssaccept, authfail -> fail -. . require * connect, gssaccept, authfail -> fail # If both GSS and SSL are required, the sslmode=require is effectively ignored and GSS is required - -nossluser disable disable * connect, authok -> plain -. . allow * connect, authok -> plain -. . prefer postgres connect, sslaccept, authfail, reconnect, authok -> plain -. . . direct connect, directsslaccept, authfail, reconnect, authok -> plain -. . . requiredirect connect, directsslaccept, authfail, reconnect, authok -> plain -. . require postgres connect, sslaccept, authfail -> fail -. . . direct connect, directsslaccept, authfail -> fail -. . . requiredirect connect, directsslaccept, authfail -> fail -. prefer * * connect, gssaccept, authok -> gss -. require * * connect, gssaccept, authok -> gss - }; - - note("Running tests with both GSS and SSL enabled in server"); - test_matrix($node, $server_config, - ['testuser', 'gssuser', 'ssluser', 'nogssuser', 'nossluser'], - \@all_gssencmodes, \@all_sslmodes, \@all_sslnegotiations, - parse_table($test_table)); -} - -### -### Test negotiation over unix domain sockets. -### -SKIP: -{ - skip "Unix domain sockets not supported" unless ($unixdir ne ""); - - # libpq doesn't attempt SSL or GSSAPI over Unix domain - # sockets. The server would reject them too. - connect_test($node, "user=localuser gssencmode=prefer sslmode=prefer host=$unixdir", 'connect, authok -> plain'); - connect_test($node, "user=localuser gssencmode=require sslmode=prefer host=$unixdir", '- -> fail'); -} - -done_testing(); - - -### Helper functions - -# Test the cube of parameters: user, gssencmode, sslmode, and sslnegotitation -sub test_matrix -{ - local $Test::Builder::Level = $Test::Builder::Level + 1; - - my ($pg_node, $node_conf, - $test_users, $gssencmodes, $sslmodes, $sslnegotiations, %expected) = @_; - - foreach my $test_user (@{$test_users}) - { - foreach my $gssencmode (@{$gssencmodes}) - { - foreach my $client_mode (@{$sslmodes}) - { - # sslnegotiation only makes a difference if SSL is enabled. This saves a few combinations. - my ($key, $expected_events); - foreach my $negotiation (@{$sslnegotiations}) - { - $key = "$test_user $gssencmode $client_mode $negotiation"; - $expected_events = $expected{$key}; - if (!defined($expected_events)) { - $expected_events = ""; - } - connect_test($pg_node, "user=$test_user gssencmode=$gssencmode sslmode=$client_mode sslnegotiation=$negotiation", $expected_events); - } - } - } - } -} - -# Try to establish a connection to the server using libpq. Verify the -# negotiation events and outcome. -sub connect_test -{ - local $Test::Builder::Level = $Test::Builder::Level + 1; - - my ($node, $connstr, $expected_events_and_outcome) = @_; - - my $test_name = " '$connstr' -> $expected_events_and_outcome"; - - my $connstr_full = ""; - $connstr_full .= "dbname=postgres " unless $connstr =~ m/dbname=/; - $connstr_full .= "host=$host hostaddr=$hostaddr " unless $connstr =~ m/host=/; - $connstr_full .= $connstr; - - # Get the current size of the logfile before running the test. - # After the test, we can then check just the new lines that have - # appeared. (This is the same approach that the $node->log_contains - # function uses). - my $log_location = -s $node->logfile; - - # XXX: Pass command with -c, because I saw intermittent test - # failures like this: - # - # ack Broken pipe: write( 13, 'SELECT current_enc()' ) at /usr/local/lib/perl5/site_perl/IPC/Run/IO.pm line 550. - # - # I think that happens if the connection fails before we write the - # query to its stdin. This test gets a lot of connection failures - # on purpose. - my ($ret, $stdout, $stderr) = $node->psql( - 'postgres', - '', - extra_params => ['-w', '-c', 'SELECT current_enc()'], - connstr => "$connstr_full", - on_error_stop => 0); - - my $outcome = $ret == 0 ? $stdout : 'fail'; - - # Parse the EVENTS from the log file. - my $log_contents = - PostgreSQL::Test::Utils::slurp_file($node->logfile, $log_location); - my @events = parse_log_events($log_contents); - - # Check that the events and outcome match the expected events and - # outcome - my $events_and_outcome = join(', ', @events) . " -> $outcome"; - is($events_and_outcome, $expected_events_and_outcome, $test_name) or diag("$stderr"); -} - -# Parse a test table. See comment at top of the file for the format. -sub parse_table -{ - my ($table) = @_; - my @lines = split /\n/, $table; - - my %expected; - - my ($user, $gssencmode, $sslmode, $sslnegotiation); - foreach my $line (@lines) { - - # Trim comments - $line =~ s/#.*$//; - - # Trim whitespace at beginning and end - $line =~ s/^\s+//; - $line =~ s/\s+$//; - - # Ignore empty lines (includes comment-only lines) - next if $line eq ''; - - $line =~ m/^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)\s*->\s*(\S+)\s*$/ or die "could not parse line \"$line\""; - $user = $1 unless $1 eq "."; - $gssencmode = $2 unless $2 eq "."; - $sslmode = $3 unless $3 eq "."; - $sslnegotiation = $4 unless $4 eq "."; - - # Normalize the whitespace in the "EVENTS -> OUTCOME" part - my @events = split /,\s*/, $5; - my $outcome = $6; - my $events_str = join(', ', @events); - $events_str =~ s/\s+$//; # trim whitespace - my $events_and_outcome = "$events_str -> $outcome"; - - my %expanded = expand_expected_line($user, $gssencmode, $sslmode, $sslnegotiation, $events_and_outcome); - %expected = (%expected, %expanded); - } - return %expected; -} - -# Expand wildcards on a test table line -sub expand_expected_line -{ - my ($user, $gssencmode, $sslmode, $sslnegotiation, $expected) = @_; - - my %result; - if ($user eq '*') { - foreach my $x (@all_test_users) { - %result = (%result, expand_expected_line($x, $gssencmode, $sslmode, $sslnegotiation, $expected)); - } - } elsif ($gssencmode eq '*') { - foreach my $x (@all_gssencmodes) { - %result = (%result, expand_expected_line($user, $x, $sslmode, $sslnegotiation, $expected)); - } - } elsif ($sslmode eq '*') { - foreach my $x (@all_sslmodes) { - %result = (%result, expand_expected_line($user, $gssencmode, $x, $sslnegotiation, $expected)); - } - } elsif ($sslnegotiation eq '*') { - foreach my $x (@all_sslnegotiations) { - %result = (%result, expand_expected_line($user, $gssencmode, $sslmode, $x, $expected)); - } - } else { - $result{"$user $gssencmode $sslmode $sslnegotiation"} = $expected; - } - return %result; -} - -# Scrape the server log for the negotiation events that match the -# EVENTS field of the test tables. -sub parse_log_events -{ - my ($log_contents) = (@_); - - my @events = (); - - my @lines = split /\n/, $log_contents; - foreach my $line (@lines) { - push @events, "reconnect" if $line =~ /connection received/ && scalar(@events) > 0; - push @events, "connect" if $line =~ /connection received/ && scalar(@events) == 0; - push @events, "sslaccept" if $line =~ /SSLRequest accepted/; - push @events, "sslreject" if $line =~ /SSLRequest rejected/; - push @events, "directsslaccept" if $line =~ /direct SSL connection accepted/; - push @events, "directsslreject" if $line =~ /direct SSL connection rejected/; - push @events, "gssaccept" if $line =~ /GSSENCRequest accepted/; - push @events, "gssreject" if $line =~ /GSSENCRequest rejected/; - push @events, "authfail" if $line =~ /no pg_hba.conf entry/; - push @events, "authok" if $line =~ /connection authenticated/; - } - - # No events at all is represented by "-" - if (scalar @events == 0) { - push @events, "-" - } - - return @events; -} diff --git a/src/test/meson.build b/src/test/meson.build index 702213bc6f6..c3d0dfedf1c 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -4,7 +4,6 @@ subdir('regress') subdir('isolation') subdir('authentication') -subdir('libpq_encryption') subdir('recovery') subdir('subscription') subdir('modules') -- cgit v1.2.3