Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a83edea

Browse files
Honor run_as_owner option in tablesync worker.
Commit 4826759 introduced "run_as_owner" subscription option so that subscription runs with either the permissions of the subscription owner or the permission of the table owner. However, tablesync workers did not use this option for the initial data copy. With this change, tablesync workers run with appropriate permissions based on "run_as_owner" option. Ajin Cherian, with changes and regression tests added by me. Reported-By: Amit Kapila Author: Ajin Cherian, Masahiko Sawada Reviewed-by: Ajin Cherian, Amit Kapila Discussion: https://postgr.es/m/CAA4eK1L=qzRHPEn+qeMoKQGFBzqGoLBzt_ov0A89iFFiut+ppA@mail.gmail.com
1 parent 26eaf82 commit a83edea

File tree

2 files changed

+75
-27
lines changed

2 files changed

+75
-27
lines changed

src/backend/replication/logical/tablesync.c

+39-25
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
#include "utils/rls.h"
121121
#include "utils/snapmgr.h"
122122
#include "utils/syscache.h"
123+
#include "utils/usercontext.h"
123124

124125
static bool table_states_valid = false;
125126
static List *table_states_not_ready = NIL;
@@ -1252,7 +1253,9 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
12521253
WalRcvExecResult *res;
12531254
char originname[NAMEDATALEN];
12541255
RepOriginId originid;
1256+
UserContext ucxt;
12551257
bool must_use_password;
1258+
bool run_as_owner;
12561259

12571260
/* Check the state of the table synchronization. */
12581261
StartTransactionCommand();
@@ -1374,31 +1377,6 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
13741377
*/
13751378
rel = table_open(MyLogicalRepWorker->relid, RowExclusiveLock);
13761379

1377-
/*
1378-
* Check that our table sync worker has permission to insert into the
1379-
* target table.
1380-
*/
1381-
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
1382-
ACL_INSERT);
1383-
if (aclresult != ACLCHECK_OK)
1384-
aclcheck_error(aclresult,
1385-
get_relkind_objtype(rel->rd_rel->relkind),
1386-
RelationGetRelationName(rel));
1387-
1388-
/*
1389-
* COPY FROM does not honor RLS policies. That is not a problem for
1390-
* subscriptions owned by roles with BYPASSRLS privilege (or superuser,
1391-
* who has it implicitly), but other roles should not be able to
1392-
* circumvent RLS. Disallow logical replication into RLS enabled
1393-
* relations for such roles.
1394-
*/
1395-
if (check_enable_rls(RelationGetRelid(rel), InvalidOid, false) == RLS_ENABLED)
1396-
ereport(ERROR,
1397-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1398-
errmsg("user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"",
1399-
GetUserNameFromId(GetUserId(), true),
1400-
RelationGetRelationName(rel))));
1401-
14021380
/*
14031381
* Start a transaction in the remote node in REPEATABLE READ mode. This
14041382
* ensures that both the replication slot we create (see below) and the
@@ -1456,6 +1434,39 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
14561434
originname)));
14571435
}
14581436

1437+
/*
1438+
* Make sure that the copy command runs as the table owner, unless
1439+
* the user has opted out of that behaviour.
1440+
*/
1441+
run_as_owner = MySubscription->runasowner;
1442+
if (!run_as_owner)
1443+
SwitchToUntrustedUser(rel->rd_rel->relowner, &ucxt);
1444+
1445+
/*
1446+
* Check that our table sync worker has permission to insert into the
1447+
* target table.
1448+
*/
1449+
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
1450+
ACL_INSERT);
1451+
if (aclresult != ACLCHECK_OK)
1452+
aclcheck_error(aclresult,
1453+
get_relkind_objtype(rel->rd_rel->relkind),
1454+
RelationGetRelationName(rel));
1455+
1456+
/*
1457+
* COPY FROM does not honor RLS policies. That is not a problem for
1458+
* subscriptions owned by roles with BYPASSRLS privilege (or superuser,
1459+
* who has it implicitly), but other roles should not be able to
1460+
* circumvent RLS. Disallow logical replication into RLS enabled
1461+
* relations for such roles.
1462+
*/
1463+
if (check_enable_rls(RelationGetRelid(rel), InvalidOid, false) == RLS_ENABLED)
1464+
ereport(ERROR,
1465+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1466+
errmsg("user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"",
1467+
GetUserNameFromId(GetUserId(), true),
1468+
RelationGetRelationName(rel))));
1469+
14591470
/* Now do the initial data copy */
14601471
PushActiveSnapshot(GetTransactionSnapshot());
14611472
copy_table(rel);
@@ -1469,6 +1480,9 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
14691480
res->err)));
14701481
walrcv_clear_result(res);
14711482

1483+
if(!run_as_owner)
1484+
RestoreUserContext(&ucxt);
1485+
14721486
table_close(rel, NoLock);
14731487

14741488
/* Make the copy visible. */

src/test/subscription/t/033_run_as_table_owner.pl

+36-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ sub revoke_superuser
7070

7171
# Create publisher and subscriber nodes with schemas owned and published by
7272
# "regress_alice" but subscribed and replicated by different role
73-
# "regress_admin". For partitioned tables, layout the partitions differently
74-
# on the publisher than on the subscriber.
73+
# "regress_admin" and "regress_admin2". For partitioned tables, layout the
74+
# partitions differently on the publisher than on the subscriber.
7575
#
7676
$node_publisher = PostgreSQL::Test::Cluster->new('publisher');
7777
$node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
@@ -86,6 +86,7 @@ sub revoke_superuser
8686
$node->safe_psql(
8787
'postgres', qq(
8888
CREATE ROLE regress_admin SUPERUSER LOGIN;
89+
CREATE ROLE regress_admin2 SUPERUSER LOGIN;
8990
CREATE ROLE regress_alice NOSUPERUSER LOGIN;
9091
GRANT CREATE ON DATABASE postgres TO regress_alice;
9192
SET SESSION AUTHORIZATION regress_alice;
@@ -192,4 +193,37 @@ sub revoke_superuser
192193
expect_replication("alice.unpartitioned", 3, 7, 13,
193194
"with INHERIT but not SET ROLE can replicate");
194195

196+
# Remove the subscrition and truncate the table for the initial data sync
197+
# tests.
198+
$node_subscriber->safe_psql(
199+
'postgres', qq(
200+
DROP SUBSCRIPTION admin_sub;
201+
TRUNCATE alice.unpartitioned;
202+
));
203+
204+
# Create a new subscription "admin_sub" owned by regress_admin2. It's
205+
# disabled so that we revoke superuser privilege after creation.
206+
$node_subscriber->safe_psql(
207+
'postgres', qq(
208+
SET SESSION AUTHORIZATION regress_admin2;
209+
CREATE SUBSCRIPTION admin_sub CONNECTION '$publisher_connstr' PUBLICATION alice
210+
WITH (run_as_owner = false, password_required = false, copy_data = true, enabled = false);
211+
));
212+
213+
# Revoke superuser privilege for "regress_admin2", and give it the
214+
# ability to SET ROLE. Then enable the subscription "admin_sub".
215+
revoke_superuser("regress_admin2");
216+
$node_subscriber->safe_psql(
217+
'postgres', qq(
218+
GRANT regress_alice TO regress_admin2 WITH INHERIT FALSE, SET TRUE;
219+
ALTER SUBSCRIPTION admin_sub ENABLE;
220+
));
221+
222+
# Because the initial data sync is working as the table owner, all
223+
# data should be copied.
224+
$node_subscriber->wait_for_subscription_sync($node_publisher,
225+
'admin_sub');
226+
expect_replication("alice.unpartitioned", 3, 7, 13,
227+
"table owner can do the initial data copy");
228+
195229
done_testing();

0 commit comments

Comments
 (0)