On Mon, Mar 28, 2022 at 04:20:07PM +0900, Michael Paquier wrote: > See the attached, for reference, but it would fail with EXEC_BACKEND > on WIN32.
Ditto. -- Michael
From 69e02734fd0199ba02cc34bc468b04584bdf0efd Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Mon, 28 Mar 2022 16:20:40 +0900 Subject: [PATCH v5] Add a pg_ident_file_mappings view. This view is similar to pg_hba_file_rules view, and can be also helpful to help diagnosing configuration problems. A following commit will add the possibility to include files in pg_hba and pg_ident configuration files, which will then make this view even more useful. Catversion is bumped. Author: Julien Rouhaud Reviewed-by: FIXME Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud --- src/include/catalog/pg_proc.dat | 6 + src/include/libpq/hba.h | 1 + src/backend/catalog/system_views.sql | 6 + src/backend/libpq/hba.c | 31 +++-- src/backend/utils/adt/hbafuncs.c | 136 ++++++++++++++++++++ src/test/authentication/t/003_auth_views.pl | 108 ++++++++++++++++ src/test/regress/expected/rules.out | 6 + src/test/regress/expected/sysviews.out | 6 + src/test/regress/sql/sysviews.sql | 2 + doc/src/sgml/catalogs.sgml | 108 ++++++++++++++++ doc/src/sgml/client-auth.sgml | 10 ++ doc/src/sgml/func.sgml | 5 +- 12 files changed, 409 insertions(+), 16 deletions(-) create mode 100644 src/test/authentication/t/003_auth_views.pl diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 5e612a6b67..915bc19176 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6115,6 +6115,12 @@ proargmodes => '{o,o,o,o,o,o,o,o,o}', proargnames => '{line_number,type,database,user_name,address,netmask,auth_method,options,error}', prosrc => 'pg_hba_file_rules' }, +{ oid => '9556', descr => 'show pg_ident.conf mappings', + proname => 'pg_ident_file_mappings', prorows => '1000', proretset => 't', + provolatile => 'v', prorettype => 'record', proargtypes => '', + proallargtypes => '{int4,text,text,text,text}', proargmodes => '{o,o,o,o,o}', + proargnames => '{line_number,map_name,sys_name,pg_username,error}', + prosrc => 'pg_ident_file_mappings' }, { oid => '1371', descr => 'view system lock information', proname => 'pg_lock_status', prorows => '1000', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 13ecb329f8..90036f7bcd 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -171,6 +171,7 @@ extern int check_usermap(const char *usermap_name, const char *pg_role, const char *auth_user, bool case_sensitive); extern HbaLine *parse_hba_line(TokenizedAuthLine *tok_line, int elevel); +extern IdentLine *parse_ident_line(TokenizedAuthLine *tok_line, int elevel); extern bool pg_isblank(const char c); extern MemoryContext tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, int elevel); diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 9570a53e7b..9eaa51df29 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -617,6 +617,12 @@ CREATE VIEW pg_hba_file_rules AS REVOKE ALL ON pg_hba_file_rules FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pg_hba_file_rules() FROM PUBLIC; +CREATE VIEW pg_ident_file_mappings AS + SELECT * FROM pg_ident_file_mappings() AS A; + +REVOKE ALL ON pg_ident_file_mappings FROM PUBLIC; +REVOKE EXECUTE ON FUNCTION pg_ident_file_mappings() FROM PUBLIC; + CREATE VIEW pg_timezone_abbrevs AS SELECT * FROM pg_timezone_abbrevs(); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 673135144d..f8393ca8ed 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -887,25 +887,22 @@ do { \ } while (0) /* - * Macros for handling pg_ident problems. - * Much as above, but currently the message level is hardwired as LOG - * and there is no provision for an err_msg string. + * Macros for handling pg_ident problems, similar as above. * * IDENT_FIELD_ABSENT: - * Log a message and exit the function if the given ident field ListCell is - * not populated. + * Reports when the given ident field ListCell is not populated. * * IDENT_MULTI_VALUE: - * Log a message and exit the function if the given ident token List has more - * than one element. + * Reports when the given ident token List has more than one element. */ #define IDENT_FIELD_ABSENT(field) \ do { \ if (!field) { \ - ereport(LOG, \ + ereport(elevel, \ (errcode(ERRCODE_CONFIG_FILE_ERROR), \ errmsg("missing entry in file \"%s\" at end of line %d", \ IdentFileName, line_num))); \ + *err_msg = psprintf("missing entry at end of line"); \ return NULL; \ } \ } while (0) @@ -913,11 +910,12 @@ do { \ #define IDENT_MULTI_VALUE(tokens) \ do { \ if (tokens->length > 1) { \ - ereport(LOG, \ + ereport(elevel, \ (errcode(ERRCODE_CONFIG_FILE_ERROR), \ errmsg("multiple values in ident field"), \ errcontext("line %d of configuration file \"%s\"", \ line_num, IdentFileName))); \ + *err_msg = psprintf("multiple values in ident field"); \ return NULL; \ } \ } while (0) @@ -2306,7 +2304,8 @@ load_hba(void) * Parse one tokenised line from the ident config file and store the result in * an IdentLine structure. * - * If parsing fails, log a message and return NULL. + * If parsing fails, log a message at ereport level elevel, store an error + * string in tok_line->err_msg and return NULL. * * If ident_user is a regular expression (ie. begins with a slash), it is * compiled and stored in IdentLine structure. @@ -2315,10 +2314,11 @@ load_hba(void) * to have set a memory context that will be reset if this function returns * NULL. */ -static IdentLine * -parse_ident_line(TokenizedAuthLine *tok_line) +IdentLine * +parse_ident_line(TokenizedAuthLine *tok_line, int elevel) { int line_num = tok_line->line_num; + char **err_msg = &tok_line->err_msg; ListCell *field; List *tokens; AuthToken *token; @@ -2372,11 +2372,14 @@ parse_ident_line(TokenizedAuthLine *tok_line) char errstr[100]; pg_regerror(r, &parsedline->re, errstr, sizeof(errstr)); - ereport(LOG, + ereport(elevel, (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), errmsg("invalid regular expression \"%s\": %s", parsedline->ident_user + 1, errstr))); + *err_msg = psprintf("invalid regular expression \"%s\": %s", + parsedline->ident_user + 1, errstr); + pfree(wstr); return NULL; } @@ -2627,7 +2630,7 @@ load_ident(void) continue; } - if ((newline = parse_ident_line(tok_line)) == NULL) + if ((newline = parse_ident_line(tok_line, LOG)) == NULL) { /* Parse error; remember there's trouble */ ok = false; diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c index f46cd935a1..ee70c7115c 100644 --- a/src/backend/utils/adt/hbafuncs.c +++ b/src/backend/utils/adt/hbafuncs.c @@ -28,6 +28,9 @@ static ArrayType *get_hba_options(HbaLine *hba); static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, int lineno, HbaLine *hba, const char *err_msg); static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); +static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int lineno, IdentLine *ident, const char *err_msg); +static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); /* @@ -426,3 +429,136 @@ pg_hba_file_rules(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + +/* Number of columns in pg_ident_file_mappings view */ +#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS 5 + +/* + * fill_ident_line: build one row of pg_ident_file_mappings view, add it to + * tuplestore + * + * tuple_store: where to store data + * tupdesc: tuple descriptor for the view + * lineno: pg_hba.conf line number (must always be valid) + * ident: parsed line data (can be NULL, in which case err_msg should be set) + * err_msg: error message (NULL if none) + * + * Note: leaks memory, but we don't care since this is run in a short-lived + * memory context. + */ +static void +fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int lineno, IdentLine *ident, const char *err_msg) +{ + Datum values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; + bool nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; + HeapTuple tuple; + int index; + + Assert(tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + index = 0; + + /* line_number */ + values[index++] = Int32GetDatum(lineno); + + if (ident != NULL) + { + values[index++] = CStringGetTextDatum(ident->usermap); + values[index++] = CStringGetTextDatum(ident->ident_user); + values[index++] = CStringGetTextDatum(ident->pg_role); + } + else + { + /* no parsing result, so set relevant fields to nulls */ + memset(&nulls[1], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 2) * sizeof(bool)); + } + + /* error */ + if (err_msg) + values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = CStringGetTextDatum(err_msg); + else + nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true; + + tuple = heap_form_tuple(tupdesc, values, nulls); + tuplestore_puttuple(tuple_store, tuple); +} + +/* + * Read the pg_ident.conf file and fill the tuplestore with view records. + */ +static void +fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) +{ + FILE *file; + List *ident_lines = NIL; + ListCell *line; + MemoryContext linecxt; + MemoryContext identcxt; + MemoryContext oldcxt; + + /* + * In the unlikely event that we can't open pg_hba.conf, we throw an + * error, rather than trying to report it via some sort of view entry. + * (Most other error conditions should result in a message in a view + * entry.) + */ + file = AllocateFile(IdentFileName, "r"); + if (file == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open usermap file \"%s\": %m", + IdentFileName))); + + linecxt = tokenize_auth_file(HbaFileName, file, &ident_lines, DEBUG3); + FreeFile(file); + + /* Now parse all the lines */ + identcxt = AllocSetContextCreate(CurrentMemoryContext, + "ident parser context", + ALLOCSET_SMALL_SIZES); + oldcxt = MemoryContextSwitchTo(identcxt); + foreach(line, ident_lines) + { + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line); + IdentLine *identline = NULL; + + /* don't parse lines that already have errors */ + if (tok_line->err_msg == NULL) + identline = parse_ident_line(tok_line, DEBUG3); + + fill_ident_line(tuple_store, tupdesc, tok_line->line_num, identline, + tok_line->err_msg); + } + + /* Free tokenizer memory */ + MemoryContextDelete(linecxt); + /* Free parse_ident_line memory */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(identcxt); +} + +/* + * SQL-accessible SRF to return all the entries in the pg_ident.conf file. + */ +Datum +pg_ident_file_mappings(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsi; + + /* + * Build tuplestore to hold the result rows. We must use the Materialize + * mode to be safe against HBA file changes while the cursor is open. It's + * also more efficient than having to look up our current position in the + * parsed list every time. + */ + SetSingleFuncCall(fcinfo, 0); + + /* Fill the tuplestore */ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + fill_ident_view(rsi->setResult, rsi->setDesc); + + PG_RETURN_NULL(); +} diff --git a/src/test/authentication/t/003_auth_views.pl b/src/test/authentication/t/003_auth_views.pl new file mode 100644 index 0000000000..c237a50788 --- /dev/null +++ b/src/test/authentication/t/003_auth_views.pl @@ -0,0 +1,108 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +# Set of tests checking pg_hba_file_rules and pg_ident_file_mappings. + +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Initialize primary node +my $node = PostgreSQL::Test::Cluster->new('primary'); +$node->init; +$node->start; + +my $result; + +# Check that the initial views don't report any error +$result = $node->safe_psql('postgres', + "SELECT count(*) FROM pg_hba_file_rules WHERE error IS NOT NULL"); +is($result, '0', 'no errors in the initial setup of pg_hba.conf'); +$result = $node->safe_psql('postgres', + "SELECT count(*) FROM pg_ident_file_mappings WHERE error IS NOT NULL"); +is($result, '0', 'no errors in the initial setup of pg_ident.conf'); + +# Builds with -DEXEC_BACKEND would attempt to use the updated configuration +# files for each new connection. +my $exec_backend = $node->safe_psql('postgres', + qq(SELECT count(name) FROM pg_config WHERE name = 'CFLAGS' AND setting ~ 'EXEC_BACKEND';) +); + +SKIP: +{ + skip "cancel test requires a Unix shell", 4 if $exec_backend; + + # Add some sample lines in pg_hba/pg_ident conf files to trigger more + # behaviors. No reload is needed, as each system view tested above does + # its own parsing of the configuration files it works on when executed. + $node->append_conf( + 'pg_hba.conf', qq( +# Correct line, parsed correctly. +host dummy_db dummy_user 1.2.3.4/32 reject +# Incomplete line, leading to an error +host +host incorrect_db +host incorrect_db incorrect_user +host incorrect_db incorrect_user incorrect_host +)); + $node->append_conf( + 'pg_ident.conf', qq( +# Correct line, parsed correctly. +dummy_map dummy_os_user dummy_pg_role +# Error with incomplete lines. +incorrect_map +incorrect_map os_user +# Errors with lines that have multiple values, for each field. +incorrect_map_1,incorrect_map_2 +incorrect_map os_user_1,os_user_2 +incorrect_map os_user pg_role_1,pg_role_2 +)); + + # Check the existence of the dummy, still correct, entry added above. + $result = $node->safe_psql( + 'postgres', + qq(SELECT type, database, address, netmask, auth_method + FROM pg_hba_file_rules + WHERE error IS NULL AND user_name[1] = 'dummy_user' + ORDER BY line_number DESC LIMIT 1; + )); + is( $result, + qq(host|{dummy_db}|1.2.3.4|255.255.255.255|reject), + 'parsed dummy line of pg_hba.conf without errors'); + + # Check pg_hba.conf for its expected set of errors. + $result = $node->safe_psql( + 'postgres', + qq(SELECT error, count(error) FROM pg_hba_file_rules +WHERE error IS NOT NULL GROUP BY error ORDER BY error;)); + is( $result, qq(end-of-line before authentication method|1 +end-of-line before database specification|1 +end-of-line before IP address specification|1 +end-of-line before role specification|1), + 'parsed dummy lines of pg_hba.conf with expected errors'); + + # Check the existence of the dummy, still correct, entry added above. + $result = $node->safe_psql( + 'postgres', + qq(SELECT sys_name, pg_username + FROM pg_ident_file_mappings WHERE map_name = 'dummy_map' + AND error IS NULL; + )); + is( $result, + qq(dummy_os_user|dummy_pg_role), + 'parsed dummy line of pg_ident.conf without errors'); + + # Check pg_ident.conf for its expected set of errors. + $result = $node->safe_psql( + 'postgres', + qq(SELECT error, count(error) FROM pg_ident_file_mappings +WHERE error IS NOT NULL GROUP BY error ORDER BY error;)); + is( $result, qq(missing entry at end of line|2 +multiple values in ident field|3), + 'parsed dummy lines of pg_ident.conf with expected errors'); + +} + +done_testing(); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 27d19b4bf1..5a20e5a7e1 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1347,6 +1347,12 @@ pg_hba_file_rules| SELECT a.line_number, a.options, a.error FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error); +pg_ident_file_mappings| SELECT a.line_number, + a.map_name, + a.sys_name, + a.pg_username, + a.error + FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_username, error); pg_indexes| SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 442eeb1e3f..31ba549883 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -55,6 +55,12 @@ select count(*) > 0 as ok from pg_hba_file_rules; t (1 row) +select count(*) >= 0 as ok from pg_ident_file_mappings; + ok +---- + t +(1 row) + -- There will surely be at least one active lock select count(*) > 0 as ok from pg_locks; ok diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql index 4980f07be2..1148014e47 100644 --- a/src/test/regress/sql/sysviews.sql +++ b/src/test/regress/sql/sysviews.sql @@ -28,6 +28,8 @@ select count(*) >= 0 as ok from pg_file_settings; -- There will surely be at least one rule select count(*) > 0 as ok from pg_hba_file_rules; +select count(*) >= 0 as ok from pg_ident_file_mappings; + -- There will surely be at least one active lock select count(*) > 0 as ok from pg_locks; diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 94f01e4099..75fedfa07e 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9591,6 +9591,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l <entry>summary of client authentication configuration file contents</entry> </row> + <row> + <entry><link linkend="view-pg-hba-file-rules"><structname>pg_ident_file_mappings</structname></link></entry> + <entry>summary of client user name mapping configuration file contents</entry> + </row> + <row> <entry><link linkend="view-pg-indexes"><structname>pg_indexes</structname></link></entry> <entry>indexes</entry> @@ -10589,6 +10594,109 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para> </sect1> + <sect1 id="view-pg-ident-file-mappings"> + <title><structname>pg_ident_file_mappings</structname></title> + + <indexterm zone="view-pg-ident-file-mappings"> + <primary>pg_ident_file_mappings</primary> + </indexterm> + + <para> + The view <structname>pg_ident_file_mappings</structname> provides a summary + of the contents of the client user name mapping configuration file, + <link linkend="auth-username-maps"><filename>pg_ident.conf</filename></link>. + A row appears in this view for each + non-empty, non-comment line in the file, with annotations indicating + whether the rule could be applied successfully. + </para> + + <para> + This view can be helpful for checking whether planned changes in the + authentication configuration file will work, or for diagnosing a previous + failure. Note that this view reports on the <emphasis>current</emphasis> + contents of the file, not on what was last loaded by the server. + </para> + + <para> + By default, the <structname>pg_ident_file_mappings</structname> view can be + read only by superusers. + </para> + + <table> + <title><structname>pg_ident_file_mappings</structname> Columns</title> <tgroup + cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>line_number</structfield> <type>int4</type> + </para> + <para> + Line number of this rule in <filename>pg_ident.conf</filename> + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>map_name</structfield> <type>text</type> + </para> + <para> + Name of the map + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>sys_name</structfield> <type>text</type> + </para> + <para> + Detected user name of the client + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pg_username</structfield> <type>text</type> + </para> + <para> + Requested PostgreSQL user name + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>error</structfield> <type>text</type> + </para> + <para> + If not null, an error message indicating why this line could not be + processed + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <para> + Usually, a row reflecting an incorrect entry will have values for only + the <structfield>line_number</structfield> and <structfield>error</structfield> fields. + </para> + + <para> + See <xref linkend="client-authentication"/> for more information about + client authentication configuration. + </para> + </sect1> + <sect1 id="view-pg-indexes"> <title><structname>pg_indexes</structname></title> diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 02f0489112..142b0affcb 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -896,6 +896,16 @@ mymap /^(.*)@otherdomain\.com$ guest -HUP</literal>) to make it re-read the file. </para> + <para> + The system view + <link linkend="view-pg-ident-file-mappings"><structname>pg_ident_file_mappings</structname></link> + can be helpful for pre-testing changes to the + <filename>pg_ident.conf</filename> file, or for diagnosing problems if + loading of the file did not have the desired effects. Rows in the view with + non-null <structfield>error</structfield> fields indicate problems in the + corresponding lines of the file. + </para> + <para> A <filename>pg_ident.conf</filename> file that could be used in conjunction with the <filename>pg_hba.conf</filename> file in <xref diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 8a802fb225..b32cc61886 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -25475,8 +25475,9 @@ SELECT collation for ('foo' COLLATE "de_DE"); sending a <systemitem>SIGHUP</systemitem> signal to the postmaster process, which in turn sends <systemitem>SIGHUP</systemitem> to each of its children.) You can use the - <link linkend="view-pg-file-settings"><structname>pg_file_settings</structname></link> and - <link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link> views + <link linkend="view-pg-file-settings"><structname>pg_file_settings</structname></link>, + <link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link> and + <link linkend="view-pg-hba-file-rules"><structname>pg_ident_file_mappings</structname></link> views to check the configuration files for possible errors, before reloading. </para></entry> </row> -- 2.35.1
signature.asc
Description: PGP signature