summaryrefslogtreecommitdiff
path: root/src/test/subscription
diff options
context:
space:
mode:
authorRobert Haas2023-04-04 15:25:23 +0000
committerRobert Haas2023-04-04 15:25:23 +0000
commit1e10d49b65d6c26c61fee07999e4cd59eab2b765 (patch)
treeae1f0b88eda66ecbe945d92b62ffe1fbe41f661a /src/test/subscription
parent3077324b03e81ab9894f1f510952273b78d98569 (diff)
Perform logical replication actions as the table owner.
Up until now, logical replication actions have been performed as the subscription owner, who will generally be a superuser. Commit cec57b1a0fbcd3833086ba686897c5883e0a2afc documented hazards associated with that situation, namely, that any user who owns a table on the subscriber side could assume the privileges of the subscription owner by attaching a trigger, expression index, or some other kind of executable code to it. As a remedy, it suggested not creating configurations where users who are not fully trusted own tables on the subscriber. Although that will work, it basically precludes using logical replication in the way that people typically want to use it, namely, to replicate a database from one node to another without necessarily having any restrictions on which database users can own tables. So, instead, change logical replication to execute INSERT, UPDATE, DELETE, and TRUNCATE operations as the table owner when they are replicated. Since this involves switching the active user frequently within a session that is authenticated as the subscription user, also impose SECURITY_RESTRICTED_OPERATION restrictions on logical replication code. As an exception, if the table owner can SET ROLE to the subscription owner, these restrictions have no security value, so don't impose them in that case. Subscription owners are now required to have the ability to SET ROLE to every role that owns a table that the subscription is replicating. If they don't, replication will fail. Superusers, who normally own subscriptions, satisfy this property by default. Non-superusers users who own subscriptions will need to be granted the roles that own relevant tables. Patch by me, reviewed (but not necessarily in its entirety) by Jelte Fennema, Jeff Davis, and Noah Misch. Discussion: http://postgr.es/m/CA+TgmoaSCkg9ww9oppPqqs+9RVqCexYCE6Aq=UsYPfnOoDeFkw@mail.gmail.com
Diffstat (limited to 'src/test/subscription')
-rw-r--r--src/test/subscription/t/027_nosuperuser.pl165
1 files changed, 78 insertions, 87 deletions
diff --git a/src/test/subscription/t/027_nosuperuser.pl b/src/test/subscription/t/027_nosuperuser.pl
index e770e0615c9..8a7e79cacac 100644
--- a/src/test/subscription/t/027_nosuperuser.pl
+++ b/src/test/subscription/t/027_nosuperuser.pl
@@ -76,22 +76,6 @@ sub grant_superuser
ALTER ROLE $role SUPERUSER));
}
-sub revoke_bypassrls
-{
- my ($role) = @_;
- $node_subscriber->safe_psql(
- 'postgres', qq(
- ALTER ROLE $role NOBYPASSRLS));
-}
-
-sub grant_bypassrls
-{
- my ($role) = @_;
- $node_subscriber->safe_psql(
- 'postgres', qq(
- ALTER ROLE $role BYPASSRLS));
-}
-
# Create publisher and subscriber nodes with schemas owned and published by
# "regress_alice" but subscribed and replicated by different role
# "regress_admin". For partitioned tables, layout the partitions differently
@@ -177,59 +161,32 @@ expect_failure(
2,
5,
7,
- qr/ERROR: ( [A-Z0-9]+:)? permission denied for table unpartitioned/msi,
+ qr/ERROR: ( [A-Z0-9]+:)? role "regress_admin" cannot SET ROLE to "regress_alice"/msi,
"non-superuser admin fails to replicate update");
grant_superuser("regress_admin");
expect_replication("alice.unpartitioned", 2, 7, 9,
"admin with restored superuser privilege replicates update");
-# Grant INSERT, UPDATE, DELETE privileges on the target tables to
-# "regress_admin" so that superuser privileges are not necessary for
-# replication.
-#
-# Note that UPDATE and DELETE also require SELECT privileges, which
-# will be granted in subsequent test.
-#
+# Privileges on the target role suffice for non-superuser replication.
$node_subscriber->safe_psql(
'postgres', qq(
ALTER ROLE regress_admin NOSUPERUSER;
-SET SESSION AUTHORIZATION regress_alice;
-GRANT INSERT,UPDATE,DELETE ON
- alice.unpartitioned,
- alice.hashpart, alice.hashpart_a, alice.hashpart_b
- TO regress_admin;
-REVOKE SELECT ON alice.unpartitioned FROM regress_admin;
+GRANT regress_alice TO regress_admin;
));
publish_insert("alice.unpartitioned", 11);
expect_replication("alice.unpartitioned", 3, 7, 11,
- "nosuperuser admin with INSERT privileges can replicate into unpartitioned"
+ "nosuperuser admin with privileges on role can replicate INSERT into unpartitioned"
);
publish_update("alice.unpartitioned", 7 => 13);
-expect_failure(
- "alice.unpartitioned",
- 3,
- 7,
- 11,
- qr/ERROR: ( [A-Z0-9]+:)? permission denied for table unpartitioned/msi,
- "non-superuser admin without SELECT privileges fails to replicate update"
+expect_replication("alice.unpartitioned", 3, 9, 13,
+ "nosuperuser admin with privileges on role can replicate UPDATE into unpartitioned"
);
-# Now grant SELECT
-#
-$node_subscriber->safe_psql(
- 'postgres', qq(
-SET SESSION AUTHORIZATION regress_alice;
-GRANT SELECT ON
- alice.unpartitioned,
- alice.hashpart, alice.hashpart_a, alice.hashpart_b
- TO regress_admin;
-));
-
publish_delete("alice.unpartitioned", 9);
expect_replication("alice.unpartitioned", 2, 11, 13,
- "nosuperuser admin with all table privileges can replicate into unpartitioned"
+ "nosuperuser admin with privileges on role can replicate DELETE into unpartitioned"
);
# Test partitioning
@@ -240,80 +197,114 @@ publish_insert("alice.hashpart", 103);
publish_update("alice.hashpart", 102 => 120);
publish_delete("alice.hashpart", 101);
expect_replication("alice.hashpart", 2, 103, 120,
- "nosuperuser admin with all table privileges can replicate into hashpart"
+ "nosuperuser admin with privileges on role can replicate into hashpart"
);
-
-# Enable RLS on the target table and check that "regress_admin" can
-# only replicate into it when superuser or bypassrls.
-#
+# Force RLS on the target table and check that replication fails.
$node_subscriber->safe_psql(
'postgres', qq(
SET SESSION AUTHORIZATION regress_alice;
ALTER TABLE alice.unpartitioned ENABLE ROW LEVEL SECURITY;
+ALTER TABLE alice.unpartitioned FORCE ROW LEVEL SECURITY;
));
-revoke_superuser("regress_admin");
publish_insert("alice.unpartitioned", 15);
expect_failure(
"alice.unpartitioned",
2,
11,
13,
- qr/ERROR: ( [A-Z0-9]+:)? user "regress_admin" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
- "non-superuser admin fails to replicate insert into rls enabled table");
-grant_superuser("regress_admin");
+ qr/ERROR: ( [A-Z0-9]+:)? user "regress_alice" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
+ "replication of insert into table with forced rls fails");
+
+# Since replication acts as the table owner, replication will succeed if we don't force it.
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ALTER TABLE alice.unpartitioned NO FORCE ROW LEVEL SECURITY;
+));
expect_replication("alice.unpartitioned", 3, 11, 15,
- "admin with restored superuser privilege replicates insert into rls enabled unpartitioned"
+ "non-superuser admin can replicate insert if rls is not forced"
);
-revoke_superuser("regress_admin");
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ALTER TABLE alice.unpartitioned FORCE ROW LEVEL SECURITY;
+));
publish_update("alice.unpartitioned", 11 => 17);
expect_failure(
"alice.unpartitioned",
3,
11,
15,
- qr/ERROR: ( [A-Z0-9]+:)? user "regress_admin" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
- "non-superuser admin fails to replicate update into rls enabled unpartitioned"
+ qr/ERROR: ( [A-Z0-9]+:)? user "regress_alice" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
+ "replication of update into table with forced rls fails"
);
-
-grant_bypassrls("regress_admin");
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ALTER TABLE alice.unpartitioned NO FORCE ROW LEVEL SECURITY;
+));
expect_replication("alice.unpartitioned", 3, 13, 17,
- "admin with bypassrls replicates update into rls enabled unpartitioned");
+ "non-superuser admin can replicate update if rls is not forced");
-revoke_bypassrls("regress_admin");
-publish_delete("alice.unpartitioned", 13);
+# Remove some of alice's privileges on her own table. Then replication should fail.
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+REVOKE SELECT, INSERT ON alice.unpartitioned FROM regress_alice;
+));
+publish_insert("alice.unpartitioned", 19);
expect_failure(
"alice.unpartitioned",
3,
13,
17,
- qr/ERROR: ( [A-Z0-9]+:)? user "regress_admin" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
- "non-superuser admin without bypassrls fails to replicate delete into rls enabled unpartitioned"
+ qr/ERROR: ( [A-Z0-9]+:)? permission denied for table unpartitioned/msi,
+ "replication of insert fails if table owner lacks insert permission"
);
-grant_bypassrls("regress_admin");
-expect_replication("alice.unpartitioned", 2, 15, 17,
- "admin with bypassrls replicates delete into rls enabled unpartitioned");
-grant_superuser("regress_admin");
-# Alter the subscription owner to "regress_alice". She has neither superuser
-# nor bypassrls, but as the table owner should be able to replicate.
-#
+# alice needs INSERT but not SELECT to replicate an INSERT.
$node_subscriber->safe_psql(
'postgres', qq(
-ALTER SUBSCRIPTION admin_sub DISABLE;
-ALTER ROLE regress_alice SUPERUSER;
-ALTER SUBSCRIPTION admin_sub OWNER TO regress_alice;
-ALTER ROLE regress_alice NOSUPERUSER;
-ALTER SUBSCRIPTION admin_sub ENABLE;
+GRANT INSERT ON alice.unpartitioned TO regress_alice;
));
+expect_replication("alice.unpartitioned", 4, 13, 19,
+ "restoring insert permission permits replication to continue");
-publish_insert("alice.unpartitioned", 23);
-publish_update("alice.unpartitioned", 15 => 25);
-publish_delete("alice.unpartitioned", 17);
-expect_replication("alice.unpartitioned", 2, 23, 25,
- "nosuperuser nobypassrls table owner can replicate delete into unpartitioned despite rls"
+# Now let's try an UPDATE and a DELETE.
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+REVOKE UPDATE, DELETE ON alice.unpartitioned FROM regress_alice;
+));
+publish_update("alice.unpartitioned", 13 => 21);
+publish_delete("alice.unpartitioned", 15);
+expect_failure(
+ "alice.unpartitioned",
+ 4,
+ 13,
+ 19,
+ qr/ERROR: ( [A-Z0-9]+:)? permission denied for table unpartitioned/msi,
+ "replication of update/delete fails if table owner lacks corresponding permission"
);
+# Restoring UPDATE and DELETE is insufficient.
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+GRANT UPDATE, DELETE ON alice.unpartitioned TO regress_alice;
+));
+expect_failure(
+ "alice.unpartitioned",
+ 4,
+ 13,
+ 19,
+ qr/ERROR: ( [A-Z0-9]+:)? permission denied for table unpartitioned/msi,
+ "replication of update/delete fails if table owner lacks SELECT permission"
+);
+
+# alice needs INSERT but not SELECT to replicate an INSERT.
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+GRANT SELECT ON alice.unpartitioned TO regress_alice;
+));
+expect_replication("alice.unpartitioned", 3, 17, 21,
+ "restoring SELECT permission permits replication to continue");
+
done_testing();