From 57e02d95ace2a390ea1e40266735529f313b31ec Mon Sep 17 00:00:00 2001
From: Michael Banck <michael.banck@credativ.de>
Date: Thu, 31 Oct 2024 22:36:12 +0100
Subject: [PATCH v3] Add new pg_manage_extensions predefined role.

This allows any role that is granted this new predefined role to CREATE, UPDATE
or DROP extensions, no matter whether they are trusted or not.
---
 doc/src/sgml/user-manag.sgml      | 11 +++++++++++
 src/backend/commands/extension.c  | 11 ++++++-----
 src/include/catalog/pg_authid.dat |  5 +++++
 3 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index ed18704a9c2..2a44f41da4f 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -669,6 +669,17 @@ GRANT pg_signal_backend TO admin_user;
      </listitem>
     </varlistentry>
 
+    <varlistentry id="predefined-role-pg-manage-extensions" xreflabel="pg_manage_extensions">
+     <term><varname>pg_manage_extensions</varname></term>
+     <listitem>
+      <para>
+       <literal>pg_manage_extensions</literal> allows creating, altering or
+       dropping extensions, even if the extensions are untrusted or the user
+       does not have <literal>CREATE</literal> rights on the database.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="predefined-role-pg-monitor" xreflabel="pg_monitor">
      <term><varname>pg_monitor</varname></term>
      <term><varname>pg_read_all_settings</varname></term>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index d9bb4ce5f1e..2426c3e0eda 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -996,13 +996,14 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 	ListCell   *lc2;
 
 	/*
-	 * Enforce superuser-ness if appropriate.  We postpone these checks until
-	 * here so that the control flags are correctly associated with the right
+	 * Enforce superuser-ness/membership of the pg_manage_extensions
+	 * predefined role if appropriate.  We postpone these checks until here
+	 * so that the control flags are correctly associated with the right
 	 * script(s) if they happen to be set in secondary control files.
 	 */
 	if (control->superuser && !superuser())
 	{
-		if (extension_is_trusted(control))
+		if (extension_is_trusted(control) || has_privs_of_role(GetUserId(), ROLE_PG_MANAGE_EXTENSIONS))
 			switch_to_superuser = true;
 		else if (from_version == NULL)
 			ereport(ERROR,
@@ -1011,7 +1012,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 							control->name),
 					 control->trusted
 					 ? errhint("Must have CREATE privilege on current database to create this extension.")
-					 : errhint("Must be superuser to create this extension.")));
+					 : errhint("Must be superuser or member of pg_manage_extensions to create this extension.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -1019,7 +1020,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 							control->name),
 					 control->trusted
 					 ? errhint("Must have CREATE privilege on current database to update this extension.")
-					 : errhint("Must be superuser to update this extension.")));
+					 : errhint("Must be superuser or member of pg_manage_extensions to update this extension.")));
 	}
 
 	filename = get_extension_script_filename(control, from_version, version);
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index eb4dab5c6aa..0e3eb20e2b9 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -104,5 +104,10 @@
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '8801', oid_symbol => 'ROLE_PG_MANAGE_EXTENSIONS',
+  rolname => 'pg_manage_extensions', rolsuper => 'f', rolinherit => 't',
+  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+  rolpassword => '_null_', rolvaliduntil => '_null_' },
 
 ]
-- 
2.39.5

