Add system view pg_ident_file_mappings
authorMichael Paquier <michael@paquier.xyz>
Tue, 29 Mar 2022 01:15:48 +0000 (10:15 +0900)
committerMichael Paquier <michael@paquier.xyz>
Tue, 29 Mar 2022 01:15:48 +0000 (10:15 +0900)
This view is similar to pg_hba_file_rules view, except that it is
associated with the parsing of pg_ident.conf.  Similarly to its cousin,
this view is useful to check via SQL if changes planned in pg_ident.conf
would work upon reload or restart, or to diagnose a previous failure.

Bumps catalog version.

Author: Julien Rouhaud
Reviewed-by: Aleksander Alekseev, Michael Paquier
Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya@jrouhaud

12 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/client-auth.sgml
doc/src/sgml/func.sgml
src/backend/catalog/system_views.sql
src/backend/libpq/hba.c
src/backend/utils/adt/hbafuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/libpq/hba.h
src/test/regress/expected/rules.out
src/test/regress/expected/sysviews.out
src/test/regress/sql/sysviews.sql

index 23e06b81a4fdaf0f7edb635442685d036bebee73..7f4f79d1b556c20284a301da20ecfbc4aad2821d 100644 (file)
@@ -9591,6 +9591,11 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>summary of client authentication configuration file contents</entry>
      </row>
 
+     <row>
+      <entry><link linkend="view-pg-ident-file-mappings"><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,108 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</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 <literal>NULL</literal>, 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>
 
index 02f048911295695c144a55e846722902d3af4332..142b0affcb6ae8e84f83860cad1829489b304cfa 100644 (file)
@@ -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
index 3a9d62b4084ae34bb0c2572d082d2725ddb44e9f..93f11faada5b8efe7905647ca0532d505435f725 100644 (file)
@@ -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>
index 9570a53e7bece01f43a381236f15ef838bf77077..9eaa51df290564d5196bb18037d4ef7c154708e5 100644 (file)
@@ -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();
 
index 673135144d4d01ebe56fc39f45867137673f6608..f8393ca8ed42dffbdf35cbd2aa926dd1a77674b1 100644 (file)
@@ -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;
index f46cd935a1c6d1c062a8a1a2e65c09cb61fe5b2a..9fe7b62c9a0159d2b81ab6a4ad76c5f1a14b8257 100644 (file)
@@ -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_ident.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_ident.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();
+}
index 05c677e8161dc3e36f3e20808c0d9c0d9718c73e..96649193d9c8039394dfc92ef6ac5ae9eb734f59 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202203272
+#define CATALOG_VERSION_NO 202203291
 
 #endif
index deb00307f6d9fd1671b81193b0e3bddd53766eb0..01e1dd4d6d17d9613623d33c48c09fd737c807a2 100644 (file)
   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 => '',
index 13ecb329f801cdb105327be617bda53c6d8f67cf..90036f7bcd3e878232e825e84c9240b32c9c8dcc 100644 (file)
@@ -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);
index 92e1a2f6d8cb428d638767c68549250055433fdb..423b9b99fb6052bff5456e7bc46860d717514c1c 100644 (file)
@@ -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,
index 92b48502dd39d5eb84ec4619617906c570819714..579b861d84f31386724ed3b41216054aa0b3bab8 100644 (file)
@@ -56,6 +56,14 @@ select count(*) > 0 as ok, count(*) FILTER (WHERE error IS NOT NULL) = 0 AS no_e
  t  | t
 (1 row)
 
+-- There may be no rules, and there should be no errors.
+select count(*) >= 0 as ok, count(*) FILTER (WHERE error IS NOT NULL) = 0 AS no_err
+  from pg_ident_file_mappings;
+ ok | no_err 
+----+--------
+ t  | t
+(1 row)
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;
  ok 
index 77e48ef7cccc4224dbc4f1fe44e98a31fb887164..351e469c77bb79706a74219ef34787995dc07ed7 100644 (file)
@@ -29,6 +29,10 @@ select count(*) >= 0 as ok from pg_file_settings;
 select count(*) > 0 as ok, count(*) FILTER (WHERE error IS NOT NULL) = 0 AS no_err
   from pg_hba_file_rules;
 
+-- There may be no rules, and there should be no errors.
+select count(*) >= 0 as ok, count(*) FILTER (WHERE error IS NOT NULL) = 0 AS no_err
+  from pg_ident_file_mappings;
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;