Skip to content

Commit 76563f8

Browse files
peterejchampio
andcommitted
postgres_fdw: improve security checks
SCRAM pass-through should not bypass the FDW security check as it was implemented for postgres_fdw in commit 761c795. This commit improves the security check by adding new SCRAM pass-through checks to ensure that the required SCRAM connection options are not overwritten by the user mapping or foreign server options. This is meant to match the security requirements for a password-using connection. Since libpq has no SCRAM-specific equivalent of PQconnectionUsedPassword(), we enforce this instead by making the use_scram_passthrough option of postgres_fdw imply require_auth=scram-sha-256. This means that if use_scram_passthrough is set, some situations that might otherwise have worked are preempted, for example GSSAPI with delegated credentials. This could be enhanced in the future if there is desire for more flexibility. Reported-by: Jacob Champion <jacob.champion@enterprisedb.com> Author: Matheus Alcantara <mths.dev@pm.me> Co-authored-by: Jacob Champion <jacob.champion@enterprisedb.com> Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com> Discussion: https://www.postgresql.org/message-id/flat/CAFY6G8ercA1KES%3DE_0__R9QCTR805TTyYr1No8qF8ZxmMg8z2Q%40mail.gmail.com
1 parent a8eeb22 commit 76563f8

File tree

3 files changed

+132
-22
lines changed

3 files changed

+132
-22
lines changed

contrib/postgres_fdw/connection.c

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ static void postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
184184
enum pgfdwVersion api_version);
185185
static int pgfdw_conn_check(PGconn *conn);
186186
static bool pgfdw_conn_checkable(void);
187+
static bool pgfdw_has_required_scram_options(const char **keywords, const char **values);
187188

188189
/*
189190
* Get a PGconn which can be used to execute queries on the remote PostgreSQL
@@ -455,6 +456,15 @@ pgfdw_security_check(const char **keywords, const char **values, UserMapping *us
455456
}
456457
}
457458

459+
/*
460+
* Ok if SCRAM pass-through is being used and all required SCRAM options
461+
* are set correctly. If pgfdw_has_required_scram_options returns true we
462+
* assume that UseScramPassthrough is also true since SCRAM options are
463+
* only set when UseScramPassthrough is enabled.
464+
*/
465+
if (MyProcPort->has_scram_keys && pgfdw_has_required_scram_options(keywords, values))
466+
return;
467+
458468
ereport(ERROR,
459469
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
460470
errmsg("password or GSSAPI delegated credentials required"),
@@ -485,9 +495,10 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
485495
* and UserMapping. (Some of them might not be libpq options, in
486496
* which case we'll just waste a few array slots.) Add 4 extra slots
487497
* for application_name, fallback_application_name, client_encoding,
488-
* end marker.
498+
* end marker, and 3 extra slots for scram keys and required scram
499+
* pass-through options.
489500
*/
490-
n = list_length(server->options) + list_length(user->options) + 4 + 2;
501+
n = list_length(server->options) + list_length(user->options) + 4 + 3;
491502
keywords = (const char **) palloc(n * sizeof(char *));
492503
values = (const char **) palloc(n * sizeof(char *));
493504

@@ -556,6 +567,7 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
556567
values[n] = GetDatabaseEncodingName();
557568
n++;
558569

570+
/* Add required SCRAM pass-through connection options if it's enabled. */
559571
if (MyProcPort->has_scram_keys && UseScramPassthrough(server, user))
560572
{
561573
int len;
@@ -582,16 +594,20 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
582594
if (encoded_len < 0)
583595
elog(ERROR, "could not encode SCRAM server key");
584596
n++;
597+
598+
/*
599+
* Require scram-sha-256 to ensure that no other auth method is
600+
* used when connecting with foreign server.
601+
*/
602+
keywords[n] = "require_auth";
603+
values[n] = "scram-sha-256";
604+
n++;
585605
}
586606

587607
keywords[n] = values[n] = NULL;
588608

589-
/*
590-
* Verify the set of connection parameters only if scram pass-through
591-
* is not being used because the password is not necessary.
592-
*/
593-
if (!(MyProcPort->has_scram_keys && UseScramPassthrough(server, user)))
594-
check_conn_params(keywords, values, user);
609+
/* Verify the set of connection parameters. */
610+
check_conn_params(keywords, values, user);
595611

596612
/* first time, allocate or get the custom wait event */
597613
if (pgfdw_we_connect == 0)
@@ -609,12 +625,8 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
609625
server->servername),
610626
errdetail_internal("%s", pchomp(PQerrorMessage(conn)))));
611627

612-
/*
613-
* Perform post-connection security checks only if scram pass-through
614-
* is not being used because the password is not necessary.
615-
*/
616-
if (!(MyProcPort->has_scram_keys && UseScramPassthrough(server, user)))
617-
pgfdw_security_check(keywords, values, user, conn);
628+
/* Perform post-connection security checks. */
629+
pgfdw_security_check(keywords, values, user, conn);
618630

619631
/* Prepare new session for use */
620632
configure_remote_session(conn);
@@ -725,6 +737,15 @@ check_conn_params(const char **keywords, const char **values, UserMapping *user)
725737
if (!UserMappingPasswordRequired(user))
726738
return;
727739

740+
/*
741+
* Ok if SCRAM pass-through is being used and all required scram options
742+
* are set correctly. If pgfdw_has_required_scram_options returns true we
743+
* assume that UseScramPassthrough is also true since SCRAM options are
744+
* only set when UseScramPassthrough is enabled.
745+
*/
746+
if (MyProcPort->has_scram_keys && pgfdw_has_required_scram_options(keywords, values))
747+
return;
748+
728749
ereport(ERROR,
729750
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
730751
errmsg("password or GSSAPI delegated credentials required"),
@@ -2487,3 +2508,56 @@ pgfdw_conn_checkable(void)
24872508
return false;
24882509
#endif
24892510
}
2511+
2512+
/*
2513+
* Ensure that require_auth and SCRAM keys are correctly set on values. SCRAM
2514+
* keys used to pass-through are coming from the initial connection from the
2515+
* client with the server.
2516+
*
2517+
* All required SCRAM options are set by postgres_fdw, so we just need to
2518+
* ensure that these options are not overwritten by the user.
2519+
*/
2520+
static bool
2521+
pgfdw_has_required_scram_options(const char **keywords, const char **values)
2522+
{
2523+
bool has_scram_server_key = false;
2524+
bool has_scram_client_key = false;
2525+
bool has_require_auth = false;
2526+
bool has_scram_keys = false;
2527+
2528+
/*
2529+
* Continue iterating even if we found the keys that we need to validate
2530+
* to make sure that there is no other declaration of these keys that can
2531+
* overwrite the first.
2532+
*/
2533+
for (int i = 0; keywords[i] != NULL; i++)
2534+
{
2535+
if (strcmp(keywords[i], "scram_client_key") == 0)
2536+
{
2537+
if (values[i] != NULL && values[i][0] != '\0')
2538+
has_scram_client_key = true;
2539+
else
2540+
has_scram_client_key = false;
2541+
}
2542+
2543+
if (strcmp(keywords[i], "scram_server_key") == 0)
2544+
{
2545+
if (values[i] != NULL && values[i][0] != '\0')
2546+
has_scram_server_key = true;
2547+
else
2548+
has_scram_server_key = false;
2549+
}
2550+
2551+
if (strcmp(keywords[i], "require_auth") == 0)
2552+
{
2553+
if (values[i] != NULL && strcmp(values[i], "scram-sha-256") == 0)
2554+
has_require_auth = true;
2555+
else
2556+
has_require_auth = false;
2557+
}
2558+
}
2559+
2560+
has_scram_keys = has_scram_client_key && has_scram_server_key && MyProcPort->has_scram_keys;
2561+
2562+
return (has_scram_keys && has_require_auth);
2563+
}

contrib/postgres_fdw/t/001_auth_scram.pl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,47 @@
6868
test_auth($node2, $db2, "t2",
6969
"SCRAM auth directly on foreign server should still succeed");
7070

71+
# Ensure that trust connections fail without superuser opt-in.
72+
unlink($node1->data_dir . '/pg_hba.conf');
73+
unlink($node2->data_dir . '/pg_hba.conf');
74+
75+
$node1->append_conf(
76+
'pg_hba.conf', qq{
77+
local db0 all scram-sha-256
78+
local db1 all trust
79+
}
80+
);
81+
$node2->append_conf(
82+
'pg_hba.conf', qq{
83+
local all all password
84+
}
85+
);
86+
87+
$node1->restart;
88+
$node2->restart;
89+
90+
my ($ret, $stdout, $stderr) = $node1->psql(
91+
$db0,
92+
qq'select count(1) from t',
93+
connstr => $node1->connstr($db0) . " user=$user");
94+
95+
is($ret, 3, 'loopback trust fails on the same cluster');
96+
like(
97+
$stderr,
98+
qr/failed: authentication method requirement "scram-sha-256"/,
99+
'expected error from loopback trust (same cluster)');
100+
101+
($ret, $stdout, $stderr) = $node1->psql(
102+
$db0,
103+
qq'select count(1) from t2',
104+
connstr => $node1->connstr($db0) . " user=$user");
105+
106+
is($ret, 3, 'loopback password fails on a different cluster');
107+
like(
108+
$stderr,
109+
qr/failed: authentication method requirement "scram-sha-256"/,
110+
'expected error from loopback password (different cluster)');
111+
71112
# Helper functions
72113

73114
sub test_auth

doc/src/sgml/postgres-fdw.sgml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -788,10 +788,8 @@ OPTIONS (ADD password_required 'false');
788788
<itemizedlist>
789789
<listitem>
790790
<para>
791-
The remote server must request SCRAM authentication. (If desired,
792-
enforce this on the client side (FDW side) with the option
793-
<literal>require_auth</literal>.) If another authentication method
794-
is requested by the server, then that one will be used normally.
791+
The remote server must request the <literal>scram-sha-256</literal>
792+
authentication method; otherwise, the connection will fail.
795793
</para>
796794
</listitem>
797795

@@ -805,10 +803,7 @@ OPTIONS (ADD password_required 'false');
805803

806804
<listitem>
807805
<para>
808-
The user mapping password is not used. (It could be set to support
809-
other authentication methods, but that would arguably violate the
810-
point of this feature, which is to avoid storing plain-text
811-
passwords.)
806+
The user mapping password is not used.
812807
</para>
813808
</listitem>
814809

0 commit comments

Comments
 (0)