#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;
WalRcvExecResult *res;
char originname[NAMEDATALEN];
RepOriginId originid;
+ UserContext ucxt;
bool must_use_password;
+ bool run_as_owner;
/* Check the state of the table synchronization. */
StartTransactionCommand();
*/
rel = table_open(MyLogicalRepWorker->relid, RowExclusiveLock);
- /*
- * Check that our table sync worker has permission to insert into the
- * target table.
- */
- aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
- ACL_INSERT);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult,
- get_relkind_objtype(rel->rd_rel->relkind),
- RelationGetRelationName(rel));
-
- /*
- * COPY FROM does not honor RLS policies. That is not a problem for
- * subscriptions owned by roles with BYPASSRLS privilege (or superuser,
- * who has it implicitly), but other roles should not be able to
- * circumvent RLS. Disallow logical replication into RLS enabled
- * relations for such roles.
- */
- if (check_enable_rls(RelationGetRelid(rel), InvalidOid, false) == RLS_ENABLED)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"",
- GetUserNameFromId(GetUserId(), true),
- RelationGetRelationName(rel))));
-
/*
* Start a transaction in the remote node in REPEATABLE READ mode. This
* ensures that both the replication slot we create (see below) and the
originname)));
}
+ /*
+ * 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.
+ */
+ aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
+ ACL_INSERT);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult,
+ get_relkind_objtype(rel->rd_rel->relkind),
+ RelationGetRelationName(rel));
+
+ /*
+ * COPY FROM does not honor RLS policies. That is not a problem for
+ * subscriptions owned by roles with BYPASSRLS privilege (or superuser,
+ * who has it implicitly), but other roles should not be able to
+ * circumvent RLS. Disallow logical replication into RLS enabled
+ * relations for such roles.
+ */
+ if (check_enable_rls(RelationGetRelid(rel), InvalidOid, false) == RLS_ENABLED)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"",
+ GetUserNameFromId(GetUserId(), true),
+ RelationGetRelationName(rel))));
+
/* Now do the initial data copy */
PushActiveSnapshot(GetTransactionSnapshot());
copy_table(rel);
res->err)));
walrcv_clear_result(res);
+ if(!run_as_owner)
+ RestoreUserContext(&ucxt);
+
table_close(rel, NoLock);
/* Make the copy visible. */
# 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');
$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;
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
+# data 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();