Add system view pg_ident_file_mappings

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
This commit is contained in:
Michael Paquier 2022-03-29 10:15:48 +09:00
parent 091a971bb5
commit a2c84990be
12 changed files with 305 additions and 17 deletions

View 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>

View 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

View 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>

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

View 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;

View 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();
}

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202203272
#define CATALOG_VERSION_NO 202203291
#endif

View File

@ -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 => '',

View 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);

View 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,

View 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

View 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;