55use strict;
66use warnings;
77use PostgreSQL::Test::Cluster;
8- use Test::More tests => 100 ;
8+ use Test::More tests => 14 ;
99
1010my ($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)' );
11198my %remainder_a = (
11299 publisher => 0,
113100 subscriber => 1);
@@ -117,10 +104,6 @@ sub grant_bypassrls
117104
118105for 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
170133SET SESSION AUTHORIZATION regress_alice;
171134
172135CREATE 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(
177140SET SESSION AUTHORIZATION regress_admin;
178141CREATE 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(
240184ALTER ROLE regress_admin NOSUPERUSER;
241185SET 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(
264205SET 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
346271ALTER ROLE regress_alice NOSUPERUSER;
347272ALTER 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