diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 4f70b1f4b8..fb4472356d 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -336,7 +336,7 @@
cause replication conflicts, as will enabled
row-level security on target tables
that the subscription owner is subject to, without regard to whether any
- policy would ordinary reject the INSERT,
+ policy would ordinarily reject the INSERT,
UPDATE, DELETE or
TRUNCATE which is being replicated. This restriction on
row-level security may be lifted in a future version of
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index a79d502adc..c9af775bc1 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1991,6 +1991,12 @@ FindReplTupleInLocalRel(EState *estate, Relation localrel,
Oid idxoid;
bool found;
+ /*
+ * Regardless of the top-level operation, we're performing a read here, so
+ * check for SELECT privileges.
+ */
+ TargetPrivilegesCheck(localrel, ACL_SELECT);
+
*localslot = table_slot_create(localrel, &estate->es_tupleTable);
idxoid = GetRelationIdentityOrPK(localrel);
diff --git a/src/test/subscription/t/027_nosuperuser.pl b/src/test/subscription/t/027_nosuperuser.pl
index c9e4aeba06..71aa91e7b6 100644
--- a/src/test/subscription/t/027_nosuperuser.pl
+++ b/src/test/subscription/t/027_nosuperuser.pl
@@ -5,7 +5,7 @@
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
-use Test::More tests => 100;
+use Test::More tests => 14;
my ($node_publisher, $node_subscriber, $publisher_connstr, $result, $offset);
$offset = 0;
@@ -95,19 +95,6 @@ $node_subscriber->init;
$node_publisher->start;
$node_subscriber->start;
$publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
-my %range_a = (
- publisher => 'FROM (0) TO (15)',
- subscriber => 'FROM (0) TO (5)');
-my %range_b = (
- publisher => 'FROM (15) TO (30)',
- subscriber => 'FROM (5) TO (30)');
-my %list_a = (
- publisher => 'IN (1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29)',
- subscriber => 'IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)',
-);
-my %list_b = (
- publisher => 'IN (2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30)',
- subscriber => 'IN (17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)');
my %remainder_a = (
publisher => 0,
subscriber => 1);
@@ -117,10 +104,6 @@ my %remainder_b = (
for my $node ($node_publisher, $node_subscriber)
{
- my $range_a = $range_a{$node->name};
- my $range_b = $range_b{$node->name};
- my $list_a = $list_a{$node->name};
- my $list_b = $list_b{$node->name};
my $remainder_a = $remainder_a{$node->name};
my $remainder_b = $remainder_b{$node->name};
$node->safe_psql('postgres', qq(
@@ -135,26 +118,6 @@ for my $node ($node_publisher, $node_subscriber)
ALTER TABLE alice.unpartitioned REPLICA IDENTITY FULL;
GRANT SELECT ON TABLE alice.unpartitioned TO regress_admin;
- CREATE TABLE alice.rangepart (i INTEGER) PARTITION BY RANGE (i);
- ALTER TABLE alice.rangepart REPLICA IDENTITY FULL;
- GRANT SELECT ON TABLE alice.rangepart TO regress_admin;
- CREATE TABLE alice.rangepart_a PARTITION OF alice.rangepart
- FOR VALUES $range_a;
- ALTER TABLE alice.rangepart_a REPLICA IDENTITY FULL;
- CREATE TABLE alice.rangepart_b PARTITION OF alice.rangepart
- FOR VALUES $range_b;
- ALTER TABLE alice.rangepart_b REPLICA IDENTITY FULL;
-
- CREATE TABLE alice.listpart (i INTEGER) PARTITION BY LIST (i);
- ALTER TABLE alice.listpart REPLICA IDENTITY FULL;
- GRANT SELECT ON TABLE alice.listpart TO regress_admin;
- CREATE TABLE alice.listpart_a PARTITION OF alice.listpart
- FOR VALUES $list_a;
- ALTER TABLE alice.listpart_a REPLICA IDENTITY FULL;
- CREATE TABLE alice.listpart_b PARTITION OF alice.listpart
- FOR VALUES $list_b;
- ALTER TABLE alice.listpart_b REPLICA IDENTITY FULL;
-
CREATE TABLE alice.hashpart (i INTEGER) PARTITION BY HASH (i);
ALTER TABLE alice.hashpart REPLICA IDENTITY FULL;
GRANT SELECT ON TABLE alice.hashpart TO regress_admin;
@@ -170,7 +133,7 @@ $node_publisher->safe_psql('postgres', qq(
SET SESSION AUTHORIZATION regress_alice;
CREATE PUBLICATION alice
- FOR TABLE alice.unpartitioned, alice.rangepart, alice.listpart, alice.hashpart
+ FOR TABLE alice.unpartitioned, alice.hashpart
WITH (publish_via_partition_root = true);
));
$node_subscriber->safe_psql('postgres', qq(
@@ -178,163 +141,125 @@ SET SESSION AUTHORIZATION regress_admin;
CREATE SUBSCRIPTION admin_sub CONNECTION '$publisher_connstr' PUBLICATION alice;
));
+$node_publisher->wait_for_catchup('admin_sub');
+
+# Wait for initial sync to finish as well
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
# Verify that "regress_admin" can replicate into the tables
#
-my @tbl = (qw(unpartitioned rangepart listpart hashpart));
-for my $tbl (@tbl)
-{
- publish_insert("alice.$tbl", 1);
- publish_insert("alice.$tbl", 3);
- publish_insert("alice.$tbl", 5);
- expect_replication(
- "alice.$tbl", 3, 1, 5,
- "superuser admin replicates insert into $tbl");
- publish_update("alice.$tbl", 1 => 7);
- expect_replication(
- "alice.$tbl", 3, 3, 7,
- "superuser admin replicates update into $tbl");
- publish_delete("alice.$tbl", 3);
- expect_replication(
- "alice.$tbl", 2, 5, 7,
- "superuser admin replicates delete into $tbl");
-}
+publish_insert("alice.unpartitioned", 1);
+publish_insert("alice.unpartitioned", 3);
+publish_insert("alice.unpartitioned", 5);
+publish_update("alice.unpartitioned", 1 => 7);
+publish_delete("alice.unpartitioned", 3);
+expect_replication(
+ "alice.unpartitioned", 2, 5, 7,
+ "superuser admin replicates into unpartitioned");
-# Repeatedly revoke and restore superuser privilege for "regress_admin", verifying
-# that replication fails while superuser privilege is missing, but works again and
-# catches up once superuser is restored.
+# Revoke and restore superuser privilege for "regress_admin",
+# verifying that replication fails while superuser privilege is
+# missing, but works again and catches up once superuser is restored.
#
-for my $tbl (@tbl)
-{
- revoke_superuser("regress_admin");
- publish_insert("alice.$tbl", 3);
- expect_failure("alice.$tbl", 2, 5, 7,
- qr/ERROR: permission denied for table $tbl/msi,
- "non-superuser admin fails to replicate insert");
- grant_superuser("regress_admin");
- expect_replication("alice.$tbl", 3, 3, 7,
- "admin with restored superuser privilege replicates insert");
+revoke_superuser("regress_admin");
+publish_update("alice.unpartitioned", 5 => 9);
+expect_failure("alice.unpartitioned", 2, 5, 7,
+ qr/ERROR: permission denied for table unpartitioned/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");
- revoke_superuser("regress_admin");
- publish_update("alice.$tbl", 3 => 9);
- expect_failure("alice.$tbl", 3, 3, 7,
- qr/ERROR: permission denied for table $tbl/msi,
- "non-superuser admin fails to replicate update");
- grant_superuser("regress_admin");
- expect_replication("alice.$tbl", 3, 5, 9,
- "admin with restored superuser privilege replicates update");
-
- revoke_superuser("regress_admin");
- publish_delete("alice.$tbl", 5);
- expect_failure("alice.$tbl", 3, 5, 9,
- qr/ERROR: permission denied for table $tbl/msi,
- "non-superuser admin fails to replicate delete");
- grant_superuser("regress_admin");
- expect_replication("alice.$tbl", 2, 7, 9,
- "admin with restored superuser privilege replicates delete");
-}
-
-# Grant privileges on the target tables to "regress_admin" so that superuser
-# privileges are not necessary for replication.
+# 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.
#
$node_subscriber->safe_psql('postgres', qq(
ALTER ROLE regress_admin NOSUPERUSER;
SET SESSION AUTHORIZATION regress_alice;
-GRANT ALL PRIVILEGES ON
+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;
+));
+
+publish_insert("alice.unpartitioned", 11);
+expect_replication("alice.unpartitioned", 3, 7, 11,
+ "nosuperuser admin with INSERT privileges can replicate into unpartitioned");
+
+publish_update("alice.unpartitioned", 7 => 13);
+expect_failure("alice.unpartitioned", 3, 7, 11,
+ qr/ERROR: permission denied for table unpartitioned/msi,
+ "non-superuser admin without SELECT privileges fails to replicate update");
+
+# Now grant SELECT
+#
+$node_subscriber->safe_psql('postgres', qq(
+SET SESSION AUTHORIZATION regress_alice;
+GRANT SELECT ON
alice.unpartitioned,
- alice.rangepart, alice.rangepart_a, alice.rangepart_b,
- alice.listpart, alice.listpart_a, alice.listpart_b,
alice.hashpart, alice.hashpart_a, alice.hashpart_b
TO regress_admin;
));
-for my $tbl (@tbl)
-{
- publish_insert("alice.$tbl", 11);
- publish_update("alice.$tbl", 7 => 13);
- publish_delete("alice.$tbl", 9);
- expect_replication("alice.$tbl", 2, 11, 13,
- "nosuperuser admin with all table privileges can replicate into $tbl");
-}
-# Enable RLS on the target tables and check that "regress_admin" can only
-# replicate into them when superuser. Note that RLS must be enabled on the
-# partitions, not the partitioned tables, since the partitions are the targets
-# of the replication.
+publish_delete("alice.unpartitioned", 9);
+expect_replication("alice.unpartitioned", 2, 11, 13,
+ "nosuperuser admin with all table privileges can replicate into unpartitioned");
+
+# Test partitioning
+#
+publish_insert("alice.hashpart", 101);
+publish_insert("alice.hashpart", 102);
+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");
+
+
+# Enable RLS on the target table and check that "regress_admin" can
+# only replicate into it when superuser or bypassrls.
#
$node_subscriber->safe_psql('postgres', qq(
SET SESSION AUTHORIZATION regress_alice;
ALTER TABLE alice.unpartitioned ENABLE ROW LEVEL SECURITY;
-ALTER TABLE alice.rangepart_a ENABLE ROW LEVEL SECURITY;
-ALTER TABLE alice.rangepart_b ENABLE ROW LEVEL SECURITY;
-ALTER TABLE alice.listpart_a ENABLE ROW LEVEL SECURITY;
-ALTER TABLE alice.listpart_b ENABLE ROW LEVEL SECURITY;
-ALTER TABLE alice.hashpart_a ENABLE ROW LEVEL SECURITY;
-ALTER TABLE alice.hashpart_b ENABLE ROW LEVEL SECURITY;
));
-for my $tbl (@tbl)
-{
- revoke_superuser("regress_admin");
- publish_insert("alice.$tbl", 15);
- expect_failure("alice.$tbl", 2, 11, 13,
- qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
- "non-superuser admin fails to replicate insert into rls enabled table");
- grant_superuser("regress_admin");
- expect_replication("alice.$tbl", 3, 11, 15,
- "admin with restored superuser privilege replicates insert into rls enabled $tbl");
- revoke_superuser("regress_admin");
- publish_update("alice.$tbl", 11 => 17);
- expect_failure("alice.$tbl", 3, 11, 15,
- qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
- "non-superuser admin fails to replicate update into rls enabled $tbl");
+revoke_superuser("regress_admin");
+publish_insert("alice.unpartitioned", 15);
+expect_failure("alice.unpartitioned", 2, 11, 13,
+ qr/ERROR: "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");
+expect_replication("alice.unpartitioned", 3, 11, 15,
+ "admin with restored superuser privilege replicates insert into rls enabled unpartitioned");
- grant_superuser("regress_admin");
- expect_replication("alice.$tbl", 3, 13, 17,
- "admin with restored superuser privilege replicates update into rls enabled $tbl");
+revoke_superuser("regress_admin");
+publish_update("alice.unpartitioned", 11 => 17);
+expect_failure("alice.unpartitioned", 3, 11, 15,
+ qr/ERROR: "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");
- revoke_superuser("regress_admin");
- publish_delete("alice.$tbl", 13);
- expect_failure("alice.$tbl", 3, 13, 17,
- qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
- "non-superuser admin fails to replicate delete into rls enabled $tbl");
- grant_superuser("regress_admin");
- expect_replication("alice.$tbl", 2, 15, 17,
- "admin with restored superuser privilege replicates delete into rls enabled $tbl");
-}
+grant_bypassrls("regress_admin");
+expect_replication("alice.unpartitioned", 3, 13, 17,
+ "admin with bypassrls replicates update into rls enabled unpartitioned");
-# Revoke superuser from "regress_admin". Check that the admin can now only
-# replicate into alice's table when admin has the bypassrls privilege.
-#
-for my $tbl (@tbl)
-{
- revoke_superuser("regress_admin");
- revoke_bypassrls("regress_admin");
- publish_insert("alice.$tbl", 19);
- expect_failure("alice.$tbl", 2, 15, 17,
- qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
- "nobypassrls admin fails to replicate insert into rls enabled $tbl");
- grant_bypassrls("regress_admin");
- expect_replication("alice.$tbl", 3, 15, 19,
- "admin with bypassrls privilege replicates insert into rls enabled $tbl");
-
- revoke_bypassrls("regress_admin");
- publish_update("alice.$tbl", 15 => 21);
- expect_failure("alice.$tbl", 3, 15, 19,
- qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
- "nobypassrls admin fails to replicate update into rls enabled $tbl");
-
- grant_bypassrls("regress_admin");
- expect_replication("alice.$tbl", 3, 17, 21,
- "admin with restored bypassrls privilege replicates update into rls enabled $tbl");
-
- revoke_bypassrls("regress_admin");
- publish_delete("alice.$tbl", 17);
- expect_failure("alice.$tbl", 3, 17, 21,
- qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
- "nobypassrls admin fails to replicate delete into rls enabled $tbl");
- grant_bypassrls("regress_admin");
- expect_replication("alice.$tbl", 2, 19, 21,
- "admin with restored bypassrls privilege replicates delete into rls enabled $tbl");
-}
+revoke_bypassrls("regress_admin");
+publish_delete("alice.unpartitioned", 13);
+expect_failure("alice.unpartitioned", 3, 13, 17,
+ qr/ERROR: "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");
+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.
@@ -346,18 +271,10 @@ ALTER SUBSCRIPTION admin_sub OWNER TO regress_alice;
ALTER ROLE regress_alice NOSUPERUSER;
ALTER SUBSCRIPTION admin_sub ENABLE;
));
-for my $tbl (@tbl)
-{
- publish_insert("alice.$tbl", 23);
- expect_replication(
- "alice.$tbl", 3, 19, 23,
- "nosuperuser nobypassrls table owner can replicate insert into $tbl despite rls");
- publish_update("alice.$tbl", 19 => 25);
- expect_replication(
- "alice.$tbl", 3, 21, 25,
- "nosuperuser nobypassrls table owner can replicate update into $tbl despite rls");
- publish_delete("alice.$tbl", 21);
- expect_replication(
- "alice.$tbl", 2, 23, 25,
- "nosuperuser nobypassrls table owner can replicate delete into $tbl despite rls");
-}
+
+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");