Hi!
Thanks for comments!
> Just letting the superuser decide who gets to create subscriptions
> seems good enough from here.
I've prepare patch with new system role, i'm not sure about name, called it
"pg_subscription_users".
In that patch we don't check permissions on target tables, i don't know, should
we check it?
--------
Efimkin Evgeny
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 3f2f674a1a..0f7f60fcd0 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -201,9 +201,10 @@
<para>
Subscriptions are dumped by <command>pg_dump</command> if the current user
- is a superuser. Otherwise a warning is written and subscriptions are
- skipped, because non-superusers cannot read all subscription information
- from the <structname>pg_subscription</structname> catalog.
+ is a superuser or member of role pg_subscription_users, other users should
+ use <literal>no-subscriptions</literal> because non-superusers cannot read
+ all subscription information from the <structname>pg_subscription</structname>
+ catalog.
</para>
<para>
@@ -522,7 +523,8 @@
</para>
<para>
- To create a subscription, the user must be a superuser.
+ To create a subscription, the user must be a superuser or be member of role
+ <literal>pg_subscription_users</literal>.
</para>
<para>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 6106244d32..9c23a6280d 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -556,6 +556,10 @@ DROP ROLE doomed_role;
<literal>pg_read_all_stats</literal> and
<literal>pg_stat_scan_tables</literal>.</entry>
</row>
+ <row>
+ <entry>pg_subscription_users</entry>
+ <entry>Allow create/drop subscriptions and be owner of subscription. Read pg_subscription</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -579,6 +583,12 @@ DROP ROLE doomed_role;
other system information normally restricted to superusers.
</para>
+ <para>
+ The <literal>pg_subscription_users</literal> role are intended to allow
+ administrators trusted, but non-superuser, which are able to create/drop subscription.
+ </para>
+
+
<para>
Care should be taken when granting these roles to ensure they are only used where
needed and with the understanding that these roles grant access to privileged
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 7723f01327..56bf04631e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -939,9 +939,12 @@ REVOKE ALL ON pg_replication_origin_status FROM public;
-- All columns of pg_subscription except subconninfo are readable.
REVOKE ALL ON pg_subscription FROM public;
-GRANT SELECT (subdbid, subname, subowner, subenabled, subslotname, subpublications)
+GRANT SELECT (tableoid, oid, subdbid, subname, subowner, subenabled,
+ subslotname, subsynccommit, subpublications)
ON pg_subscription TO public;
+GRANT SELECT (subconninfo)
+ ON pg_subscription TO pg_subscription_users;
--
-- We have a few function definitions in here, too.
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index a60a15193a..e1555ed6eb 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -26,6 +26,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
+#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_subscription_rel.h"
@@ -342,10 +343,10 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
if (create_slot)
PreventInTransactionBlock(isTopLevel, "CREATE SUBSCRIPTION ... WITH (create_slot = true)");
- if (!superuser())
+ if (!is_member_of_role(GetUserId(), DEFAULT_ROLE_SUBSCRIPTION_USERS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to create subscriptions"))));
+ (errmsg("must be member of role \"pg_subscription_users\" to create subscriptions"))));
rel = table_open(SubscriptionRelationId, RowExclusiveLock);
@@ -1035,12 +1036,12 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
NameStr(form->subname));
/* New owner must be a superuser */
- if (!superuser_arg(newOwnerId))
+ if (!is_member_of_role(newOwnerId, DEFAULT_ROLE_SUBSCRIPTION_USERS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of subscription \"%s\"",
NameStr(form->subname)),
- errhint("The owner of a subscription must be a superuser.")));
+ errhint("The owner of a subscription must be member of role \"pg_subscription_users\".")));
form->subowner = newOwnerId;
CatalogTupleUpdate(rel, &tup->t_self, tup);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4c98ae4d7f..8751801b89 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4111,7 +4111,7 @@ getSubscriptions(Archive *fout)
if (dopt->no_subscriptions || fout->remoteVersion < 100000)
return;
- if (!is_superuser(fout))
+ if (!is_superuser(fout) && fout->remoteVersion < 120000)
{
int n;
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index c21f97adcf..8f613ac9b3 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -55,6 +55,11 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '4572', oid_symbol => 'DEFAULT_ROLE_SUBSCRIPTION_USERS',
+ rolname => 'pg_subscription_users', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4200', oid_symbol => 'DEFAULT_ROLE_SIGNAL_BACKENDID',
rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 4fcbf7efe9..9b48030ae1 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -4,6 +4,8 @@
CREATE ROLE regress_subscription_user LOGIN SUPERUSER;
CREATE ROLE regress_subscription_user2;
CREATE ROLE regress_subscription_user_dummy LOGIN NOSUPERUSER;
+CREATE ROLE regress_subscription_user3 LOGIN NOSUPERUSER;
+GRANT pg_subscription_users to regress_subscription_user3;
SET SESSION AUTHORIZATION 'regress_subscription_user';
-- fail - no publications
CREATE SUBSCRIPTION testsub CONNECTION 'foo';
@@ -40,10 +42,10 @@ SELECT obj_description(s.oid, 'pg_subscription') FROM pg_subscription s;
-- fail - name already exists
CREATE SUBSCRIPTION testsub CONNECTION 'dbname=doesnotexist' PUBLICATION testpub WITH (connect = false);
ERROR: subscription "testsub" already exists
--- fail - must be superuser
+-- fail - must be member of role "pg_subscription_users"
SET SESSION AUTHORIZATION 'regress_subscription_user2';
CREATE SUBSCRIPTION testsub2 CONNECTION 'dbname=doesnotexist' PUBLICATION foo WITH (connect = false);
-ERROR: must be superuser to create subscriptions
+ERROR: must be member of role "pg_subscription_users" to create subscriptions
SET SESSION AUTHORIZATION 'regress_subscription_user';
-- fail - invalid option combinations
CREATE SUBSCRIPTION testsub2 CONNECTION 'dbname=doesnotexist' PUBLICATION testpub WITH (connect = false, copy_data = true);
@@ -70,6 +72,12 @@ ALTER SUBSCRIPTION testsub3 ENABLE;
ERROR: cannot enable subscription that does not have a slot name
ALTER SUBSCRIPTION testsub3 REFRESH PUBLICATION;
ERROR: ALTER SUBSCRIPTION ... REFRESH is not allowed for disabled subscriptions
+-- ok - member of pg_subcription_users
+SET SESSION AUTHORIZATION 'regress_subscription_user3';
+CREATE SUBSCRIPTION testsub4 CONNECTION 'dbname=doesnotexist' PUBLICATION foo WITH (slot_name = NONE, connect = false);
+WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
+DROP SUBSCRIPTION testsub4;
+SET SESSION AUTHORIZATION 'regress_subscription_user';
DROP SUBSCRIPTION testsub3;
-- fail - invalid connection string
ALTER SUBSCRIPTION testsub CONNECTION 'foobar';
@@ -134,10 +142,10 @@ HINT: Available values: local, remote_write, remote_apply, on, off.
-- rename back to keep the rest simple
ALTER SUBSCRIPTION testsub_foo RENAME TO testsub;
--- fail - new owner must be superuser
+-- fail - new owner must be member of role "pg_subscription_users"
ALTER SUBSCRIPTION testsub OWNER TO regress_subscription_user2;
ERROR: permission denied to change owner of subscription "testsub"
-HINT: The owner of a subscription must be a superuser.
+HINT: The owner of a subscription must be member of role "pg_subscription_users".
ALTER ROLE regress_subscription_user2 SUPERUSER;
-- now it works
ALTER SUBSCRIPTION testsub OWNER TO regress_subscription_user2;
@@ -159,3 +167,5 @@ RESET SESSION AUTHORIZATION;
DROP ROLE regress_subscription_user;
DROP ROLE regress_subscription_user2;
DROP ROLE regress_subscription_user_dummy;
+REVOKE pg_subscription_users FROM regress_subscription_user3;
+DROP ROLE regress_subscription_user3;
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 36fa1bbac8..09440a9ce5 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -5,6 +5,8 @@
CREATE ROLE regress_subscription_user LOGIN SUPERUSER;
CREATE ROLE regress_subscription_user2;
CREATE ROLE regress_subscription_user_dummy LOGIN NOSUPERUSER;
+CREATE ROLE regress_subscription_user3 LOGIN NOSUPERUSER;
+GRANT pg_subscription_users to regress_subscription_user3;
SET SESSION AUTHORIZATION 'regress_subscription_user';
-- fail - no publications
@@ -33,7 +35,7 @@ SELECT obj_description(s.oid, 'pg_subscription') FROM pg_subscription s;
-- fail - name already exists
CREATE SUBSCRIPTION testsub CONNECTION 'dbname=doesnotexist' PUBLICATION testpub WITH (connect = false);
--- fail - must be superuser
+-- fail - must be member of role "pg_subscription_users"
SET SESSION AUTHORIZATION 'regress_subscription_user2';
CREATE SUBSCRIPTION testsub2 CONNECTION 'dbname=doesnotexist' PUBLICATION foo WITH (connect = false);
SET SESSION AUTHORIZATION 'regress_subscription_user';
@@ -53,6 +55,11 @@ CREATE SUBSCRIPTION testsub3 CONNECTION 'dbname=doesnotexist' PUBLICATION testpu
-- fail
ALTER SUBSCRIPTION testsub3 ENABLE;
ALTER SUBSCRIPTION testsub3 REFRESH PUBLICATION;
+-- ok - member of pg_subcription_users
+SET SESSION AUTHORIZATION 'regress_subscription_user3';
+CREATE SUBSCRIPTION testsub4 CONNECTION 'dbname=doesnotexist' PUBLICATION foo WITH (slot_name = NONE, connect = false);
+DROP SUBSCRIPTION testsub4;
+SET SESSION AUTHORIZATION 'regress_subscription_user';
DROP SUBSCRIPTION testsub3;
@@ -96,7 +103,7 @@ ALTER SUBSCRIPTION testsub_foo SET (synchronous_commit = foobar);
-- rename back to keep the rest simple
ALTER SUBSCRIPTION testsub_foo RENAME TO testsub;
--- fail - new owner must be superuser
+-- fail - new owner must be member of role "pg_subscription_users"
ALTER SUBSCRIPTION testsub OWNER TO regress_subscription_user2;
ALTER ROLE regress_subscription_user2 SUPERUSER;
-- now it works
@@ -121,3 +128,5 @@ RESET SESSION AUTHORIZATION;
DROP ROLE regress_subscription_user;
DROP ROLE regress_subscription_user2;
DROP ROLE regress_subscription_user_dummy;
+REVOKE pg_subscription_users FROM regress_subscription_user3;
+DROP ROLE regress_subscription_user3;
\ No newline at end of file