Skip to content

Commit 96a6f11

Browse files
committed
More cleanup of a2ab9c0.
Require SELECT privileges when performing UPDATE or DELETE, to be consistent with the way a normal UPDATE or DELETE command works. Simplify subscription test it so that it runs faster. Also, wait for initial table sync to complete to avoid intermittent failures. Minor doc fixup. Discussion: https://postgr.es/m/CAA4eK1L3-qAtLO4sNGaNhzcyRi_Ufmh2YPPnUjkROBK0tN%3Dx%3Dg%40mail.gmail.com Discussion: https://postgr.es/m/1514479.1641664638%40sss.pgh.pa.us Discussion: https://postgr.es/m/Ydkfj5IsZg7mQR0g@paquier.xyz
1 parent 54b1cb7 commit 96a6f11

File tree

3 files changed

+117
-194
lines changed

3 files changed

+117
-194
lines changed

doc/src/sgml/logical-replication.sgml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@
336336
cause replication conflicts, as will enabled
337337
<link linkend="ddl-rowsecurity">row-level security</link> on target tables
338338
that the subscription owner is subject to, without regard to whether any
339-
policy would ordinary reject the <command>INSERT</command>,
339+
policy would ordinarily reject the <command>INSERT</command>,
340340
<command>UPDATE</command>, <command>DELETE</command> or
341341
<command>TRUNCATE</command> which is being replicated. This restriction on
342342
row-level security may be lifted in a future version of

src/backend/replication/logical/worker.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,12 @@ FindReplTupleInLocalRel(EState *estate, Relation localrel,
19911991
Oid idxoid;
19921992
bool found;
19931993

1994+
/*
1995+
* Regardless of the top-level operation, we're performing a read here, so
1996+
* check for SELECT privileges.
1997+
*/
1998+
TargetPrivilegesCheck(localrel, ACL_SELECT);
1999+
19942000
*localslot = table_slot_create(localrel, &estate->es_tupleTable);
19952001

19962002
idxoid = GetRelationIdentityOrPK(localrel);

src/test/subscription/t/027_nosuperuser.pl

Lines changed: 110 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use strict;
66
use warnings;
77
use PostgreSQL::Test::Cluster;
8-
use Test::More tests => 100;
8+
use Test::More tests => 14;
99

1010
my ($node_publisher, $node_subscriber, $publisher_connstr, $result, $offset);
1111
$offset = 0;
@@ -95,19 +95,6 @@ sub grant_bypassrls
9595
$node_publisher->start;
9696
$node_subscriber->start;
9797
$publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
98-
my %range_a = (
99-
publisher => 'FROM (0) TO (15)',
100-
subscriber => 'FROM (0) TO (5)');
101-
my %range_b = (
102-
publisher => 'FROM (15) TO (30)',
103-
subscriber => 'FROM (5) TO (30)');
104-
my %list_a = (
105-
publisher => 'IN (1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29)',
106-
subscriber => 'IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)',
107-
);
108-
my %list_b = (
109-
publisher => 'IN (2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30)',
110-
subscriber => 'IN (17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)');
11198
my %remainder_a = (
11299
publisher => 0,
113100
subscriber => 1);
@@ -117,10 +104,6 @@ sub grant_bypassrls
117104

118105
for my $node ($node_publisher, $node_subscriber)
119106
{
120-
my $range_a = $range_a{$node->name};
121-
my $range_b = $range_b{$node->name};
122-
my $list_a = $list_a{$node->name};
123-
my $list_b = $list_b{$node->name};
124107
my $remainder_a = $remainder_a{$node->name};
125108
my $remainder_b = $remainder_b{$node->name};
126109
$node->safe_psql('postgres', qq(
@@ -135,26 +118,6 @@ sub grant_bypassrls
135118
ALTER TABLE alice.unpartitioned REPLICA IDENTITY FULL;
136119
GRANT SELECT ON TABLE alice.unpartitioned TO regress_admin;
137120
138-
CREATE TABLE alice.rangepart (i INTEGER) PARTITION BY RANGE (i);
139-
ALTER TABLE alice.rangepart REPLICA IDENTITY FULL;
140-
GRANT SELECT ON TABLE alice.rangepart TO regress_admin;
141-
CREATE TABLE alice.rangepart_a PARTITION OF alice.rangepart
142-
FOR VALUES $range_a;
143-
ALTER TABLE alice.rangepart_a REPLICA IDENTITY FULL;
144-
CREATE TABLE alice.rangepart_b PARTITION OF alice.rangepart
145-
FOR VALUES $range_b;
146-
ALTER TABLE alice.rangepart_b REPLICA IDENTITY FULL;
147-
148-
CREATE TABLE alice.listpart (i INTEGER) PARTITION BY LIST (i);
149-
ALTER TABLE alice.listpart REPLICA IDENTITY FULL;
150-
GRANT SELECT ON TABLE alice.listpart TO regress_admin;
151-
CREATE TABLE alice.listpart_a PARTITION OF alice.listpart
152-
FOR VALUES $list_a;
153-
ALTER TABLE alice.listpart_a REPLICA IDENTITY FULL;
154-
CREATE TABLE alice.listpart_b PARTITION OF alice.listpart
155-
FOR VALUES $list_b;
156-
ALTER TABLE alice.listpart_b REPLICA IDENTITY FULL;
157-
158121
CREATE TABLE alice.hashpart (i INTEGER) PARTITION BY HASH (i);
159122
ALTER TABLE alice.hashpart REPLICA IDENTITY FULL;
160123
GRANT SELECT ON TABLE alice.hashpart TO regress_admin;
@@ -170,171 +133,133 @@ sub grant_bypassrls
170133
SET SESSION AUTHORIZATION regress_alice;
171134
172135
CREATE PUBLICATION alice
173-
FOR TABLE alice.unpartitioned, alice.rangepart, alice.listpart, alice.hashpart
136+
FOR TABLE alice.unpartitioned, alice.hashpart
174137
WITH (publish_via_partition_root = true);
175138
));
176139
$node_subscriber->safe_psql('postgres', qq(
177140
SET SESSION AUTHORIZATION regress_admin;
178141
CREATE SUBSCRIPTION admin_sub CONNECTION '$publisher_connstr' PUBLICATION alice;
179142
));
180143

144+
$node_publisher->wait_for_catchup('admin_sub');
145+
146+
# Wait for initial sync to finish as well
147+
my $synced_query =
148+
"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('s', 'r');";
149+
$node_subscriber->poll_query_until('postgres', $synced_query)
150+
or die "Timed out while waiting for subscriber to synchronize data";
151+
181152
# Verify that "regress_admin" can replicate into the tables
182153
#
183-
my @tbl = (qw(unpartitioned rangepart listpart hashpart));
184-
for my $tbl (@tbl)
185-
{
186-
publish_insert("alice.$tbl", 1);
187-
publish_insert("alice.$tbl", 3);
188-
publish_insert("alice.$tbl", 5);
189-
expect_replication(
190-
"alice.$tbl", 3, 1, 5,
191-
"superuser admin replicates insert into $tbl");
192-
publish_update("alice.$tbl", 1 => 7);
193-
expect_replication(
194-
"alice.$tbl", 3, 3, 7,
195-
"superuser admin replicates update into $tbl");
196-
publish_delete("alice.$tbl", 3);
197-
expect_replication(
198-
"alice.$tbl", 2, 5, 7,
199-
"superuser admin replicates delete into $tbl");
200-
}
201-
202-
# Repeatedly revoke and restore superuser privilege for "regress_admin", verifying
203-
# that replication fails while superuser privilege is missing, but works again and
204-
# catches up once superuser is restored.
154+
publish_insert("alice.unpartitioned", 1);
155+
publish_insert("alice.unpartitioned", 3);
156+
publish_insert("alice.unpartitioned", 5);
157+
publish_update("alice.unpartitioned", 1 => 7);
158+
publish_delete("alice.unpartitioned", 3);
159+
expect_replication(
160+
"alice.unpartitioned", 2, 5, 7,
161+
"superuser admin replicates into unpartitioned");
162+
163+
# Revoke and restore superuser privilege for "regress_admin",
164+
# verifying that replication fails while superuser privilege is
165+
# missing, but works again and catches up once superuser is restored.
205166
#
206-
for my $tbl (@tbl)
207-
{
208-
revoke_superuser("regress_admin");
209-
publish_insert("alice.$tbl", 3);
210-
expect_failure("alice.$tbl", 2, 5, 7,
211-
qr/ERROR: permission denied for table $tbl/msi,
212-
"non-superuser admin fails to replicate insert");
213-
grant_superuser("regress_admin");
214-
expect_replication("alice.$tbl", 3, 3, 7,
215-
"admin with restored superuser privilege replicates insert");
216-
217-
revoke_superuser("regress_admin");
218-
publish_update("alice.$tbl", 3 => 9);
219-
expect_failure("alice.$tbl", 3, 3, 7,
220-
qr/ERROR: permission denied for table $tbl/msi,
221-
"non-superuser admin fails to replicate update");
222-
grant_superuser("regress_admin");
223-
expect_replication("alice.$tbl", 3, 5, 9,
224-
"admin with restored superuser privilege replicates update");
225-
226-
revoke_superuser("regress_admin");
227-
publish_delete("alice.$tbl", 5);
228-
expect_failure("alice.$tbl", 3, 5, 9,
229-
qr/ERROR: permission denied for table $tbl/msi,
230-
"non-superuser admin fails to replicate delete");
231-
grant_superuser("regress_admin");
232-
expect_replication("alice.$tbl", 2, 7, 9,
233-
"admin with restored superuser privilege replicates delete");
234-
}
235-
236-
# Grant privileges on the target tables to "regress_admin" so that superuser
237-
# privileges are not necessary for replication.
167+
revoke_superuser("regress_admin");
168+
publish_update("alice.unpartitioned", 5 => 9);
169+
expect_failure("alice.unpartitioned", 2, 5, 7,
170+
qr/ERROR: permission denied for table unpartitioned/msi,
171+
"non-superuser admin fails to replicate update");
172+
grant_superuser("regress_admin");
173+
expect_replication("alice.unpartitioned", 2, 7, 9,
174+
"admin with restored superuser privilege replicates update");
175+
176+
# Grant INSERT, UPDATE, DELETE privileges on the target tables to
177+
# "regress_admin" so that superuser privileges are not necessary for
178+
# replication.
179+
#
180+
# Note that UPDATE and DELETE also require SELECT privileges, which
181+
# will be granted in subsequent test.
238182
#
239183
$node_subscriber->safe_psql('postgres', qq(
240184
ALTER ROLE regress_admin NOSUPERUSER;
241185
SET SESSION AUTHORIZATION regress_alice;
242-
GRANT ALL PRIVILEGES ON
186+
GRANT INSERT,UPDATE,DELETE ON
243187
alice.unpartitioned,
244-
alice.rangepart, alice.rangepart_a, alice.rangepart_b,
245-
alice.listpart, alice.listpart_a, alice.listpart_b,
246188
alice.hashpart, alice.hashpart_a, alice.hashpart_b
247189
TO regress_admin;
190+
REVOKE SELECT ON alice.unpartitioned FROM regress_admin;
248191
));
249-
for my $tbl (@tbl)
250-
{
251-
publish_insert("alice.$tbl", 11);
252-
publish_update("alice.$tbl", 7 => 13);
253-
publish_delete("alice.$tbl", 9);
254-
expect_replication("alice.$tbl", 2, 11, 13,
255-
"nosuperuser admin with all table privileges can replicate into $tbl");
256-
}
257192

258-
# Enable RLS on the target tables and check that "regress_admin" can only
259-
# replicate into them when superuser. Note that RLS must be enabled on the
260-
# partitions, not the partitioned tables, since the partitions are the targets
261-
# of the replication.
193+
publish_insert("alice.unpartitioned", 11);
194+
expect_replication("alice.unpartitioned", 3, 7, 11,
195+
"nosuperuser admin with INSERT privileges can replicate into unpartitioned");
196+
197+
publish_update("alice.unpartitioned", 7 => 13);
198+
expect_failure("alice.unpartitioned", 3, 7, 11,
199+
qr/ERROR: permission denied for table unpartitioned/msi,
200+
"non-superuser admin without SELECT privileges fails to replicate update");
201+
202+
# Now grant SELECT
262203
#
263204
$node_subscriber->safe_psql('postgres', qq(
264205
SET SESSION AUTHORIZATION regress_alice;
265-
ALTER TABLE alice.unpartitioned ENABLE ROW LEVEL SECURITY;
266-
ALTER TABLE alice.rangepart_a ENABLE ROW LEVEL SECURITY;
267-
ALTER TABLE alice.rangepart_b ENABLE ROW LEVEL SECURITY;
268-
ALTER TABLE alice.listpart_a ENABLE ROW LEVEL SECURITY;
269-
ALTER TABLE alice.listpart_b ENABLE ROW LEVEL SECURITY;
270-
ALTER TABLE alice.hashpart_a ENABLE ROW LEVEL SECURITY;
271-
ALTER TABLE alice.hashpart_b ENABLE ROW LEVEL SECURITY;
206+
GRANT SELECT ON
207+
alice.unpartitioned,
208+
alice.hashpart, alice.hashpart_a, alice.hashpart_b
209+
TO regress_admin;
272210
));
273-
for my $tbl (@tbl)
274-
{
275-
revoke_superuser("regress_admin");
276-
publish_insert("alice.$tbl", 15);
277-
expect_failure("alice.$tbl", 2, 11, 13,
278-
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
279-
"non-superuser admin fails to replicate insert into rls enabled table");
280-
grant_superuser("regress_admin");
281-
expect_replication("alice.$tbl", 3, 11, 15,
282-
"admin with restored superuser privilege replicates insert into rls enabled $tbl");
283-
284-
revoke_superuser("regress_admin");
285-
publish_update("alice.$tbl", 11 => 17);
286-
expect_failure("alice.$tbl", 3, 11, 15,
287-
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
288-
"non-superuser admin fails to replicate update into rls enabled $tbl");
289-
290-
grant_superuser("regress_admin");
291-
expect_replication("alice.$tbl", 3, 13, 17,
292-
"admin with restored superuser privilege replicates update into rls enabled $tbl");
293-
294-
revoke_superuser("regress_admin");
295-
publish_delete("alice.$tbl", 13);
296-
expect_failure("alice.$tbl", 3, 13, 17,
297-
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
298-
"non-superuser admin fails to replicate delete into rls enabled $tbl");
299-
grant_superuser("regress_admin");
300-
expect_replication("alice.$tbl", 2, 15, 17,
301-
"admin with restored superuser privilege replicates delete into rls enabled $tbl");
302-
}
303211

304-
# Revoke superuser from "regress_admin". Check that the admin can now only
305-
# replicate into alice's table when admin has the bypassrls privilege.
212+
publish_delete("alice.unpartitioned", 9);
213+
expect_replication("alice.unpartitioned", 2, 11, 13,
214+
"nosuperuser admin with all table privileges can replicate into unpartitioned");
215+
216+
# Test partitioning
306217
#
307-
for my $tbl (@tbl)
308-
{
309-
revoke_superuser("regress_admin");
310-
revoke_bypassrls("regress_admin");
311-
publish_insert("alice.$tbl", 19);
312-
expect_failure("alice.$tbl", 2, 15, 17,
313-
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
314-
"nobypassrls admin fails to replicate insert into rls enabled $tbl");
315-
grant_bypassrls("regress_admin");
316-
expect_replication("alice.$tbl", 3, 15, 19,
317-
"admin with bypassrls privilege replicates insert into rls enabled $tbl");
318-
319-
revoke_bypassrls("regress_admin");
320-
publish_update("alice.$tbl", 15 => 21);
321-
expect_failure("alice.$tbl", 3, 15, 19,
322-
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
323-
"nobypassrls admin fails to replicate update into rls enabled $tbl");
324-
325-
grant_bypassrls("regress_admin");
326-
expect_replication("alice.$tbl", 3, 17, 21,
327-
"admin with restored bypassrls privilege replicates update into rls enabled $tbl");
328-
329-
revoke_bypassrls("regress_admin");
330-
publish_delete("alice.$tbl", 17);
331-
expect_failure("alice.$tbl", 3, 17, 21,
332-
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "$tbl\w*"/msi,
333-
"nobypassrls admin fails to replicate delete into rls enabled $tbl");
334-
grant_bypassrls("regress_admin");
335-
expect_replication("alice.$tbl", 2, 19, 21,
336-
"admin with restored bypassrls privilege replicates delete into rls enabled $tbl");
337-
}
218+
publish_insert("alice.hashpart", 101);
219+
publish_insert("alice.hashpart", 102);
220+
publish_insert("alice.hashpart", 103);
221+
publish_update("alice.hashpart", 102 => 120);
222+
publish_delete("alice.hashpart", 101);
223+
expect_replication("alice.hashpart", 2, 103, 120,
224+
"nosuperuser admin with all table privileges can replicate into hashpart");
225+
226+
227+
# Enable RLS on the target table and check that "regress_admin" can
228+
# only replicate into it when superuser or bypassrls.
229+
#
230+
$node_subscriber->safe_psql('postgres', qq(
231+
SET SESSION AUTHORIZATION regress_alice;
232+
ALTER TABLE alice.unpartitioned ENABLE ROW LEVEL SECURITY;
233+
));
234+
235+
revoke_superuser("regress_admin");
236+
publish_insert("alice.unpartitioned", 15);
237+
expect_failure("alice.unpartitioned", 2, 11, 13,
238+
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
239+
"non-superuser admin fails to replicate insert into rls enabled table");
240+
grant_superuser("regress_admin");
241+
expect_replication("alice.unpartitioned", 3, 11, 15,
242+
"admin with restored superuser privilege replicates insert into rls enabled unpartitioned");
243+
244+
revoke_superuser("regress_admin");
245+
publish_update("alice.unpartitioned", 11 => 17);
246+
expect_failure("alice.unpartitioned", 3, 11, 15,
247+
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
248+
"non-superuser admin fails to replicate update into rls enabled unpartitioned");
249+
250+
grant_bypassrls("regress_admin");
251+
expect_replication("alice.unpartitioned", 3, 13, 17,
252+
"admin with bypassrls replicates update into rls enabled unpartitioned");
253+
254+
revoke_bypassrls("regress_admin");
255+
publish_delete("alice.unpartitioned", 13);
256+
expect_failure("alice.unpartitioned", 3, 13, 17,
257+
qr/ERROR: "regress_admin" cannot replicate into relation with row-level security enabled: "unpartitioned\w*"/msi,
258+
"non-superuser admin without bypassrls fails to replicate delete into rls enabled unpartitioned");
259+
grant_bypassrls("regress_admin");
260+
expect_replication("alice.unpartitioned", 2, 15, 17,
261+
"admin with bypassrls replicates delete into rls enabled unpartitioned");
262+
grant_superuser("regress_admin");
338263

339264
# Alter the subscription owner to "regress_alice". She has neither superuser
340265
# nor bypassrls, but as the table owner should be able to replicate.
@@ -346,18 +271,10 @@ sub grant_bypassrls
346271
ALTER ROLE regress_alice NOSUPERUSER;
347272
ALTER SUBSCRIPTION admin_sub ENABLE;
348273
));
349-
for my $tbl (@tbl)
350-
{
351-
publish_insert("alice.$tbl", 23);
352-
expect_replication(
353-
"alice.$tbl", 3, 19, 23,
354-
"nosuperuser nobypassrls table owner can replicate insert into $tbl despite rls");
355-
publish_update("alice.$tbl", 19 => 25);
356-
expect_replication(
357-
"alice.$tbl", 3, 21, 25,
358-
"nosuperuser nobypassrls table owner can replicate update into $tbl despite rls");
359-
publish_delete("alice.$tbl", 21);
360-
expect_replication(
361-
"alice.$tbl", 2, 23, 25,
362-
"nosuperuser nobypassrls table owner can replicate delete into $tbl despite rls");
363-
}
274+
275+
publish_insert("alice.unpartitioned", 23);
276+
publish_update("alice.unpartitioned", 15 => 25);
277+
publish_delete("alice.unpartitioned", 17);
278+
expect_replication(
279+
"alice.unpartitioned", 2, 23, 25,
280+
"nosuperuser nobypassrls table owner can replicate delete into unpartitioned despite rls");

0 commit comments

Comments
 (0)