| |
| # 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(); |