v2-0001-Add-new-predefined-role-pg_create_subscriptions.patch
application/octet-stream
Filename: v2-0001-Add-new-predefined-role-pg_create_subscriptions.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Add new predefined role pg_create_subscriptions.
| File | + | − |
|---|---|---|
| src/backend/catalog/pg_subscription.c | 1 | 0 |
| src/backend/catalog/system_views.sql | 1 | 0 |
| src/backend/commands/subscriptioncmds.c | 93 | 19 |
| src/backend/replication/libpqwalreceiver/libpqwalreceiver.c | 35 | 7 |
| src/backend/replication/logical/tablesync.c | 8 | 1 |
| src/backend/replication/logical/worker.c | 6 | 0 |
| src/backend/replication/walreceiver.c | 1 | 1 |
| src/include/catalog/pg_authid.dat | 5 | 0 |
| src/include/catalog/pg_subscription.h | 3 | 0 |
| src/include/replication/walreceiver.h | 4 | 3 |
| src/test/regress/expected/subscription.out | 2 | 2 |
| src/test/subscription/t/027_nosuperuser.pl | 1 | 1 |
From 10b45ea629e57c661844879ac1c0ac5a1301dad7 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 20 Jan 2023 09:52:58 -0500
Subject: [PATCH v2] Add new predefined role pg_create_subscriptions.
This role can be granted to non-superusers to allow them to issue
CREATE SUBSCRIPTION. Non-superusers are required to specify a password
for authentication and the remote side must use the password, similar
to what is required for postgres_fdw and dblink.
A superuser who wants a non-superuser to own a subscription that
does not rely on password authentication may set the new
password_required=false property on that subscription. A non-superuser
may not modify a subscription with password_required=false, nor may a
non-superuser set this property to false. This works much like the
existing password_required property for postgres_fdw. In both cases,
the actual semantics are that a password is not required if either
(1) the property is set to false or (2) the relevant user is the
superuser.
This commit also allows non-superusers to use ALTER SUBSCRIPTION
.. SKIP. It is good enough to be the owner of the subscription.
XXX. pg_dump changes not included.
XXX. Documentation changes not included.
XXX. Catversion bump required.
---
src/backend/catalog/pg_subscription.c | 1 +
src/backend/catalog/system_views.sql | 1 +
src/backend/commands/subscriptioncmds.c | 112 +++++++++++++++---
.../libpqwalreceiver/libpqwalreceiver.c | 42 +++++--
src/backend/replication/logical/tablesync.c | 9 +-
src/backend/replication/logical/worker.c | 6 +
src/backend/replication/walreceiver.c | 2 +-
src/include/catalog/pg_authid.dat | 5 +
src/include/catalog/pg_subscription.h | 3 +
src/include/replication/walreceiver.h | 7 +-
src/test/regress/expected/subscription.out | 4 +-
src/test/subscription/t/027_nosuperuser.pl | 2 +-
12 files changed, 160 insertions(+), 34 deletions(-)
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index a56ae311c3..087eae0bcb 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -71,6 +71,7 @@ GetSubscription(Oid subid, bool missing_ok)
sub->stream = subform->substream;
sub->twophasestate = subform->subtwophasestate;
sub->disableonerr = subform->subdisableonerr;
+ sub->passwordrequired = subform->subpasswordrequired;
/* Get conninfo */
datum = SysCacheGetAttr(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8608e3fa5b..8465a88a18 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1301,6 +1301,7 @@ REVOKE ALL ON pg_replication_origin_status FROM public;
REVOKE ALL ON pg_subscription FROM public;
GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
subbinary, substream, subtwophasestate, subdisableonerr,
+ subpasswordrequired,
subslotname, subsynccommit, subpublications, suborigin)
ON pg_subscription TO public;
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 464db6d247..da554dbb1f 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -23,6 +23,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
+#include "catalog/pg_authid_d.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_subscription_rel.h"
#include "catalog/pg_type.h"
@@ -64,8 +65,9 @@
#define SUBOPT_STREAMING 0x00000100
#define SUBOPT_TWOPHASE_COMMIT 0x00000200
#define SUBOPT_DISABLE_ON_ERR 0x00000400
-#define SUBOPT_LSN 0x00000800
-#define SUBOPT_ORIGIN 0x00001000
+#define SUBOPT_PASSWORD_REQUIRED 0x00000800
+#define SUBOPT_LSN 0x00001000
+#define SUBOPT_ORIGIN 0x00002000
/* check if the 'val' has 'bits' set */
#define IsSet(val, bits) (((val) & (bits)) == (bits))
@@ -88,6 +90,7 @@ typedef struct SubOpts
char streaming;
bool twophase;
bool disableonerr;
+ bool passwordrequired;
char *origin;
XLogRecPtr lsn;
} SubOpts;
@@ -144,6 +147,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
opts->twophase = false;
if (IsSet(supported_opts, SUBOPT_DISABLE_ON_ERR))
opts->disableonerr = false;
+ if (IsSet(supported_opts, SUBOPT_PASSWORD_REQUIRED))
+ opts->passwordrequired = true;
if (IsSet(supported_opts, SUBOPT_ORIGIN))
opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
@@ -274,6 +279,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
opts->specified_opts |= SUBOPT_DISABLE_ON_ERR;
opts->disableonerr = defGetBoolean(defel);
}
+ else if (IsSet(supported_opts, SUBOPT_PASSWORD_REQUIRED) &&
+ strcmp(defel->defname, "password_required") == 0)
+ {
+ if (IsSet(opts->specified_opts, SUBOPT_PASSWORD_REQUIRED))
+ errorConflictingDefElem(defel, pstate);
+
+ opts->specified_opts |= SUBOPT_PASSWORD_REQUIRED;
+ opts->passwordrequired = defGetBoolean(defel);
+ }
else if (IsSet(supported_opts, SUBOPT_ORIGIN) &&
strcmp(defel->defname, "origin") == 0)
{
@@ -560,7 +574,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
SUBOPT_SLOT_NAME | SUBOPT_COPY_DATA |
SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
- SUBOPT_DISABLE_ON_ERR | SUBOPT_ORIGIN);
+ SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
+ SUBOPT_ORIGIN);
parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
/*
@@ -572,10 +587,26 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
if (opts.create_slot)
PreventInTransactionBlock(isTopLevel, "CREATE SUBSCRIPTION ... WITH (create_slot = true)");
- if (!superuser())
+ /*
+ * We don't want to allow unprivileged users to be able to trigger attempts
+ * to access arbitrary network destinations, so require the user to have
+ * been specifically authorized to perform this operation.
+ */
+ if (!has_privs_of_role(owner, ROLE_PG_CREATE_SUBSCRIPTION))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to create subscriptions")));
+ errmsg("must have privileges of pg_create_subscription to create subscriptions")));
+
+ /*
+ * Non-superusers are required to set a password for authentication, and
+ * that password must be used by the target server, but the superuser can
+ * exempt a subscription from this requirement.
+ */
+ if (!opts.passwordrequired && !superuser_arg(owner))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("password_required=false is superuser-only"),
+ errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
/*
* If built with appropriate switch, whine when regression-testing
@@ -614,7 +645,12 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
load_file("libpqwalreceiver", false);
/* Check the connection info string. */
- walrcv_check_conninfo(conninfo);
+ if (!walrcv_check_conninfo(conninfo) &&
+ opts.passwordrequired && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ errmsg("password is required"),
+ errdetail("Non-superusers must provide a password in the connection string.")));
/* Everything ok, form a new tuple. */
memset(values, 0, sizeof(values));
@@ -636,6 +672,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
LOGICALREP_TWOPHASE_STATE_PENDING :
LOGICALREP_TWOPHASE_STATE_DISABLED);
values[Anum_pg_subscription_subdisableonerr - 1] = BoolGetDatum(opts.disableonerr);
+ values[Anum_pg_subscription_subpasswordrequired - 1] = BoolGetDatum(opts.passwordrequired);
values[Anum_pg_subscription_subconninfo - 1] =
CStringGetTextDatum(conninfo);
if (opts.slot_name)
@@ -672,9 +709,12 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
List *tables;
ListCell *lc;
char table_state;
+ bool must_use_password;
/* Try to connect to the publisher. */
- wrconn = walrcv_connect(conninfo, true, stmt->subname, &err);
+ must_use_password = !superuser_arg(owner) && opts.passwordrequired;
+ wrconn = walrcv_connect(conninfo, true, must_use_password,
+ stmt->subname, &err);
if (!wrconn)
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_FAILURE),
@@ -799,12 +839,15 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
} SubRemoveRels;
SubRemoveRels *sub_remove_rels;
WalReceiverConn *wrconn;
+ bool must_use_password;
/* Load the library providing us libpq calls. */
load_file("libpqwalreceiver", false);
/* Try to connect to the publisher. */
- wrconn = walrcv_connect(sub->conninfo, true, sub->name, &err);
+ must_use_password = !superuser_arg(sub->owner) && sub->passwordrequired;
+ wrconn = walrcv_connect(sub->conninfo, true, must_use_password,
+ sub->name, &err);
if (!wrconn)
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_FAILURE),
@@ -1039,6 +1082,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
sub = GetSubscription(subid, false);
+ /*
+ * Don't allow non-superuser modification of a subscription with
+ * password_required=false.
+ */
+ if (!sub->passwordrequired && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("password_required=false is superuser-only"),
+ errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
+
/* Lock the subscription so nobody else can do anything with it. */
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
@@ -1054,7 +1107,7 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
supported_opts = (SUBOPT_SLOT_NAME |
SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
SUBOPT_STREAMING | SUBOPT_DISABLE_ON_ERR |
- SUBOPT_ORIGIN);
+ SUBOPT_PASSWORD_REQUIRED | SUBOPT_ORIGIN);
parse_subscription_options(pstate, stmt->options,
supported_opts, &opts);
@@ -1111,6 +1164,21 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
= true;
}
+ if (IsSet(opts.specified_opts, SUBOPT_PASSWORD_REQUIRED))
+ {
+ /* Non-superuser may not disable password_required. */
+ if (!opts.passwordrequired && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("password_required=false is superuser-only"),
+ errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
+
+ values[Anum_pg_subscription_subpasswordrequired - 1]
+ = BoolGetDatum(opts.passwordrequired);
+ replaces[Anum_pg_subscription_subpasswordrequired - 1]
+ = true;
+ }
+
if (IsSet(opts.specified_opts, SUBOPT_ORIGIN))
{
values[Anum_pg_subscription_suborigin - 1] =
@@ -1148,7 +1216,13 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
/* Load the library providing us libpq calls. */
load_file("libpqwalreceiver", false);
/* Check the connection info string. */
- walrcv_check_conninfo(stmt->conninfo);
+ if (!walrcv_check_conninfo(stmt->conninfo) &&
+ sub->passwordrequired &&
+ !superuser_arg(sub->owner))
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ errmsg("password is required"),
+ errdetail("Non-superusers must provide a password in the connection string.")));
values[Anum_pg_subscription_subconninfo - 1] =
CStringGetTextDatum(stmt->conninfo);
@@ -1305,11 +1379,6 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
/* ALTER SUBSCRIPTION ... SKIP supports only LSN option */
Assert(IsSet(opts.specified_opts, SUBOPT_LSN));
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to skip transaction")));
-
/*
* If the user sets subskiplsn, we do a sanity check to make
* sure that the specified LSN is a probable value.
@@ -1379,6 +1448,7 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
ObjectAddress myself;
HeapTuple tup;
Oid subid;
+ Oid subowner;
Datum datum;
bool isnull;
char *subname;
@@ -1391,6 +1461,7 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
WalReceiverConn *wrconn;
Form_pg_subscription form;
List *rstates;
+ bool must_use_password;
/*
* Lock pg_subscription with AccessExclusiveLock to ensure that the
@@ -1420,6 +1491,8 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
form = (Form_pg_subscription) GETSTRUCT(tup);
subid = form->oid;
+ subowner = form->subowner;
+ must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
/* must be owner */
if (!object_ownercheck(SubscriptionRelationId, subid, GetUserId()))
@@ -1578,7 +1651,8 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
load_file("libpqwalreceiver", false);
- wrconn = walrcv_connect(conninfo, true, subname, &err);
+ wrconn = walrcv_connect(conninfo, true, must_use_password,
+ subname, &err);
if (wrconn == NULL)
{
if (!slotname)
@@ -1727,13 +1801,13 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
NameStr(form->subname));
- /* New owner must be a superuser */
- if (!superuser_arg(newOwnerId))
+ /* New owner must have pg_create_subscription */
+ if (!has_privs_of_role(newOwnerId, ROLE_PG_CREATE_SUBSCRIPTION))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of subscription \"%s\"",
NameStr(form->subname)),
- errhint("The owner of a subscription must be a superuser.")));
+ errhint("The owner of a subscription must have the privileges of pg_create_subscription.")));
form->subowner = newOwnerId;
CatalogTupleUpdate(rel, &tup->t_self, tup);
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 560ec974fa..bbd0cba19f 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -49,9 +49,9 @@ struct WalReceiverConn
/* Prototypes for interface functions */
static WalReceiverConn *libpqrcv_connect(const char *conninfo,
- bool logical, const char *appname,
- char **err);
-static void libpqrcv_check_conninfo(const char *conninfo);
+ bool logical, bool must_use_password,
+ const char *appname, char **err);
+static bool libpqrcv_check_conninfo(const char *conninfo);
static char *libpqrcv_get_conninfo(WalReceiverConn *conn);
static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
char **sender_host, int *sender_port);
@@ -122,8 +122,8 @@ _PG_init(void)
* Returns NULL on error and fills the err with palloc'ed error message.
*/
static WalReceiverConn *
-libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
- char **err)
+libpqrcv_connect(const char *conninfo, bool logical, bool must_use_password,
+ const char *appname, char **err)
{
WalReceiverConn *conn;
const char *keys[6];
@@ -180,6 +180,13 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
if (PQstatus(conn->streamConn) != CONNECTION_OK)
goto bad_connection_errmsg;
+ if (must_use_password && !PQconnectionUsedPassword(conn->streamConn))
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ errmsg("password is required"),
+ errdetail("Non-superuser cannot connect if the server does not request a password."),
+ errhint("Target server's authentication method must be changed.")));
+
if (logical)
{
PGresult *res;
@@ -212,13 +219,20 @@ bad_connection:
}
/*
- * Validate connection info string (just try to parse it)
+ * Validate connection info string, and determine whether it might cause
+ * local filesystem access to be attempted.
+ *
+ * If the connection string can't be parsed, this function will raise
+ * an error and will not return. If it can, it will return true if this
+ * connection string specifies a password and false otherwise.
*/
-static void
+static bool
libpqrcv_check_conninfo(const char *conninfo)
{
PQconninfoOption *opts = NULL;
+ PQconninfoOption *opt;
char *err = NULL;
+ bool result = false;
opts = PQconninfoParse(conninfo, &err);
if (opts == NULL)
@@ -232,7 +246,21 @@ libpqrcv_check_conninfo(const char *conninfo)
errmsg("invalid connection string syntax: %s", errcopy)));
}
+ for (opt = opts; opt->keyword != NULL; ++opt)
+ {
+ /* Ignore connection options that are not present. */
+ if (opt->val == NULL)
+ continue;
+
+ if (strcmp(opt->keyword, "password") == 0 && opt->val[0] != '\0')
+ {
+ result = true;
+ break;
+ }
+ }
+
PQconninfoFree(opts);
+ return result;
}
/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 07eea504ba..57e4836927 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -1237,6 +1237,7 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
WalRcvExecResult *res;
char originname[NAMEDATALEN];
RepOriginId originid;
+ bool must_use_password;
/* Check the state of the table synchronization. */
StartTransactionCommand();
@@ -1269,13 +1270,19 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
slotname,
NAMEDATALEN);
+ /* Is the use of a password mandatory? */
+ must_use_password = MySubscription->passwordrequired &&
+ !superuser_arg(MySubscription->owner);
+
/*
* Here we use the slot name instead of the subscription name as the
* application_name, so that it is different from the leader apply worker,
* so that synchronous replication can distinguish them.
*/
LogRepWorkerWalRcvConn =
- walrcv_connect(MySubscription->conninfo, true, slotname, &err);
+ walrcv_connect(MySubscription->conninfo, true,
+ must_use_password,
+ slotname, &err);
if (LogRepWorkerWalRcvConn == NULL)
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_FAILURE),
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index cfb2ab6248..f7128f46fc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -4529,6 +4529,7 @@ ApplyWorkerMain(Datum main_arg)
RepOriginId originid;
TimeLineID startpointTLI;
char *err;
+ bool must_use_password;
myslotname = MySubscription->slotname;
@@ -4554,7 +4555,12 @@ ApplyWorkerMain(Datum main_arg)
origin_startpos = replorigin_session_get_progress(false);
CommitTransactionCommand();
+ /* Is the use of a password mandatory? */
+ must_use_password = MySubscription->passwordrequired &&
+ !superuser_arg(MySubscription->owner);
+
LogRepWorkerWalRcvConn = walrcv_connect(MySubscription->conninfo, true,
+ must_use_password,
MySubscription->name, &err);
if (LogRepWorkerWalRcvConn == NULL)
ereport(ERROR,
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index b0cfddd548..3c844234d3 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -296,7 +296,7 @@ WalReceiverMain(void)
PG_SETMASK(&UnBlockSig);
/* Establish the connection to the primary for XLOG streaming */
- wrconn = walrcv_connect(conninfo, false,
+ wrconn = walrcv_connect(conninfo, false, false,
cluster_name[0] ? cluster_name : "walreceiver",
&err);
if (!wrconn)
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index f2e5663c9f..8920f3027e 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -94,5 +94,10 @@
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '9535', oid_symbol => 'ROLE_PG_CREATE_SUBSCRIPTION',
+ rolname => 'pg_create_subscription', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
]
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index b0f2a1705d..6319f598d8 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -88,6 +88,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
bool subdisableonerr; /* True if a worker error should cause the
* subscription to be disabled */
+ bool subpasswordrequired; /* Must connection use a password? */
+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
/* Connection string to the publisher */
text subconninfo BKI_FORCE_NOT_NULL;
@@ -131,6 +133,7 @@ typedef struct Subscription
bool disableonerr; /* Indicates if the subscription should be
* automatically disabled if a worker error
* occurs */
+ bool passwordrequired; /* Must connection use a password? */
char *conninfo; /* Connection string to the publisher */
char *slotname; /* Name of the replication slot */
char *synccommit; /* Synchronous commit setting for worker */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index decffe352d..215e375876 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -239,6 +239,7 @@ typedef struct WalRcvExecResult
*/
typedef WalReceiverConn *(*walrcv_connect_fn) (const char *conninfo,
bool logical,
+ bool must_use_password,
const char *appname,
char **err);
@@ -247,7 +248,7 @@ typedef WalReceiverConn *(*walrcv_connect_fn) (const char *conninfo,
*
* Parse and validate the connection string given as of 'conninfo'.
*/
-typedef void (*walrcv_check_conninfo_fn) (const char *conninfo);
+typedef bool (*walrcv_check_conninfo_fn) (const char *conninfo);
/*
* walrcv_get_conninfo_fn
@@ -405,8 +406,8 @@ typedef struct WalReceiverFunctionsType
extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
-#define walrcv_connect(conninfo, logical, appname, err) \
- WalReceiverFunctions->walrcv_connect(conninfo, logical, appname, err)
+#define walrcv_connect(conninfo, logical, must_use_password, appname, err) \
+ WalReceiverFunctions->walrcv_connect(conninfo, logical, must_use_password, appname, err)
#define walrcv_check_conninfo(conninfo) \
WalReceiverFunctions->walrcv_check_conninfo(conninfo)
#define walrcv_get_conninfo(conn) \
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 3f99b14394..582652f422 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -78,7 +78,7 @@ ERROR: subscription "regress_testsub" already exists
-- fail - must be superuser
SET SESSION AUTHORIZATION 'regress_subscription_user2';
CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION foo WITH (connect = false);
-ERROR: must be superuser to create subscriptions
+ERROR: must have privileges of pg_create_subscription to create subscriptions
SET SESSION AUTHORIZATION 'regress_subscription_user';
-- fail - invalid option combinations
CREATE SUBSCRIPTION regress_testsub2 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, copy_data = true);
@@ -221,7 +221,7 @@ ALTER SUBSCRIPTION regress_testsub_foo RENAME TO regress_testsub;
-- fail - new owner must be superuser
ALTER SUBSCRIPTION regress_testsub OWNER TO regress_subscription_user2;
ERROR: permission denied to change owner of subscription "regress_testsub"
-HINT: The owner of a subscription must be a superuser.
+HINT: The owner of a subscription must have the privileges of pg_create_subscription.
ALTER ROLE regress_subscription_user2 SUPERUSER;
-- now it works
ALTER SUBSCRIPTION regress_testsub OWNER TO regress_subscription_user2;
diff --git a/src/test/subscription/t/027_nosuperuser.pl b/src/test/subscription/t/027_nosuperuser.pl
index 59192dbe2f..e770e0615c 100644
--- a/src/test/subscription/t/027_nosuperuser.pl
+++ b/src/test/subscription/t/027_nosuperuser.pl
@@ -150,7 +150,7 @@ CREATE PUBLICATION alice
$node_subscriber->safe_psql(
'postgres', qq(
SET SESSION AUTHORIZATION regress_admin;
-CREATE SUBSCRIPTION admin_sub CONNECTION '$publisher_connstr' PUBLICATION alice;
+CREATE SUBSCRIPTION admin_sub CONNECTION '$publisher_connstr' PUBLICATION alice WITH (password_required=false);
));
# Wait for initial sync to finish
--
2.37.1 (Apple Git-137.1)