mirror of
https://github.com/postgres/postgres.git
synced 2025-05-01 01:04:50 +03:00
SSL TAP test backend library independence refactoring
The SSL TAP tests were tightly coupled to the OpenSSL implementation, making it hard to add support for additional SSL/TLS backends. This refactoring makes the test avoid depending on specific implementations The SSLServer Perl module is renamed SSL::Server, which in turn use SSL::Backend::X where X is the backend pointed to by with_ssl. Each backend will implement its own module responsible for setting up keys, certs and to resolve sslkey values to their implementation specific value (file paths or vault nicknames etc). Further, switch_server_cert now takes a set of named parameters rather than a fixed set which used defaults. The modules also come with POD documentation. There are a few testcases which still use OpenSSL specifics, but it's not entirely clear how to abstract those until we have another library implemented. Original patch by me, with lots of rework by Andrew Dunstan to turn it into better Perl. Discussion: https://postgr.es/m/AA18A362-CA65-4F9A-AF61-76AE318FE97C@yesql.se
This commit is contained in:
parent
e07d4ddc55
commit
4a7e964fc6
@ -8,18 +8,25 @@ use PostgreSQL::Test::Cluster;
|
||||
use PostgreSQL::Test::Utils;
|
||||
use Test::More;
|
||||
|
||||
use File::Copy;
|
||||
|
||||
use FindBin;
|
||||
use lib $FindBin::RealBin;
|
||||
|
||||
use SSLServer;
|
||||
use SSL::Server;
|
||||
|
||||
if ($ENV{with_ssl} ne 'openssl')
|
||||
{
|
||||
plan skip_all => 'OpenSSL not supported by this build';
|
||||
}
|
||||
|
||||
my $ssl_server = SSL::Server->new();
|
||||
sub sslkey
|
||||
{
|
||||
return $ssl_server->sslkey(@_);
|
||||
}
|
||||
sub switch_server_cert
|
||||
{
|
||||
$ssl_server->switch_server_cert(@_);
|
||||
}
|
||||
#### Some configuration
|
||||
|
||||
# This is the hostname used to connect to the server. This cannot be a
|
||||
@ -32,39 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
|
||||
# Allocation of base connection string shared among multiple tests.
|
||||
my $common_connstr;
|
||||
|
||||
# The client's private key must not be world-readable, so take a copy
|
||||
# of the key stored in the code tree and update its permissions.
|
||||
#
|
||||
# This changes to using keys stored in a temporary path for the rest of
|
||||
# the tests. To get the full path for inclusion in connection strings, the
|
||||
# %key hash can be interrogated.
|
||||
my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
|
||||
my %key;
|
||||
my @keys = (
|
||||
"client.key", "client-revoked.key",
|
||||
"client-der.key", "client-encrypted-pem.key",
|
||||
"client-encrypted-der.key", "client-dn.key");
|
||||
foreach my $keyfile (@keys)
|
||||
{
|
||||
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
|
||||
or die
|
||||
"couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
|
||||
chmod 0600, "$cert_tempdir/$keyfile"
|
||||
or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
|
||||
$key{$keyfile} = "$cert_tempdir/$keyfile";
|
||||
$key{$keyfile} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
|
||||
}
|
||||
|
||||
# Also make a copy of that explicitly world-readable. We can't
|
||||
# necessarily rely on the file in the source tree having those
|
||||
# permissions.
|
||||
copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
|
||||
or die
|
||||
"couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
|
||||
chmod 0644, "$cert_tempdir/client_wrongperms.key"
|
||||
or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
|
||||
$key{'client_wrongperms.key'} = "$cert_tempdir/client_wrongperms.key";
|
||||
$key{'client_wrongperms.key'} =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
|
||||
#### Set up the server.
|
||||
|
||||
note "setting up data directory";
|
||||
@ -79,31 +53,31 @@ $node->start;
|
||||
|
||||
# Run this before we lock down access below.
|
||||
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
|
||||
is($result, 'OpenSSL', 'ssl_library parameter');
|
||||
is($result, $ssl_server->ssl_library(), 'ssl_library parameter');
|
||||
|
||||
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
|
||||
'trust');
|
||||
$ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR,
|
||||
$SERVERHOSTCIDR, 'trust');
|
||||
|
||||
note "testing password-protected keys";
|
||||
|
||||
open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
|
||||
print $sslconf "ssl=on\n";
|
||||
print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
|
||||
print $sslconf "ssl_key_file='server-password.key'\n";
|
||||
print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
|
||||
close $sslconf;
|
||||
switch_server_cert($node,
|
||||
certfile => 'server-cn-only',
|
||||
cafile => 'root+client_ca',
|
||||
keyfile => 'server-password',
|
||||
passphrase_cmd => 'echo wrongpassword',
|
||||
restart => 'no' );
|
||||
|
||||
command_fails(
|
||||
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
|
||||
'restart fails with password-protected key file with wrong password');
|
||||
$node->_update_pid(0);
|
||||
|
||||
open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
|
||||
print $sslconf "ssl=on\n";
|
||||
print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
|
||||
print $sslconf "ssl_key_file='server-password.key'\n";
|
||||
print $sslconf "ssl_passphrase_command='echo secret1'\n";
|
||||
close $sslconf;
|
||||
switch_server_cert($node,
|
||||
certfile => 'server-cn-only',
|
||||
cafile => 'root+client_ca',
|
||||
keyfile => 'server-password',
|
||||
passphrase_cmd => 'echo secret1',
|
||||
restart => 'no');
|
||||
|
||||
command_ok(
|
||||
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
|
||||
@ -136,7 +110,7 @@ command_ok(
|
||||
|
||||
note "running client tests";
|
||||
|
||||
switch_server_cert($node, 'server-cn-only');
|
||||
switch_server_cert($node, certfile => 'server-cn-only');
|
||||
|
||||
# Set of default settings for SSL parameters in connection string. This
|
||||
# makes the tests protected against any defaults the environment may have
|
||||
@ -256,7 +230,7 @@ $node->connect_fails(
|
||||
);
|
||||
|
||||
# Test Subject Alternative Names.
|
||||
switch_server_cert($node, 'server-multiple-alt-names');
|
||||
switch_server_cert($node, certfile => 'server-multiple-alt-names');
|
||||
|
||||
$common_connstr =
|
||||
"$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
|
||||
@ -285,7 +259,7 @@ $node->connect_fails(
|
||||
|
||||
# Test certificate with a single Subject Alternative Name. (this gives a
|
||||
# slightly different error message, that's all)
|
||||
switch_server_cert($node, 'server-single-alt-name');
|
||||
switch_server_cert($node, certfile => 'server-single-alt-name');
|
||||
|
||||
$common_connstr =
|
||||
"$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
|
||||
@ -309,7 +283,7 @@ $node->connect_fails(
|
||||
|
||||
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
|
||||
# should be ignored when the certificate has both.
|
||||
switch_server_cert($node, 'server-cn-and-alt-names');
|
||||
switch_server_cert($node, certfile => 'server-cn-and-alt-names');
|
||||
|
||||
$common_connstr =
|
||||
"$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full";
|
||||
@ -327,7 +301,7 @@ $node->connect_fails(
|
||||
|
||||
# Finally, test a server certificate that has no CN or SANs. Of course, that's
|
||||
# not a very sensible certificate, but libpq should handle it gracefully.
|
||||
switch_server_cert($node, 'server-no-names');
|
||||
switch_server_cert($node, certfile => 'server-no-names');
|
||||
$common_connstr =
|
||||
"$default_ssl_connstr user=ssltestuser dbname=trustdb sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR";
|
||||
|
||||
@ -342,7 +316,7 @@ $node->connect_fails(
|
||||
qr/could not get server's host name from server certificate/);
|
||||
|
||||
# Test that the CRL works
|
||||
switch_server_cert($node, 'server-revoked');
|
||||
switch_server_cert($node, certfile => 'server-revoked');
|
||||
|
||||
$common_connstr =
|
||||
"$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
|
||||
@ -410,34 +384,34 @@ $node->connect_fails(
|
||||
|
||||
# correct client cert in unencrypted PEM
|
||||
$node->connect_ok(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
|
||||
"certificate authorization succeeds with correct client cert in PEM format"
|
||||
);
|
||||
|
||||
# correct client cert in unencrypted DER
|
||||
$node->connect_ok(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-der.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-der.key'),
|
||||
"certificate authorization succeeds with correct client cert in DER format"
|
||||
);
|
||||
|
||||
# correct client cert in encrypted PEM
|
||||
$node->connect_ok(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='dUmmyP^#+'",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='dUmmyP^#+'",
|
||||
"certificate authorization succeeds with correct client cert in encrypted PEM format"
|
||||
);
|
||||
|
||||
# correct client cert in encrypted DER
|
||||
$node->connect_ok(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-der.key'} sslpassword='dUmmyP^#+'",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-der.key') . " sslpassword='dUmmyP^#+'",
|
||||
"certificate authorization succeeds with correct client cert in encrypted DER format"
|
||||
);
|
||||
|
||||
# correct client cert in encrypted PEM with wrong password
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword='wrong'",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword='wrong'",
|
||||
"certificate authorization fails with correct client cert and wrong password in encrypted PEM format",
|
||||
expected_stderr =>
|
||||
qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": bad decrypt\E!
|
||||
qr!private key file \".*client-encrypted-pem\.key\": bad decrypt!,
|
||||
);
|
||||
|
||||
|
||||
@ -445,7 +419,7 @@ $node->connect_fails(
|
||||
my $dn_connstr = "$common_connstr dbname=certdb_dn";
|
||||
|
||||
$node->connect_ok(
|
||||
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
|
||||
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
|
||||
"certificate authorization succeeds with DN mapping",
|
||||
log_like => [
|
||||
qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
|
||||
@ -455,14 +429,14 @@ $node->connect_ok(
|
||||
$dn_connstr = "$common_connstr dbname=certdb_dn_re";
|
||||
|
||||
$node->connect_ok(
|
||||
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
|
||||
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
|
||||
"certificate authorization succeeds with DN regex mapping");
|
||||
|
||||
# same thing but using explicit CN
|
||||
$dn_connstr = "$common_connstr dbname=certdb_cn";
|
||||
|
||||
$node->connect_ok(
|
||||
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=$key{'client-dn.key'}",
|
||||
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt " . sslkey('client-dn.key'),
|
||||
"certificate authorization succeeds with CN mapping",
|
||||
# the full DN should still be used as the authenticated identity
|
||||
log_like => [
|
||||
@ -480,18 +454,18 @@ TODO:
|
||||
|
||||
# correct client cert in encrypted PEM with empty password
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'} sslpassword=''",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key') . " sslpassword=''",
|
||||
"certificate authorization fails with correct client cert and empty password in encrypted PEM format",
|
||||
expected_stderr =>
|
||||
qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
|
||||
qr!private key file \".*client-encrypted-pem\.key\": processing error!
|
||||
);
|
||||
|
||||
# correct client cert in encrypted PEM with no password
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client-encrypted-pem.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client-encrypted-pem.key'),
|
||||
"certificate authorization fails with correct client cert and no password in encrypted PEM format",
|
||||
expected_stderr =>
|
||||
qr!\Qprivate key file "$key{'client-encrypted-pem.key'}": processing error\E!
|
||||
qr!private key file \".*client-encrypted-pem\.key\": processing error!
|
||||
);
|
||||
|
||||
}
|
||||
@ -534,12 +508,12 @@ command_like(
|
||||
'-P',
|
||||
'null=_null_',
|
||||
'-d',
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
|
||||
'-c',
|
||||
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
|
||||
],
|
||||
qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
|
||||
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/CN=ssltestuser,$serialno,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
|
||||
^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
|
||||
'pg_stat_ssl with client certificate');
|
||||
|
||||
# client key with wrong permissions
|
||||
@ -548,16 +522,16 @@ SKIP:
|
||||
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
|
||||
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client_wrongperms.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client_wrongperms.key'),
|
||||
"certificate authorization fails because of file permissions",
|
||||
expected_stderr =>
|
||||
qr!\Qprivate key file "$key{'client_wrongperms.key'}" has group or world access\E!
|
||||
qr!private key file \".*client_wrongperms\.key\" has group or world access!
|
||||
);
|
||||
}
|
||||
|
||||
# client cert belonging to another user
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
|
||||
"$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
|
||||
"certificate authorization fails with client cert belonging to another user",
|
||||
expected_stderr =>
|
||||
qr/certificate authentication failed for user "anotheruser"/,
|
||||
@ -567,7 +541,7 @@ $node->connect_fails(
|
||||
|
||||
# revoked client cert
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
|
||||
"certificate authorization fails with revoked client cert",
|
||||
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
|
||||
# revoked certificates should not authenticate the user
|
||||
@ -580,13 +554,13 @@ $common_connstr =
|
||||
"$default_ssl_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR host=localhost";
|
||||
|
||||
$node->connect_ok(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client.crt " . sslkey('client.key'),
|
||||
"auth_option clientcert=verify-full succeeds with matching username and Common Name",
|
||||
# verify-full does not provide authentication
|
||||
log_unlike => [qr/connection authenticated:/],);
|
||||
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
|
||||
"$common_connstr user=anotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
|
||||
"auth_option clientcert=verify-full fails with mismatching username and Common Name",
|
||||
expected_stderr =>
|
||||
qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,
|
||||
@ -596,15 +570,15 @@ $node->connect_fails(
|
||||
# Check that connecting with auth-option verify-ca in pg_hba :
|
||||
# works, when username doesn't match Common Name
|
||||
$node->connect_ok(
|
||||
"$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=$key{'client.key'}",
|
||||
"$common_connstr user=yetanotheruser sslcert=ssl/client.crt " . sslkey('client.key'),
|
||||
"auth_option clientcert=verify-ca succeeds with mismatching username and Common Name",
|
||||
# verify-full does not provide authentication
|
||||
log_unlike => [qr/connection authenticated:/],);
|
||||
|
||||
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
|
||||
switch_server_cert($node, 'server-cn-only', 'root_ca');
|
||||
switch_server_cert($node, certfile => 'server-cn-only', cafile => 'root_ca');
|
||||
$common_connstr =
|
||||
"$default_ssl_connstr user=ssltestuser dbname=certdb sslkey=$key{'client.key'} sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR host=localhost";
|
||||
"$default_ssl_connstr user=ssltestuser dbname=certdb " . sslkey('client.key') . " sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR host=localhost";
|
||||
|
||||
$node->connect_ok(
|
||||
"$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
|
||||
@ -615,12 +589,11 @@ $node->connect_fails(
|
||||
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
|
||||
|
||||
# test server-side CRL directory
|
||||
switch_server_cert($node, 'server-cn-only', undef, undef,
|
||||
'root+client-crldir');
|
||||
switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir');
|
||||
|
||||
# revoked client cert
|
||||
$node->connect_fails(
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=$key{'client-revoked.key'}",
|
||||
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
|
||||
"certificate authorization fails with revoked client cert with server-side CRL directory",
|
||||
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
|
||||
|
||||
|
@ -14,13 +14,24 @@ use File::Copy;
|
||||
use FindBin;
|
||||
use lib $FindBin::RealBin;
|
||||
|
||||
use SSLServer;
|
||||
use SSL::Server;
|
||||
|
||||
if ($ENV{with_ssl} ne 'openssl')
|
||||
{
|
||||
plan skip_all => 'OpenSSL not supported by this build';
|
||||
}
|
||||
|
||||
my $ssl_server = SSL::Server->new();
|
||||
sub sslkey
|
||||
{
|
||||
return $ssl_server->sslkey(@_);
|
||||
}
|
||||
sub switch_server_cert
|
||||
{
|
||||
$ssl_server->switch_server_cert(@_);
|
||||
}
|
||||
|
||||
|
||||
# This is the hostname used to connect to the server.
|
||||
my $SERVERHOSTADDR = '127.0.0.1';
|
||||
# This is the pattern to use in pg_hba.conf to match incoming connections.
|
||||
@ -46,9 +57,9 @@ $ENV{PGPORT} = $node->port;
|
||||
$node->start;
|
||||
|
||||
# Configure server for SSL connections, with password handling.
|
||||
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
|
||||
$ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
|
||||
"scram-sha-256", 'password' => "pass", 'password_enc' => "scram-sha-256");
|
||||
switch_server_cert($node, 'server-cn-only');
|
||||
switch_server_cert($node, certfile => 'server-cn-only');
|
||||
$ENV{PGPASSWORD} = "pass";
|
||||
$common_connstr =
|
||||
"dbname=trustdb sslmode=require sslcert=invalid sslrootcert=invalid hostaddr=$SERVERHOSTADDR host=localhost";
|
||||
|
@ -12,7 +12,7 @@ use File::Copy;
|
||||
use FindBin;
|
||||
use lib $FindBin::RealBin;
|
||||
|
||||
use SSLServer;
|
||||
use SSL::Server;
|
||||
|
||||
if ($ENV{with_ssl} ne 'openssl')
|
||||
{
|
||||
@ -20,6 +20,15 @@ if ($ENV{with_ssl} ne 'openssl')
|
||||
}
|
||||
|
||||
#### Some configuration
|
||||
my $ssl_server = SSL::Server->new();
|
||||
sub sslkey
|
||||
{
|
||||
return $ssl_server->sslkey(@_);
|
||||
}
|
||||
sub switch_server_cert
|
||||
{
|
||||
$ssl_server->switch_server_cert(@_);
|
||||
}
|
||||
|
||||
# This is the hostname used to connect to the server. This cannot be a
|
||||
# hostname, because the server certificate is always for the domain
|
||||
@ -31,17 +40,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
|
||||
# Allocation of base connection string shared among multiple tests.
|
||||
my $common_connstr;
|
||||
|
||||
# The client's private key must not be world-readable, so take a copy
|
||||
# of the key stored in the code tree and update its permissions.
|
||||
my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
|
||||
my $client_tmp_key = "$cert_tempdir/client_ext.key";
|
||||
copy("ssl/client_ext.key", "$cert_tempdir/client_ext.key")
|
||||
or die
|
||||
"couldn't copy ssl/client_ext.key to $cert_tempdir/client_ext.key for permissions change: $!";
|
||||
chmod 0600, "$cert_tempdir/client_ext.key"
|
||||
or die "failed to change permissions on $cert_tempdir/client_ext.key: $!";
|
||||
$client_tmp_key =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
|
||||
|
||||
#### Set up the server.
|
||||
|
||||
note "setting up data directory";
|
||||
@ -54,13 +52,13 @@ $ENV{PGHOST} = $node->host;
|
||||
$ENV{PGPORT} = $node->port;
|
||||
$node->start;
|
||||
|
||||
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
|
||||
$ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
|
||||
'trust', extensions => [ qw(sslinfo) ]);
|
||||
|
||||
# We aren't using any CRL's in this suite so we can keep using server-revoked
|
||||
# as server certificate for simple client.crt connection much like how the
|
||||
# 001 test does.
|
||||
switch_server_cert($node, 'server-revoked');
|
||||
switch_server_cert($node, certfile => 'server-revoked');
|
||||
|
||||
# Set of default settings for SSL parameters in connection string. This
|
||||
# makes the tests protected against any defaults the environment may have
|
||||
@ -69,7 +67,7 @@ my $default_ssl_connstr = "sslkey=invalid sslcert=invalid sslrootcert=invalid ss
|
||||
|
||||
$common_connstr =
|
||||
"$default_ssl_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR host=localhost " .
|
||||
"user=ssltestuser sslcert=ssl/client_ext.crt sslkey=$client_tmp_key";
|
||||
"user=ssltestuser sslcert=ssl/client_ext.crt " . sslkey('client_ext.key');
|
||||
|
||||
# Make sure we can connect even though previous test suites have established this
|
||||
$node->connect_ok(
|
||||
|
226
src/test/ssl/t/SSL/Backend/OpenSSL.pm
Normal file
226
src/test/ssl/t/SSL/Backend/OpenSSL.pm
Normal file
@ -0,0 +1,226 @@
|
||||
|
||||
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
SSL::Backend::OpenSSL
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use SSL::Backend::OpenSSL;
|
||||
|
||||
my $backend = SSL::Backend::OpenSSL->new();
|
||||
|
||||
$backend->init($pgdata);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
SSL::Backend::OpenSSL implements the library specific parts in SSL::Server
|
||||
for a PostgreSQL cluster compiled against OpenSSL.
|
||||
|
||||
=cut
|
||||
|
||||
package SSL::Backend::OpenSSL;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use File::Copy;
|
||||
|
||||
=pod
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item SSL::Backend::OpenSSL->new()
|
||||
|
||||
Create a new instance of the OpenSSL backend.
|
||||
|
||||
=cut
|
||||
|
||||
sub new
|
||||
{
|
||||
my ($class) = @_;
|
||||
|
||||
my $self = { _library => 'OpenSSL', key => {} };
|
||||
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item $backend->init(pgdata)
|
||||
|
||||
Install certificates, keys and CRL files required to run the tests against an
|
||||
OpenSSL backend.
|
||||
|
||||
=cut
|
||||
|
||||
sub init
|
||||
{
|
||||
my ($self, $pgdata) = @_;
|
||||
|
||||
# Install server certificates and keys into the cluster data directory.
|
||||
_copy_files("ssl/server-*.crt", $pgdata);
|
||||
_copy_files("ssl/server-*.key", $pgdata);
|
||||
chmod(0600, glob "$pgdata/server-*.key")
|
||||
or die "failed to change permissions on server keys: $!";
|
||||
_copy_files("ssl/root+client_ca.crt", $pgdata);
|
||||
_copy_files("ssl/root_ca.crt", $pgdata);
|
||||
_copy_files("ssl/root+client.crl", $pgdata);
|
||||
mkdir("$pgdata/root+client-crldir")
|
||||
or die "unable to create server CRL dir $pgdata/root+client-crldir: $!";
|
||||
_copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
|
||||
|
||||
# The client's private key must not be world-readable, so take a copy
|
||||
# of the key stored in the code tree and update its permissions.
|
||||
#
|
||||
# This changes to using keys stored in a temporary path for the rest of
|
||||
# the tests. To get the full path for inclusion in connection strings, the
|
||||
# %key hash can be interrogated.
|
||||
my $cert_tempdir = PostgreSQL::Test::Utils::tempdir();
|
||||
my @keys = (
|
||||
"client.key", "client-revoked.key",
|
||||
"client-der.key", "client-encrypted-pem.key",
|
||||
"client-encrypted-der.key", "client-dn.key",
|
||||
"client_ext.key");
|
||||
foreach my $keyfile (@keys)
|
||||
{
|
||||
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
|
||||
or die
|
||||
"couldn't copy ssl/$keyfile to $cert_tempdir/$keyfile for permissions change: $!";
|
||||
chmod 0600, "$cert_tempdir/$keyfile"
|
||||
or die "failed to change permissions on $cert_tempdir/$keyfile: $!";
|
||||
$self->{key}->{$keyfile} = "$cert_tempdir/$keyfile";
|
||||
$self->{key}->{$keyfile} =~ s!\\!/!g
|
||||
if $PostgreSQL::Test::Utils::windows_os;
|
||||
}
|
||||
|
||||
# Also make a copy of client.key explicitly world-readable in order to be
|
||||
# able to test incorrect permissions. We can't necessarily rely on the
|
||||
# file in the source tree having those permissions.
|
||||
copy("ssl/client.key", "$cert_tempdir/client_wrongperms.key")
|
||||
or die
|
||||
"couldn't copy ssl/client_key to $cert_tempdir/client_wrongperms.key for permission change: $!";
|
||||
chmod 0644, "$cert_tempdir/client_wrongperms.key"
|
||||
or die "failed to change permissions on $cert_tempdir/client_wrongperms.key: $!";
|
||||
$self->{key}->{'client_wrongperms.key'} = "$cert_tempdir/client_wrongperms.key";
|
||||
$self->{key}->{'client_wrongperms.key'} =~ s!\\!/!g
|
||||
if $PostgreSQL::Test::Utils::windows_os;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item $backend->get_sslkey(key)
|
||||
|
||||
Get an 'sslkey' connection string parameter for the specified B<key> which has
|
||||
the correct path for direct inclusion in a connection string.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_sslkey
|
||||
{
|
||||
my ($self, $keyfile) = @_;
|
||||
|
||||
return " sslkey=$self->{key}->{$keyfile}";
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item $backend->set_server_cert(params)
|
||||
|
||||
Change the configuration to use given server cert, key and crl file(s). The
|
||||
following paramters are supported:
|
||||
|
||||
=over
|
||||
|
||||
=item cafile => B<value>
|
||||
|
||||
The CA certificate file to use for the C<ssl_ca_file> GUC. If omitted it will
|
||||
default to 'root+client_ca.crt'.
|
||||
|
||||
=item certfile => B<value>
|
||||
|
||||
The server certificate file to use for the C<ssl_cert_file> GUC.
|
||||
|
||||
=item keyfile => B<value>
|
||||
|
||||
The private key file to use for the C<ssl_key_file GUC>. If omitted it will
|
||||
default to the B<certfile>.key.
|
||||
|
||||
=item crlfile => B<value>
|
||||
|
||||
The CRL file to use for the C<ssl_crl_file> GUC. If omitted it will default to
|
||||
'root+client.crl'.
|
||||
|
||||
=item crldir => B<value>
|
||||
|
||||
The CRL directory to use for the C<ssl_crl_dir> GUC. If omitted,
|
||||
C<no ssl_crl_dir> configuration parameter will be set.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub set_server_cert
|
||||
{
|
||||
my ($self, $params) = @_;
|
||||
|
||||
$params->{cafile} = 'root+client_ca' unless defined $params->{cafile};
|
||||
$params->{crlfile} = 'root+client.crl' unless defined $params->{crlfile};
|
||||
$params->{keyfile} = $params->{certfile} unless defined $params->{keyfile};
|
||||
|
||||
my $sslconf =
|
||||
"ssl_ca_file='$params->{cafile}.crt'\n"
|
||||
. "ssl_cert_file='$params->{certfile}.crt'\n"
|
||||
. "ssl_key_file='$params->{keyfile}.key'\n"
|
||||
. "ssl_crl_file='$params->{crlfile}'\n";
|
||||
$sslconf .= "ssl_crl_dir='$params->{crldir}'\n"
|
||||
if defined $params->{crldir};
|
||||
|
||||
return $sslconf;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item $backend->get_library()
|
||||
|
||||
Returns the name of the SSL library, in this case "OpenSSL".
|
||||
|
||||
=cut
|
||||
|
||||
sub get_library
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{_library};
|
||||
}
|
||||
|
||||
# Internal method for copying a set of files, taking into account wildcards
|
||||
sub _copy_files
|
||||
{
|
||||
my $orig = shift;
|
||||
my $dest = shift;
|
||||
|
||||
my @orig_files = glob $orig;
|
||||
foreach my $orig_file (@orig_files)
|
||||
{
|
||||
my $base_file = basename($orig_file);
|
||||
copy($orig_file, "$dest/$base_file")
|
||||
or die "Could not copy $orig_file to $dest";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
353
src/test/ssl/t/SSL/Server.pm
Normal file
353
src/test/ssl/t/SSL/Server.pm
Normal file
@ -0,0 +1,353 @@
|
||||
|
||||
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
SSL::Server - Class for setting up SSL in a PostgreSQL Cluster
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use PostgreSQL::Test::Cluster;
|
||||
use SSL::Server;
|
||||
|
||||
# Create a new cluster
|
||||
my $node = PostgreSQL::Test::Cluster->new('primary');
|
||||
|
||||
# Initialize and start the new cluster
|
||||
$node->init;
|
||||
$node->start;
|
||||
|
||||
# Initialize SSL Server functionality for the cluster
|
||||
my $ssl_server = SSL::Server->new();
|
||||
|
||||
# Configure SSL on the newly formed cluster
|
||||
$server->configure_test_server_for_ssl($node, '127.0.0.1', '127.0.0.1/32', 'trust');
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
SSL::Server configures an existing test cluster, for the SSL regression tests.
|
||||
|
||||
The server is configured as follows:
|
||||
|
||||
=over
|
||||
|
||||
=item * SSL enabled, with the server certificate specified by arguments to switch_server_cert function.
|
||||
|
||||
=item * reject non-SSL connections
|
||||
|
||||
=item * a database called trustdb that lets anyone in
|
||||
|
||||
=item * another database called certdb that uses certificate authentication, ie. the client must present a valid certificate signed by the client CA
|
||||
|
||||
=back
|
||||
|
||||
The server is configured to only accept connections from localhost. If you
|
||||
want to run the client from another host, you'll have to configure that
|
||||
manually.
|
||||
|
||||
Note: Someone running these test could have key or certificate files in their
|
||||
~/.postgresql/, which would interfere with the tests. The way to override that
|
||||
is to specify sslcert=invalid and/or sslrootcert=invalid if no actual
|
||||
certificate is used for a particular test. libpq will ignore specifications
|
||||
that name nonexisting files. (sslkey and sslcrl do not need to specified
|
||||
explicitly because an invalid sslcert or sslrootcert, respectively, causes
|
||||
those to be ignored.)
|
||||
|
||||
The SSL::Server module presents a SSL library abstraction to the test writer,
|
||||
which in turn use modules in SSL::Backend which implements the SSL library
|
||||
specific infrastructure. Currently only OpenSSL is supported.
|
||||
|
||||
=cut
|
||||
|
||||
package SSL::Server;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgreSQL::Test::Cluster;
|
||||
use PostgreSQL::Test::Utils;
|
||||
use Test::More;
|
||||
use SSL::Backend::OpenSSL;
|
||||
|
||||
=pod
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item SSL::Server->new(flavor)
|
||||
|
||||
Create a new SSL Server object for configuring a PostgreSQL test cluster
|
||||
node for accepting SSL connections using the with B<flavor> selected SSL
|
||||
backend. If B<flavor> isn't set, the C<with_ssl> environment variable will
|
||||
be used for selecting backend. Currently only C<openssl> is supported.
|
||||
|
||||
=cut
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $flavor = shift || $ENV{with_ssl};
|
||||
die "SSL flavor not defined" unless $flavor;
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
if ($flavor =~ /\Aopenssl\z/i)
|
||||
{
|
||||
$self->{flavor} = 'openssl';
|
||||
$self->{backend} = SSL::Backend::OpenSSL->new();
|
||||
}
|
||||
else
|
||||
{
|
||||
die "SSL flavor $flavor unknown";
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item sslkey(filename)
|
||||
|
||||
Return a C<sslkey> construct for the specified key for use in a connection
|
||||
string.
|
||||
|
||||
=cut
|
||||
|
||||
sub sslkey
|
||||
{
|
||||
my $self = shift;
|
||||
my $keyfile = shift;
|
||||
my $backend = $self->{backend};
|
||||
|
||||
return $backend->get_sslkey($keyfile);
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item $server->configure_test_server_for_ssl(node, host, cidr, auth, params)
|
||||
|
||||
Configure the cluster specified by B<node> or listening on SSL connections.
|
||||
The following databases will be created in the cluster: trustdb, certdb,
|
||||
certdb_dn, certdb_dn_re, certdb_cn, verifydb. The following users will be
|
||||
created in the cluster: ssltestuser, md5testuser, anotheruser, yetanotheruser.
|
||||
If B<< $params{password} >> is set, it will be used as password for all users
|
||||
with the password encoding B<< $params{password_enc} >> (except for md5testuser
|
||||
which always have MD5). Extensions defined in B<< @{$params{extension}} >>
|
||||
will be created in all the above created databases. B<host> is used for
|
||||
C<listen_addresses> and B<cidr> for configuring C<pg_hba.conf>.
|
||||
|
||||
=cut
|
||||
|
||||
sub configure_test_server_for_ssl
|
||||
{
|
||||
my $self=shift;
|
||||
my ($node, $serverhost, $servercidr, $authmethod, %params) = @_;
|
||||
my $backend = $self->{backend};
|
||||
my $pgdata = $node->data_dir;
|
||||
|
||||
my @databases = ( 'trustdb', 'certdb', 'certdb_dn', 'certdb_dn_re', 'certdb_cn', 'verifydb' );
|
||||
|
||||
# Create test users and databases
|
||||
$node->psql('postgres', "CREATE USER ssltestuser");
|
||||
$node->psql('postgres', "CREATE USER md5testuser");
|
||||
$node->psql('postgres', "CREATE USER anotheruser");
|
||||
$node->psql('postgres', "CREATE USER yetanotheruser");
|
||||
|
||||
foreach my $db (@databases)
|
||||
{
|
||||
$node->psql('postgres', "CREATE DATABASE $db");
|
||||
}
|
||||
|
||||
# Update password of each user as needed.
|
||||
if (defined($params{password}))
|
||||
{
|
||||
die "Password encryption must be specified when password is set"
|
||||
unless defined($params{password_enc});
|
||||
|
||||
$node->psql('postgres',
|
||||
"SET password_encryption='$params{password_enc}'; ALTER USER ssltestuser PASSWORD '$params{password}';"
|
||||
);
|
||||
# A special user that always has an md5-encrypted password
|
||||
$node->psql('postgres',
|
||||
"SET password_encryption='md5'; ALTER USER md5testuser PASSWORD '$params{password}';"
|
||||
);
|
||||
$node->psql('postgres',
|
||||
"SET password_encryption='$params{password_enc}'; ALTER USER anotheruser PASSWORD '$params{password}';"
|
||||
);
|
||||
}
|
||||
|
||||
# Create any extensions requested in the setup
|
||||
if (defined($params{extensions}))
|
||||
{
|
||||
foreach my $extension (@{$params{extensions}})
|
||||
{
|
||||
foreach my $db (@databases)
|
||||
{
|
||||
$node->psql($db, "CREATE EXTENSION $extension CASCADE;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# enable logging etc.
|
||||
open my $conf, '>>', "$pgdata/postgresql.conf";
|
||||
print $conf "fsync=off\n";
|
||||
print $conf "log_connections=on\n";
|
||||
print $conf "log_hostname=on\n";
|
||||
print $conf "listen_addresses='$serverhost'\n";
|
||||
print $conf "log_statement=all\n";
|
||||
|
||||
# enable SSL and set up server key
|
||||
print $conf "include 'sslconfig.conf'\n";
|
||||
|
||||
close $conf;
|
||||
|
||||
# SSL configuration will be placed here
|
||||
open my $sslconf, '>', "$pgdata/sslconfig.conf";
|
||||
close $sslconf;
|
||||
|
||||
# Perform backend specific configuration
|
||||
$backend->init($pgdata);
|
||||
|
||||
# Stop and restart server to load new listen_addresses.
|
||||
$node->restart;
|
||||
|
||||
# Change pg_hba after restart because hostssl requires ssl=on
|
||||
_configure_hba_for_ssl($node, $servercidr, $authmethod);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item $server->ssl_library()
|
||||
|
||||
Get the name of the currently used SSL backend.
|
||||
|
||||
=cut
|
||||
|
||||
sub ssl_library
|
||||
{
|
||||
my $self = shift;
|
||||
my $backend = $self->{backend};
|
||||
|
||||
return $backend->get_library();
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item switch_server_cert(params)
|
||||
|
||||
Change the configuration to use the given set of certificate, key, ca and
|
||||
CRL, and potentially reload the configuration by restarting the server so
|
||||
that the configuration takes effect. Restarting is the default, passing
|
||||
B<< $params{restart} >> => 'no' opts out of it leaving the server running.
|
||||
The following params are supported:
|
||||
|
||||
=over
|
||||
|
||||
=item cafile => B<value>
|
||||
|
||||
The CA certificate to use. Implementation is SSL backend specific.
|
||||
|
||||
=item certfile => B<value>
|
||||
|
||||
The certificate file to use. Implementation is SSL backend specific.
|
||||
|
||||
=item keyfile => B<value>
|
||||
|
||||
The private key to to use. Implementation is SSL backend specific.
|
||||
|
||||
=item crlfile => B<value>
|
||||
|
||||
The CRL file to use. Implementation is SSL backend specific.
|
||||
|
||||
=item crldir => B<value>
|
||||
|
||||
The CRL directory to use. Implementation is SSL backend specific.
|
||||
|
||||
=item passphrase_cmd => B<value>
|
||||
|
||||
The passphrase command to use. If not set, an empty passphrase command will
|
||||
be set.
|
||||
|
||||
=item restart => B<value>
|
||||
|
||||
If set to 'no', the server won't be restarted after updating the settings.
|
||||
If omitted, or any other value is passed, the server will be restarted before
|
||||
returning.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub switch_server_cert
|
||||
{
|
||||
my $self = shift;
|
||||
my $node = shift;
|
||||
my $backend = $self->{backend};
|
||||
my %params = @_;
|
||||
my $pgdata = $node->data_dir;
|
||||
|
||||
open my $sslconf, '>', "$pgdata/sslconfig.conf";
|
||||
print $sslconf "ssl=on\n";
|
||||
print $sslconf $backend->set_server_cert(\%params);
|
||||
print $sslconf "ssl_passphrase_command='" . $params{passphrase_cmd} . "'\n"
|
||||
if defined $params{passphrase_cmd};
|
||||
close $sslconf;
|
||||
|
||||
return if (defined($params{restart}) && $params{restart} eq 'no');
|
||||
|
||||
$node->restart;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
# Internal function for configuring pg_hba.conf for SSL connections.
|
||||
sub _configure_hba_for_ssl
|
||||
{
|
||||
my ($node, $servercidr, $authmethod) = @_;
|
||||
my $pgdata = $node->data_dir;
|
||||
|
||||
# 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.
|
||||
#
|
||||
# When connecting to certdb, also check the client certificate.
|
||||
open my $hba, '>', "$pgdata/pg_hba.conf";
|
||||
print $hba
|
||||
"# TYPE DATABASE USER ADDRESS METHOD OPTIONS\n";
|
||||
print $hba
|
||||
"hostssl trustdb md5testuser $servercidr md5\n";
|
||||
print $hba
|
||||
"hostssl trustdb all $servercidr $authmethod\n";
|
||||
print $hba
|
||||
"hostssl verifydb ssltestuser $servercidr $authmethod clientcert=verify-full\n";
|
||||
print $hba
|
||||
"hostssl verifydb anotheruser $servercidr $authmethod clientcert=verify-full\n";
|
||||
print $hba
|
||||
"hostssl verifydb yetanotheruser $servercidr $authmethod clientcert=verify-ca\n";
|
||||
print $hba
|
||||
"hostssl certdb all $servercidr cert\n";
|
||||
print $hba
|
||||
"hostssl certdb_dn all $servercidr cert clientname=DN map=dn\n",
|
||||
"hostssl certdb_dn_re all $servercidr cert clientname=DN map=dnre\n",
|
||||
"hostssl certdb_cn all $servercidr cert clientname=CN map=cn\n";
|
||||
close $hba;
|
||||
|
||||
# Also set the ident maps. Note: fields with commas must be quoted
|
||||
open my $map, ">", "$pgdata/pg_ident.conf";
|
||||
print $map
|
||||
"# MAPNAME SYSTEM-USERNAME PG-USERNAME\n",
|
||||
"dn \"CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG\" ssltestuser\n",
|
||||
"dnre \"/^.*OU=Testing,.*\$\" ssltestuser\n",
|
||||
"cn ssltestuser-dn ssltestuser\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
@ -1,219 +0,0 @@
|
||||
|
||||
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
|
||||
|
||||
# This module sets up a test server, for the SSL regression tests.
|
||||
#
|
||||
# The server is configured as follows:
|
||||
#
|
||||
# - SSL enabled, with the server certificate specified by argument to
|
||||
# switch_server_cert function.
|
||||
# - ssl/root+client_ca.crt as the CA root for validating client certs.
|
||||
# - reject non-SSL connections
|
||||
# - a database called trustdb that lets anyone in
|
||||
# - another database called certdb that uses certificate authentication, ie.
|
||||
# the client must present a valid certificate signed by the client CA
|
||||
#
|
||||
# The server is configured to only accept connections from localhost. If you
|
||||
# want to run the client from another host, you'll have to configure that
|
||||
# manually.
|
||||
#
|
||||
# Note: Someone running these test could have key or certificate files
|
||||
# in their ~/.postgresql/, which would interfere with the tests. The
|
||||
# way to override that is to specify sslcert=invalid and/or
|
||||
# sslrootcert=invalid if no actual certificate is used for a
|
||||
# particular test. libpq will ignore specifications that name
|
||||
# nonexisting files. (sslkey and sslcrl do not need to specified
|
||||
# explicitly because an invalid sslcert or sslrootcert, respectively,
|
||||
# causes those to be ignored.)
|
||||
|
||||
package SSLServer;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgreSQL::Test::Cluster;
|
||||
use PostgreSQL::Test::Utils;
|
||||
use File::Basename;
|
||||
use File::Copy;
|
||||
use Test::More;
|
||||
|
||||
use Exporter 'import';
|
||||
our @EXPORT = qw(
|
||||
configure_test_server_for_ssl
|
||||
switch_server_cert
|
||||
);
|
||||
|
||||
# Copy a set of files, taking into account wildcards
|
||||
sub copy_files
|
||||
{
|
||||
my $orig = shift;
|
||||
my $dest = shift;
|
||||
|
||||
my @orig_files = glob $orig;
|
||||
foreach my $orig_file (@orig_files)
|
||||
{
|
||||
my $base_file = basename($orig_file);
|
||||
copy($orig_file, "$dest/$base_file")
|
||||
or die "Could not copy $orig_file to $dest";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
# serverhost: what to put in listen_addresses, e.g. '127.0.0.1'
|
||||
# servercidr: what to put in pg_hba.conf, e.g. '127.0.0.1/32'
|
||||
sub configure_test_server_for_ssl
|
||||
{
|
||||
my ($node, $serverhost, $servercidr, $authmethod, %params) = @_;
|
||||
my $pgdata = $node->data_dir;
|
||||
|
||||
my @databases = ( 'trustdb', 'certdb', 'certdb_dn', 'certdb_dn_re', 'certdb_cn', 'verifydb' );
|
||||
|
||||
# Create test users and databases
|
||||
$node->psql('postgres', "CREATE USER ssltestuser");
|
||||
$node->psql('postgres', "CREATE USER md5testuser");
|
||||
$node->psql('postgres', "CREATE USER anotheruser");
|
||||
$node->psql('postgres', "CREATE USER yetanotheruser");
|
||||
|
||||
foreach my $db (@databases)
|
||||
{
|
||||
$node->psql('postgres', "CREATE DATABASE $db");
|
||||
}
|
||||
|
||||
# Update password of each user as needed.
|
||||
if (defined($params{password}))
|
||||
{
|
||||
die "Password encryption must be specified when password is set"
|
||||
unless defined($params{password_enc});
|
||||
|
||||
$node->psql('postgres',
|
||||
"SET password_encryption='$params{password_enc}'; ALTER USER ssltestuser PASSWORD '$params{password}';"
|
||||
);
|
||||
# A special user that always has an md5-encrypted password
|
||||
$node->psql('postgres',
|
||||
"SET password_encryption='md5'; ALTER USER md5testuser PASSWORD '$params{password}';"
|
||||
);
|
||||
$node->psql('postgres',
|
||||
"SET password_encryption='$params{password_enc}'; ALTER USER anotheruser PASSWORD '$params{password}';"
|
||||
);
|
||||
}
|
||||
|
||||
# Create any extensions requested in the setup
|
||||
if (defined($params{extensions}))
|
||||
{
|
||||
foreach my $extension (@{$params{extensions}})
|
||||
{
|
||||
foreach my $db (@databases)
|
||||
{
|
||||
$node->psql($db, "CREATE EXTENSION $extension CASCADE;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# enable logging etc.
|
||||
open my $conf, '>>', "$pgdata/postgresql.conf";
|
||||
print $conf "fsync=off\n";
|
||||
print $conf "log_connections=on\n";
|
||||
print $conf "log_hostname=on\n";
|
||||
print $conf "listen_addresses='$serverhost'\n";
|
||||
print $conf "log_statement=all\n";
|
||||
|
||||
# enable SSL and set up server key
|
||||
print $conf "include 'sslconfig.conf'\n";
|
||||
|
||||
close $conf;
|
||||
|
||||
# ssl configuration will be placed here
|
||||
open my $sslconf, '>', "$pgdata/sslconfig.conf";
|
||||
close $sslconf;
|
||||
|
||||
# Copy all server certificates and keys, and client root cert, to the data dir
|
||||
copy_files("ssl/server-*.crt", $pgdata);
|
||||
copy_files("ssl/server-*.key", $pgdata);
|
||||
chmod(0600, glob "$pgdata/server-*.key") or die $!;
|
||||
copy_files("ssl/root+client_ca.crt", $pgdata);
|
||||
copy_files("ssl/root_ca.crt", $pgdata);
|
||||
copy_files("ssl/root+client.crl", $pgdata);
|
||||
mkdir("$pgdata/root+client-crldir");
|
||||
copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
|
||||
|
||||
# Stop and restart server to load new listen_addresses.
|
||||
$node->restart;
|
||||
|
||||
# Change pg_hba after restart because hostssl requires ssl=on
|
||||
configure_hba_for_ssl($node, $servercidr, $authmethod);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# Change the configuration to use given server cert file, and reload
|
||||
# the server so that the configuration takes effect.
|
||||
sub switch_server_cert
|
||||
{
|
||||
my $node = $_[0];
|
||||
my $certfile = $_[1];
|
||||
my $cafile = $_[2] || "root+client_ca";
|
||||
my $crlfile = "root+client.crl";
|
||||
my $crldir;
|
||||
my $pgdata = $node->data_dir;
|
||||
|
||||
# defaults to use crl file
|
||||
if (defined $_[3] || defined $_[4])
|
||||
{
|
||||
$crlfile = $_[3];
|
||||
$crldir = $_[4];
|
||||
}
|
||||
|
||||
open my $sslconf, '>', "$pgdata/sslconfig.conf";
|
||||
print $sslconf "ssl=on\n";
|
||||
print $sslconf "ssl_ca_file='$cafile.crt'\n";
|
||||
print $sslconf "ssl_cert_file='$certfile.crt'\n";
|
||||
print $sslconf "ssl_key_file='$certfile.key'\n";
|
||||
print $sslconf "ssl_crl_file='$crlfile'\n" if defined $crlfile;
|
||||
print $sslconf "ssl_crl_dir='$crldir'\n" if defined $crldir;
|
||||
close $sslconf;
|
||||
|
||||
$node->restart;
|
||||
return;
|
||||
}
|
||||
|
||||
sub configure_hba_for_ssl
|
||||
{
|
||||
my ($node, $servercidr, $authmethod) = @_;
|
||||
my $pgdata = $node->data_dir;
|
||||
|
||||
# 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.
|
||||
#
|
||||
# When connecting to certdb, also check the client certificate.
|
||||
open my $hba, '>', "$pgdata/pg_hba.conf";
|
||||
print $hba
|
||||
"# TYPE DATABASE USER ADDRESS METHOD OPTIONS\n";
|
||||
print $hba
|
||||
"hostssl trustdb md5testuser $servercidr md5\n";
|
||||
print $hba
|
||||
"hostssl trustdb all $servercidr $authmethod\n";
|
||||
print $hba
|
||||
"hostssl verifydb ssltestuser $servercidr $authmethod clientcert=verify-full\n";
|
||||
print $hba
|
||||
"hostssl verifydb anotheruser $servercidr $authmethod clientcert=verify-full\n";
|
||||
print $hba
|
||||
"hostssl verifydb yetanotheruser $servercidr $authmethod clientcert=verify-ca\n";
|
||||
print $hba
|
||||
"hostssl certdb all $servercidr cert\n";
|
||||
print $hba
|
||||
"hostssl certdb_dn all $servercidr cert clientname=DN map=dn\n",
|
||||
"hostssl certdb_dn_re all $servercidr cert clientname=DN map=dnre\n",
|
||||
"hostssl certdb_cn all $servercidr cert clientname=CN map=cn\n";
|
||||
close $hba;
|
||||
|
||||
# Also set the ident maps. Note: fields with commas must be quoted
|
||||
open my $map, ">", "$pgdata/pg_ident.conf";
|
||||
print $map
|
||||
"# MAPNAME SYSTEM-USERNAME PG-USERNAME\n",
|
||||
"dn \"CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG\" ssltestuser\n",
|
||||
"dnre \"/^.*OU=Testing,.*\$\" ssltestuser\n",
|
||||
"cn ssltestuser-dn ssltestuser\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
Loading…
x
Reference in New Issue
Block a user