342 lines
9.8 KiB
Perl
342 lines
9.8 KiB
Perl
|
|
# Copyright (c) 2021-2023, PostgreSQL Global Development Group
|
|
|
|
# Tests for peer authentication and user name map.
|
|
# The test is skipped if the platform does not support peer authentication,
|
|
# and is only able to run with Unix-domain sockets.
|
|
|
|
use strict;
|
|
use warnings;
|
|
use PostgreSQL::Test::Cluster;
|
|
use PostgreSQL::Test::Utils;
|
|
use Test::More;
|
|
if (!$use_unix_sockets)
|
|
{
|
|
plan skip_all =>
|
|
"authentication tests cannot run without Unix-domain sockets";
|
|
}
|
|
|
|
# Delete pg_hba.conf from the given node, add a new entry to it
|
|
# and then execute a reload to refresh it.
|
|
sub reset_pg_hba
|
|
{
|
|
my $node = shift;
|
|
my $hba_method = shift;
|
|
|
|
unlink($node->data_dir . '/pg_hba.conf');
|
|
$node->append_conf('pg_hba.conf', "local all all $hba_method");
|
|
$node->reload;
|
|
return;
|
|
}
|
|
|
|
# Delete pg_ident.conf from the given node, add a new entry to it
|
|
# and then execute a reload to refresh it.
|
|
sub reset_pg_ident
|
|
{
|
|
my $node = shift;
|
|
my $map_name = shift;
|
|
my $system_user = shift;
|
|
my $pg_user = shift;
|
|
|
|
unlink($node->data_dir . '/pg_ident.conf');
|
|
$node->append_conf('pg_ident.conf', "$map_name $system_user $pg_user");
|
|
$node->reload;
|
|
return;
|
|
}
|
|
|
|
# Test access for a single role, useful to wrap all tests into one.
|
|
sub test_role
|
|
{
|
|
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
|
|
|
my ($node, $role, $method, $expected_res, $test_details, %params) = @_;
|
|
my $status_string = 'failed';
|
|
$status_string = 'success' if ($expected_res eq 0);
|
|
|
|
my $connstr = "user=$role";
|
|
my $testname =
|
|
"authentication $status_string for method $method, role $role "
|
|
. $test_details;
|
|
|
|
if ($expected_res eq 0)
|
|
{
|
|
$node->connect_ok($connstr, $testname, %params);
|
|
}
|
|
else
|
|
{
|
|
# No checks of the error message, only the status code.
|
|
$node->connect_fails($connstr, $testname, %params);
|
|
}
|
|
}
|
|
|
|
# Find $pattern in log file of $node.
|
|
sub find_in_log
|
|
{
|
|
my ($node, $offset, $pattern) = @_;
|
|
|
|
my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile, $offset);
|
|
return 0 if (length($log) <= 0);
|
|
|
|
return $log =~ m/$pattern/;
|
|
}
|
|
|
|
my $node = PostgreSQL::Test::Cluster->new('node');
|
|
$node->init;
|
|
$node->append_conf('postgresql.conf', "log_connections = on\n");
|
|
$node->start;
|
|
|
|
# Set pg_hba.conf with the peer authentication.
|
|
reset_pg_hba($node, 'peer');
|
|
|
|
# Check if peer authentication is supported on this platform.
|
|
my $log_offset = -s $node->logfile;
|
|
$node->psql('postgres');
|
|
if (find_in_log(
|
|
$node, $log_offset,
|
|
qr/peer authentication is not supported on this platform/))
|
|
{
|
|
plan skip_all => 'peer authentication is not supported on this platform';
|
|
}
|
|
|
|
# Add a database role and a group, to use for the user name map.
|
|
$node->safe_psql('postgres', qq{CREATE ROLE testmapuser LOGIN});
|
|
$node->safe_psql('postgres', "CREATE ROLE testmapgroup NOLOGIN");
|
|
$node->safe_psql('postgres', "GRANT testmapgroup TO testmapuser");
|
|
# Note the double quotes here.
|
|
$node->safe_psql('postgres', 'CREATE ROLE "testmapgroupliteral\\1" LOGIN');
|
|
$node->safe_psql('postgres', 'GRANT "testmapgroupliteral\\1" TO testmapuser');
|
|
|
|
# Extract as well the system user for the user name map.
|
|
my $system_user =
|
|
$node->safe_psql('postgres',
|
|
q(select (string_to_array(SYSTEM_USER, ':'))[2]));
|
|
|
|
# Tests without the user name map.
|
|
# Failure as connection is attempted with a database role not mapping
|
|
# to an authorized system user.
|
|
test_role(
|
|
$node, qq{testmapuser}, 'peer', 2,
|
|
'without user name map',
|
|
log_like => [qr/Peer authentication failed for user "testmapuser"/]);
|
|
|
|
# Tests with a user name map.
|
|
reset_pg_ident($node, 'mypeermap', $system_user, 'testmapuser');
|
|
reset_pg_hba($node, 'peer map=mypeermap');
|
|
|
|
# Success as the database role matches with the system user in the map.
|
|
test_role($node, qq{testmapuser}, 'peer', 0, 'with user name map',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Tests with the "all" keyword.
|
|
reset_pg_ident($node, 'mypeermap', $system_user, 'all');
|
|
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'with keyword "all" as database user in user name map',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Tests with the "all" keyword, but quoted (no effect here).
|
|
reset_pg_ident($node, 'mypeermap', $system_user, '"all"');
|
|
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
2,
|
|
'with quoted keyword "all" as database user in user name map',
|
|
log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]);
|
|
|
|
# Success as the regexp of the database user matches
|
|
reset_pg_ident($node, 'mypeermap', $system_user, qq{/^testm.*\$});
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'with regexp of database user in user name map',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Failure as the regexp of the database user does not match.
|
|
reset_pg_ident($node, 'mypeermap', $system_user, qq{/^doesnotmatch.*\$});
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
2,
|
|
'with bad regexp of database user in user name map',
|
|
log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]);
|
|
|
|
# Test with regular expression in user name map.
|
|
# Extract the last 3 characters from the system_user
|
|
# or the entire system_user (if its length is <= -3).
|
|
my $regex_test_string = substr($system_user, -3);
|
|
|
|
# Success as the system user regular expression matches.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$},
|
|
'testmapuser');
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'with regexp of system user in user name map',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Success as both regular expressions match.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$},
|
|
qq{/^testm.*\$});
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'with regexps for both system and database user in user name map',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Success as the regular expression matches and database role is the "all"
|
|
# keyword.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, 'all');
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'with regexp of system user and keyword "all" in user name map',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Success as the regular expression matches and \1 is replaced in the given
|
|
# subexpression.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^$system_user(.*)\$}, 'test\1mapuser');
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'with regular expression in user name map with \1 replaced',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Success as the regular expression matches and \1 is replaced in the given
|
|
# subexpression, even if quoted.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^$system_user(.*)\$},
|
|
'"test\1mapuser"');
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'with regular expression in user name map with quoted \1 replaced',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Failure as the regular expression does not include a subexpression, but
|
|
# the database user contains \1, requesting a replacement.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^$system_user\$}, '\1testmapuser');
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer', 2,
|
|
'with regular expression in user name map with \1 not replaced',
|
|
log_like => [
|
|
qr/regular expression "\^$system_user\$" has no subexpressions as requested by backreference in "\\1testmapuser"/
|
|
]);
|
|
|
|
# Concatenate system_user to system_user.
|
|
my $bad_regex_test_string = $system_user . $system_user;
|
|
|
|
# Failure as the regexp of system user does not match.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^.*$bad_regex_test_string\$},
|
|
'testmapuser');
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
2,
|
|
'with regexp of system user in user name map',
|
|
log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]);
|
|
|
|
# Test using a group role match for the database user.
|
|
reset_pg_ident($node, 'mypeermap', $system_user, '+testmapgroup');
|
|
|
|
test_role($node, qq{testmapuser}, 'peer', 0, 'plain user with group',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
test_role(
|
|
$node, qq{testmapgroup}, 'peer', 2,
|
|
'group user with group',
|
|
log_like => [qr/role "testmapgroup" is not permitted to log in/]);
|
|
|
|
# Now apply quotes to the group match, nullifying its effect.
|
|
reset_pg_ident($node, 'mypeermap', $system_user, '"+testmapgroup"');
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
2,
|
|
'plain user with quoted group name',
|
|
log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]);
|
|
|
|
# Test using a regexp for the system user, with a group membership
|
|
# check for the database user.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$},
|
|
'+testmapgroup');
|
|
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'regexp of system user as group member',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
test_role(
|
|
$node,
|
|
qq{testmapgroup},
|
|
'peer',
|
|
2,
|
|
'regexp of system user as non-member of group',
|
|
log_like => [qr/role "testmapgroup" is not permitted to log in/]);
|
|
|
|
# Test that membership checks and regexes will use literal \1 instead of
|
|
# replacing it, as subexpression replacement is not allowed in this case.
|
|
reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string(.*)\$},
|
|
'+testmapgroupliteral\\1');
|
|
|
|
test_role(
|
|
$node,
|
|
qq{testmapuser},
|
|
'peer',
|
|
0,
|
|
'membership check with literal \1',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
# Do the same with a quoted regular expression for the database user this
|
|
# time. No replacement of \1 is done.
|
|
reset_pg_ident(
|
|
$node, 'mypeermap',
|
|
qq{/^.*$regex_test_string(.*)\$},
|
|
'"/^testmapgroupliteral\\\\1$"');
|
|
|
|
test_role(
|
|
$node,
|
|
'testmapgroupliteral\\\\1',
|
|
'peer',
|
|
0,
|
|
'regexp of database user with literal \1',
|
|
log_like =>
|
|
[qr/connection authenticated: identity="$system_user" method=peer/]);
|
|
|
|
done_testing();
|