diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index c56d42dcd2..6874e8c2ce 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -120,6 +120,7 @@ #include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/usercontext.h" static bool table_states_valid = false; static List *table_states_not_ready = NIL; @@ -1253,6 +1254,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) char originname[NAMEDATALEN]; RepOriginId originid; bool must_use_password; + UserContext ucxt; + bool run_as_owner; /* Check the state of the table synchronization. */ StartTransactionCommand(); @@ -1374,6 +1377,15 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) */ rel = table_open(MyLogicalRepWorker->relid, RowExclusiveLock); + /* + * Make sure that the copy command runs as the table owner, unless + * the user has opted out of that behaviour. + */ + run_as_owner = MySubscription->runasowner; + + if (!run_as_owner) + SwitchToUntrustedUser(rel->rd_rel->relowner, &ucxt); + /* * Check that our table sync worker has permission to insert into the * target table. @@ -1469,6 +1481,9 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) res->err))); walrcv_clear_result(res); + if(!run_as_owner) + RestoreUserContext(&ucxt); + table_close(rel, NoLock); /* Make the copy visible. */ diff --git a/src/test/subscription/t/033_run_as_table_owner.pl b/src/test/subscription/t/033_run_as_table_owner.pl index 0aa8a093ef..6d4051072c 100644 --- a/src/test/subscription/t/033_run_as_table_owner.pl +++ b/src/test/subscription/t/033_run_as_table_owner.pl @@ -70,8 +70,8 @@ sub revoke_superuser # 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 -# on the publisher than on the subscriber. +# "regress_admin" and "regress_admin2". For partitioned tables, layout the +# partitions differently on the publisher than on the subscriber. # $node_publisher = PostgreSQL::Test::Cluster->new('publisher'); $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber'); @@ -86,15 +86,18 @@ for my $node ($node_publisher, $node_subscriber) $node->safe_psql( 'postgres', qq( CREATE ROLE regress_admin SUPERUSER LOGIN; + CREATE ROLE regress_admin2 SUPERUSER LOGIN; CREATE ROLE regress_alice NOSUPERUSER LOGIN; GRANT CREATE ON DATABASE postgres TO regress_alice; SET SESSION AUTHORIZATION regress_alice; CREATE SCHEMA alice; GRANT USAGE ON SCHEMA alice TO regress_admin; + GRANT USAGE ON SCHEMA alice TO regress_admin2; CREATE TABLE alice.unpartitioned (i INTEGER); ALTER TABLE alice.unpartitioned REPLICA IDENTITY FULL; GRANT SELECT ON TABLE alice.unpartitioned TO regress_admin; + GRANT SELECT ON TABLE alice.unpartitioned TO regress_admin2; )); } $node_publisher->safe_psql( @@ -192,4 +195,37 @@ GRANT regress_alice TO regress_admin WITH INHERIT TRUE, SET FALSE; expect_replication("alice.unpartitioned", 3, 7, 13, "with INHERIT but not SET ROLE can replicate"); +# Remove the subscrition and truncate the table for the initial data sync +# tests. +$node_subscriber->safe_psql( + 'postgres', qq( +DROP SUBSCRIPTION admin_sub; +TRUNCATE alice.unpartitioned; +)); + +# Create a new subscription "admin_sub" owned by regress_admin2. It's +# disabled so that we revoke superuser privilege after creation. +$node_subscriber->safe_psql( + 'postgres', qq( +SET SESSION AUTHORIZATION regress_admin2; +CREATE SUBSCRIPTION admin_sub CONNECTION '$publisher_connstr' PUBLICATION alice +WITH (run_as_owner = false, password_required = false, copy_data = true, enabled = false); +)); + +# Revoke superuser privilege for "regress_admin2", and give it the +# ability to SET ROLE. Then enable the subscription "admin_sub". +revoke_superuser("regress_admin2"); +$node_subscriber->safe_psql( + 'postgres', qq( +GRANT regress_alice TO regress_admin2 WITH INHERIT FALSE, SET TRUE; +ALTER SUBSCRIPTION admin_sub ENABLE; +)); + +# Because the initial data sync is working as the table owner, all +# dat should be copied. +$node_subscriber->wait_for_subscription_sync($node_publisher, + 'admin_sub'); +expect_replication("alice.unpartitioned", 3, 7, 13, + "table owner can do the initial data copy"); + done_testing();