aboutsummaryrefslogtreecommitdiff
path: root/src/test/authentication/t/004_file_inclusion.pl
blob: 55d28ad58644f0126ad75c2016240514f317c9a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# Copyright (c) 2021-2023, PostgreSQL Global Development Group

# Tests for include directives in HBA and ident files.  This test can
# only run with Unix-domain sockets.

use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use File::Basename qw(basename);
use Test::More;
use Data::Dumper;
if (!$use_unix_sockets)
{
	plan skip_all =>
	  "authentication tests cannot run without Unix-domain sockets";
}

# Stores the number of lines created for each file.  hba_rule and ident_rule
# are used to respectively track pg_hba_file_rules.rule_number and
# pg_ident_file_mappings.map_number, which are the global counters associated
# to each view tracking the priority of each entry processed.
my %line_counters = ('hba_rule' => 0, 'ident_rule' => 0);

# Add some data to the given HBA configuration file, generating the contents
# expected to match pg_hba_file_rules.
#
# Note that this function maintains %line_counters, used to generate the
# catalog output for file lines and rule numbers.
#
# If the entry starts with "include", the function does not increase
# the general hba rule number as an include directive generates no data
# in pg_hba_file_rules.
#
# This function returns the entry of pg_hba_file_rules expected when this
# is loaded by the backend.
sub add_hba_line
{
	my $node = shift;
	my $filename = shift;
	my $entry = shift;
	my $globline;
	my $fileline;
	my @tokens;
	my $line;

	# Append the entry to the given file
	$node->append_conf($filename, $entry);

	my $base_filename = basename($filename);

	# Get the current %line_counters for the file.
	if (not defined $line_counters{$filename})
	{
		$line_counters{$filename} = 0;
	}
	$fileline = ++$line_counters{$filename};

	# Include directive, that does not generate a view entry.
	return '' if ($entry =~ qr/^include/);

	# Increment pg_hba_file_rules.rule_number and save it.
	$globline = ++$line_counters{'hba_rule'};

	# Generate the expected pg_hba_file_rules line
	@tokens = split(/ /, $entry);
	$tokens[1] = '{' . $tokens[1] . '}';    # database
	$tokens[2] = '{' . $tokens[2] . '}';    # user_name

	# Append empty options and error
	push @tokens, '';
	push @tokens, '';

	# Final line expected, output of the SQL query.
	$line = "";
	$line .= "\n" if ($globline > 1);
	$line .= "$globline|$base_filename|$fileline|";
	$line .= join('|', @tokens);

	return $line;
}

# Add some data to the given ident configuration file, generating the
# contents expected to match pg_ident_file_mappings.
#
# Note that this function maintains %line_counters, generating catalog
# entries for the file line and the map number.
#
# If the entry starts with "include", the function does not increase
# the general map number as an include directive generates no data in
# pg_ident_file_mappings.
#
# This works pretty much the same as add_hba_line() above, except that it
# returns an entry to match with pg_ident_file_mappings.
sub add_ident_line
{
	my $node = shift;
	my $filename = shift;
	my $entry = shift;
	my $globline;
	my $fileline;
	my @tokens;
	my $line;

	my $base_filename = basename($filename);

	# Append the entry to the given file
	$node->append_conf($filename, $entry);

	# Get the current %line_counters counter for the file
	if (not defined $line_counters{$filename})
	{
		$line_counters{$filename} = 0;
	}
	$fileline = ++$line_counters{$filename};

	# Include directive, that does not generate a view entry.
	return '' if ($entry =~ qr/^include/);

	# Increment pg_ident_file_mappings.map_number and get it.
	$globline = ++$line_counters{'ident_rule'};

	# Generate the expected pg_ident_file_mappings line
	@tokens = split(/ /, $entry);
	# Append empty error
	push @tokens, '';

	# Final line expected, output of the SQL query.
	$line = "";
	$line .= "\n" if ($globline > 1);
	$line .= "$globline|$base_filename|$fileline|";
	$line .= join('|', @tokens);

	return $line;
}

# Locations for the entry points of the HBA and ident files.
my $hba_file = 'subdir1/pg_hba_custom.conf';
my $ident_file = 'subdir2/pg_ident_custom.conf';

my $node = PostgreSQL::Test::Cluster->new('primary');
$node->init;
$node->start;

my $data_dir = $node->data_dir;

note "Generating HBA structure with include directives";

my $hba_expected = '';
my $ident_expected = '';

# customise main auth file names
$node->safe_psql('postgres',
	"ALTER SYSTEM SET hba_file = '$data_dir/$hba_file'");
$node->safe_psql('postgres',
	"ALTER SYSTEM SET ident_file = '$data_dir/$ident_file'");

# Remove the original ones, this node links to non-default ones now.
unlink("$data_dir/pg_hba.conf");
unlink("$data_dir/pg_ident.conf");

# Generate HBA contents with include directives.
mkdir("$data_dir/subdir1");
mkdir("$data_dir/hba_inc");
mkdir("$data_dir/hba_inc_if");
mkdir("$data_dir/hba_pos");

# First, make sure that we will always be able to connect.
$hba_expected .= add_hba_line($node, "$hba_file", 'local all all trust');

# "include".  Note that as $hba_file is located in $data_dir/subdir1,
# pg_hba_pre.conf is located at the root of the data directory.
$hba_expected .=
  add_hba_line($node, "$hba_file", "include ../pg_hba_pre.conf");
$hba_expected .=
  add_hba_line($node, 'pg_hba_pre.conf', "local pre all reject");
$hba_expected .= add_hba_line($node, "$hba_file", "local all all reject");
add_hba_line($node, "$hba_file", "include ../hba_pos/pg_hba_pos.conf");
$hba_expected .=
  add_hba_line($node, 'hba_pos/pg_hba_pos.conf', "local pos all reject");
# When an include directive refers to a relative path, it is compiled
# from the base location of the file loaded from.
$hba_expected .=
  add_hba_line($node, 'hba_pos/pg_hba_pos.conf', "include pg_hba_pos2.conf");
$hba_expected .=
  add_hba_line($node, 'hba_pos/pg_hba_pos2.conf', "local pos2 all reject");
$hba_expected .=
  add_hba_line($node, 'hba_pos/pg_hba_pos2.conf', "local pos3 all reject");

# include_if_exists data, nothing generated for the catalog.
# Missing file, no catalog entries.
$hba_expected .=
  add_hba_line($node, "$hba_file", "include_if_exists ../hba_inc_if/none");
# File with some contents loaded.
$hba_expected .=
  add_hba_line($node, "$hba_file", "include_if_exists ../hba_inc_if/some");
$hba_expected .=
  add_hba_line($node, 'hba_inc_if/some', "local if_some all reject");

# include_dir
$hba_expected .= add_hba_line($node, "$hba_file", "include_dir ../hba_inc");
$hba_expected .=
  add_hba_line($node, 'hba_inc/01_z.conf', "local dir_z all reject");
$hba_expected .=
  add_hba_line($node, 'hba_inc/02_a.conf', "local dir_a all reject");
# Garbage file not suffixed by .conf, so it will be ignored.
$node->append_conf('hba_inc/garbageconf', "should not be included");

# Authentication file expanded in an existing entry for database names.
# As it is expanded, ignore the output generated.
add_hba_line($node, $hba_file, 'local @../dbnames.conf all reject');
$node->append_conf('dbnames.conf', "db1");
$node->append_conf('dbnames.conf', "db3");
$hba_expected .= "\n"
  . $line_counters{'hba_rule'} . "|"
  . basename($hba_file) . "|"
  . $line_counters{$hba_file}
  . '|local|{db1,db3}|{all}|reject||';

note "Generating ident structure with include directives";

mkdir("$data_dir/subdir2");
mkdir("$data_dir/ident_inc");
mkdir("$data_dir/ident_inc_if");
mkdir("$data_dir/ident_pos");

# include.  Note that pg_ident_pre.conf is located at the root of the data
# directory.
$ident_expected .=
  add_ident_line($node, "$ident_file", "include ../pg_ident_pre.conf");
$ident_expected .= add_ident_line($node, 'pg_ident_pre.conf', "pre foo bar");
$ident_expected .= add_ident_line($node, "$ident_file", "test a b");
$ident_expected .= add_ident_line($node, "$ident_file",
	"include ../ident_pos/pg_ident_pos.conf");
$ident_expected .=
  add_ident_line($node, 'ident_pos/pg_ident_pos.conf', "pos foo bar");
# When an include directive refers to a relative path, it is compiled
# from the base location of the file loaded from.
$ident_expected .= add_ident_line($node, 'ident_pos/pg_ident_pos.conf',
	"include pg_ident_pos2.conf");
$ident_expected .=
  add_ident_line($node, 'ident_pos/pg_ident_pos2.conf', "pos2 foo bar");
$ident_expected .=
  add_ident_line($node, 'ident_pos/pg_ident_pos2.conf', "pos3 foo bar");

# include_if_exists
# Missing file, no catalog entries.
$ident_expected .= add_ident_line($node, "$ident_file",
	"include_if_exists ../ident_inc_if/none");
# File with some contents loaded.
$ident_expected .= add_ident_line($node, "$ident_file",
	"include_if_exists ../ident_inc_if/some");
$ident_expected .=
  add_ident_line($node, 'ident_inc_if/some', "if_some foo bar");

# include_dir
$ident_expected .=
  add_ident_line($node, "$ident_file", "include_dir ../ident_inc");
$ident_expected .=
  add_ident_line($node, 'ident_inc/01_z.conf', "dir_z foo bar");
$ident_expected .=
  add_ident_line($node, 'ident_inc/02_a.conf', "dir_a foo bar");
# Garbage file not suffixed by .conf, so it will be ignored.
$node->append_conf('ident_inc/garbageconf', "should not be included");

$node->restart;

# Note that the base path is filtered out, keeping only the file name
# to bypass portability issues.  The configuration files had better
# have unique names.
my $contents = $node->safe_psql(
	'postgres',
	qq(SELECT rule_number,
  regexp_replace(file_name, '.*/', ''),
  line_number,
  type,
  database,
  user_name,
  auth_method,
  options,
  error
 FROM pg_hba_file_rules ORDER BY rule_number;));
is($contents, $hba_expected, 'check contents of pg_hba_file_rules');

$contents = $node->safe_psql(
	'postgres',
	qq(SELECT map_number,
  regexp_replace(file_name, '.*/', ''),
  line_number,
  map_name,
  sys_name,
  pg_username,
  error
 FROM pg_ident_file_mappings ORDER BY map_number));
is($contents, $ident_expected, 'check contents of pg_ident_file_mappings');

done_testing();