aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeff Davis <jdavis@postgresql.org>2024-01-12 13:42:09 -0800
committerJeff Davis <jdavis@postgresql.org>2024-01-12 13:42:09 -0800
commit4c03ac7e2bc46988fe4ecf3b1aef393488786f12 (patch)
treee1ff9956c956f5c94c1de28a1ab65b7ef019620b /src
parent9c00e4c7751f50e81636b0e837809b309bfe7ef6 (diff)
downloadpostgresql-4c03ac7e2bc46988fe4ecf3b1aef393488786f12.tar.gz
postgresql-4c03ac7e2bc46988fe4ecf3b1aef393488786f12.zip
Re-validate connection string in libpqrcv_connect().
A superuser may create a subscription with password_required=true, but which uses a connection string without a password. Previously, if the owner of such a subscription was changed to a non-superuser, the non-superuser was able to utilize a password from another source (like a password file or the PGPASSWORD environment variable), which should not have been allowed. This commit adds a step to re-validate the connection string before connecting. Reported-by: Jeff Davis Author: Vignesh C Reviewed-by: Peter Smith, Robert Haas, Amit Kapila Discussion: https://www.postgresql.org/message-id/flat/e5892973ae2a80a1a3e0266806640dae3c428100.camel%40j-davis.com Backpatch-through: 16
Diffstat (limited to 'src')
-rw-r--r--src/backend/replication/libpqwalreceiver/libpqwalreceiver.c9
-rw-r--r--src/test/subscription/t/027_nosuperuser.pl80
2 files changed, 89 insertions, 0 deletions
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 77812f12a9b..a5b1507559f 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -138,6 +138,15 @@ libpqrcv_connect(const char *conninfo, bool logical, bool must_use_password,
int i = 0;
/*
+ * Re-validate connection string. The validation already happened at DDL
+ * time, but the subscription owner may have changed. If we don't recheck
+ * with the correct must_use_password, it's possible that the connection
+ * will obtain the password from a different source, such as PGPASSFILE or
+ * PGPASSWORD.
+ */
+ libpqrcv_check_conninfo(conninfo, must_use_password);
+
+ /*
* We use the expand_dbname parameter to process the connection string (or
* URI), and pass some extra options.
*/
diff --git a/src/test/subscription/t/027_nosuperuser.pl b/src/test/subscription/t/027_nosuperuser.pl
index d7a7e3ef5bb..525ef7917f7 100644
--- a/src/test/subscription/t/027_nosuperuser.pl
+++ b/src/test/subscription/t/027_nosuperuser.pl
@@ -303,4 +303,84 @@ GRANT SELECT ON alice.unpartitioned TO regress_alice;
expect_replication("alice.unpartitioned", 3, 17, 21,
"restoring SELECT permission permits replication to continue");
+# If the subscription connection requires a password ('password_required'
+# is true) then a non-superuser must specify that password in the connection
+# string.
+$ENV{"PGPASSWORD"} = 'secret';
+
+my $node_publisher1 = PostgreSQL::Test::Cluster->new('publisher1');
+my $node_subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$node_publisher1->init(allows_streaming => 'logical');
+$node_subscriber1->init;
+$node_publisher1->start;
+$node_subscriber1->start;
+my $publisher_connstr1 =
+ $node_publisher1->connstr . ' user=regress_test_user dbname=postgres';
+my $publisher_connstr2 =
+ $node_publisher1->connstr
+ . ' user=regress_test_user dbname=postgres password=secret';
+
+for my $node ($node_publisher1, $node_subscriber1)
+{
+ $node->safe_psql(
+ 'postgres', qq(
+ CREATE ROLE regress_test_user PASSWORD 'secret' LOGIN REPLICATION;
+ GRANT CREATE ON DATABASE postgres TO regress_test_user;
+ GRANT PG_CREATE_SUBSCRIPTION TO regress_test_user;
+ ));
+}
+
+$node_publisher1->safe_psql(
+ 'postgres', qq(
+SET SESSION AUTHORIZATION regress_test_user;
+CREATE PUBLICATION regress_test_pub;
+));
+$node_subscriber1->safe_psql(
+ 'postgres', qq(
+CREATE SUBSCRIPTION regress_test_sub CONNECTION '$publisher_connstr1' PUBLICATION regress_test_pub;
+));
+
+# Wait for initial sync to finish
+$node_subscriber1->wait_for_subscription_sync($node_publisher1,
+ 'regress_test_sub');
+
+# Setup pg_hba configuration so that logical replication connection without
+# password is not allowed.
+unlink($node_publisher1->data_dir . '/pg_hba.conf');
+$node_publisher1->append_conf('pg_hba.conf',
+ qq{local all regress_test_user md5});
+$node_publisher1->reload;
+
+# Change the subscription owner to a non-superuser
+$node_subscriber1->safe_psql(
+ 'postgres', qq(
+ALTER SUBSCRIPTION regress_test_sub OWNER TO regress_test_user;
+));
+
+# Non-superuser must specify password in the connection string
+my ($ret, $stdout, $stderr) = $node_subscriber1->psql(
+ 'postgres', qq(
+SET SESSION AUTHORIZATION regress_test_user;
+ALTER SUBSCRIPTION regress_test_sub REFRESH PUBLICATION;
+));
+isnt($ret, 0,
+ "non zero exit for subscription whose owner is a non-superuser must specify password parameter of the connection string"
+);
+ok( $stderr =~ m/DETAIL: Non-superusers must provide a password in the connection string./,
+ 'subscription whose owner is a non-superuser must specify password parameter of the connection string'
+);
+
+delete $ENV{"PGPASSWORD"};
+
+# It should succeed after including the password parameter of the connection
+# string.
+($ret, $stdout, $stderr) = $node_subscriber1->psql(
+ 'postgres', qq(
+SET SESSION AUTHORIZATION regress_test_user;
+ALTER SUBSCRIPTION regress_test_sub CONNECTION '$publisher_connstr2';
+ALTER SUBSCRIPTION regress_test_sub REFRESH PUBLICATION;
+));
+is($ret, 0,
+ "Non-superuser will be able to refresh the publication after specifying the password parameter of the connection string"
+);
done_testing();