SYSTEM_USER reserved word implementation

Started by Drouvot, Bertrandover 3 years ago42 messages
#1Drouvot, Bertrand
bdrouvot@amazon.com
1 attachment(s)

Hi hackers,

The SYSTEM_USER is a sql reserved word as mentioned in [1]https://www.postgresql.org/docs/current/sql-keywords-appendix.html and is
currently not implemented.

Please find attached a patch proposal to make use of the SYSTEM_USER so
that it returns the authenticated identity (if any) (aka authn_id in the
Port struct).

Indeed in some circumstances, the authenticated identity is not the
SESSION_USER and then the information is lost from the connection point
of view (it could still be retrieved thanks to commit 9afffcb833 and
log_connections set to on).

_Example 1, using the gss authentification._

Say we have this entry in pg_hba.conf:

host all all 0.0.0.0/0 gss map=mygssmap

and the related mapping in pg_ident.conf

mygssmap   /^(.*@.*)\.LOCAL$    mary

Then, connecting with a valid Kerberos Ticket that contains
“bertrand@BDTFOREST.LOCAL” as the default principal that way: psql -U
mary -h myhostname -d postgres,

we will get:

postgres=> select current_user, session_user;
 current_user | session_user
--------------+--------------
 mary         | mary
(1 row)

While the SYSTEM_USER would produce the Kerberos principal:

postgres=> select system_user;
       system_user
--------------------------
bertrand@BDTFOREST.LOCAL
(1 row)

_Example 2, using the peer authentification._

Say we have this entry in pg_hba.conf:

local all john peer map=mypeermap

and the related mapping in pg_ident.conf

mypeermap postgres john

Then connected localy as the system user postgres and connecting to the
database that way: psql -U john -d postgres, we will get:

postgres=> select current_user, session_user;
 current_user | session_user
--------------+--------------
 john         | john
(1 row)

While the SYSTEM_USER would produce the system user that requested the
connection:

postgres=> select system_user;
 system_user
-------------
 postgres
(1 row)

Thanks to those examples we have seen some situations where the
information related to the authenticated identity has been lost from the
connection point of view (means not visible in the current_session or in
the session_user).

The purpose of this patch is to make it visible through the SYSTEM_USER
sql reserved word.

_Remarks: _

- In case port->authn_id is NULL then the patch is returning the
SESSION_USER for the SYSTEM_USER. Perhaps it should return NULL instead.

- There is another thread [2]/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com to expose port->authn_id to extensions and
triggers thanks to a new API. This thread [2]/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com leads to discussions about
providing this information to the parallel workers too. While the new
MyClientConnectionInfo being discussed in [2]/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com could be useful to hold
the client information that needs to be shared between the backend and
any parallel workers, it does not seem to be needed in the case
port->authn_id is exposed through SYSTEM_USER (like it is not for
CURRENT_USER and SESSION_USER).

I will add this patch to the next commitfest.
I look forward to your feedback.

Bertrand

[1]: https://www.postgresql.org/docs/current/sql-keywords-appendix.html
[2]: /messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com
/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com

Attachments:

v1-0001-system_user-implementation.patchtext/plain; charset=UTF-8; name=v1-0001-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 478a216dbb..07bb333a3b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23660,6 +23660,20 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the identity (if any) that the user presented during the
+        authentication cycle, before they were assigned a database role.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index e44ad68cda..63cf3d8bb7 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2543,6 +2543,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..29dcc77724 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -842,7 +842,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15457,6 +15457,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18173,6 +18177,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18578,6 +18583,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0dc2fc472e..8256cc177d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2299,6 +2299,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..8ad50e31d2 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1917,6 +1917,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index e8bba3670c..f8e69fb7ec 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetSystemUser())));
+	else
+		return session_user(fcinfo);
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c3937a60fd..ffdd818f1e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9319,6 +9319,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index b25bd0e583..4984da23a7 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -473,6 +473,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -544,6 +545,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user (the authenticated identity), which may be different
+ * from the session user.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -814,6 +825,19 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user during normal backend startup.
+ */
+void
+InitializeSystemUser(const char *system_user)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	/* If system_user is not NULL, store it in long-lived storage */
+	if (system_user)
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6b9082604f..a3199a17ea 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -835,6 +835,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyProcPort->authn_id);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a33..4406396055 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0af130fbc5..de173c1387 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -364,6 +364,8 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void InitializeSystemUser(const char *system_user);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 51505eee85..c23b0f2507 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1186,6 +1186,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..2f6e6e4d38 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -307,6 +307,15 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT CURRENT_USER,SYSTEM_USER;',
+	qr/^test1|test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Drouvot, Bertrand (#1)
Re: SYSTEM_USER reserved word implementation

"Drouvot, Bertrand" <bdrouvot@amazon.com> writes:

Please find attached a patch proposal to make use of the SYSTEM_USER so
that it returns the authenticated identity (if any) (aka authn_id in the
Port struct).

On what grounds do you argue that that's the appropriate meaning of
SYSTEM_USER?

regards, tom lane

#3Joe Conway
mail@joeconway.com
In reply to: Tom Lane (#2)
Re: SYSTEM_USER reserved word implementation

On 6/22/22 09:49, Tom Lane wrote:

"Drouvot, Bertrand" <bdrouvot@amazon.com> writes:

Please find attached a patch proposal to make use of the SYSTEM_USER so
that it returns the authenticated identity (if any) (aka authn_id in the
Port struct).

On what grounds do you argue that that's the appropriate meaning of
SYSTEM_USER?

What else do you imagine it might mean?

Here is SQL Server interpretation for example:

https://docs.microsoft.com/en-us/sql/t-sql/functions/system-user-transact-sql?view=sql-server-ver16

And Oracle:
http://luna-ext.di.fc.ul.pt/oracle11g/timesten.112/e13070/ttsql257.htm#i1120532

"SYSTEM_USER

Returns the name of the current data store user
as identified by the operating system."

Seems equivalent.

--
Joe Conway
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Joe Conway (#3)
Re: SYSTEM_USER reserved word implementation

Joe Conway <mail@joeconway.com> writes:

On 6/22/22 09:49, Tom Lane wrote:

On what grounds do you argue that that's the appropriate meaning of
SYSTEM_USER?

What else do you imagine it might mean?

I was hoping for some citation of the SQL spec.

Here is SQL Server interpretation for example:
"SYSTEM_USER
Returns the name of the current data store user
as identified by the operating system."

Meh. That's as clear as mud. (a) A big part of the question here
is what is meant by "current" user, in the face of operations like
SET ROLE. (b) "as identified by the operating system" does more to
confuse me than anything else. The operating system only deals in
OS user names; does that wording mean that what you get back is an OS
user name rather than a SQL role name?

My immediate guess would be that the SQL committee only intends
to deal in SQL role names and therefore SYSTEM_USER is defined
to return one of those, but I've not gone looking in the spec
to be sure.

I'm also not that clear on what we expect authn_id to be, but
a quick troll in the code makes it look like it's not necessarily
a SQL role name, but might be some external identifier such as a
Kerberos principal. If that's the case I think it's going to be
inappropriate to use SQL-spec syntax to return it. I don't object
to inventing some PG-specific function for the purpose, though.

BTW, are there any security concerns about exposing such identifiers?

regards, tom lane

#5Joe Conway
mail@joeconway.com
In reply to: Tom Lane (#4)
Re: SYSTEM_USER reserved word implementation

On 6/22/22 10:51, Tom Lane wrote:

My immediate guess would be that the SQL committee only intends
to deal in SQL role names and therefore SYSTEM_USER is defined
to return one of those, but I've not gone looking in the spec
to be sure.

I only have a draft copy, but in SQL 2016 I find relatively thin
documentation for what SYSTEM_USER is supposed to represent:

The value specified by SYSTEM_USER is equal to an
implementation-defined string that represents the
operating system user who executed the SQL-client
module that contains the externally-invoked procedure
whose execution caused the SYSTEM_USER <general value
specification> to be evaluated.

I'm also not that clear on what we expect authn_id to be, but
a quick troll in the code makes it look like it's not necessarily
a SQL role name, but might be some external identifier such as a
Kerberos principal. If that's the case I think it's going to be
inappropriate to use SQL-spec syntax to return it. I don't object
to inventing some PG-specific function for the purpose, though.

To me the Kerberos principal makes perfect sense given the definition above.

BTW, are there any security concerns about exposing such identifiers?

On the contrary, I would argue that not having the identifier for the
external "user" available is a security concern. Ideally you want to be
able to trace actions inside Postgres to the actual user that invoked them.

--
Joe Conway
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Joe Conway (#5)
Re: SYSTEM_USER reserved word implementation

Joe Conway <mail@joeconway.com> writes:

On 6/22/22 10:51, Tom Lane wrote:

My immediate guess would be that the SQL committee only intends
to deal in SQL role names and therefore SYSTEM_USER is defined
to return one of those, but I've not gone looking in the spec
to be sure.

I only have a draft copy, but in SQL 2016 I find relatively thin
documentation for what SYSTEM_USER is supposed to represent:

The value specified by SYSTEM_USER is equal to an
implementation-defined string that represents the
operating system user who executed the SQL-client
module that contains the externally-invoked procedure
whose execution caused the SYSTEM_USER <general value
specification> to be evaluated.

Huh. Okay, if it's implementation-defined then we can define it
as "whatever auth.c put into authn_id". Objection withdrawn.

regards, tom lane

#7Jacob Champion
jchampion@timescale.com
In reply to: Joe Conway (#5)
Re: SYSTEM_USER reserved word implementation

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

On the contrary, I would argue that not having the identifier for the
external "user" available is a security concern. Ideally you want to be
able to trace actions inside Postgres to the actual user that invoked them.

If auditing is also the use case for SYSTEM_USER, you'll probably want
to review the arguments for making it available to parallel workers
that were made in the other thread [1]/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com.

Initial comments on the patch:

In case port->authn_id is NULL then the patch is returning the SESSION_USER for the SYSTEM_USER. Perhaps it should return NULL instead.

If the spec says that SYSTEM_USER "represents the operating system
user", but we don't actually know who that user was (authn_id is
NULL), then I think SYSTEM_USER should also be NULL so as not to
mislead auditors.

--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -473,6 +473,7 @@ static Oid    AuthenticatedUserId = InvalidOid;
static Oid    SessionUserId = InvalidOid;
static Oid    OuterUserId = InvalidOid;
static Oid    CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;

/* We also have to remember the superuser state of some of these levels */
static bool AuthenticatedUserIsSuperuser = false;

What's the rationale for introducing a new global for this? A downside
is that now there are two sources of truth, for a security-critical
attribute of the connection.

--Jacob

[1]: /messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jacob Champion (#7)
Re: SYSTEM_USER reserved word implementation

Jacob Champion <jchampion@timescale.com> writes:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

In case port->authn_id is NULL then the patch is returning the SESSION_USER for the SYSTEM_USER. Perhaps it should return NULL instead.

If the spec says that SYSTEM_USER "represents the operating system
user", but we don't actually know who that user was (authn_id is
NULL), then I think SYSTEM_USER should also be NULL so as not to
mislead auditors.

Yeah, that seems like a fundamental type mismatch. If we don't know
the OS user identifier, substituting a SQL role name is surely not
the right thing.

I think a case could be made for ONLY returning non-null when authn_id
represents some externally-verified identifier (OS user ID gotten via
peer identification, Kerberos principal, etc).

regards, tom lane

#9Joe Conway
mail@joeconway.com
In reply to: Tom Lane (#8)
Re: SYSTEM_USER reserved word implementation

On 6/22/22 11:52, Tom Lane wrote:

Jacob Champion <jchampion@timescale.com> writes:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

In case port->authn_id is NULL then the patch is returning the SESSION_USER for the SYSTEM_USER. Perhaps it should return NULL instead.

If the spec says that SYSTEM_USER "represents the operating system
user", but we don't actually know who that user was (authn_id is
NULL), then I think SYSTEM_USER should also be NULL so as not to
mislead auditors.

Yeah, that seems like a fundamental type mismatch. If we don't know
the OS user identifier, substituting a SQL role name is surely not
the right thing.

+1 agreed

I think a case could be made for ONLY returning non-null when authn_id
represents some externally-verified identifier (OS user ID gotten via
peer identification, Kerberos principal, etc).

But -1 on that.

I think any time we have a non-null authn_id we should expose it. Are
there examples of cases when we have authn_id but for some reason don't
trust the value of it?

--
Joe Conway
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#10Joe Conway
mail@joeconway.com
In reply to: Jacob Champion (#7)
Re: SYSTEM_USER reserved word implementation

On 6/22/22 11:35, Jacob Champion wrote:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -473,6 +473,7 @@ static Oid    AuthenticatedUserId = InvalidOid;
static Oid    SessionUserId = InvalidOid;
static Oid    OuterUserId = InvalidOid;
static Oid    CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;

/* We also have to remember the superuser state of some of these levels */
static bool AuthenticatedUserIsSuperuser = false;

What's the rationale for introducing a new global for this? A downside
is that now there are two sources of truth, for a security-critical
attribute of the connection.

Why would you want to do it differently than
SessionUserId/OuterUserId/CurrentUserId? It is analogous, no?

--
Joe Conway
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Joe Conway (#9)
Re: SYSTEM_USER reserved word implementation

Joe Conway <mail@joeconway.com> writes:

On 6/22/22 11:52, Tom Lane wrote:

I think a case could be made for ONLY returning non-null when authn_id
represents some externally-verified identifier (OS user ID gotten via
peer identification, Kerberos principal, etc).

But -1 on that.

I think any time we have a non-null authn_id we should expose it. Are
there examples of cases when we have authn_id but for some reason don't
trust the value of it?

I'm more concerned about whether we have a consistent story about what
SYSTEM_USER means (another way of saying "what type is it"). If it's
just the same as SESSION_USER it doesn't seem like we've added much.

Maybe, instead of just being the raw user identifier, it should be
something like "auth_method:user_identifier" so that one can tell
what the identifier actually is and how it was verified.

regards, tom lane

#12Joe Conway
mail@joeconway.com
In reply to: Tom Lane (#11)
Re: SYSTEM_USER reserved word implementation

On 6/22/22 12:28, Tom Lane wrote:

Joe Conway <mail@joeconway.com> writes:

On 6/22/22 11:52, Tom Lane wrote:

I think a case could be made for ONLY returning non-null when authn_id
represents some externally-verified identifier (OS user ID gotten via
peer identification, Kerberos principal, etc).

But -1 on that.

I think any time we have a non-null authn_id we should expose it. Are
there examples of cases when we have authn_id but for some reason don't
trust the value of it?

I'm more concerned about whether we have a consistent story about what
SYSTEM_USER means (another way of saying "what type is it"). If it's
just the same as SESSION_USER it doesn't seem like we've added much.

Maybe, instead of just being the raw user identifier, it should be
something like "auth_method:user_identifier" so that one can tell
what the identifier actually is and how it was verified.

Oh, that's an interesting thought -- I like that.

--
Joe Conway
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#13Jacob Champion
jchampion@timescale.com
In reply to: Joe Conway (#10)
Re: SYSTEM_USER reserved word implementation

On Wed, Jun 22, 2022 at 9:26 AM Joe Conway <mail@joeconway.com> wrote:

On 6/22/22 11:35, Jacob Champion wrote:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

Why would you want to do it differently than
SessionUserId/OuterUserId/CurrentUserId? It is analogous, no?

Like I said, now there are two different sources of truth, and
additional code to sync the two, and two different APIs to set what
should be a single write-once attribute. But if SystemUser is instead
derived from authn_id, like what's just been proposed with
`method:authn_id`, I think there's a better argument for separating
the two.

--Jacob

#14David G. Johnston
david.g.johnston@gmail.com
In reply to: Tom Lane (#11)
Re: SYSTEM_USER reserved word implementation

On Wed, Jun 22, 2022 at 9:28 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Joe Conway <mail@joeconway.com> writes:

On 6/22/22 11:52, Tom Lane wrote:

I think a case could be made for ONLY returning non-null when authn_id
represents some externally-verified identifier (OS user ID gotten via
peer identification, Kerberos principal, etc).

But -1 on that.

I think any time we have a non-null authn_id we should expose it. Are
there examples of cases when we have authn_id but for some reason don't
trust the value of it?

I'm more concerned about whether we have a consistent story about what
SYSTEM_USER means (another way of saying "what type is it"). If it's
just the same as SESSION_USER it doesn't seem like we've added much.

Maybe, instead of just being the raw user identifier, it should be
something like "auth_method:user_identifier" so that one can tell
what the identifier actually is and how it was verified.

I was thinking this was trying to make the following possible:

psql -U postgres
# set session authorization other_superuser;
# set role other_role;
# select system_user, session_user, current_user;
postgres | other_superuser | other_role

Though admittedly using "system" for that seems somehow wrong.
connection_user would make more sense. Then the system_user would be, if
applicable, an external identifier that got matched with the assigned
connection_user. I can definitely see having the external identifier be a
structured value.

David J.

#15Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Joe Conway (#12)
Re: SYSTEM_USER reserved word implementation

Hi,

On 6/22/22 6:32 PM, Joe Conway wrote:

CAUTION: This email originated from outside of the organization. Do
not click links or open attachments unless you can confirm the sender
and know the content is safe.

On 6/22/22 12:28, Tom Lane wrote:

Joe Conway <mail@joeconway.com> writes:

On 6/22/22 11:52, Tom Lane wrote:

I think a case could be made for ONLY returning non-null when authn_id
represents some externally-verified identifier (OS user ID gotten via
peer identification, Kerberos principal, etc).

But -1 on that.

I think any time we have a non-null authn_id we should expose it. Are
there examples of cases when we have authn_id but for some reason don't
trust the value of it?

I'm more concerned about whether we have a consistent story about what
SYSTEM_USER means (another way of saying "what type is it").  If it's
just the same as SESSION_USER it doesn't seem like we've added much.

Maybe, instead of just being the raw user identifier, it should be
something like "auth_method:user_identifier" so that one can tell
what the identifier actually is and how it was verified.

Oh, that's an interesting thought -- I like that.

Thanks Joe and Tom for your feedback.

I like this idea too and that's also more aligned with what
log_connections set to on would report (aka the auth method).

Baring any objections, I'll work on that idea.

Bertrand

#16Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Jacob Champion (#7)
Re: SYSTEM_USER reserved word implementation

Hi,

On 6/22/22 5:35 PM, Jacob Champion wrote:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

On the contrary, I would argue that not having the identifier for the
external "user" available is a security concern. Ideally you want to be
able to trace actions inside Postgres to the actual user that invoked them.

If auditing is also the use case for SYSTEM_USER, you'll probably want
to review the arguments for making it available to parallel workers
that were made in the other thread [1].

Thanks Jacob for your feedback.

I did some testing initially around the parallel workers and did not see
any issues at that time.

I just had another look and I agree that the parallel workers case needs
to be addressed.

I'll have a closer look to what you have done in [1]/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com.

Thanks

Bertrand

[1]: /messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com

#17Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Drouvot, Bertrand (#16)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 6/23/22 10:06 AM, Drouvot, Bertrand wrote:

Hi,

On 6/22/22 5:35 PM, Jacob Champion wrote:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

On the contrary, I would argue that not having the identifier for the
external "user" available is a security concern. Ideally you want to be
able to trace actions inside Postgres to the actual user that
invoked them.

If auditing is also the use case for SYSTEM_USER, you'll probably want
to review the arguments for making it available to parallel workers
that were made in the other thread [1].

Thanks Jacob for your feedback.

I did some testing initially around the parallel workers and did not
see any issues at that time.

I just had another look and I agree that the parallel workers case
needs to be addressed.

I'll have a closer look to what you have done in [1].

Thanks

Bertrand

Please find attached patch version 2.

It does contain:

- Tom's idea implementation (aka presenting the system_user as
auth_method:authn_id)

- A fix for the parallel workers issue mentioned by Jacob. The patch now
propagates the SYSTEM_USER to the parallel workers.

- Doc updates

- Tap tests (some of them are coming from [1]/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com)

Looking forward to your feedback,

Thanks

Bertrand

[1]: /messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com
/messages/by-id/793d990837ae5c06a558d58d62de9378ab525d83.camel@vmware.com

Attachments:

v2-0001-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0001-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b652460a1..a59d33074b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23660,6 +23660,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index df0cd77558..3b49189bfd 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -76,6 +76,7 @@
 #define PARALLEL_KEY_REINDEX_STATE			UINT64CONST(0xFFFFFFFFFFFF000C)
 #define PARALLEL_KEY_RELMAPPER_STATE		UINT64CONST(0xFFFFFFFFFFFF000D)
 #define PARALLEL_KEY_UNCOMMITTEDENUMS		UINT64CONST(0xFFFFFFFFFFFF000E)
+#define PARALLEL_KEY_SYSTEMUSER				UINT64CONST(0xFFFFFFFFFFFF000F)
 
 /* Fixed-size parallel state. */
 typedef struct FixedParallelState
@@ -212,6 +213,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	Size		reindexlen = 0;
 	Size		relmapperlen = 0;
 	Size		uncommittedenumslen = 0;
+	Size		systemuserlen = 0;
 	Size		segsize = 0;
 	int			i;
 	FixedParallelState *fps;
@@ -272,8 +274,10 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_estimate_chunk(&pcxt->estimator, relmapperlen);
 		uncommittedenumslen = EstimateUncommittedEnumsSpace();
 		shm_toc_estimate_chunk(&pcxt->estimator, uncommittedenumslen);
+		systemuserlen = EstimateSystemUserSpace();
+		shm_toc_estimate_chunk(&pcxt->estimator, systemuserlen);
 		/* If you add more chunks here, you probably need to add keys. */
-		shm_toc_estimate_keys(&pcxt->estimator, 11);
+		shm_toc_estimate_keys(&pcxt->estimator, 12);
 
 		/* Estimate space need for error queues. */
 		StaticAssertStmt(BUFFERALIGN(PARALLEL_ERROR_QUEUE_SIZE) ==
@@ -352,6 +356,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		char	   *session_dsm_handle_space;
 		char	   *entrypointstate;
 		char	   *uncommittedenumsspace;
+		char	   *systemuserspace;
 		Size		lnamelen;
 
 		/* Serialize shared libraries we have loaded. */
@@ -422,6 +427,12 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_insert(pcxt->toc, PARALLEL_KEY_UNCOMMITTEDENUMS,
 					   uncommittedenumsspace);
 
+		/* Serialize our Systemuser. */
+		systemuserspace = shm_toc_allocate(pcxt->toc, systemuserlen);
+		SerializeSystemUser(systemuserlen, systemuserspace);
+		shm_toc_insert(pcxt->toc, PARALLEL_KEY_SYSTEMUSER,
+					   systemuserspace);
+
 		/* Allocate space for worker information. */
 		pcxt->worker = palloc0(sizeof(ParallelWorkerInfo) * pcxt->nworkers);
 
@@ -1270,6 +1281,7 @@ ParallelWorkerMain(Datum main_arg)
 	char	   *reindexspace;
 	char	   *relmapperspace;
 	char	   *uncommittedenumsspace;
+	char	   *systemuserspace;
 	StringInfoData msgbuf;
 	char	   *session_dsm_handle_space;
 	Snapshot	tsnapshot;
@@ -1479,6 +1491,11 @@ ParallelWorkerMain(Datum main_arg)
 										   false);
 	RestoreUncommittedEnums(uncommittedenumsspace);
 
+	/* Restore the SystemUser. */
+	systemuserspace = shm_toc_lookup(toc, PARALLEL_KEY_SYSTEMUSER,
+										 false);
+	RestoreSystemUser(systemuserspace);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index eaec697bb3..431a94a3de 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2543,6 +2543,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..29dcc77724 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -842,7 +842,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15457,6 +15457,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18173,6 +18177,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18578,6 +18583,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0dc2fc472e..8256cc177d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2299,6 +2299,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..8ad50e31d2 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1917,6 +1917,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index e8bba3670c..aa28b32cf0 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(sysuser)));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c3937a60fd..ffdd818f1e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9319,6 +9319,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index b25bd0e583..75f1fe35e2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -473,6 +473,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -544,6 +545,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -814,6 +825,32 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user during normal backend startup.
+ */
+void
+InitializeSystemUser(const Port *port)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (port->authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(port->hba->auth_method));
+		Size authn_id_len = strlen(port->authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(port->hba->auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, port->authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
 
 /*
  * Change session auth ID while running
@@ -932,6 +969,53 @@ GetUserNameFromId(Oid roleid, bool noerr)
 	return result;
 }
 
+/*
+ * Calculate the space needed to serialize SystemUser.
+ */
+Size
+EstimateSystemUserSpace(void)
+{
+	Size		size = 1;
+	const char	*sysuser = GetSystemUser();
+
+	if (sysuser)
+		size = add_size(size, strlen(sysuser) + 1);
+
+	return size;
+}
+
+/*
+ * Serialize SystemUser for use by parallel workers.
+ */
+void
+SerializeSystemUser(Size maxsize, char *start_address)
+{
+	const char 	*sysuser = GetSystemUser();
+
+	Assert(maxsize > 0);
+
+	if (sysuser)
+	{
+		Size len;
+		len = strlcpy(start_address, sysuser, maxsize) + 1;
+		Assert(len <= maxsize);
+	}
+	else
+		start_address[0] = '\0';
+}
+
+/*
+ * Restore SystemUser from its serialized representation.
+ */
+void
+RestoreSystemUser(char *sysuser)
+{
+	if (sysuser[0] == '\0')
+		SystemUser = NULL;
+	else
+		SystemUser = MemoryContextStrdup(TopMemoryContext, sysuser);
+}
+
 
 /*-------------------------------------------------------------------------
  *				Interlock-file support
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6b9082604f..d9a879382e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -835,6 +835,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyProcPort);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a33..4406396055 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0af130fbc5..8d761512fd 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -364,6 +364,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+typedef struct Port MyPort;
+extern void InitializeSystemUser(const MyPort *port);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
@@ -486,6 +490,10 @@ extern bool has_rolreplication(Oid roleid);
 typedef void (*shmem_request_hook_type) (void);
 extern PGDLLIMPORT shmem_request_hook_type shmem_request_hook;
 
+extern Size EstimateSystemUserSpace(void);
+extern void SerializeSystemUser(Size maxsize, char *start_address);
+extern void RestoreSystemUser(char *sysuser);
+
 /* in executor/nodeHash.c */
 extern size_t get_hash_memory_limit(void);
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 51505eee85..c23b0f2507 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1186,6 +1186,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..9bf17985d5 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -307,6 +307,29 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"CREATE TEMP TABLE mytab (n) as SELECT NULL FROM generate_series(1, 200000);\n"
+	. "SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER IS DISTINCT FROM n) FROM mytab;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#18Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Drouvot, Bertrand (#17)
Re: SYSTEM_USER reserved word implementation

On 6/24/22 11:49 AM, Drouvot, Bertrand wrote:

Hi,

On 6/23/22 10:06 AM, Drouvot, Bertrand wrote:

Hi,

On 6/22/22 5:35 PM, Jacob Champion wrote:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

On the contrary, I would argue that not having the identifier for the
external "user" available is a security concern. Ideally you want
to be
able to trace actions inside Postgres to the actual user that
invoked them.

If auditing is also the use case for SYSTEM_USER, you'll probably want
to review the arguments for making it available to parallel workers
that were made in the other thread [1].

Thanks Jacob for your feedback.

I did some testing initially around the parallel workers and did not
see any issues at that time.

I just had another look and I agree that the parallel workers case
needs to be addressed.

I'll have a closer look to what you have done in [1].

Thanks

Bertrand

Please find attached patch version 2.

It does contain:

- Tom's idea implementation (aka presenting the system_user as
auth_method:authn_id)

- A fix for the parallel workers issue mentioned by Jacob. The patch
now propagates the SYSTEM_USER to the parallel workers.

- Doc updates

- Tap tests (some of them are coming from [1])

Looking forward to your feedback,

Thanks

Bertrand

FWIW here is a link to the commitfest entry:
https://commitfest.postgresql.org/38/3703/

Bertrand

#19Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Drouvot, Bertrand (#18)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 6/24/22 2:47 PM, Drouvot, Bertrand wrote:

On 6/24/22 11:49 AM, Drouvot, Bertrand wrote:

Hi,

On 6/23/22 10:06 AM, Drouvot, Bertrand wrote:

Hi,

On 6/22/22 5:35 PM, Jacob Champion wrote:

On Wed, Jun 22, 2022 at 8:10 AM Joe Conway <mail@joeconway.com> wrote:

On the contrary, I would argue that not having the identifier for the
external "user" available is a security concern. Ideally you want
to be
able to trace actions inside Postgres to the actual user that
invoked them.

If auditing is also the use case for SYSTEM_USER, you'll probably want
to review the arguments for making it available to parallel workers
that were made in the other thread [1].

Thanks Jacob for your feedback.

I did some testing initially around the parallel workers and did not
see any issues at that time.

I just had another look and I agree that the parallel workers case
needs to be addressed.

I'll have a closer look to what you have done in [1].

Thanks

Bertrand

Please find attached patch version 2.

It does contain:

- Tom's idea implementation (aka presenting the system_user as
auth_method:authn_id)

- A fix for the parallel workers issue mentioned by Jacob. The patch
now propagates the SYSTEM_USER to the parallel workers.

- Doc updates

- Tap tests (some of them are coming from [1])

Looking forward to your feedback,

Thanks

Bertrand

FWIW here is a link to the commitfest entry:
https://commitfest.postgresql.org/38/3703/

Bertrand

Attached a tiny rebase to make the CF bot CompilerWarnings happy.

Bertrand

Attachments:

v2-0002-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0002-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b652460a1..a59d33074b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23660,6 +23660,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index df0cd77558..3b49189bfd 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -76,6 +76,7 @@
 #define PARALLEL_KEY_REINDEX_STATE			UINT64CONST(0xFFFFFFFFFFFF000C)
 #define PARALLEL_KEY_RELMAPPER_STATE		UINT64CONST(0xFFFFFFFFFFFF000D)
 #define PARALLEL_KEY_UNCOMMITTEDENUMS		UINT64CONST(0xFFFFFFFFFFFF000E)
+#define PARALLEL_KEY_SYSTEMUSER				UINT64CONST(0xFFFFFFFFFFFF000F)
 
 /* Fixed-size parallel state. */
 typedef struct FixedParallelState
@@ -212,6 +213,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	Size		reindexlen = 0;
 	Size		relmapperlen = 0;
 	Size		uncommittedenumslen = 0;
+	Size		systemuserlen = 0;
 	Size		segsize = 0;
 	int			i;
 	FixedParallelState *fps;
@@ -272,8 +274,10 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_estimate_chunk(&pcxt->estimator, relmapperlen);
 		uncommittedenumslen = EstimateUncommittedEnumsSpace();
 		shm_toc_estimate_chunk(&pcxt->estimator, uncommittedenumslen);
+		systemuserlen = EstimateSystemUserSpace();
+		shm_toc_estimate_chunk(&pcxt->estimator, systemuserlen);
 		/* If you add more chunks here, you probably need to add keys. */
-		shm_toc_estimate_keys(&pcxt->estimator, 11);
+		shm_toc_estimate_keys(&pcxt->estimator, 12);
 
 		/* Estimate space need for error queues. */
 		StaticAssertStmt(BUFFERALIGN(PARALLEL_ERROR_QUEUE_SIZE) ==
@@ -352,6 +356,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		char	   *session_dsm_handle_space;
 		char	   *entrypointstate;
 		char	   *uncommittedenumsspace;
+		char	   *systemuserspace;
 		Size		lnamelen;
 
 		/* Serialize shared libraries we have loaded. */
@@ -422,6 +427,12 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_insert(pcxt->toc, PARALLEL_KEY_UNCOMMITTEDENUMS,
 					   uncommittedenumsspace);
 
+		/* Serialize our Systemuser. */
+		systemuserspace = shm_toc_allocate(pcxt->toc, systemuserlen);
+		SerializeSystemUser(systemuserlen, systemuserspace);
+		shm_toc_insert(pcxt->toc, PARALLEL_KEY_SYSTEMUSER,
+					   systemuserspace);
+
 		/* Allocate space for worker information. */
 		pcxt->worker = palloc0(sizeof(ParallelWorkerInfo) * pcxt->nworkers);
 
@@ -1270,6 +1281,7 @@ ParallelWorkerMain(Datum main_arg)
 	char	   *reindexspace;
 	char	   *relmapperspace;
 	char	   *uncommittedenumsspace;
+	char	   *systemuserspace;
 	StringInfoData msgbuf;
 	char	   *session_dsm_handle_space;
 	Snapshot	tsnapshot;
@@ -1479,6 +1491,11 @@ ParallelWorkerMain(Datum main_arg)
 										   false);
 	RestoreUncommittedEnums(uncommittedenumsspace);
 
+	/* Restore the SystemUser. */
+	systemuserspace = shm_toc_lookup(toc, PARALLEL_KEY_SYSTEMUSER,
+										 false);
+	RestoreSystemUser(systemuserspace);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index eaec697bb3..431a94a3de 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2543,6 +2543,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..29dcc77724 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -842,7 +842,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15457,6 +15457,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18173,6 +18177,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18578,6 +18583,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0dc2fc472e..8256cc177d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2299,6 +2299,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..8ad50e31d2 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1917,6 +1917,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index e8bba3670c..aa28b32cf0 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(sysuser)));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c3937a60fd..ffdd818f1e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9319,6 +9319,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index b25bd0e583..e872536f01 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -473,6 +473,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -544,6 +545,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -814,6 +825,32 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user during normal backend startup.
+ */
+void
+InitializeSystemUser(const Port *port)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (port->authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(port->hba->auth_method));
+		Size authn_id_len = strlen(port->authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(port->hba->auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, port->authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
 
 /*
  * Change session auth ID while running
@@ -932,6 +969,53 @@ GetUserNameFromId(Oid roleid, bool noerr)
 	return result;
 }
 
+/*
+ * Calculate the space needed to serialize SystemUser.
+ */
+Size
+EstimateSystemUserSpace(void)
+{
+	Size		size = 1;
+	const char	*sysuser = GetSystemUser();
+
+	if (sysuser)
+		size = add_size(size, strlen(sysuser) + 1);
+
+	return size;
+}
+
+/*
+ * Serialize SystemUser for use by parallel workers.
+ */
+void
+SerializeSystemUser(Size maxsize, char *start_address)
+{
+	const char 	*sysuser = GetSystemUser();
+
+	Assert(maxsize > 0);
+
+	if (sysuser)
+	{
+		Size len;
+		len = strlcpy(start_address, sysuser, maxsize) + 1;
+		Assert(len <= maxsize);
+		start_address += len;
+	}
+	start_address[0] = '\0';
+}
+
+/*
+ * Restore SystemUser from its serialized representation.
+ */
+void
+RestoreSystemUser(char *sysuser)
+{
+	if (sysuser[0] == '\0')
+		SystemUser = NULL;
+	else
+		SystemUser = MemoryContextStrdup(TopMemoryContext, sysuser);
+}
+
 
 /*-------------------------------------------------------------------------
  *				Interlock-file support
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6b9082604f..d9a879382e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -835,6 +835,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyProcPort);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a33..4406396055 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0af130fbc5..8d761512fd 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -364,6 +364,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+typedef struct Port MyPort;
+extern void InitializeSystemUser(const MyPort *port);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
@@ -486,6 +490,10 @@ extern bool has_rolreplication(Oid roleid);
 typedef void (*shmem_request_hook_type) (void);
 extern PGDLLIMPORT shmem_request_hook_type shmem_request_hook;
 
+extern Size EstimateSystemUserSpace(void);
+extern void SerializeSystemUser(Size maxsize, char *start_address);
+extern void RestoreSystemUser(char *sysuser);
+
 /* in executor/nodeHash.c */
 extern size_t get_hash_memory_limit(void);
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 51505eee85..c23b0f2507 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1186,6 +1186,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..9bf17985d5 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -307,6 +307,29 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"CREATE TEMP TABLE mytab (n) as SELECT NULL FROM generate_series(1, 200000);\n"
+	. "SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER IS DISTINCT FROM n) FROM mytab;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#20Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Drouvot, Bertrand (#19)
Re: SYSTEM_USER reserved word implementation

On 2022-Jun-25, Drouvot, Bertrand wrote:

diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0af130fbc5..8d761512fd 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -364,6 +364,10 @@ extern void InitializeSessionUserIdStandalone(void);
extern void SetSessionAuthorization(Oid userid, bool is_superuser);
extern Oid	GetCurrentRoleId(void);
extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+typedef struct Port MyPort;
+extern void InitializeSystemUser(const MyPort *port);
+extern const char* GetSystemUser(void);

This typedef here looks quite suspicious. I think this should suffice:

+/* kluge to avoid including libpq/libpq-be.h here */
+struct Port;
+extern void InitializeSystemUser(struct Port *port);

I suspect that having a typedef called MyPort is going to wreak serious
havoc for pgindent.

--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/

#21Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Alvaro Herrera (#20)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 6/27/22 7:32 PM, Alvaro Herrera wrote:

On 2022-Jun-25, Drouvot, Bertrand wrote:

diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0af130fbc5..8d761512fd 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -364,6 +364,10 @@ extern void InitializeSessionUserIdStandalone(void);
extern void SetSessionAuthorization(Oid userid, bool is_superuser);
extern Oid   GetCurrentRoleId(void);
extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+typedef struct Port MyPort;
+extern void InitializeSystemUser(const MyPort *port);
+extern const char* GetSystemUser(void);

This typedef here looks quite suspicious. I think this should suffice:

+/* kluge to avoid including libpq/libpq-be.h here */
+struct Port;
+extern void InitializeSystemUser(struct Port *port);

I suspect that having a typedef called MyPort is going to wreak serious
havoc for pgindent.

Good catch, thanks!

Attached new version to fix it as suggested.

Regards,

Bertrand

Attachments:

v2-0003-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0003-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b652460a1..a59d33074b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23660,6 +23660,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index df0cd77558..3b49189bfd 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -76,6 +76,7 @@
 #define PARALLEL_KEY_REINDEX_STATE			UINT64CONST(0xFFFFFFFFFFFF000C)
 #define PARALLEL_KEY_RELMAPPER_STATE		UINT64CONST(0xFFFFFFFFFFFF000D)
 #define PARALLEL_KEY_UNCOMMITTEDENUMS		UINT64CONST(0xFFFFFFFFFFFF000E)
+#define PARALLEL_KEY_SYSTEMUSER				UINT64CONST(0xFFFFFFFFFFFF000F)
 
 /* Fixed-size parallel state. */
 typedef struct FixedParallelState
@@ -212,6 +213,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	Size		reindexlen = 0;
 	Size		relmapperlen = 0;
 	Size		uncommittedenumslen = 0;
+	Size		systemuserlen = 0;
 	Size		segsize = 0;
 	int			i;
 	FixedParallelState *fps;
@@ -272,8 +274,10 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_estimate_chunk(&pcxt->estimator, relmapperlen);
 		uncommittedenumslen = EstimateUncommittedEnumsSpace();
 		shm_toc_estimate_chunk(&pcxt->estimator, uncommittedenumslen);
+		systemuserlen = EstimateSystemUserSpace();
+		shm_toc_estimate_chunk(&pcxt->estimator, systemuserlen);
 		/* If you add more chunks here, you probably need to add keys. */
-		shm_toc_estimate_keys(&pcxt->estimator, 11);
+		shm_toc_estimate_keys(&pcxt->estimator, 12);
 
 		/* Estimate space need for error queues. */
 		StaticAssertStmt(BUFFERALIGN(PARALLEL_ERROR_QUEUE_SIZE) ==
@@ -352,6 +356,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		char	   *session_dsm_handle_space;
 		char	   *entrypointstate;
 		char	   *uncommittedenumsspace;
+		char	   *systemuserspace;
 		Size		lnamelen;
 
 		/* Serialize shared libraries we have loaded. */
@@ -422,6 +427,12 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_insert(pcxt->toc, PARALLEL_KEY_UNCOMMITTEDENUMS,
 					   uncommittedenumsspace);
 
+		/* Serialize our Systemuser. */
+		systemuserspace = shm_toc_allocate(pcxt->toc, systemuserlen);
+		SerializeSystemUser(systemuserlen, systemuserspace);
+		shm_toc_insert(pcxt->toc, PARALLEL_KEY_SYSTEMUSER,
+					   systemuserspace);
+
 		/* Allocate space for worker information. */
 		pcxt->worker = palloc0(sizeof(ParallelWorkerInfo) * pcxt->nworkers);
 
@@ -1270,6 +1281,7 @@ ParallelWorkerMain(Datum main_arg)
 	char	   *reindexspace;
 	char	   *relmapperspace;
 	char	   *uncommittedenumsspace;
+	char	   *systemuserspace;
 	StringInfoData msgbuf;
 	char	   *session_dsm_handle_space;
 	Snapshot	tsnapshot;
@@ -1479,6 +1491,11 @@ ParallelWorkerMain(Datum main_arg)
 										   false);
 	RestoreUncommittedEnums(uncommittedenumsspace);
 
+	/* Restore the SystemUser. */
+	systemuserspace = shm_toc_lookup(toc, PARALLEL_KEY_SYSTEMUSER,
+										 false);
+	RestoreSystemUser(systemuserspace);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index eaec697bb3..431a94a3de 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2543,6 +2543,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..29dcc77724 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -842,7 +842,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15457,6 +15457,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18173,6 +18177,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18578,6 +18583,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0dc2fc472e..8256cc177d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2299,6 +2299,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..8ad50e31d2 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1917,6 +1917,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index e8bba3670c..aa28b32cf0 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(sysuser)));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c3937a60fd..ffdd818f1e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9319,6 +9319,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index b25bd0e583..e872536f01 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -473,6 +473,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -544,6 +545,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -814,6 +825,32 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user during normal backend startup.
+ */
+void
+InitializeSystemUser(const Port *port)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (port->authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(port->hba->auth_method));
+		Size authn_id_len = strlen(port->authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(port->hba->auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, port->authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
 
 /*
  * Change session auth ID while running
@@ -932,6 +969,53 @@ GetUserNameFromId(Oid roleid, bool noerr)
 	return result;
 }
 
+/*
+ * Calculate the space needed to serialize SystemUser.
+ */
+Size
+EstimateSystemUserSpace(void)
+{
+	Size		size = 1;
+	const char	*sysuser = GetSystemUser();
+
+	if (sysuser)
+		size = add_size(size, strlen(sysuser) + 1);
+
+	return size;
+}
+
+/*
+ * Serialize SystemUser for use by parallel workers.
+ */
+void
+SerializeSystemUser(Size maxsize, char *start_address)
+{
+	const char 	*sysuser = GetSystemUser();
+
+	Assert(maxsize > 0);
+
+	if (sysuser)
+	{
+		Size len;
+		len = strlcpy(start_address, sysuser, maxsize) + 1;
+		Assert(len <= maxsize);
+		start_address += len;
+	}
+	start_address[0] = '\0';
+}
+
+/*
+ * Restore SystemUser from its serialized representation.
+ */
+void
+RestoreSystemUser(char *sysuser)
+{
+	if (sysuser[0] == '\0')
+		SystemUser = NULL;
+	else
+		SystemUser = MemoryContextStrdup(TopMemoryContext, sysuser);
+}
+
 
 /*-------------------------------------------------------------------------
  *				Interlock-file support
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6b9082604f..d9a879382e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -835,6 +835,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyProcPort);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a33..4406396055 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0af130fbc5..32fe3b8983 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -364,6 +364,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+struct Port;
+extern void InitializeSystemUser(const struct Port *port);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
@@ -486,6 +490,10 @@ extern bool has_rolreplication(Oid roleid);
 typedef void (*shmem_request_hook_type) (void);
 extern PGDLLIMPORT shmem_request_hook_type shmem_request_hook;
 
+extern Size EstimateSystemUserSpace(void);
+extern void SerializeSystemUser(Size maxsize, char *start_address);
+extern void RestoreSystemUser(char *sysuser);
+
 /* in executor/nodeHash.c */
 extern size_t get_hash_memory_limit(void);
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 51505eee85..c23b0f2507 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1186,6 +1186,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..9bf17985d5 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -307,6 +307,29 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"CREATE TEMP TABLE mytab (n) as SELECT NULL FROM generate_series(1, 200000);\n"
+	. "SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER IS DISTINCT FROM n) FROM mytab;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#22Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Drouvot, Bertrand (#21)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 6/28/22 9:18 AM, Drouvot, Bertrand wrote:

Attached new version to fix it as suggested.

Just to update current and new readers (if any) of this thread.

It has been agreed that the work on this patch is on hold until the
ClientConnectionInfo related work is finished (see the discussion in [1]https://commitfest.postgresql.org/39/3563/).

Having said that I'm attaching a new patch
"v2-0004-system_user-implementation.patch" for the SYSTEM_USER.

This new patch currently does not apply on master (so the CF bot will
fail and this is expected) but does currently apply on top of
"v2-0001-Allow-parallel-workers-to-read-authn_id.patch" provided in [1]https://commitfest.postgresql.org/39/3563/.

The reason of it, is that it helps the testing for [1]https://commitfest.postgresql.org/39/3563/.

[1]: https://commitfest.postgresql.org/39/3563/

Regards,

--
Bertrand Drouvot
Amazon Web Services: https://aws.amazon.com

Attachments:

v2-0004-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0004-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 053d4dc650..3f5057564b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23663,6 +23663,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..965bf431b6 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,9 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+	InitializeSystemUser(MyClientConnectionInfo);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f..e0d912cda2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2569,6 +2569,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f9037761f9..71613a3807 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -847,7 +847,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15421,6 +15421,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18137,6 +18141,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18542,6 +18547,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f7207..64a76b26cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2297,6 +2297,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 16a0fe59e2..2c257a7612 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1916,6 +1916,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index d22e1f277b..e5c1dbdd46 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(sysuser)));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d575aa0066..1a52981b0a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9348,6 +9348,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index dc4c6104d6..73b723cc8d 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -472,6 +472,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -543,6 +544,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -813,6 +824,32 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(ClientConnectionInfo conninfo)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (conninfo.authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(conninfo.auth_method));
+		Size authn_id_len = strlen(conninfo.authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(conninfo.auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, conninfo.authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0d557a8684..df174b9d3f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -855,6 +855,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyClientConnectionInfo);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122..7380a345dc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 3e9297e399..176d3a00ae 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -356,6 +356,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+struct ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb685..d0bc2ac92e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1327,6 +1327,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..7ce3b33da6 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -176,6 +176,15 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO public;'
+);
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -307,6 +316,28 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER IS DISTINCT FROM n) FROM nulls;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#23Jacob Champion
jchampion@timescale.com
In reply to: Drouvot, Bertrand (#22)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

On Fri, Aug 12, 2022 at 6:32 AM Drouvot, Bertrand <bdrouvot@amazon.com> wrote:

It has been agreed that the work on this patch is on hold until the
ClientConnectionInfo related work is finished (see the discussion in [1]).

Having said that I'm attaching a new patch
"v2-0004-system_user-implementation.patch" for the SYSTEM_USER.

(Not a full review.) Now that the implementation has increased in
complexity, the original tests for the parallel workers have become
underpowered. As a concrete example, I forgot to serialize auth_method
during my most recent rewrite, and the tests still passed.

I think it'd be better to check the contents of SYSTEM_USER, where we
can, rather than only testing for existence. Something like the
attached, maybe? And it would also be good to add a similar test to
the authentication suite, so that you don't have to have Kerberos
enabled to fully test SYSTEM_USER.

--Jacob

Attachments:

kerb-tests.patch.txttext/plain; charset=US-ASCII; name=kerb-tests.patch.txtDownload
commit adaff75cb96ec842d15e15df2ee42dd4b3fa1349
Author: Jacob Champion <jchampion@timescale.com>
Date:   Tue Aug 16 09:32:45 2022 -0700

    squash! SYSTEM_USER implementation
    
    Check the contents of SYSTEM_USER in the Kerberos tests, not just its
    existence.

diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 7ce3b33da6..be0dd7c62d 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -178,11 +178,11 @@ $node->safe_psql('postgres', 'CREATE USER test1;');
 
 # Set up a table for SYSTEM_USER parallel worker testing.
 $node->safe_psql('postgres',
-    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+    "CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 200000);"
 );
 
 $node->safe_psql('postgres',
-    'GRANT SELECT ON nulls TO public;'
+    'GRANT SELECT ON ids TO public;'
 );
 
 note "running tests";
@@ -333,7 +333,7 @@ test_query(
 	. "SET parallel_setup_cost TO 0;\n"
 	. "SET parallel_tuple_cost TO 0;\n"
 	. "SET max_parallel_workers_per_gather TO 2;\n"
-	. "SELECT bool_and(SYSTEM_USER IS DISTINCT FROM n) FROM nulls;",
+	. "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
 	qr/^t$/s,
 	'gssencmode=require',
 	'testing system_user with parallel workers');
#24Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Jacob Champion (#23)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 8/16/22 6:52 PM, Jacob Champion wrote:

On Fri, Aug 12, 2022 at 6:32 AM Drouvot, Bertrand <bdrouvot@amazon.com> wrote:

It has been agreed that the work on this patch is on hold until the
ClientConnectionInfo related work is finished (see the discussion in [1]).

Having said that I'm attaching a new patch
"v2-0004-system_user-implementation.patch" for the SYSTEM_USER.

(Not a full review.) Now that the implementation has increased in
complexity, the original tests for the parallel workers have become
underpowered. As a concrete example, I forgot to serialize auth_method
during my most recent rewrite, and the tests still passed.

I think it'd be better to check the contents of SYSTEM_USER, where we
can, rather than only testing for existence. Something like the
attached, maybe?

Yeah, fully agree, thanks for pointing out!

I've included your suggestion in v2-0005 attached (it's expected to see
the CF bot failing for the same reason as mentioned up-thread).

And it would also be good to add a similar test to
the authentication suite, so that you don't have to have Kerberos
enabled to fully test SYSTEM_USER.

Agree, I'll look at what can be done here.

Regards,

--
Bertrand Drouvot
Amazon Web Services: https://aws.amazon.com

Attachments:

v2-0005-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0005-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 053d4dc650..3f5057564b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23663,6 +23663,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..965bf431b6 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,9 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+	InitializeSystemUser(MyClientConnectionInfo);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f..e0d912cda2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2569,6 +2569,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f9037761f9..71613a3807 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -847,7 +847,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15421,6 +15421,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18137,6 +18141,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18542,6 +18547,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f7207..64a76b26cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2297,6 +2297,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 16a0fe59e2..2c257a7612 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1916,6 +1916,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index d22e1f277b..e5c1dbdd46 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(sysuser)));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d575aa0066..1a52981b0a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9348,6 +9348,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index df1ff7c448..c4d0ce638b 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -472,6 +472,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -543,6 +544,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -813,6 +824,32 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(ClientConnectionInfo conninfo)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (conninfo.authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(conninfo.auth_method));
+		Size authn_id_len = strlen(conninfo.authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(conninfo.auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, conninfo.authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0d557a8684..df174b9d3f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -855,6 +855,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyClientConnectionInfo);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122..7380a345dc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 3e9297e399..176d3a00ae 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -356,6 +356,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+struct ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb685..d0bc2ac92e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1327,6 +1327,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..be0dd7c62d 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -176,6 +176,15 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    "CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON ids TO public;'
+);
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -307,6 +316,28 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#25Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Drouvot, Bertrand (#24)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 8/17/22 9:51 AM, Drouvot, Bertrand wrote:

On 8/16/22 6:52 PM, Jacob Champion wrote:

And it would also be good to add a similar test to
the authentication suite, so that you don't have to have Kerberos
enabled to fully test SYSTEM_USER.

Agree, I'll look at what can be done here.

I added authentication/t/003_peer.pl in
v2-0006-system_user-implementation.patch attached.

It does the peer authentication and SYSTEM_USER testing with and without
a user name map.

$ make -C src/test/authentication check PROVE_TESTS=t/003_peer.pl
PROVE_FLAGS=-v

ok 1 - users with peer authentication have the correct SYSTEM_USER
ok 2 - parallel workers return the correct SYSTEM_USER when peer
authentication is used
ok 3 - user name map is well defined and working
ok 4 - users with peer authentication and user name map have the correct
SYSTEM_USER
ok 5 - parallel workers return the correct SYSTEM_USER when peer
authentication and user name map is used
1..5
ok
All tests successful.

That way one could test the SYSTEM_USER behavior without the need to
have kerberos enabled.

Regards,

--
Bertrand Drouvot
Amazon Web Services: https://aws.amazon.com

Attachments:

v2-0006-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0006-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 053d4dc650..3f5057564b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23663,6 +23663,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..965bf431b6 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,9 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+	InitializeSystemUser(MyClientConnectionInfo);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f..e0d912cda2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2569,6 +2569,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f9037761f9..71613a3807 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -847,7 +847,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15421,6 +15421,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18137,6 +18141,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18542,6 +18547,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f7207..64a76b26cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2297,6 +2297,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 16a0fe59e2..2c257a7612 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1916,6 +1916,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index d22e1f277b..e5c1dbdd46 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(sysuser)));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d575aa0066..1a52981b0a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9348,6 +9348,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index df1ff7c448..c4d0ce638b 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -472,6 +472,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -543,6 +544,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -813,6 +824,32 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(ClientConnectionInfo conninfo)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (conninfo.authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(conninfo.auth_method));
+		Size authn_id_len = strlen(conninfo.authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(conninfo.auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, conninfo.authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0d557a8684..df174b9d3f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -855,6 +855,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyClientConnectionInfo);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122..7380a345dc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 3e9297e399..176d3a00ae 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -356,6 +356,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+struct ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb685..d0bc2ac92e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1327,6 +1327,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl
new file mode 100644
index 0000000000..27ab75a61d
--- /dev/null
+++ b/src/test/authentication/t/003_peer.pl
@@ -0,0 +1,100 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# Tests for peer authentication, user name map  and SYSTEM_USER.
+# The peer authentication method is checked through this test.
+# This test can only run with Unix-domain sockets.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+if (!$use_unix_sockets)
+{
+	plan skip_all =>
+	  "authentication tests cannot run without Unix-domain sockets";
+}
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+	my $node       = shift;
+	my $hba_method = shift;
+
+	unlink($node->data_dir . '/pg_hba.conf');
+	# just for testing purposes, use a continuation line
+	$node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+	$node->reload;
+	return;
+}
+
+# Initialize primary node
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init;
+$node->start;
+
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+   "CREATE TABLE ids (id) AS SELECT 'peer:'||session_user FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON ids TO public;'
+);
+
+# Get the session_user to define the user name map test.
+my $session_user =
+   $node->safe_psql('postgres', 'select session_user');
+
+# Create a new user for the user name map test.
+$node->safe_psql('postgres',
+   "CREATE user john;");
+
+# Set pg_hba.conf with the peer authentication.
+reset_pg_hba($node, 'peer');
+
+# Test SYSTEM_USER when peer authentication is used.
+my $res =
+  $node->safe_psql('postgres', "select true where system_user = 'peer:'||session_user;");
+is($res, 't', "users with peer authentication have the correct SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+        SELECT bool_and(SYSTEM_USER = id) FROM ids;');
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication is used");
+
+# Define a user name map.
+$node->append_conf('pg_ident.conf', qq{mypeermap $session_user john});
+
+# Set pg_hba.conf with the peer authentication and the user name map.
+reset_pg_hba($node, 'peer map=mypeermap');
+
+# Test that the user name map is well defined and working.
+$res =
+  $node->safe_psql('postgres', "select true where session_user = 'john'", connstr => "user=john");
+is($res, 't', "user name map is well defined and working");
+
+# Test SYSTEM_USER when peer authentication is used with user name map.
+$res =
+  $node->safe_psql('postgres', qq{select true where system_user = 'peer:'||'$session_user'}, connstr => "user=john");
+is($res, 't', "users with peer authentication and user name map have the correct SYSTEM_USER");
+
+# Test SYSTEM_USER with user name map and parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+        SELECT bool_and(SYSTEM_USER = id) FROM ids;',
+		connstr => "user=john");
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication and user name map is used");
+
+done_testing();
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..be0dd7c62d 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -176,6 +176,15 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    "CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON ids TO public;'
+);
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -307,6 +316,28 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#26Michael Paquier
michael@paquier.xyz
In reply to: Drouvot, Bertrand (#25)
Re: SYSTEM_USER reserved word implementation

On Wed, Aug 17, 2022 at 04:48:42PM +0200, Drouvot, Bertrand wrote:

That way one could test the SYSTEM_USER behavior without the need to have
kerberos enabled.

I was looking at this patch and noticed that SYSTEM_USER returns a
"name", meaning that the value would be automatically truncated at 63
characters. We shouldn't imply that as authn_ids can be longer than
that, and this issue gets a bit worse once with the auth_method
appended to the string.

+if (!$use_unix_sockets)
+{
+   plan skip_all =>
+     "authentication tests cannot run without Unix-domain sockets";
+}

Are you sure that !$use_unix_sockets is safe here? Could we have
platforms where we use our port's getpeereid() with $use_unix_sockets
works? That would cause the test to fail with ENOSYS. Hmm. Without
being able to rely on HAVE_GETPEEREID, we could check for the error
generated when the fallback implementation does not work, and skip the
rest of the test.
--
Michael

#27Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Michael Paquier (#26)
Re: SYSTEM_USER reserved word implementation

Hi,

On 8/24/22 6:27 AM, Michael Paquier wrote:

On Wed, Aug 17, 2022 at 04:48:42PM +0200, Drouvot, Bertrand wrote:

That way one could test the SYSTEM_USER behavior without the need to have
kerberos enabled.

I was looking at this patch

Thanks for looking at it!

and noticed that SYSTEM_USER returns a
"name", meaning that the value would be automatically truncated at 63
characters. We shouldn't imply that as authn_ids can be longer than
that, and this issue gets a bit worse once with the auth_method
appended to the string.

Good catch! I'll fix that in the next version.

Hmm, I think it would make sense to keep system_user() with his friends
current_user() and session_user().

But now that system_user() will not return a name anymore (but a text),
I think name.c is no longer the right place, what do you think? (If so,
where would you suggest?)

+if (!$use_unix_sockets)
+{
+   plan skip_all =>
+     "authentication tests cannot run without Unix-domain sockets";
+}

Are you sure that !$use_unix_sockets is safe here? Could we have
platforms where we use our port's getpeereid() with $use_unix_sockets
works? That would cause the test to fail with ENOSYS. Hmm. Without
being able to rely on HAVE_GETPEEREID, we could check for the error
generated when the fallback implementation does not work, and skip the
rest of the test.

Oh right, I did not think about that, thanks for the suggestion.

I'll change this in the next version and simply skip the rest of the
test in case we get "peer authentication is not supported on this platform".

Regards,

--

Bertrand Drouvot
Amazon Web Services: https://aws.amazon.com

#28Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Drouvot, Bertrand (#27)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 8/24/22 8:26 PM, Drouvot, Bertrand wrote:

Hi,

On 8/24/22 6:27 AM, Michael Paquier wrote:

On Wed, Aug 17, 2022 at 04:48:42PM +0200, Drouvot, Bertrand wrote:

That way one could test the SYSTEM_USER behavior without the need to
have
kerberos enabled.

I was looking at this patch

Thanks for looking at it!

and noticed that SYSTEM_USER returns a
"name", meaning that the value would be automatically truncated at 63
characters.  We shouldn't imply that as authn_ids can be longer than
that, and this issue gets a bit worse once with the auth_method
appended to the string.

Good catch! I'll fix that in the next version.

Hmm, I think it would make sense to keep system_user() with his
friends current_user() and session_user().

But now that system_user() will not return a name anymore (but a
text), I think name.c is no longer the right place, what do you think?
(If so, where would you suggest?)

system_user() now returns a text and I moved it to miscinit.c in the new
version attached (I think it makes more sense now).

+if (!$use_unix_sockets)
+{
+   plan skip_all =>
+     "authentication tests cannot run without Unix-domain sockets";
+}

Are you sure that !$use_unix_sockets is safe here?  Could we have
platforms where we use our port's getpeereid() with $use_unix_sockets
works?  That would cause the test to fail with ENOSYS.  Hmm. Without
being able to rely on HAVE_GETPEEREID, we could check for the error
generated when the fallback implementation does not work, and skip the
rest of the test.

Oh right, I did not think about that, thanks for the suggestion.

I'll change this in the next version and simply skip the rest of the
test in case we get "peer authentication is not supported on this
platform".

New version attached is also addressing Michael's remark regarding the
peer authentication TAP test.

Regards,

--
Bertrand Drouvot
Amazon Web Services: https://aws.amazon.com

Attachments:

v2-0007-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0007-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f87afefeae..470ff74a28 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23663,6 +23663,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..965bf431b6 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,9 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+	InitializeSystemUser(MyClientConnectionInfo);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f..e0d912cda2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2569,6 +2569,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c8bd66dd54..59c658a306 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -847,7 +847,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15423,6 +15423,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18139,6 +18143,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18544,6 +18549,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f7207..64a76b26cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2297,6 +2297,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 16a0fe59e2..2c257a7612 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1916,6 +1916,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index d22e1f277b..a1e71c7dc8 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -271,7 +271,6 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
-
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
  */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8280711f7e..a93cd0408e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9349,6 +9349,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 683f616b1a..3da87861bd 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -477,6 +477,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -548,6 +549,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -818,6 +829,46 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(ClientConnectionInfo conninfo)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (conninfo.authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(conninfo.auth_method));
+		Size authn_id_len = strlen(conninfo.authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(conninfo.auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, conninfo.authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
+
+/*
+ * SQL-function SYSTEM_USER
+ */
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(CStringGetDatum(sysuser));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0d557a8684..df174b9d3f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -855,6 +855,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyClientConnectionInfo);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122..061db181cc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'text',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 65cf4ba50f..e49c696c3a 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -357,6 +357,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+struct ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb685..d0bc2ac92e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1327,6 +1327,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl
new file mode 100644
index 0000000000..f2f3541f58
--- /dev/null
+++ b/src/test/authentication/t/003_peer.pl
@@ -0,0 +1,107 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# Tests for peer authentication, user name map and SYSTEM_USER.
+# The peer authentication method is checked through this test.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+	my $node       = shift;
+	my $hba_method = shift;
+
+	unlink($node->data_dir . '/pg_hba.conf');
+	# just for testing purposes, use a continuation line
+	$node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+	$node->reload;
+	return;
+}
+
+# Initialize primary node
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init;
+$node->start;
+
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+   "CREATE TABLE ids (id) AS SELECT 'peer:'||session_user FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON ids TO public;'
+);
+
+# Get the session_user to define the user name map test.
+my $session_user =
+   $node->safe_psql('postgres', 'select session_user');
+
+# Create a new user for the user name map test.
+$node->safe_psql('postgres',
+   "CREATE user john;");
+
+# Set pg_hba.conf with the peer authentication.
+reset_pg_hba($node, 'peer');
+
+# Check that peer authentication is supported on this platform
+my $log_location = -s $node->logfile;
+
+$node->psql('postgres', undef, connstr  => "user=$session_user");
+
+my $logfile = slurp_file($node->logfile, $log_location);
+
+# If not supported, then skip the rest of the test
+if ($logfile =~ qr/peer authentication is not supported on this platform/)
+{
+    plan skip_all => 'peer authentication is not supported on this platform';
+}
+
+# Test SYSTEM_USER when peer authentication is used.
+my $res =
+  $node->safe_psql('postgres', "select true where system_user = 'peer:'||session_user;");
+is($res, 't', "users with peer authentication have the correct SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+        SELECT bool_and(SYSTEM_USER = id) FROM ids;');
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication is used");
+
+# Define a user name map.
+$node->append_conf('pg_ident.conf', qq{mypeermap $session_user john});
+
+# Set pg_hba.conf with the peer authentication and the user name map.
+reset_pg_hba($node, 'peer map=mypeermap');
+
+# Test that the user name map is well defined and working.
+$res =
+  $node->safe_psql('postgres', "select true where session_user = 'john'", connstr => "user=john");
+is($res, 't', "user name map is well defined and working");
+
+# Test SYSTEM_USER when peer authentication is used with user name map.
+$res =
+  $node->safe_psql('postgres', qq{select true where system_user = 'peer:'||'$session_user'}, connstr => "user=john");
+is($res, 't', "users with peer authentication and user name map have the correct SYSTEM_USER");
+
+# Test SYSTEM_USER with user name map and parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+        SELECT bool_and(SYSTEM_USER = id) FROM ids;',
+		connstr => "user=john");
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication and user name map is used");
+
+done_testing();
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..be0dd7c62d 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -176,6 +176,15 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    "CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON ids TO public;'
+);
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -307,6 +316,28 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#29Michael Paquier
michael@paquier.xyz
In reply to: Drouvot, Bertrand (#28)
Re: SYSTEM_USER reserved word implementation

On Thu, Aug 25, 2022 at 08:21:05PM +0200, Drouvot, Bertrand wrote:

system_user() now returns a text and I moved it to miscinit.c in the new
version attached (I think it makes more sense now).

+/* kluge to avoid including libpq/libpq-be.h here */
+struct ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);

FWIW, I was also wondering about the need for all this initialization
stanza and the extra SystemUser in TopMemoryContext. Now that we have
MyClientConnectionInfo, I was thinking to just build the string in the
SQL function as that's the only code path that needs to know about
it. True that this approach saves some extra palloc() calls each time
the function is called.

New version attached is also addressing Michael's remark regarding the peer
authentication TAP test.

Thanks. I've wanted some basic tests for the peer authentication for
some time now, independently on this thread, so it would make sense to
split that into a first patch and stress the buildfarm to see what
happens, then add these tests for SYSTEM_USER on top of the new test.
--
Michael

#30Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Michael Paquier (#29)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 8/26/22 3:02 AM, Michael Paquier wrote:

On Thu, Aug 25, 2022 at 08:21:05PM +0200, Drouvot, Bertrand wrote:

system_user() now returns a text and I moved it to miscinit.c in the new
version attached (I think it makes more sense now).

+/* kluge to avoid including libpq/libpq-be.h here */
+struct ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);

FWIW, I was also wondering about the need for all this initialization
stanza and the extra SystemUser in TopMemoryContext. Now that we have
MyClientConnectionInfo, I was thinking to just build the string in the
SQL function as that's the only code path that needs to know about
it.

Agree that the extra SystemUser is not needed strictly speaking and that
we could build it each time the system_user function is called.

True that this approach saves some extra palloc() calls each time
the function is called.

Right, with the current approach the SystemUser just needs to be
constructed one time.

I also think that it's more consistent to have such a global variable
with his friends SessionUserId/OuterUserId/CurrentUserId (but at an
extra memory cost in TopMemoryContext).

Looks like there is pros and cons for both approach.

I'm +1 for the current approach but I don't have a strong opinion about
it so I'm also ok to change it the way you described if you think it's
better.

New version attached is also addressing Michael's remark regarding the peer
authentication TAP test.

Thanks. I've wanted some basic tests for the peer authentication for
some time now, independently on this thread, so it would make sense to
split that into a first patch and stress the buildfarm to see what
happens, then add these tests for SYSTEM_USER on top of the new test.

Makes fully sense, I've created a new thread [1]/messages/by-id/aa60994b-1c66-ca7a-dab9-9a200dbac3d2@amazon.com for this purpose, thanks!

For the moment I'm keeping the peer TAP test as it is in the current
thread so that we can test the SYSTEM_USER behavior.

I just realized that the previous patch version contained useless change
in name.c: attached a new version so that name.c now remains untouched.

[1]: /messages/by-id/aa60994b-1c66-ca7a-dab9-9a200dbac3d2@amazon.com
/messages/by-id/aa60994b-1c66-ca7a-dab9-9a200dbac3d2@amazon.com

Regards,

--
Bertrand Drouvot
Amazon Web Services: https://aws.amazon.com

Attachments:

v2-0008-system_user-implementation.patchtext/plain; charset=UTF-8; name=v2-0008-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f87afefeae..470ff74a28 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23663,6 +23663,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..965bf431b6 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,9 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+	InitializeSystemUser(MyClientConnectionInfo);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 636794ca6f..e0d912cda2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2569,6 +2569,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b5ab9d9c9a..05ed3c270c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -850,7 +850,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15454,6 +15454,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18170,6 +18174,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18575,6 +18580,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fabb5f7207..64a76b26cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2297,6 +2297,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 16a0fe59e2..2c257a7612 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1916,6 +1916,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9959f6910e..06658b7a3b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9346,6 +9346,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 683f616b1a..3da87861bd 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -477,6 +477,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -548,6 +549,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -818,6 +829,46 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(ClientConnectionInfo conninfo)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (conninfo.authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(conninfo.auth_method));
+		Size authn_id_len = strlen(conninfo.authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(conninfo.auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, conninfo.authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
+
+/*
+ * SQL-function SYSTEM_USER
+ */
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(CStringGetDatum(sysuser));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0d557a8684..df174b9d3f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -855,6 +855,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyClientConnectionInfo);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122..061db181cc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'text',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 65cf4ba50f..e49c696c3a 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -357,6 +357,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+struct ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb685..d0bc2ac92e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1327,6 +1327,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl
new file mode 100644
index 0000000000..f2f3541f58
--- /dev/null
+++ b/src/test/authentication/t/003_peer.pl
@@ -0,0 +1,107 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# Tests for peer authentication, user name map and SYSTEM_USER.
+# The peer authentication method is checked through this test.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+	my $node       = shift;
+	my $hba_method = shift;
+
+	unlink($node->data_dir . '/pg_hba.conf');
+	# just for testing purposes, use a continuation line
+	$node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+	$node->reload;
+	return;
+}
+
+# Initialize primary node
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init;
+$node->start;
+
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+   "CREATE TABLE ids (id) AS SELECT 'peer:'||session_user FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON ids TO public;'
+);
+
+# Get the session_user to define the user name map test.
+my $session_user =
+   $node->safe_psql('postgres', 'select session_user');
+
+# Create a new user for the user name map test.
+$node->safe_psql('postgres',
+   "CREATE user john;");
+
+# Set pg_hba.conf with the peer authentication.
+reset_pg_hba($node, 'peer');
+
+# Check that peer authentication is supported on this platform
+my $log_location = -s $node->logfile;
+
+$node->psql('postgres', undef, connstr  => "user=$session_user");
+
+my $logfile = slurp_file($node->logfile, $log_location);
+
+# If not supported, then skip the rest of the test
+if ($logfile =~ qr/peer authentication is not supported on this platform/)
+{
+    plan skip_all => 'peer authentication is not supported on this platform';
+}
+
+# Test SYSTEM_USER when peer authentication is used.
+my $res =
+  $node->safe_psql('postgres', "select true where system_user = 'peer:'||session_user;");
+is($res, 't', "users with peer authentication have the correct SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+        SELECT bool_and(SYSTEM_USER = id) FROM ids;');
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication is used");
+
+# Define a user name map.
+$node->append_conf('pg_ident.conf', qq{mypeermap $session_user john});
+
+# Set pg_hba.conf with the peer authentication and the user name map.
+reset_pg_hba($node, 'peer map=mypeermap');
+
+# Test that the user name map is well defined and working.
+$res =
+  $node->safe_psql('postgres', "select true where session_user = 'john'", connstr => "user=john");
+is($res, 't', "user name map is well defined and working");
+
+# Test SYSTEM_USER when peer authentication is used with user name map.
+$res =
+  $node->safe_psql('postgres', qq{select true where system_user = 'peer:'||'$session_user'}, connstr => "user=john");
+is($res, 't', "users with peer authentication and user name map have the correct SYSTEM_USER");
+
+# Test SYSTEM_USER with user name map and parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+        SELECT bool_and(SYSTEM_USER = id) FROM ids;',
+		connstr => "user=john");
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication and user name map is used");
+
+done_testing();
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..be0dd7c62d 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -176,6 +176,15 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    "CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+    'GRANT SELECT ON ids TO public;'
+);
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -307,6 +316,28 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#31Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#29)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

On Fri, Aug 26, 2022 at 10:02:26AM +0900, Michael Paquier wrote:

FWIW, I was also wondering about the need for all this initialization
stanza and the extra SystemUser in TopMemoryContext. Now that we have
MyClientConnectionInfo, I was thinking to just build the string in the
SQL function as that's the only code path that needs to know about
it. True that this approach saves some extra palloc() calls each time
the function is called.

At the end, fine by me to keep this approach as that's more
consistent. I have reviewed the patch, and a few things caught my
attention:
- I think that we'd better switch InitializeSystemUser() to have two
const char * as arguments for authn_id and an auth_method, so as there
is no need to use tweaks with UserAuth or ClientConnectionInfo in
miscadmin.h to bypass an inclusion of libpq-be.h or hba.h.
- The OID of the new function should be in the range 8000-9999, as
taught by unused_oids.
- Environments where the code is built without krb5 support would skip
the test where SYSTEM_USER should be not NULL when authenticated, so I
have added a test for that with MD5 in src/test/authentication/.
- Docs have been reworded, and I have applied an indentation.
- No need to use 200k rows in the table used to force the parallel
scan, as long as the costs are set.

It is a bit late here, so I may have missed something. For now, how
does the attached look to you?
--
Michael

Attachments:

v3-0001-Add-support-for-SYSTEM_USER.patchtext/x-diff; charset=us-asciiDownload
From f8175b4887c03dc596483988e2171346480e2bda Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 7 Sep 2022 17:46:41 +0900
Subject: [PATCH v3] Add support for SYSTEM_USER

---
 src/include/catalog/pg_proc.dat           |  3 ++
 src/include/miscadmin.h                   |  3 ++
 src/include/nodes/primnodes.h             |  1 +
 src/include/parser/kwlist.h               |  1 +
 src/backend/access/transam/parallel.c     |  4 ++
 src/backend/executor/execExprInterp.c     |  5 +++
 src/backend/parser/gram.y                 |  8 +++-
 src/backend/parser/parse_expr.c           |  1 +
 src/backend/parser/parse_target.c         |  3 ++
 src/backend/utils/adt/name.c              |  1 -
 src/backend/utils/adt/ruleutils.c         |  3 ++
 src/backend/utils/init/miscinit.c         | 51 +++++++++++++++++++++++
 src/backend/utils/init/postinit.c         |  2 +
 src/test/authentication/t/001_password.pl | 44 +++++++++++++++++++
 src/test/kerberos/t/001_auth.pl           | 28 ++++++++++++-
 doc/src/sgml/func.sgml                    | 18 ++++++++
 16 files changed, 172 insertions(+), 4 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a07e737a33..68bb032d3e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '9977', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'text',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 65cf4ba50f..0a034d2893 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -357,6 +357,9 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void InitializeSystemUser(const char *authn_id,
+								 const char *auth_method);
+extern const char *GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 40661334bb..74fbc6a4af 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1318,6 +1318,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9a7cc0c6bd..ccc927851c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -409,6 +409,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..0a8de9e6de 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,10 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+	InitializeSystemUser(MyClientConnectionInfo.authn_id,
+						 hba_authname(MyClientConnectionInfo.auth_method));
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9b9bbf00a9..c51578c0b9 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2537,6 +2537,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0492ff9a66..1536f44170 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -747,7 +747,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15242,6 +15242,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -17123,6 +17127,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -17503,6 +17508,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7aaf1c673f..96da28608f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2235,6 +2235,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4e1593d900..1e4bb10fd6 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1916,6 +1916,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index d22e1f277b..a1e71c7dc8 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -271,7 +271,6 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
-
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
  */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2b7b1b0c0f..c621b4328e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9210,6 +9210,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 683f616b1a..2c5261d022 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -477,6 +477,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -548,6 +549,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -818,6 +829,46 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(const char *authn_id, const char *auth_method)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (authn_id)
+	{
+		/* Build system user as auth_method:authn_id */
+		char	   *system_user;
+		Size		authname_len = strlen(auth_method);
+		Size		authn_id_len = strlen(authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, auth_method);
+		strcat(system_user, ":");
+		strcat(system_user, authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
+
+/*
+ * SQL-function SYSTEM_USER
+ */
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(CStringGetDatum(sysuser));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0d557a8684..d1eb5ed93a 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -855,6 +855,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyClientConnectionInfo.authn_id,
+							 hba_authname(MyClientConnectionInfo.auth_method));
 		am_superuser = superuser();
 	}
 
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..b0b7aac4c0 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,11 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for tests of SYSTEM_USER.
+$node->safe_psql(
+	'postgres',
+	"CREATE TABLE sysuser_data (n) AS SELECT NULL FROM generate_series(1, 10);
+	 GRANT ALL ON sysuser_data TO md5_role;");
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +87,25 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# SYSTEM_USER is null when not authenticated.
+my $res = $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication use SYSTEM_USER = NULL");
+
+# Test SYSTEM_USER with parallel workers when not authenticated.
+$res = $node->safe_psql(
+	'postgres', "
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM sysuser_data;
+    ",
+	connstr => "user=md5_role");
+is($res, 't',
+	"users with trust authentication use SYSTEM_USER = NULL in parallel workers"
+);
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
@@ -120,6 +144,26 @@ test_role($node, 'md5_role', 'md5', 0,
 	log_like =>
 	  [qr/connection authenticated: identity="md5_role" method=md5/]);
 
+# Test SYSTEM_USER <> NULL with parallel workers.
+$node->safe_psql(
+	'postgres',
+	"TRUNCATE sysuser_data;
+INSERT INTO sysuser_data SELECT 'md5:md5_role' FROM generate_series(1, 10);",
+	connstr => "user=md5_role");
+$res = $node->safe_psql(
+	'postgres', "
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM sysuser_data;
+    ",
+	connstr => "user=md5_role");
+is($res, 't',
+	"users with md5 authentication use SYSTEM_USER = md5:role in parallel workers"
+);
+
 # Tests for channel binding without SSL.
 # Using the password authentication method; channel binding can't work
 reset_pg_hba($node, 'password');
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..f7d8228b24 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -176,6 +176,13 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+	"CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 10);"
+);
+
+$node->safe_psql('postgres', 'GRANT SELECT ON ids TO public;');
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -307,6 +314,23 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query($node, 'test1', 'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s, 'gssencmode=require', 'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	  . "SET parallel_setup_cost TO 0;\n"
+	  . "SET parallel_tuple_cost TO 0;\n"
+	  . "SET max_parallel_workers_per_gather TO 2;\n"
+	  . "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 67eb380632..e7f4932a15 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23666,6 +23666,24 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle before they were
+        assigned a database role. It is represented as
+        <literal>auth_method:identity</literal> or
+        <literal>NULL</literal> if the user has not been authenticated (for
+        example if <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
-- 
2.37.2

#32Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Michael Paquier (#31)
Re: SYSTEM_USER reserved word implementation

Hi,

On 9/7/22 10:48 AM, Michael Paquier wrote:

On Fri, Aug 26, 2022 at 10:02:26AM +0900, Michael Paquier wrote:

FWIW, I was also wondering about the need for all this initialization
stanza and the extra SystemUser in TopMemoryContext. Now that we have
MyClientConnectionInfo, I was thinking to just build the string in the
SQL function as that's the only code path that needs to know about
it. True that this approach saves some extra palloc() calls each time
the function is called.

At the end, fine by me to keep this approach as that's more
consistent. I have reviewed the patch,

Thanks for looking at it!

and a few things caught my
attention:
- I think that we'd better switch InitializeSystemUser() to have two
const char * as arguments for authn_id and an auth_method, so as there
is no need to use tweaks with UserAuth or ClientConnectionInfo in
miscadmin.h to bypass an inclusion of libpq-be.h or hba.h.

Good point, thanks! And there is no need to pass the whole
ClientConnectionInfo (should we add more fields to it in the future).

- The OID of the new function should be in the range 8000-9999, as
taught by unused_oids.

Thanks for pointing out!

My reasoning was to use one available OID close to the ones used for
session_user and current_user.

- Environments where the code is built without krb5 support would skip
the test where SYSTEM_USER should be not NULL when authenticated, so I
have added a test for that with MD5 in src/test/authentication/.

Good point, thanks for the new test (as that would also not be tested
(once added) in the new peer TAP test [1]https://commitfest.postgresql.org/39/3845/ for platforms where peer
authentication is not supported).

- Docs have been reworded, and I have applied an indentation.

Thanks, looks good to me.

- No need to use 200k rows in the table used to force the parallel
scan, as long as the costs are set.

Right.

It is a bit late here, so I may have missed something. For now, how
does the attached look to you?

+# Test SYSTEM_USER <> NULL with parallel workers.

Nit: What about "Test SYSTEM_USER get the correct value with parallel
workers" as that's what we are actually testing.

Except the Nit above, that looks all good to me.

[1]: https://commitfest.postgresql.org/39/3845/

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services:https://aws.amazon.com

#33Jacob Champion
jchampion@timescale.com
In reply to: Drouvot, Bertrand (#32)
Re: SYSTEM_USER reserved word implementation

On 9/7/22 07:46, Drouvot, Bertrand wrote:

Except the Nit above, that looks all good to me.

A few additional comments:

+        assigned a database role. It is represented as
+        <literal>auth_method:identity</literal> or
+        <literal>NULL</literal> if the user has not been authenticated (for
+        example if <xref linkend="auth-trust"/> has been used).
+       </para></entry>

This is rendered as

... (for example if Section 21.4 has been used).

which IMO isn't too helpful. Maybe a <link> would read better than an
<xref>?

Also, this function's placement in the docs (with the System Catalog
Information Functions) seems wrong to me. I think it should go up above
in the Session Information Functions, with current_user et al.

+               /* Build system user as auth_method:authn_id */
+               char       *system_user;
+               Size            authname_len = strlen(auth_method);
+               Size            authn_id_len = strlen(authn_id);
+
+               system_user = palloc0(authname_len + authn_id_len + 2);
+               strcat(system_user, auth_method);
+               strcat(system_user, ":");
+               strcat(system_user, authn_id);

If we're palloc'ing anyway, can this be replaced with a single psprintf()?

+       /* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+       InitializeSystemUser(MyClientConnectionInfo.authn_id,
+                                                hba_authname(MyClientConnectionInfo.auth_method));

It makes me a little nervous to call hba_authname(auth_method) without
checking to see that auth_method is actually valid (which is only true
if authn_id is not NULL).

We could pass the bare auth_method index, or update the documentation
for auth_method to state that it's guaranteed to be zero if authn_id is
NULL (and then enforce that).

case SVFOP_CURRENT_USER:
case SVFOP_USER:
case SVFOP_SESSION_USER:
+ case SVFOP_SYSTEM_USER:
case SVFOP_CURRENT_CATALOG:
case SVFOP_CURRENT_SCHEMA:
svf->type = NAMEOID;

Should this be moved to use TEXTOID instead?

Thanks,
--Jacob

#34Michael Paquier
michael@paquier.xyz
In reply to: Jacob Champion (#33)
Re: SYSTEM_USER reserved word implementation

On Wed, Sep 07, 2022 at 08:48:43AM -0700, Jacob Champion wrote:

Also, this function's placement in the docs (with the System Catalog
Information Functions) seems wrong to me. I think it should go up above
in the Session Information Functions, with current_user et al.

Yeah, this had better use a <link>.

+       /* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+       InitializeSystemUser(MyClientConnectionInfo.authn_id,
+                                                hba_authname(MyClientConnectionInfo.auth_method));

It makes me a little nervous to call hba_authname(auth_method) without
checking to see that auth_method is actually valid (which is only true
if authn_id is not NULL).

You have mentioned that a couple of months ago if I recall correctly,
and we pass down an enum value.

We could pass the bare auth_method index, or update the documentation
for auth_method to state that it's guaranteed to be zero if authn_id is
NULL (and then enforce that).

case SVFOP_CURRENT_USER:
case SVFOP_USER:
case SVFOP_SESSION_USER:
+ case SVFOP_SYSTEM_USER:
case SVFOP_CURRENT_CATALOG:
case SVFOP_CURRENT_SCHEMA:
svf->type = NAMEOID;

Should this be moved to use TEXTOID instead?

Yeah, it should. There is actually a second and much deeper issue
here, in the shape of a collation problem. See the assertion failure
in exprSetCollation(), because we expect SQLValueFunction nodes to
return a name or a non-collatable type. However, for this case, we'd
require a text to get rid of the 63-character limit, and that's
a collatable type. This reminds me of the recent thread to work on
getting rid of this limit for the name type..
--
Michael

#35Jacob Champion
jchampion@timescale.com
In reply to: Michael Paquier (#34)
Re: SYSTEM_USER reserved word implementation

On Wed, Sep 7, 2022 at 6:17 PM Michael Paquier <michael@paquier.xyz> wrote:

+       /* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+       InitializeSystemUser(MyClientConnectionInfo.authn_id,
+                                                hba_authname(MyClientConnectionInfo.auth_method));

It makes me a little nervous to call hba_authname(auth_method) without
checking to see that auth_method is actually valid (which is only true
if authn_id is not NULL).

You have mentioned that a couple of months ago if I recall correctly,
and we pass down an enum value.

Ah, sorry. Do you remember which thread?

I am probably misinterpreting you, but I don't see why auth_method's
being an enum helps. uaReject (and the "reject" string) is not a sane
value to be using in SYSTEM_USER, and the more call stacks away we get
from MyClientConnectionInfo, the easier it is to forget that that
value is junk. As long as the code doesn't get more complicated, I
suppose there's no real harm being done, but it'd be cleaner not to
access auth_method at all if authn_id is NULL. I won't die on that
hill, though.

There is actually a second and much deeper issue
here, in the shape of a collation problem.

Oh, none of that sounds fun. :/

--Jacob

#36Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Jacob Champion (#33)
Re: SYSTEM_USER reserved word implementation

Hi,

On 9/7/22 5:48 PM, Jacob Champion wrote:

On 9/7/22 07:46, Drouvot, Bertrand wrote:

Except the Nit above, that looks all good to me.

A few additional comments:

+        assigned a database role. It is represented as
+        <literal>auth_method:identity</literal> or
+        <literal>NULL</literal> if the user has not been authenticated (for
+        example if <xref linkend="auth-trust"/> has been used).
+       </para></entry>

This is rendered as

... (for example if Section 21.4 has been used).

which IMO isn't too helpful. Maybe a <link> would read better than an
<xref>?

Thanks for looking at it!
Good catch, V4 coming soon will make use of <link> instead.

Also, this function's placement in the docs (with the System Catalog
Information Functions) seems wrong to me. I think it should go up above
in the Session Information Functions, with current_user et al.

Agree, will move it to the Session Information Functions in V4.

+               /* Build system user as auth_method:authn_id */
+               char       *system_user;
+               Size            authname_len = strlen(auth_method);
+               Size            authn_id_len = strlen(authn_id);
+
+               system_user = palloc0(authname_len + authn_id_len + 2);
+               strcat(system_user, auth_method);
+               strcat(system_user, ":");
+               strcat(system_user, authn_id);

If we're palloc'ing anyway, can this be replaced with a single psprintf()?

Fair point, V4 will make use of psprintf().

+       /* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+       InitializeSystemUser(MyClientConnectionInfo.authn_id,
+                                                hba_authname(MyClientConnectionInfo.auth_method));

It makes me a little nervous to call hba_authname(auth_method) without
checking to see that auth_method is actually valid (which is only true
if authn_id is not NULL).

Will add additional check for safety in V4.

We could pass the bare auth_method index, or update the documentation
for auth_method to state that it's guaranteed to be zero if authn_id is
NULL (and then enforce that).

case SVFOP_CURRENT_USER:
case SVFOP_USER:
case SVFOP_SESSION_USER:
+ case SVFOP_SYSTEM_USER:
case SVFOP_CURRENT_CATALOG:
case SVFOP_CURRENT_SCHEMA:
svf->type = NAMEOID;

Should this be moved to use TEXTOID instead?

Good catch, will do in V4.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#37Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Michael Paquier (#34)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

Hi,

On 9/8/22 3:17 AM, Michael Paquier wrote:

On Wed, Sep 07, 2022 at 08:48:43AM -0700, Jacob Champion wrote:

We could pass the bare auth_method index, or update the documentation
for auth_method to state that it's guaranteed to be zero if authn_id is
NULL (and then enforce that).

case SVFOP_CURRENT_USER:
case SVFOP_USER:
case SVFOP_SESSION_USER:
+ case SVFOP_SYSTEM_USER:
case SVFOP_CURRENT_CATALOG:
case SVFOP_CURRENT_SCHEMA:
svf->type = NAMEOID;

Should this be moved to use TEXTOID instead?

Yeah, it should. There is actually a second and much deeper issue
here, in the shape of a collation problem. See the assertion failure
in exprSetCollation(), because we expect SQLValueFunction nodes to
return a name or a non-collatable type. However, for this case, we'd
require a text to get rid of the 63-character limit, and that's
a collatable type. This reminds me of the recent thread to work on
getting rid of this limit for the name type..

Please find attached V4 taking care of Jacob's previous comments.

As far the assertion failure mentioned by Michael when moving the
SVFOP_SYSTEM_USER from NAMEOID to TEXTOID: V4 is assuming that it is
safe to force the collation to C_COLLATION_OID for SQLValueFunction
having a TEXT type, but I would be happy to also hear your thoughts
about it.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

Attachments:

v4-0001-system_user-implementation.patchtext/plain; charset=UTF-8; name=v4-0001-system_user-implementation.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e1fe4fec57..fe99e65dd5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22623,6 +22623,25 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle before they were
+        assigned a database role. It is represented as
+        <literal>auth_method:identity</literal> or
+        <literal>NULL</literal> if the user has not been authenticated (for
+        example if <link linkend="auth-trust">Trust authentication</link> has
+        been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..c2a08e9414 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,14 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/*
+	 * Initialize SystemUser now that MyClientConnectionInfo is restored.
+	 * Also ensure that auth_method is actually valid, aka authn_id is not NULL.
+	 */
+	if (MyClientConnectionInfo.authn_id)
+		InitializeSystemUser(MyClientConnectionInfo.authn_id,
+							 hba_authname(MyClientConnectionInfo.auth_method));
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9b9bbf00a9..c51578c0b9 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2537,6 +2537,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3bac350bf5..453ba84494 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1142,7 +1142,8 @@ exprSetCollation(Node *expr, Oid collation)
 			((MinMaxExpr *) expr)->minmaxcollid = collation;
 			break;
 		case T_SQLValueFunction:
-			Assert((((SQLValueFunction *) expr)->type == NAMEOID) ?
+			Assert((((SQLValueFunction *) expr)->type == NAMEOID ||
+					((SQLValueFunction *) expr)->type == TEXTOID) ?
 				   (collation == C_COLLATION_OID) :
 				   (collation == InvalidOid));
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 82f03fc9c9..480e6d8c4b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -744,7 +744,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15239,6 +15239,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -17120,6 +17124,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -17500,6 +17505,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 7582faabb3..95d21dab67 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -707,6 +707,19 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 				 * Now figure out what collation to assign to this node.
 				 */
 				typcollation = get_typcollation(exprType(node));
+
+				/*
+				 * If this is a SQLValueFunction with a TEXT type, then
+				 * assign its collation to the standard C collation.
+				 */
+				if (IsA(node, SQLValueFunction))
+				{
+					if (((SQLValueFunction *) node)->type == TEXTOID)
+					{
+						typcollation = C_COLLATION_OID;
+					}
+				}
+
 				if (OidIsValid(typcollation))
 				{
 					/* Node's result is collatable; what about its input? */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7aaf1c673f..a9c5c48102 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2239,6 +2239,9 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
 			break;
+		case SVFOP_SYSTEM_USER:
+			svf->type = TEXTOID;
+			break;
 	}
 
 	return (Node *) svf;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bd8057bc3e..1cd08a65e8 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1907,6 +1907,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2b7b1b0c0f..c621b4328e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9210,6 +9210,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 683f616b1a..fa38a76c37 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -477,6 +477,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -548,6 +549,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -818,6 +829,45 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(const char *authn_id, const char *auth_method)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	/*
+	 * InitializeSystemUser should already be called once we are sure that
+	 * authn_id is not NULL (means auth_method is actually valid).
+	 * But keep the test here also for safety.
+	 */
+	if (authn_id)
+	{
+		/* Build system user as auth_method:authn_id */
+		char	   *system_user;
+
+		system_user = psprintf("%s:%s", auth_method, authn_id);
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
+
+/*
+ * SQL-function SYSTEM_USER
+ */
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(CStringGetTextDatum(sysuser));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 4a207a7391..31b7e1de5d 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -904,6 +904,10 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		/* ensure that auth_method is actually valid, aka authn_id is not NULL */
+		if (MyClientConnectionInfo.authn_id)
+			InitializeSystemUser(MyClientConnectionInfo.authn_id,
+								 hba_authname(MyClientConnectionInfo.auth_method));
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a07e737a33..68bb032d3e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '9977', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'text',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index ee48e392ed..e7ebea4ff4 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -357,6 +357,9 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void InitializeSystemUser(const char *authn_id,
+								 const char *auth_method);
+extern const char *GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 40661334bb..74fbc6a4af 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1318,6 +1318,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9a7cc0c6bd..ccc927851c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -409,6 +409,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..b0b7aac4c0 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,11 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for tests of SYSTEM_USER.
+$node->safe_psql(
+	'postgres',
+	"CREATE TABLE sysuser_data (n) AS SELECT NULL FROM generate_series(1, 10);
+	 GRANT ALL ON sysuser_data TO md5_role;");
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +87,25 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# SYSTEM_USER is null when not authenticated.
+my $res = $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication use SYSTEM_USER = NULL");
+
+# Test SYSTEM_USER with parallel workers when not authenticated.
+$res = $node->safe_psql(
+	'postgres', "
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM sysuser_data;
+    ",
+	connstr => "user=md5_role");
+is($res, 't',
+	"users with trust authentication use SYSTEM_USER = NULL in parallel workers"
+);
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
@@ -120,6 +144,26 @@ test_role($node, 'md5_role', 'md5', 0,
 	log_like =>
 	  [qr/connection authenticated: identity="md5_role" method=md5/]);
 
+# Test SYSTEM_USER <> NULL with parallel workers.
+$node->safe_psql(
+	'postgres',
+	"TRUNCATE sysuser_data;
+INSERT INTO sysuser_data SELECT 'md5:md5_role' FROM generate_series(1, 10);",
+	connstr => "user=md5_role");
+$res = $node->safe_psql(
+	'postgres', "
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM sysuser_data;
+    ",
+	connstr => "user=md5_role");
+is($res, 't',
+	"users with md5 authentication use SYSTEM_USER = md5:role in parallel workers"
+);
+
 # Tests for channel binding without SSL.
 # Using the password authentication method; channel binding can't work
 reset_pg_hba($node, 'password');
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..f7d8228b24 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -176,6 +176,13 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+	"CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 10);"
+);
+
+$node->safe_psql('postgres', 'GRANT SELECT ON ids TO public;');
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -307,6 +314,23 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query($node, 'test1', 'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s, 'gssencmode=require', 'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	  . "SET parallel_setup_cost TO 0;\n"
+	  . "SET parallel_tuple_cost TO 0;\n"
+	  . "SET max_parallel_workers_per_gather TO 2;\n"
+	  . "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
#38Jacob Champion
jchampion@timescale.com
In reply to: Drouvot, Bertrand (#37)
Re: SYSTEM_USER reserved word implementation

On 9/26/22 06:29, Drouvot, Bertrand wrote:

Please find attached V4 taking care of Jacob's previous comments.

+	/*
+	 * InitializeSystemUser should already be called once we are sure that
+	 * authn_id is not NULL (means auth_method is actually valid).
+	 * But keep the test here also for safety.
+	 */
+	if (authn_id)

Since there are only internal clients to the API, I'd argue this makes
more sense as an Assert(authn_id != NULL), but I don't think it's a
dealbreaker.

As far the assertion failure mentioned by Michael when moving the
SVFOP_SYSTEM_USER from NAMEOID to TEXTOID: V4 is assuming that it is
safe to force the collation to C_COLLATION_OID for SQLValueFunction
having a TEXT type, but I would be happy to also hear your thoughts
about it.

Unfortunately I don't have much to add here; I don't know enough about
the underlying problems.

Thanks,
--Jacob

#39Michael Paquier
michael@paquier.xyz
In reply to: Jacob Champion (#38)
1 attachment(s)
Re: SYSTEM_USER reserved word implementation

On Tue, Sep 27, 2022 at 03:38:49PM -0700, Jacob Champion wrote:

On 9/26/22 06:29, Drouvot, Bertrand wrote:
Since there are only internal clients to the API, I'd argue this makes
more sense as an Assert(authn_id != NULL), but I don't think it's a
dealbreaker.

Using an assert() looks like a good idea from here. If this is called
with a NULL authn, this could reflect a problem in the authentication
logic.

As far the assertion failure mentioned by Michael when moving the
SVFOP_SYSTEM_USER from NAMEOID to TEXTOID: V4 is assuming that it is
safe to force the collation to C_COLLATION_OID for SQLValueFunction
having a TEXT type, but I would be happy to also hear your thoughts
about it.

Unfortunately I don't have much to add here; I don't know enough about
the underlying problems.

I have been looking at that, and after putting my hands on that this
comes down to the facility introduced in 40c24bf. So, I think that
we'd better use COERCE_SQL_SYNTAX so as there is no need to worry
about the shortcuts this patch is trying to use with the collation
setup. And there are a few tests for get_func_sql_syntax() in
create_view.sql. Note that this makes the patch slightly shorter, and
simpler.

The docs still mentioned "name", and not "text".

This brings in a second point. 40c24bf has refrained from removing
SQLValueFunction, but based the experience on this thread I see a
pretty good argument in doing the jump once and for all. This
deserves a separate discussion, though. I'll do that and create a new
thread.
--
Michael

Attachments:

v5-0001-system_user-implementation.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8b72f8a215..e89703a3bf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '9977', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'text',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index ee48e392ed..e7ebea4ff4 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -357,6 +357,9 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+extern void InitializeSystemUser(const char *authn_id,
+								 const char *auth_method);
+extern const char *GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9a7cc0c6bd..ccc927851c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -409,6 +409,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 8cba888223..ee0985c7ed 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,14 @@ ParallelWorkerMain(Datum main_arg)
 										 false);
 	RestoreClientConnectionInfo(clientconninfospace);
 
+	/*
+	 * Initialize SystemUser now that MyClientConnectionInfo is restored.
+	 * Also ensure that auth_method is actually valid, aka authn_id is not NULL.
+	 */
+	if (MyClientConnectionInfo.authn_id)
+		InitializeSystemUser(MyClientConnectionInfo.authn_id,
+							 hba_authname(MyClientConnectionInfo.auth_method));
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0d8d292850..94d5142a4a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -743,7 +743,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15239,6 +15239,13 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = (Node *) makeFuncCall(SystemFuncName("system_user"),
+											   NIL,
+											   COERCE_SQL_SYNTAX,
+											   @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -17120,6 +17127,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -17500,6 +17508,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2b7b1b0c0f..c418403537 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10317,6 +10317,10 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
 			appendStringInfoChar(buf, ')');
 			return true;
 
+		case F_SYSTEM_USER:
+			appendStringInfoString(buf, "SYSTEM_USER");
+			return true;
+
 		case F_XMLEXISTS:
 			/* XMLEXISTS ... extra parens because args are c_expr */
 			appendStringInfoString(buf, "XMLEXISTS((");
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 683f616b1a..c9e55cdefd 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -477,6 +477,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -548,6 +549,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -818,6 +829,45 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user.
+ *
+ * This is built as auth_method:authn_id.
+ */
+void
+InitializeSystemUser(const char *authn_id, const char *auth_method)
+{
+	char	   *system_user;
+
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	/*
+	 * InitializeSystemUser should be called only when authn_id is not NULL,
+	 * meaning that auth_method is valid.
+	 */
+	Assert(authn_id != NULL);
+
+	system_user = psprintf("%s:%s", auth_method, authn_id);
+
+	/* Store SystemUser in long-lived storage */
+	SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+	pfree(system_user);
+}
+
+/*
+ * SQL-function SYSTEM_USER
+ */
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(CStringGetTextDatum(sysuser));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 4a207a7391..31b7e1de5d 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -904,6 +904,10 @@ InitPostgres(const char *in_dbname, Oid dboid,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		/* ensure that auth_method is actually valid, aka authn_id is not NULL */
+		if (MyClientConnectionInfo.authn_id)
+			InitializeSystemUser(MyClientConnectionInfo.authn_id,
+								 hba_authname(MyClientConnectionInfo.auth_method));
 		am_superuser = superuser();
 	}
 
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..b0b7aac4c0 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,11 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for tests of SYSTEM_USER.
+$node->safe_psql(
+	'postgres',
+	"CREATE TABLE sysuser_data (n) AS SELECT NULL FROM generate_series(1, 10);
+	 GRANT ALL ON sysuser_data TO md5_role;");
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +87,25 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# SYSTEM_USER is null when not authenticated.
+my $res = $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication use SYSTEM_USER = NULL");
+
+# Test SYSTEM_USER with parallel workers when not authenticated.
+$res = $node->safe_psql(
+	'postgres', "
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM sysuser_data;
+    ",
+	connstr => "user=md5_role");
+is($res, 't',
+	"users with trust authentication use SYSTEM_USER = NULL in parallel workers"
+);
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
@@ -120,6 +144,26 @@ test_role($node, 'md5_role', 'md5', 0,
 	log_like =>
 	  [qr/connection authenticated: identity="md5_role" method=md5/]);
 
+# Test SYSTEM_USER <> NULL with parallel workers.
+$node->safe_psql(
+	'postgres',
+	"TRUNCATE sysuser_data;
+INSERT INTO sysuser_data SELECT 'md5:md5_role' FROM generate_series(1, 10);",
+	connstr => "user=md5_role");
+$res = $node->safe_psql(
+	'postgres', "
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM sysuser_data;
+    ",
+	connstr => "user=md5_role");
+is($res, 't',
+	"users with md5 authentication use SYSTEM_USER = md5:role in parallel workers"
+);
+
 # Tests for channel binding without SSL.
 # Using the password authentication method; channel binding can't work
 reset_pg_hba($node, 'password');
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 47169a1d1e..a097e43afa 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -180,6 +180,13 @@ $node->start;
 
 $node->safe_psql('postgres', 'CREATE USER test1;');
 
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+	"CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 10);"
+);
+
+$node->safe_psql('postgres', 'GRANT SELECT ON ids TO public;');
+
 note "running tests";
 
 # Test connection success or failure, and if success, that query returns true.
@@ -311,6 +318,23 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query($node, 'test1', 'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s, 'gssencmode=require', 'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"SET min_parallel_table_scan_size TO 0;\n"
+	  . "SET parallel_setup_cost TO 0;\n"
+	  . "SET parallel_tuple_cost TO 0;\n"
+	  . "SET max_parallel_workers_per_gather TO 2;\n"
+	  . "SELECT bool_and(SYSTEM_USER = id) FROM ids;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index a828b1f6de..bf4ff30d86 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1940,7 +1940,8 @@ select
   trim(trailing ' foo ') as rt,
   trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea) as btb,
   trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea) as ltb,
-  trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb;
+  trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb,
+  SYSTEM_USER as su;
 select pg_get_viewdef('tt201v', true);
                                         pg_get_viewdef                                         
 -----------------------------------------------------------------------------------------------
@@ -1961,7 +1962,8 @@ select pg_get_viewdef('tt201v', true);
      TRIM(TRAILING FROM ' foo '::text) AS rt,                                                 +
      TRIM(BOTH '\x00'::bytea FROM '\x00546f6d00'::bytea) AS btb,                              +
      TRIM(LEADING '\x00'::bytea FROM '\x00546f6d00'::bytea) AS ltb,                           +
-     TRIM(TRAILING '\x00'::bytea FROM '\x00546f6d00'::bytea) AS rtb;
+     TRIM(TRAILING '\x00'::bytea FROM '\x00546f6d00'::bytea) AS rtb,                          +
+     SYSTEM_USER AS su;
 (1 row)
 
 -- corner cases with empty join conditions
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index 44a6775f90..913b4ee460 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -721,7 +721,8 @@ select
   trim(trailing ' foo ') as rt,
   trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea) as btb,
   trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea) as ltb,
-  trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb;
+  trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb,
+  SYSTEM_USER as su;
 select pg_get_viewdef('tt201v', true);
 
 -- corner cases with empty join conditions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d8718ed61e..409967f754 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22623,6 +22623,25 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>text</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle before they were
+        assigned a database role. It is represented as
+        <literal>auth_method:identity</literal> or
+        <literal>NULL</literal> if the user has not been authenticated (for
+        example if <link linkend="auth-trust">Trust authentication</link> has
+        been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
#40Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Michael Paquier (#39)
Re: SYSTEM_USER reserved word implementation

Hi,

On 9/28/22 5:28 AM, Michael Paquier wrote:

On Tue, Sep 27, 2022 at 03:38:49PM -0700, Jacob Champion wrote:

On 9/26/22 06:29, Drouvot, Bertrand wrote:
Since there are only internal clients to the API, I'd argue this makes
more sense as an Assert(authn_id != NULL), but I don't think it's a
dealbreaker.

Using an assert() looks like a good idea from here. If this is called
with a NULL authn, this could reflect a problem in the authentication
logic.

Agree, thanks for pointing out.

As far the assertion failure mentioned by Michael when moving the
SVFOP_SYSTEM_USER from NAMEOID to TEXTOID: V4 is assuming that it is
safe to force the collation to C_COLLATION_OID for SQLValueFunction
having a TEXT type, but I would be happy to also hear your thoughts
about it.

Unfortunately I don't have much to add here; I don't know enough about
the underlying problems.

I have been looking at that, and after putting my hands on that this
comes down to the facility introduced in 40c24bf. So, I think that
we'd better use COERCE_SQL_SYNTAX so as there is no need to worry
about the shortcuts this patch is trying to use with the collation
setup.

Nice!

And there are a few tests for get_func_sql_syntax() in
create_view.sql. Note that this makes the patch slightly shorter, and
simpler.

Agree that it does look simpler that way and that making use of
COERCE_SQL_SYNTAX does looks like a better approach. Nice catch!

The docs still mentioned "name", and not "text".

Oups, thanks for pointing out.

I had a look at v5 and it does look good to me.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

#41Michael Paquier
michael@paquier.xyz
In reply to: Drouvot, Bertrand (#40)
Re: SYSTEM_USER reserved word implementation

On Wed, Sep 28, 2022 at 12:58:48PM +0200, Drouvot, Bertrand wrote:

I had a look at v5 and it does look good to me.

Okay, cool. I have spent some time today doing a last pass over it
and an extra round of tests. Things looked fine, so applied.
--
Michael

#42Drouvot, Bertrand
bertranddrouvot.pg@gmail.com
In reply to: Michael Paquier (#41)
Re: SYSTEM_USER reserved word implementation

Hi,

On 9/29/22 8:12 AM, Michael Paquier wrote:

On Wed, Sep 28, 2022 at 12:58:48PM +0200, Drouvot, Bertrand wrote:

I had a look at v5 and it does look good to me.

Okay, cool. I have spent some time today doing a last pass over it
and an extra round of tests. Things looked fine, so applied.
--

Thanks for your precious help!

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com