mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
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
725 lines
36 KiB
Perl
725 lines
36 KiB
Perl
|
|
# 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 = "<line missing from expected output table>";
|
|
}
|
|
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;
|
|
}
|