Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit cbe6e48

Browse files
committed
Add TAP tests for include directives in HBA end ident files
This commit adds a basic set of authentication tests to check after the new keywords added by a54b658 for the HBA and ident files, aka "include", "include_if_exists" and "include_dir". This includes checks for all the positive cases originally proposed, where valid contents are generated for the HBA and ident files without any errors happening in the server, checking as well the contents of their respective system views. The error handling will be evaluated separately (-DEXEC_BACKEND makes that trickier), and what we have here covers most of the ground I would like to see covered if one manipulates the tokenization logic of hba.c in the future. While on it, some coverage is added for files included with '@' for database or user name lists. Author: Julien Rouhaud Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya@jrouhaud
1 parent ec25ba6 commit cbe6e48

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

src/test/authentication/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ tests += {
77
't/001_password.pl',
88
't/002_saslprep.pl',
99
't/003_peer.pl',
10+
't/004_file_inclusion.pl',
1011
],
1112
},
1213
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
2+
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
3+
4+
# Tests for include directives in HBA and ident files. This test can
5+
# only run with Unix-domain sockets.
6+
7+
use strict;
8+
use warnings;
9+
use PostgreSQL::Test::Cluster;
10+
use PostgreSQL::Test::Utils;
11+
use File::Basename qw(basename);
12+
use Test::More;
13+
use Data::Dumper;
14+
if (!$use_unix_sockets)
15+
{
16+
plan skip_all =>
17+
"authentication tests cannot run without Unix-domain sockets";
18+
}
19+
20+
# Stores the number of lines created for each file. hba_rule and ident_rule
21+
# are used to respectively track pg_hba_file_rules.rule_number and
22+
# pg_ident_file_mappings.map_number, which are the global counters associated
23+
# to each view tracking the priority of each entry processed.
24+
my %line_counters = ('hba_rule' => 0, 'ident_rule' => 0);
25+
26+
# Add some data to the given HBA configuration file, generating the contents
27+
# expected to match pg_hba_file_rules.
28+
#
29+
# Note that this function maintains %line_counters, used to generate the
30+
# catalog output for file lines and rule numbers.
31+
#
32+
# If the entry starts with "include", the function does not increase
33+
# the general hba rule number as an include directive generates no data
34+
# in pg_hba_file_rules.
35+
#
36+
# This function returns the entry of pg_hba_file_rules expected when this
37+
# is loaded by the backend.
38+
sub add_hba_line
39+
{
40+
my $node = shift;
41+
my $filename = shift;
42+
my $entry = shift;
43+
my $globline;
44+
my $fileline;
45+
my @tokens;
46+
my $line;
47+
48+
# Append the entry to the given file
49+
$node->append_conf($filename, $entry);
50+
51+
my $base_filename = basename($filename);
52+
53+
# Get the current %line_counters for the file.
54+
if (not defined $line_counters{$filename})
55+
{
56+
$line_counters{$filename} = 0;
57+
}
58+
$fileline = ++$line_counters{$filename};
59+
60+
# Include directive, that does not generate a view entry.
61+
return '' if ($entry =~ qr/^include/);
62+
63+
# Increment pg_hba_file_rules.rule_number and save it.
64+
$globline = ++$line_counters{'hba_rule'};
65+
66+
# Generate the expected pg_hba_file_rules line
67+
@tokens = split(/ /, $entry);
68+
$tokens[1] = '{' . $tokens[1] . '}'; # database
69+
$tokens[2] = '{' . $tokens[2] . '}'; # user_name
70+
71+
# Append empty options and error
72+
push @tokens, '';
73+
push @tokens, '';
74+
75+
# Final line expected, output of the SQL query.
76+
$line = "";
77+
$line .= "\n" if ($globline > 1);
78+
$line .= "$globline|$base_filename|$fileline|";
79+
$line .= join('|', @tokens);
80+
81+
return $line;
82+
}
83+
84+
# Add some data to the given ident configuration file, generating the
85+
# contents expected to match pg_ident_file_mappings.
86+
#
87+
# Note that this function maintains %line_counters, generating catalog
88+
# entries for the file line and the map number.
89+
#
90+
# If the entry starts with "include", the function does not increase
91+
# the general map number as an include directive generates no data in
92+
# pg_ident_file_mappings.
93+
#
94+
# This works pretty much the same as add_hba_line() above, except that it
95+
# returns an entry to match with pg_ident_file_mappings.
96+
sub add_ident_line
97+
{
98+
my $node = shift;
99+
my $filename = shift;
100+
my $entry = shift;
101+
my $globline;
102+
my $fileline;
103+
my @tokens;
104+
my $line;
105+
106+
my $base_filename = basename($filename);
107+
108+
# Append the entry to the given file
109+
$node->append_conf($filename, $entry);
110+
111+
# Get the current %line_counters counter for the file
112+
if (not defined $line_counters{$filename})
113+
{
114+
$line_counters{$filename} = 0;
115+
}
116+
$fileline = ++$line_counters{$filename};
117+
118+
# Include directive, that does not generate a view entry.
119+
return '' if ($entry =~ qr/^include/);
120+
121+
# Increment pg_ident_file_mappings.map_number and get it.
122+
$globline = ++$line_counters{'ident_rule'};
123+
124+
# Generate the expected pg_ident_file_mappings line
125+
@tokens = split(/ /, $entry);
126+
# Append empty error
127+
push @tokens, '';
128+
129+
# Final line expected, output of the SQL query.
130+
$line = "";
131+
$line .= "\n" if ($globline > 1);
132+
$line .= "$globline|$base_filename|$fileline|";
133+
$line .= join('|', @tokens);
134+
135+
return $line;
136+
}
137+
138+
# Locations for the entry points of the HBA and ident files.
139+
my $hba_file = 'subdir1/pg_hba_custom.conf';
140+
my $ident_file = 'subdir2/pg_ident_custom.conf';
141+
142+
my $node = PostgreSQL::Test::Cluster->new('primary');
143+
$node->init;
144+
$node->start;
145+
146+
my $data_dir = $node->data_dir;
147+
148+
note "Generating HBA structure with include directives";
149+
150+
my $hba_expected = '';
151+
my $ident_expected = '';
152+
153+
# customise main auth file names
154+
$node->safe_psql('postgres',
155+
"ALTER SYSTEM SET hba_file = '$data_dir/$hba_file'");
156+
$node->safe_psql('postgres',
157+
"ALTER SYSTEM SET ident_file = '$data_dir/$ident_file'");
158+
159+
# Remove the original ones, this node links to non-default ones now.
160+
unlink("$data_dir/pg_hba.conf");
161+
unlink("$data_dir/pg_ident.conf");
162+
163+
# Generate HBA contents with include directives.
164+
mkdir("$data_dir/subdir1");
165+
mkdir("$data_dir/hba_inc");
166+
mkdir("$data_dir/hba_inc_if");
167+
mkdir("$data_dir/hba_pos");
168+
169+
# First, make sure that we will always be able to connect.
170+
$hba_expected .= add_hba_line($node, "$hba_file", 'local all all trust');
171+
172+
# "include". Note that as $hba_file is located in $data_dir/subdir1,
173+
# pg_hba_pre.conf is located at the root of the data directory.
174+
$hba_expected .=
175+
add_hba_line($node, "$hba_file", "include ../pg_hba_pre.conf");
176+
$hba_expected .=
177+
add_hba_line($node, 'pg_hba_pre.conf', "local pre all reject");
178+
$hba_expected .= add_hba_line($node, "$hba_file", "local all all reject");
179+
add_hba_line($node, "$hba_file", "include ../hba_pos/pg_hba_pos.conf");
180+
$hba_expected .=
181+
add_hba_line($node, 'hba_pos/pg_hba_pos.conf', "local pos all reject");
182+
# When an include directive refers to a relative path, it is compiled
183+
# from the base location of the file loaded from.
184+
$hba_expected .=
185+
add_hba_line($node, 'hba_pos/pg_hba_pos.conf', "include pg_hba_pos2.conf");
186+
$hba_expected .=
187+
add_hba_line($node, 'hba_pos/pg_hba_pos2.conf', "local pos2 all reject");
188+
$hba_expected .=
189+
add_hba_line($node, 'hba_pos/pg_hba_pos2.conf', "local pos3 all reject");
190+
191+
# include_if_exists data, nothing generated for the catalog.
192+
# Missing file, no catalog entries.
193+
$hba_expected .=
194+
add_hba_line($node, "$hba_file", "include_if_exists ../hba_inc_if/none");
195+
# File with some contents loaded.
196+
$hba_expected .=
197+
add_hba_line($node, "$hba_file", "include_if_exists ../hba_inc_if/some");
198+
$hba_expected .=
199+
add_hba_line($node, 'hba_inc_if/some', "local if_some all reject");
200+
201+
# include_dir
202+
$hba_expected .= add_hba_line($node, "$hba_file", "include_dir ../hba_inc");
203+
$hba_expected .=
204+
add_hba_line($node, 'hba_inc/01_z.conf', "local dir_z all reject");
205+
$hba_expected .=
206+
add_hba_line($node, 'hba_inc/02_a.conf', "local dir_a all reject");
207+
# Garbage file not suffixed by .conf, so it will be ignored.
208+
$node->append_conf('hba_inc/garbageconf', "should not be included");
209+
210+
# Authentication file expanded in an existing entry for database names.
211+
# As it is expanded, ignore the output generated.
212+
add_hba_line($node, $hba_file, 'local @../dbnames.conf all reject');
213+
$node->append_conf('dbnames.conf', "db1");
214+
$node->append_conf('dbnames.conf', "db3");
215+
$hba_expected .= "\n"
216+
. $line_counters{'hba_rule'} . "|"
217+
. basename($hba_file) . "|"
218+
. $line_counters{$hba_file}
219+
. '|local|{db1,db3}|{all}|reject||';
220+
221+
note "Generating ident structure with include directives";
222+
223+
mkdir("$data_dir/subdir2");
224+
mkdir("$data_dir/ident_inc");
225+
mkdir("$data_dir/ident_inc_if");
226+
mkdir("$data_dir/ident_pos");
227+
228+
# include. Note that pg_ident_pre.conf is located at the root of the data
229+
# directory.
230+
$ident_expected .=
231+
add_ident_line($node, "$ident_file", "include ../pg_ident_pre.conf");
232+
$ident_expected .= add_ident_line($node, 'pg_ident_pre.conf', "pre foo bar");
233+
$ident_expected .= add_ident_line($node, "$ident_file", "test a b");
234+
$ident_expected .= add_ident_line($node, "$ident_file",
235+
"include ../ident_pos/pg_ident_pos.conf");
236+
$ident_expected .=
237+
add_ident_line($node, 'ident_pos/pg_ident_pos.conf', "pos foo bar");
238+
# When an include directive refers to a relative path, it is compiled
239+
# from the base location of the file loaded from.
240+
$ident_expected .= add_ident_line($node, 'ident_pos/pg_ident_pos.conf',
241+
"include pg_ident_pos2.conf");
242+
$ident_expected .=
243+
add_ident_line($node, 'ident_pos/pg_ident_pos2.conf', "pos2 foo bar");
244+
$ident_expected .=
245+
add_ident_line($node, 'ident_pos/pg_ident_pos2.conf', "pos3 foo bar");
246+
247+
# include_if_exists
248+
# Missing file, no catalog entries.
249+
$ident_expected .= add_ident_line($node, "$ident_file",
250+
"include_if_exists ../ident_inc_if/none");
251+
# File with some contents loaded.
252+
$ident_expected .= add_ident_line($node, "$ident_file",
253+
"include_if_exists ../ident_inc_if/some");
254+
$ident_expected .=
255+
add_ident_line($node, 'ident_inc_if/some', "if_some foo bar");
256+
257+
# include_dir
258+
$ident_expected .=
259+
add_ident_line($node, "$ident_file", "include_dir ../ident_inc");
260+
$ident_expected .=
261+
add_ident_line($node, 'ident_inc/01_z.conf', "dir_z foo bar");
262+
$ident_expected .=
263+
add_ident_line($node, 'ident_inc/02_a.conf', "dir_a foo bar");
264+
# Garbage file not suffixed by .conf, so it will be ignored.
265+
$node->append_conf('ident_inc/garbageconf', "should not be included");
266+
267+
$node->restart;
268+
269+
# Note that the base path is filtered out, keeping only the file name
270+
# to bypass portability issues. The configuration files had better
271+
# have unique names.
272+
my $contents = $node->safe_psql(
273+
'postgres',
274+
qq(SELECT rule_number,
275+
regexp_replace(file_name, '.*/', ''),
276+
line_number,
277+
type,
278+
database,
279+
user_name,
280+
auth_method,
281+
options,
282+
error
283+
FROM pg_hba_file_rules ORDER BY rule_number;));
284+
is($contents, $hba_expected, 'check contents of pg_hba_file_rules');
285+
286+
$contents = $node->safe_psql(
287+
'postgres',
288+
qq(SELECT map_number,
289+
regexp_replace(file_name, '.*/', ''),
290+
line_number,
291+
map_name,
292+
sys_name,
293+
pg_username,
294+
error
295+
FROM pg_ident_file_mappings ORDER BY map_number));
296+
is($contents, $ident_expected, 'check contents of pg_ident_file_mappings');
297+
298+
done_testing();

0 commit comments

Comments
 (0)