mirror of
https://github.com/postgres/postgres.git
synced 2025-04-20 00:42:27 +03:00
dblink: SCRAM authentication pass-through
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 commit 761c79508e7. (It also contains the equivalent of the subsequent fixes 76563f88cfb and 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
This commit is contained in:
parent
a3b6dfd410
commit
3642df265d
@ -13,6 +13,7 @@ PGFILEDESC = "dblink - connect to other PostgreSQL databases"
|
|||||||
|
|
||||||
REGRESS = dblink
|
REGRESS = dblink
|
||||||
REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
|
REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
|
||||||
|
TAP_TESTS = 1
|
||||||
|
|
||||||
ifdef USE_PGXS
|
ifdef USE_PGXS
|
||||||
PG_CONFIG = pg_config
|
PG_CONFIG = pg_config
|
||||||
|
@ -43,6 +43,8 @@
|
|||||||
#include "catalog/pg_foreign_server.h"
|
#include "catalog/pg_foreign_server.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "catalog/pg_user_mapping.h"
|
#include "catalog/pg_user_mapping.h"
|
||||||
|
#include "commands/defrem.h"
|
||||||
|
#include "common/base64.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "foreign/foreign.h"
|
#include "foreign/foreign.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
@ -126,6 +128,11 @@ static bool is_valid_dblink_option(const PQconninfoOption *options,
|
|||||||
const char *option, Oid context);
|
const char *option, Oid context);
|
||||||
static int applyRemoteGucs(PGconn *conn);
|
static int applyRemoteGucs(PGconn *conn);
|
||||||
static void restoreLocalGucs(int nestlevel);
|
static void restoreLocalGucs(int nestlevel);
|
||||||
|
static bool UseScramPassthrough(ForeignServer *foreign_server, UserMapping *user);
|
||||||
|
static void appendSCRAMKeysInfo(StringInfo buf);
|
||||||
|
static bool is_valid_dblink_fdw_option(const PQconninfoOption *options, const char *option,
|
||||||
|
Oid context);
|
||||||
|
static bool dblink_connstr_has_required_scram_options(const char *connstr);
|
||||||
|
|
||||||
/* Global */
|
/* Global */
|
||||||
static remoteConn *pconn = NULL;
|
static remoteConn *pconn = NULL;
|
||||||
@ -1964,7 +1971,7 @@ dblink_fdw_validator(PG_FUNCTION_ARGS)
|
|||||||
{
|
{
|
||||||
DefElem *def = (DefElem *) lfirst(cell);
|
DefElem *def = (DefElem *) lfirst(cell);
|
||||||
|
|
||||||
if (!is_valid_dblink_option(options, def->defname, context))
|
if (!is_valid_dblink_fdw_option(options, def->defname, context))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Unknown option, or invalid option for the context specified, so
|
* Unknown option, or invalid option for the context specified, so
|
||||||
@ -2596,6 +2603,67 @@ deleteConnection(const char *name)
|
|||||||
errmsg("undefined connection name")));
|
errmsg("undefined connection name")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure that require_auth and SCRAM keys are correctly set on connstr.
|
||||||
|
* SCRAM keys used to pass-through are coming from the initial connection
|
||||||
|
* from the client with the server.
|
||||||
|
*
|
||||||
|
* All required SCRAM options are set by dblink, so we just need to ensure
|
||||||
|
* that these options are not overwritten by the user.
|
||||||
|
*
|
||||||
|
* See appendSCRAMKeysInfo and its usage for more.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
dblink_connstr_has_required_scram_options(const char *connstr)
|
||||||
|
{
|
||||||
|
PQconninfoOption *options;
|
||||||
|
bool has_scram_server_key = false;
|
||||||
|
bool has_scram_client_key = false;
|
||||||
|
bool has_require_auth = false;
|
||||||
|
bool has_scram_keys = false;
|
||||||
|
|
||||||
|
options = PQconninfoParse(connstr, NULL);
|
||||||
|
if (options)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Continue iterating even if we found the keys that we need to
|
||||||
|
* validate to make sure that there is no other declaration of these
|
||||||
|
* keys that can overwrite the first.
|
||||||
|
*/
|
||||||
|
for (PQconninfoOption *option = options; option->keyword != NULL; option++)
|
||||||
|
{
|
||||||
|
if (strcmp(option->keyword, "require_auth") == 0)
|
||||||
|
{
|
||||||
|
if (option->val != NULL && strcmp(option->val, "scram-sha-256") == 0)
|
||||||
|
has_require_auth = true;
|
||||||
|
else
|
||||||
|
has_require_auth = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(option->keyword, "scram_client_key") == 0)
|
||||||
|
{
|
||||||
|
if (option->val != NULL && option->val[0] != '\0')
|
||||||
|
has_scram_client_key = true;
|
||||||
|
else
|
||||||
|
has_scram_client_key = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(option->keyword, "scram_server_key") == 0)
|
||||||
|
{
|
||||||
|
if (option->val != NULL && option->val[0] != '\0')
|
||||||
|
has_scram_server_key = true;
|
||||||
|
else
|
||||||
|
has_scram_server_key = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PQconninfoFree(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
has_scram_keys = has_scram_client_key && has_scram_server_key && MyProcPort->has_scram_keys;
|
||||||
|
|
||||||
|
return (has_scram_keys && has_require_auth);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We need to make sure that the connection made used credentials
|
* We need to make sure that the connection made used credentials
|
||||||
* which were provided by the user, so check what credentials were
|
* which were provided by the user, so check what credentials were
|
||||||
@ -2612,6 +2680,18 @@ dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr)
|
|||||||
if (PQconnectionUsedPassword(conn) && dblink_connstr_has_pw(connstr))
|
if (PQconnectionUsedPassword(conn) && dblink_connstr_has_pw(connstr))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Password was not used to connect, check if SCRAM pass-through is in
|
||||||
|
* use.
|
||||||
|
*
|
||||||
|
* If dblink_connstr_has_required_scram_options is true we assume that
|
||||||
|
* UseScramPassthrough is also true because the required SCRAM keys are
|
||||||
|
* only added if UseScramPassthrough is set, and the user is not allowed
|
||||||
|
* to add the SCRAM keys on fdw and user mapping options.
|
||||||
|
*/
|
||||||
|
if (MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr))
|
||||||
|
return;
|
||||||
|
|
||||||
#ifdef ENABLE_GSS
|
#ifdef ENABLE_GSS
|
||||||
/* If GSSAPI creds used to connect, make sure it was one delegated */
|
/* If GSSAPI creds used to connect, make sure it was one delegated */
|
||||||
if (PQconnectionUsedGSSAPI(conn) && be_gssapi_get_delegation(MyProcPort))
|
if (PQconnectionUsedGSSAPI(conn) && be_gssapi_get_delegation(MyProcPort))
|
||||||
@ -2664,12 +2744,14 @@ dblink_connstr_has_pw(const char *connstr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For non-superusers, insist that the connstr specify a password, except
|
* For non-superusers, insist that the connstr specify a password, except if
|
||||||
* if GSSAPI credentials have been delegated (and we check that they are used
|
* GSSAPI credentials have been delegated (and we check that they are used for
|
||||||
* for the connection in dblink_security_check later). This prevents a
|
* the connection in dblink_security_check later) or if SCRAM pass-through is
|
||||||
* password or GSSAPI credentials from being picked up from .pgpass, a
|
* being used. This prevents a password or GSSAPI credentials from being
|
||||||
* service file, the environment, etc. We don't want the postgres user's
|
* picked up from .pgpass, a service file, the environment, etc. We don't want
|
||||||
* passwords or Kerberos credentials to be accessible to non-superusers.
|
* the postgres user's passwords or Kerberos credentials to be accessible to
|
||||||
|
* non-superusers. In case of SCRAM pass-through insist that the connstr
|
||||||
|
* has the required SCRAM pass-through options.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
dblink_connstr_check(const char *connstr)
|
dblink_connstr_check(const char *connstr)
|
||||||
@ -2680,6 +2762,9 @@ dblink_connstr_check(const char *connstr)
|
|||||||
if (dblink_connstr_has_pw(connstr))
|
if (dblink_connstr_has_pw(connstr))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr))
|
||||||
|
return;
|
||||||
|
|
||||||
#ifdef ENABLE_GSS
|
#ifdef ENABLE_GSS
|
||||||
if (be_gssapi_get_delegation(MyProcPort))
|
if (be_gssapi_get_delegation(MyProcPort))
|
||||||
return;
|
return;
|
||||||
@ -2832,6 +2917,14 @@ get_connect_string(const char *servername)
|
|||||||
if (aclresult != ACLCHECK_OK)
|
if (aclresult != ACLCHECK_OK)
|
||||||
aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, foreign_server->servername);
|
aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, foreign_server->servername);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First append hardcoded options needed for SCRAM pass-through, so if
|
||||||
|
* the user overwrites these options we can ereport on
|
||||||
|
* dblink_connstr_check and dblink_security_check.
|
||||||
|
*/
|
||||||
|
if (MyProcPort->has_scram_keys && UseScramPassthrough(foreign_server, user_mapping))
|
||||||
|
appendSCRAMKeysInfo(&buf);
|
||||||
|
|
||||||
foreach(cell, fdw->options)
|
foreach(cell, fdw->options)
|
||||||
{
|
{
|
||||||
DefElem *def = lfirst(cell);
|
DefElem *def = lfirst(cell);
|
||||||
@ -3016,6 +3109,20 @@ is_valid_dblink_option(const PQconninfoOption *options, const char *option,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Same as is_valid_dblink_option but also check for only dblink_fdw specific
|
||||||
|
* options.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
is_valid_dblink_fdw_option(const PQconninfoOption *options, const char *option,
|
||||||
|
Oid context)
|
||||||
|
{
|
||||||
|
if (strcmp(option, "use_scram_passthrough") == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return is_valid_dblink_option(options, option, context);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copy the remote session's values of GUCs that affect datatype I/O
|
* Copy the remote session's values of GUCs that affect datatype I/O
|
||||||
* and apply them locally in a new GUC nesting level. Returns the new
|
* and apply them locally in a new GUC nesting level. Returns the new
|
||||||
@ -3085,3 +3192,66 @@ restoreLocalGucs(int nestlevel)
|
|||||||
if (nestlevel > 0)
|
if (nestlevel > 0)
|
||||||
AtEOXact_GUC(true, nestlevel);
|
AtEOXact_GUC(true, nestlevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Append SCRAM client key and server key information from the global
|
||||||
|
* MyProcPort into the given StringInfo buffer.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
appendSCRAMKeysInfo(StringInfo buf)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
int encoded_len;
|
||||||
|
char *client_key;
|
||||||
|
char *server_key;
|
||||||
|
|
||||||
|
len = pg_b64_enc_len(sizeof(MyProcPort->scram_ClientKey));
|
||||||
|
/* don't forget the zero-terminator */
|
||||||
|
client_key = palloc0(len + 1);
|
||||||
|
encoded_len = pg_b64_encode((const char *) MyProcPort->scram_ClientKey,
|
||||||
|
sizeof(MyProcPort->scram_ClientKey),
|
||||||
|
client_key, len);
|
||||||
|
if (encoded_len < 0)
|
||||||
|
elog(ERROR, "could not encode SCRAM client key");
|
||||||
|
|
||||||
|
len = pg_b64_enc_len(sizeof(MyProcPort->scram_ServerKey));
|
||||||
|
/* don't forget the zero-terminator */
|
||||||
|
server_key = palloc0(len + 1);
|
||||||
|
encoded_len = pg_b64_encode((const char *) MyProcPort->scram_ServerKey,
|
||||||
|
sizeof(MyProcPort->scram_ServerKey),
|
||||||
|
server_key, len);
|
||||||
|
if (encoded_len < 0)
|
||||||
|
elog(ERROR, "could not encode SCRAM server key");
|
||||||
|
|
||||||
|
appendStringInfo(buf, "scram_client_key='%s' ", client_key);
|
||||||
|
appendStringInfo(buf, "scram_server_key='%s' ", server_key);
|
||||||
|
appendStringInfo(buf, "require_auth='scram-sha-256' ");
|
||||||
|
|
||||||
|
pfree(client_key);
|
||||||
|
pfree(server_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
UseScramPassthrough(ForeignServer *foreign_server, UserMapping *user)
|
||||||
|
{
|
||||||
|
ListCell *cell;
|
||||||
|
|
||||||
|
foreach(cell, foreign_server->options)
|
||||||
|
{
|
||||||
|
DefElem *def = lfirst(cell);
|
||||||
|
|
||||||
|
if (strcmp(def->defname, "use_scram_passthrough") == 0)
|
||||||
|
return defGetBoolean(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(cell, user->options)
|
||||||
|
{
|
||||||
|
DefElem *def = (DefElem *) lfirst(cell);
|
||||||
|
|
||||||
|
if (strcmp(def->defname, "use_scram_passthrough") == 0)
|
||||||
|
return defGetBoolean(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -36,4 +36,9 @@ tests += {
|
|||||||
],
|
],
|
||||||
'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'],
|
'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'],
|
||||||
},
|
},
|
||||||
|
'tap': {
|
||||||
|
'tests': [
|
||||||
|
't/001_auth_scram.pl',
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
254
contrib/dblink/t/001_auth_scram.pl
Normal file
254
contrib/dblink/t/001_auth_scram.pl
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
# 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();
|
||||||
|
|
@ -150,9 +150,23 @@ dblink_connect(text connname, text connstr) returns text
|
|||||||
executing arbitrary SQL commands.
|
executing arbitrary SQL commands.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The foreign-data wrapper <filename>dblink_fdw</filename> has an additional
|
||||||
|
Boolean option <literal>use_scram_passthrough</literal> that controls
|
||||||
|
whether <filename>dblink</filename> will use the SCRAM pass-through
|
||||||
|
authentication to connect to the remote database. With SCRAM pass-through
|
||||||
|
authentication, <filename>dblink</filename> uses SCRAM-hashed secrets
|
||||||
|
instead of plain-text user passwords to connect to the remote server. This
|
||||||
|
avoids storing plain-text user passwords in PostgreSQL system catalogs.
|
||||||
|
See the documentation of the equivalent <link
|
||||||
|
linkend="postgres-fdw-option-use-scram-passthrough"><literal>use_scram_passthrough</literal></link>
|
||||||
|
option of postgres_fdw for further details and restrictions.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Only superusers may use <function>dblink_connect</function> to create
|
Only superusers may use <function>dblink_connect</function> to create
|
||||||
non-password-authenticated and non-GSSAPI-authenticated connections.
|
connections that use neither password authentication, SCRAM pass-through,
|
||||||
|
nor GSSAPI-authentication.
|
||||||
If non-superusers need this capability, use
|
If non-superusers need this capability, use
|
||||||
<function>dblink_connect_u</function> instead.
|
<function>dblink_connect_u</function> instead.
|
||||||
</para>
|
</para>
|
||||||
@ -181,8 +195,9 @@ SELECT dblink_connect('myconn', 'dbname=postgres options=-csearch_path=');
|
|||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
-- FOREIGN DATA WRAPPER functionality
|
-- FOREIGN DATA WRAPPER functionality
|
||||||
-- Note: local connection must require password authentication for this to work properly
|
-- Note: local connections that don't use SCRAM pass-through require password
|
||||||
-- Otherwise, you will receive the following error from dblink_connect():
|
-- authentication for this to work properly. Otherwise, you will receive
|
||||||
|
-- the following error from dblink_connect():
|
||||||
-- ERROR: password is required
|
-- ERROR: password is required
|
||||||
-- DETAIL: Non-superuser cannot connect if the server does not request a password.
|
-- DETAIL: Non-superuser cannot connect if the server does not request a password.
|
||||||
-- HINT: Target server's authentication method must be changed.
|
-- HINT: Target server's authentication method must be changed.
|
||||||
|
@ -756,7 +756,7 @@ OPTIONS (ADD password_required 'false');
|
|||||||
|
|
||||||
<variablelist>
|
<variablelist>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry id="postgres-fdw-option-keep-connections">
|
||||||
<term><literal>keep_connections</literal> (<type>boolean</type>)</term>
|
<term><literal>keep_connections</literal> (<type>boolean</type>)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
@ -770,7 +770,7 @@ OPTIONS (ADD password_required 'false');
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry id="postgres-fdw-option-use-scram-passthrough">
|
||||||
<term><literal>use_scram_passthrough</literal> (<type>boolean</type>)</term>
|
<term><literal>use_scram_passthrough</literal> (<type>boolean</type>)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user