mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	This enables SCRAM authentication for dblink (using dblink_fdw) when connecting to a foreign server without having to store a plain-text password on user mapping options This uses the same approach as it was implemented for postgres_fdw in commit761c79508e. (It also contains the equivalent of the subsequent fixes76563f88cfand d2028e9bbc1.) Author: Matheus Alcantara <mths.dev@pm.me> Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com> Discussion: https://www.postgresql.org/message-id/flat/CAFY6G8ercA1KES%3DE_0__R9QCTR805TTyYr1No8qF8ZxmMg8z2Q%40mail.gmail.com
		
			
				
	
	
		
			255 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
# Copyright (c) 2024-2025, PostgreSQL Global Development Group
 | 
						|
 | 
						|
# Test SCRAM authentication when opening a new connection with a foreign
 | 
						|
# server.
 | 
						|
#
 | 
						|
# The test is executed by testing the SCRAM authentifcation on a loopback
 | 
						|
# connection on the same server and with different servers.
 | 
						|
 | 
						|
use strict;
 | 
						|
use warnings FATAL => 'all';
 | 
						|
use PostgreSQL::Test::Utils;
 | 
						|
use PostgreSQL::Test::Cluster;
 | 
						|
use Test::More;
 | 
						|
 | 
						|
if (!$use_unix_sockets)
 | 
						|
{
 | 
						|
	plan skip_all => "test requires Unix-domain sockets";
 | 
						|
}
 | 
						|
 | 
						|
my $user = "user01";
 | 
						|
 | 
						|
my $db0 = "db0";                               # For node1
 | 
						|
my $db1 = "db1";                               # For node1
 | 
						|
my $db2 = "db2";                               # For node2
 | 
						|
my $fdw_server = "db1_fdw";
 | 
						|
my $fdw_server2 = "db2_fdw";
 | 
						|
my $fdw_invalid_server = "db2_fdw_invalid";    # For invalid fdw options
 | 
						|
my $fdw_invalid_server2 =
 | 
						|
  "db2_fdw_invalid2";    # For invalid scram keys fdw options
 | 
						|
 | 
						|
my $node1 = PostgreSQL::Test::Cluster->new('node1');
 | 
						|
my $node2 = PostgreSQL::Test::Cluster->new('node2');
 | 
						|
 | 
						|
$node1->init;
 | 
						|
$node2->init;
 | 
						|
 | 
						|
$node1->start;
 | 
						|
$node2->start;
 | 
						|
 | 
						|
# Test setup
 | 
						|
 | 
						|
$node1->safe_psql('postgres', qq'CREATE USER $user WITH password \'pass\'');
 | 
						|
$node2->safe_psql('postgres', qq'CREATE USER $user WITH password \'pass\'');
 | 
						|
$ENV{PGPASSWORD} = "pass";
 | 
						|
 | 
						|
$node1->safe_psql('postgres', qq'CREATE DATABASE $db0');
 | 
						|
$node1->safe_psql('postgres', qq'CREATE DATABASE $db1');
 | 
						|
$node2->safe_psql('postgres', qq'CREATE DATABASE $db2');
 | 
						|
 | 
						|
setup_table($node1, $db1, "t");
 | 
						|
setup_table($node2, $db2, "t2");
 | 
						|
 | 
						|
$node1->safe_psql($db0, 'CREATE EXTENSION IF NOT EXISTS dblink');
 | 
						|
setup_fdw_server($node1, $db0, $fdw_server, $node1, $db1);
 | 
						|
setup_fdw_server($node1, $db0, $fdw_server2, $node2, $db2);
 | 
						|
setup_invalid_fdw_server($node1, $db0, $fdw_invalid_server, $node2, $db2);
 | 
						|
setup_fdw_server($node1, $db0, $fdw_invalid_server2, $node2, $db2);
 | 
						|
 | 
						|
setup_user_mapping($node1, $db0, $fdw_server);
 | 
						|
setup_user_mapping($node1, $db0, $fdw_server2);
 | 
						|
setup_user_mapping($node1, $db0, $fdw_invalid_server);
 | 
						|
 | 
						|
# Make the user have the same SCRAM key on both servers. Forcing to have the
 | 
						|
# same iteration and salt.
 | 
						|
my $rolpassword = $node1->safe_psql('postgres',
 | 
						|
	qq"SELECT rolpassword FROM pg_authid WHERE rolname = '$user';");
 | 
						|
$node2->safe_psql('postgres', qq"ALTER ROLE $user PASSWORD '$rolpassword'");
 | 
						|
 | 
						|
unlink($node1->data_dir . '/pg_hba.conf');
 | 
						|
unlink($node2->data_dir . '/pg_hba.conf');
 | 
						|
 | 
						|
$node1->append_conf(
 | 
						|
	'pg_hba.conf', qq{
 | 
						|
local   db0             all                                     scram-sha-256
 | 
						|
local   db1             all                                     scram-sha-256
 | 
						|
}
 | 
						|
);
 | 
						|
$node2->append_conf(
 | 
						|
	'pg_hba.conf', qq{
 | 
						|
local   db2             all                                     scram-sha-256
 | 
						|
}
 | 
						|
);
 | 
						|
 | 
						|
$node1->restart;
 | 
						|
$node2->restart;
 | 
						|
 | 
						|
# End of test setup
 | 
						|
 | 
						|
test_scram_keys_is_not_overwritten($node1, $db0, $fdw_invalid_server2);
 | 
						|
 | 
						|
test_fdw_auth($node1, $db0, "t", $fdw_server,
 | 
						|
	"SCRAM auth on the same database cluster must succeed");
 | 
						|
 | 
						|
test_fdw_auth($node1, $db0, "t2", $fdw_server2,
 | 
						|
	"SCRAM auth on a different database cluster must succeed");
 | 
						|
 | 
						|
test_fdw_auth_with_invalid_overwritten_require_auth($fdw_invalid_server);
 | 
						|
 | 
						|
# Ensure that trust connections fail without superuser opt-in.
 | 
						|
unlink($node1->data_dir . '/pg_hba.conf');
 | 
						|
unlink($node2->data_dir . '/pg_hba.conf');
 | 
						|
 | 
						|
$node1->append_conf(
 | 
						|
	'pg_hba.conf', qq{
 | 
						|
local   db0             all                                     scram-sha-256
 | 
						|
local   db1             all                                     trust
 | 
						|
}
 | 
						|
);
 | 
						|
$node2->append_conf(
 | 
						|
	'pg_hba.conf', qq{
 | 
						|
local   all             all                                     password
 | 
						|
}
 | 
						|
);
 | 
						|
 | 
						|
$node1->restart;
 | 
						|
$node2->restart;
 | 
						|
 | 
						|
my ($ret, $stdout, $stderr) = $node1->psql(
 | 
						|
	$db0,
 | 
						|
	"SELECT * FROM dblink('$fdw_server', 'SELECT * FROM t') AS t(a int, b int)",
 | 
						|
	connstr => $node1->connstr($db0) . " user=$user");
 | 
						|
 | 
						|
is($ret, 3, 'loopback trust fails on the same cluster');
 | 
						|
like(
 | 
						|
	$stderr,
 | 
						|
	qr/failed: authentication method requirement "scram-sha-256" failed: server did not complete authentication/,
 | 
						|
	'expected error from loopback trust (same cluster)');
 | 
						|
 | 
						|
($ret, $stdout, $stderr) = $node1->psql(
 | 
						|
	$db0,
 | 
						|
	"SELECT * FROM dblink('$fdw_server2', 'SELECT * FROM t2') AS t2(a int, b int)",
 | 
						|
	connstr => $node1->connstr($db0) . " user=$user");
 | 
						|
 | 
						|
is($ret, 3, 'loopback password fails on a different cluster');
 | 
						|
like(
 | 
						|
	$stderr,
 | 
						|
	qr/authentication method requirement "scram-sha-256" failed: server requested a cleartext password/,
 | 
						|
	'expected error from loopback password (different cluster)');
 | 
						|
 | 
						|
# Helper functions
 | 
						|
 | 
						|
sub test_fdw_auth
 | 
						|
{
 | 
						|
	local $Test::Builder::Level = $Test::Builder::Level + 1;
 | 
						|
 | 
						|
	my ($node, $db, $tbl, $fdw, $testname) = @_;
 | 
						|
	my $connstr = $node->connstr($db) . qq' user=$user';
 | 
						|
 | 
						|
	my $ret = $node->safe_psql(
 | 
						|
		$db,
 | 
						|
		qq"SELECT count(1) FROM dblink('$fdw', 'SELECT * FROM $tbl') AS $tbl(a int, b int)",
 | 
						|
		connstr => $connstr);
 | 
						|
 | 
						|
	is($ret, '10', $testname);
 | 
						|
}
 | 
						|
 | 
						|
sub test_fdw_auth_with_invalid_overwritten_require_auth
 | 
						|
{
 | 
						|
	local $Test::Builder::Level = $Test::Builder::Level + 1;
 | 
						|
 | 
						|
	my ($fdw) = @_;
 | 
						|
 | 
						|
	my ($ret, $stdout, $stderr) = $node1->psql(
 | 
						|
		$db0,
 | 
						|
		"select * from dblink('$fdw', 'select * from t') as t(a int, b int)",
 | 
						|
		connstr => $node1->connstr($db0) . " user=$user");
 | 
						|
 | 
						|
	is($ret, 3, 'loopback trust fails when overwriting require_auth');
 | 
						|
	like(
 | 
						|
		$stderr,
 | 
						|
		qr/password or GSSAPI delegated credentials required/,
 | 
						|
		'expected error when connecting to a fdw overwriting the require_auth'
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
sub test_scram_keys_is_not_overwritten
 | 
						|
{
 | 
						|
	local $Test::Builder::Level = $Test::Builder::Level + 1;
 | 
						|
 | 
						|
	my ($node, $db, $fdw) = @_;
 | 
						|
 | 
						|
	my ($ret, $stdout, $stderr) = $node->psql(
 | 
						|
		$db,
 | 
						|
		qq'CREATE USER MAPPING FOR $user SERVER $fdw OPTIONS (user \'$user\', scram_client_key \'key\');',
 | 
						|
		connstr => $node->connstr($db) . " user=$user");
 | 
						|
 | 
						|
	is($ret, 3, 'user mapping creation fails when using scram_client_key');
 | 
						|
	like(
 | 
						|
		$stderr,
 | 
						|
		qr/ERROR:  invalid option "scram_client_key"/,
 | 
						|
		'user mapping creation fails when using scram_client_key');
 | 
						|
 | 
						|
	($ret, $stdout, $stderr) = $node->psql(
 | 
						|
		$db,
 | 
						|
		qq'CREATE USER MAPPING FOR $user SERVER $fdw OPTIONS (user \'$user\', scram_server_key \'key\');',
 | 
						|
		connstr => $node->connstr($db) . " user=$user");
 | 
						|
 | 
						|
	is($ret, 3, 'user mapping creation fails when using scram_server_key');
 | 
						|
	like(
 | 
						|
		$stderr,
 | 
						|
		qr/ERROR:  invalid option "scram_server_key"/,
 | 
						|
		'user mapping creation fails when using scram_server_key');
 | 
						|
}
 | 
						|
 | 
						|
sub setup_user_mapping
 | 
						|
{
 | 
						|
	my ($node, $db, $fdw) = @_;
 | 
						|
 | 
						|
	$node->safe_psql($db,
 | 
						|
		qq'CREATE USER MAPPING FOR $user SERVER $fdw OPTIONS (user \'$user\');'
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
sub setup_fdw_server
 | 
						|
{
 | 
						|
	my ($node, $db, $fdw, $fdw_node, $dbname) = @_;
 | 
						|
	my $host = $fdw_node->host;
 | 
						|
	my $port = $fdw_node->port;
 | 
						|
 | 
						|
	$node->safe_psql(
 | 
						|
		$db, qq'CREATE SERVER $fdw FOREIGN DATA WRAPPER dblink_fdw options (
 | 
						|
		host \'$host\', port \'$port\', dbname \'$dbname\', use_scram_passthrough \'true\') '
 | 
						|
	);
 | 
						|
	$node->safe_psql($db, qq'GRANT USAGE ON FOREIGN SERVER $fdw TO $user;');
 | 
						|
	$node->safe_psql($db, qq'GRANT ALL ON SCHEMA public TO $user');
 | 
						|
}
 | 
						|
 | 
						|
sub setup_invalid_fdw_server
 | 
						|
{
 | 
						|
	my ($node, $db, $fdw, $fdw_node, $dbname) = @_;
 | 
						|
	my $host = $fdw_node->host;
 | 
						|
	my $port = $fdw_node->port;
 | 
						|
 | 
						|
	$node->safe_psql(
 | 
						|
		$db, qq'CREATE SERVER $fdw FOREIGN DATA WRAPPER dblink_fdw options (
 | 
						|
		host \'$host\', port \'$port\', dbname \'$dbname\', use_scram_passthrough \'true\', require_auth \'none\') '
 | 
						|
	);
 | 
						|
	$node->safe_psql($db, qq'GRANT USAGE ON FOREIGN SERVER $fdw TO $user;');
 | 
						|
	$node->safe_psql($db, qq'GRANT ALL ON SCHEMA public TO $user');
 | 
						|
}
 | 
						|
 | 
						|
sub setup_table
 | 
						|
{
 | 
						|
	my ($node, $db, $tbl) = @_;
 | 
						|
 | 
						|
	$node->safe_psql($db,
 | 
						|
		qq'CREATE TABLE $tbl AS SELECT g as a, g + 1 as b FROM generate_series(1,10) g(g)'
 | 
						|
	);
 | 
						|
	$node->safe_psql($db, qq'GRANT USAGE ON SCHEMA public TO $user');
 | 
						|
	$node->safe_psql($db, qq'GRANT SELECT ON $tbl TO $user');
 | 
						|
}
 | 
						|
 | 
						|
done_testing();
 | 
						|
 |