agora inbox for [email protected]  
help / color / mirror / Atom feed
From: Julien Rouhaud <[email protected]>
Subject: [PATCH v1 1/3] Add a pg_ident_file_mappings view.
Date: Mon, 21 Feb 2022 17:38:34 +0800

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.

Author: Julien Rouhaud
Reviewed-by: FIXME
Discussion: FIXME
---
 doc/src/sgml/catalogs.sgml             | 108 ++++++++++++++
 doc/src/sgml/client-auth.sgml          |  10 ++
 doc/src/sgml/func.sgml                 |   5 +-
 src/backend/catalog/system_views.sql   |   6 +
 src/backend/libpq/hba.c                | 191 +++++++++++++++++++++++--
 src/include/catalog/pg_proc.dat        |   7 +
 src/test/regress/expected/rules.out    |   6 +
 src/test/regress/expected/sysviews.out |   7 +
 src/test/regress/sql/sysviews.sql      |   3 +
 9 files changed, 328 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 83987a9904..0de40a9626 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9530,6 +9530,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-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>
@@ -10523,6 +10528,109 @@ 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 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 df3cd5987b..bbac492043 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>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3cb69b1f87..ef13f470b3 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -607,6 +607,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 d84a40b726..9a9ce96fee 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -148,6 +148,9 @@ static ArrayType *gethba_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);
 
 
 /*
@@ -914,25 +917,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 if 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)
@@ -940,11 +940,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)
@@ -2753,7 +2754,8 @@ pg_hba_file_rules(PG_FUNCTION_ARGS)
  * 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.
@@ -2763,9 +2765,10 @@ pg_hba_file_rules(PG_FUNCTION_ARGS)
  * NULL.
  */
 static IdentLine *
-parse_ident_line(TokenizedLine *tok_line)
+parse_ident_line(TokenizedLine *tok_line, int elevel)
 {
 	int			line_num = tok_line->line_num;
+	char	  **err_msg = &tok_line->err_msg;
 	ListCell   *field;
 	List	   *tokens;
 	HbaToken   *token;
@@ -2819,11 +2822,14 @@ parse_ident_line(TokenizedLine *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;
 		}
@@ -3074,7 +3080,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;
@@ -3164,3 +3170,162 @@ hba_authname(UserAuth auth_method)
 
 	return UserAuthName[auth_method];
 }
+
+/* Number of columns in pg_hba_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 hbacxt;
+	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_file(HbaFileName, file, &ident_lines, DEBUG3);
+	FreeFile(file);
+
+	/* Now parse all the lines */
+	hbacxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "ident parser context",
+								   ALLOCSET_SMALL_SIZES);
+	oldcxt = MemoryContextSwitchTo(hbacxt);
+	foreach(line, ident_lines)
+	{
+		TokenizedLine *tok_line = (TokenizedLine *) 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);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * SQL-accessible SRF to return all the entries in the pg_ident.conf file.
+ */
+Datum
+pg_ident_file_mappings(PG_FUNCTION_ARGS)
+{
+	Tuplestorestate *tuple_store;
+	TupleDesc	tupdesc;
+	MemoryContext old_cxt;
+	ReturnSetInfo *rsi;
+
+	/*
+	 * We must use the Materialize mode to be safe against ident 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.
+	 */
+	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	/* Check to see if caller supports us returning a tuplestore */
+	if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsi->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	rsi->returnMode = SFRM_Materialize;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+	tuple_store =
+		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+							  false, work_mem);
+	rsi->setDesc = tupdesc;
+	rsi->setResult = tuple_store;
+
+	MemoryContextSwitchTo(old_cxt);
+
+	/* Fill the tuplestore */
+	fill_ident_view(tuple_store, tupdesc);
+
+	PG_RETURN_NULL();
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7f1ee97f55..2c8f5d9c13 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6121,6 +6121,13 @@
   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_usernamee,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/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 1420288d67..62cf0d8674 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_usernamee,
+    a.error
+   FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_usernamee, 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..d8a7df9498 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -55,6 +55,13 @@ select count(*) > 0 as ok from pg_hba_file_rules;
  t
 (1 row)
 
+-- We expect no user mapping in this test
+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..4c1129e787 100644
--- a/src/test/regress/sql/sysviews.sql
+++ b/src/test/regress/sql/sysviews.sql
@@ -28,6 +28,9 @@ 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;
 
+-- We expect no user mapping in this test
+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;
 
-- 
2.33.1


--j3tmyqztg67irqsk
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename="v1-0002-Allow-file-inclusion-in-pg_hba-and-pg_ident-files.patch"



view thread (64+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected]
  Subject: Re: [PATCH v1 1/3] Add a pg_ident_file_mappings view.
  In-Reply-To: <no-message-id-1860117@localhost>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox