From e982dade00a310e1828b771a275b4b5282ee942f Mon Sep 17 00:00:00 2001
From: Jelte Fennema <jelte.fennema@microsoft.com>
Date: Wed, 28 Dec 2022 09:56:04 +0100
Subject: [PATCH v2 2/2] Support using all for the db user in pg_ident.conf

While pg_hba.conf has supported the "all" keyword since a very long
time, pg_ident.conf doesn't have this same functionality. This changes
permission checking in pg_ident.conf to handle "all" differently from
any other value in the database-username column. If "all" is specified
and the system-user matches the identifier, then the user is allowed to
authenticate no matter what user it tries to authenticate as.

This change makes it much easier to have a certain database
administrator peer or cert authentication, that allows connecting as
any user. Without this change you would need to add a line to
pg_ident.conf for every user that is in the database.
---
 doc/src/sgml/client-auth.sgml         |  5 +++-
 src/backend/libpq/hba.c               | 34 +++++++++++++++++++--------
 src/backend/utils/adt/hbafuncs.c      |  2 +-
 src/include/libpq/hba.h               |  2 +-
 src/test/authentication/t/003_peer.pl | 21 +++++++++++++++++
 5 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index cc8c59206c..64c7713057 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -941,7 +941,10 @@ local   db1,db2,@demodbs  all                                   md5
    implying that they are equivalent.  The connection will be allowed if
    there is any map entry that pairs the user name obtained from the
    external authentication system with the database user name that the
-   user has requested to connect as.
+   user has requested to connect as. The value <literal>all</literal>
+   can be used as the <replaceable>database-username</replaceable> to specify
+   that all users are matched. Quoting <literal>all</literal> makes the keyword 
+   lose its special meaning.
   </para>
   <para>
    If the <replaceable>system-username</replaceable> field starts with a slash (<literal>/</literal>),
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3e966acfc0..045faa7520 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -2800,7 +2800,7 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
 	tokens = lfirst(field);
 	IDENT_MULTI_VALUE(tokens);
 	token = linitial(tokens);
-	parsedline->pg_role = pstrdup(token->string);
+	parsedline->pg_role = copy_auth_token(token);
 
 	/*
 	 * Now that the field validation is done, compile a regex from the user
@@ -2865,7 +2865,16 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
 			return;
 		}
 
-		if ((ofs = strstr(identLine->pg_role, "\\1")) != NULL)
+		/*
+		 * We can return early if the pg_role is the all keyword
+		 */
+		if (token_is_keyword(identLine->pg_role, "all"))
+		{
+			*found_p = true;
+			return;
+		}
+
+		if ((ofs = strstr(identLine->pg_role->string, "\\1")) != NULL)
 		{
 			int			offset;
 
@@ -2875,7 +2884,7 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
 				ereport(LOG,
 						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
 						 errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
-								identLine->systemuser->string + 1, identLine->pg_role)));
+								identLine->systemuser->string + 1, identLine->pg_role->string)));
 				*error_p = true;
 				return;
 			}
@@ -2884,9 +2893,9 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
 			 * length: original length minus length of \1 plus length of match
 			 * plus null terminator
 			 */
-			regexp_pgrole = palloc0(strlen(identLine->pg_role) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
-			offset = ofs - identLine->pg_role;
-			memcpy(regexp_pgrole, identLine->pg_role, offset);
+			regexp_pgrole = palloc0(strlen(identLine->pg_role->string) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
+			offset = ofs - identLine->pg_role->string;
+			memcpy(regexp_pgrole, identLine->pg_role->string, offset);
 			memcpy(regexp_pgrole + offset,
 				   ident_user + matches[1].rm_so,
 				   matches[1].rm_eo - matches[1].rm_so);
@@ -2895,7 +2904,7 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
 		else
 		{
 			/* no substitution, so copy the match */
-			regexp_pgrole = pstrdup(identLine->pg_role);
+			regexp_pgrole = pstrdup(identLine->pg_role->string);
 		}
 
 		/*
@@ -2918,16 +2927,19 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
 	}
 	else
 	{
-		/* Not regular expression, so make complete match */
 		if (case_insensitive)
 		{
-			if (pg_strcasecmp(identLine->pg_role, pg_role) == 0 &&
+			if (
+				(token_is_keyword(identLine->pg_role, "all") ||
+				 pg_strcasecmp(identLine->pg_role->string, pg_role) == 0) &&
 				pg_strcasecmp(identLine->systemuser->string, ident_user) == 0)
 				*found_p = true;
 		}
 		else
 		{
-			if (strcmp(identLine->pg_role, pg_role) == 0 &&
+			if (
+				(token_is_keyword(identLine->pg_role, "all") ||
+				 strcmp(identLine->pg_role->string, pg_role) == 0) &&
 				strcmp(identLine->systemuser->string, ident_user) == 0)
 				*found_p = true;
 		}
@@ -3074,6 +3086,7 @@ load_ident(void)
 		{
 			newline = (IdentLine *) lfirst(parsed_line_cell);
 			free_auth_token(newline->systemuser);
+			free_auth_token(newline->pg_role);
 		}
 		MemoryContextDelete(ident_context);
 		return false;
@@ -3086,6 +3099,7 @@ load_ident(void)
 		{
 			newline = (IdentLine *) lfirst(parsed_line_cell);
 			free_auth_token(newline->systemuser);
+			free_auth_token(newline->pg_role);
 		}
 	}
 	if (parsed_ident_context != NULL)
diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c
index be6b513d64..31b5f2241f 100644
--- a/src/backend/utils/adt/hbafuncs.c
+++ b/src/backend/utils/adt/hbafuncs.c
@@ -493,7 +493,7 @@ fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
 	{
 		values[index++] = CStringGetTextDatum(ident->usermap);
 		values[index++] = CStringGetTextDatum(ident->systemuser->string);
-		values[index++] = CStringGetTextDatum(ident->pg_role);
+		values[index++] = CStringGetTextDatum(ident->pg_role->string);
 	}
 	else
 	{
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index f1f27d6138..c52405387d 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -142,8 +142,8 @@ typedef struct IdentLine
 	int			linenumber;
 
 	char	   *usermap;
-	char	   *pg_role;
 	AuthToken  *systemuser;
+	AuthToken  *pg_role;
 } IdentLine;
 
 /*
diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl
index 26c34d05d3..f6d57f77f0 100644
--- a/src/test/authentication/t/003_peer.pl
+++ b/src/test/authentication/t/003_peer.pl
@@ -123,11 +123,32 @@ test_role($node, qq{testmapuser}, 'peer', 0, 'with user name map',
 	log_like =>
 	  [qr/connection authenticated: identity="$system_user" method=peer/]);
 
+# Tests with the "all" keyword
+reset_pg_ident($node, 'mypeermap', $system_user, 'all');
+
+# Success as the database role is the "all" keyword
+test_role($node, qq{testmapuser}, 'peer', 0, 'with user name map',
+	log_like =>
+	  [qr/connection authenticated: identity="$system_user" method=peer/]);
+
 # Test with regular expression in user name map.
 # Extract the last 3 characters from the system_user
 # or the entire system_user (if its length is <= -3).
 my $regex_test_string = substr($system_user, -3);
 
+# Success as the regular expression matches and database role is the "all"
+# keyword.
+reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$},
+	'all');
+test_role(
+	$node,
+	qq{testmapuser},
+	'peer',
+	0,
+	'with regular expression in user name map',
+	log_like =>
+	  [qr/connection authenticated: identity="$system_user" method=peer/]);
+
 # Success as the regular expression matches.
 reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$},
 	'testmapuser');
-- 
2.34.1

