Skip to content

Commit 3d4fa22

Browse files
committed
Add support for Kerberos credential delegation
Support GSSAPI/Kerberos credentials being delegated to the server by a client. With this, a user authenticating to PostgreSQL using Kerberos (GSSAPI) credentials can choose to delegate their credentials to the PostgreSQL server (which can choose to accept them, or not), allowing the server to then use those delegated credentials to connect to another service, such as with postgres_fdw or dblink or theoretically any other service which is able to be authenticated using Kerberos. Both postgres_fdw and dblink are changed to allow non-superuser password-less connections but only when GSSAPI credentials have been delegated to the server by the client and GSSAPI is used to authenticate to the remote system. Authors: Stephen Frost, Peifeng Qiu Reviewed-By: David Christensen Discussion: https://postgr.es/m/CO1PR05MB8023CC2CB575E0FAAD7DF4F8A8E29@CO1PR05MB8023.namprd05.prod.outlook.com
1 parent edc627a commit 3d4fa22

36 files changed

+755
-136
lines changed

contrib/dblink/dblink.c

+82-45
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "funcapi.h"
4949
#include "lib/stringinfo.h"
5050
#include "libpq-fe.h"
51+
#include "libpq/libpq-be.h"
5152
#include "libpq/libpq-be-fe-helpers.h"
5253
#include "mb/pg_wchar.h"
5354
#include "miscadmin.h"
@@ -111,7 +112,8 @@ static HeapTuple get_tuple_of_interest(Relation rel, int *pkattnums, int pknumat
111112
static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode);
112113
static char *generate_relation_name(Relation rel);
113114
static void dblink_connstr_check(const char *connstr);
114-
static void dblink_security_check(PGconn *conn, remoteConn *rconn);
115+
static bool dblink_connstr_has_pw(const char *connstr);
116+
static void dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr);
115117
static void dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
116118
bool fail, const char *fmt,...) pg_attribute_printf(5, 6);
117119
static char *get_connect_string(const char *servername);
@@ -213,7 +215,7 @@ dblink_get_conn(char *conname_or_str,
213215
errmsg("could not establish connection"),
214216
errdetail_internal("%s", msg)));
215217
}
216-
dblink_security_check(conn, rconn);
218+
dblink_security_check(conn, rconn, connstr);
217219
if (PQclientEncoding(conn) != GetDatabaseEncoding())
218220
PQsetClientEncoding(conn, GetDatabaseEncodingName());
219221
freeconn = true;
@@ -307,7 +309,7 @@ dblink_connect(PG_FUNCTION_ARGS)
307309
}
308310

309311
/* check password actually used if not superuser */
310-
dblink_security_check(conn, rconn);
312+
dblink_security_check(conn, rconn, connstr);
311313

312314
/* attempt to set client encoding to match server encoding, if needed */
313315
if (PQclientEncoding(conn) != GetDatabaseEncoding())
@@ -2584,64 +2586,99 @@ deleteConnection(const char *name)
25842586
errmsg("undefined connection name")));
25852587
}
25862588

2589+
/*
2590+
* We need to make sure that the connection made used credentials
2591+
* which were provided by the user, so check what credentials were
2592+
* used to connect and then make sure that they came from the user.
2593+
*/
25872594
static void
2588-
dblink_security_check(PGconn *conn, remoteConn *rconn)
2595+
dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr)
25892596
{
2590-
if (!superuser())
2591-
{
2592-
if (!PQconnectionUsedPassword(conn))
2593-
{
2594-
libpqsrv_disconnect(conn);
2595-
if (rconn)
2596-
pfree(rconn);
2597+
/* Superuser bypasses security check */
2598+
if (superuser())
2599+
return;
25972600

2598-
ereport(ERROR,
2599-
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
2600-
errmsg("password is required"),
2601-
errdetail("Non-superuser cannot connect if the server does not request a password."),
2602-
errhint("Target server's authentication method must be changed.")));
2603-
}
2604-
}
2601+
/* If password was used to connect, make sure it was one provided */
2602+
if (PQconnectionUsedPassword(conn) && dblink_connstr_has_pw(connstr))
2603+
return;
2604+
2605+
#ifdef ENABLE_GSS
2606+
/* If GSSAPI creds used to connect, make sure it was one delegated */
2607+
if (PQconnectionUsedGSSAPI(conn) && be_gssapi_get_deleg(MyProcPort))
2608+
return;
2609+
#endif
2610+
2611+
/* Otherwise, fail out */
2612+
libpqsrv_disconnect(conn);
2613+
if (rconn)
2614+
pfree(rconn);
2615+
2616+
ereport(ERROR,
2617+
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
2618+
errmsg("password or GSSAPI delegated credentials required"),
2619+
errdetail("Non-superusers may only connect using credentials they provide, eg: password in connection string or delegated GSSAPI credentials"),
2620+
errhint("Ensure provided credentials match target server's authentication method.")));
26052621
}
26062622

26072623
/*
2608-
* For non-superusers, insist that the connstr specify a password. This
2609-
* prevents a password from being picked up from .pgpass, a service file,
2610-
* the environment, etc. We don't want the postgres user's passwords
2611-
* to be accessible to non-superusers.
2624+
* Function to check if the connection string includes an explicit
2625+
* password, needed to ensure that non-superuser password-based auth
2626+
* is using a provided password and not one picked up from the
2627+
* environment.
26122628
*/
2613-
static void
2614-
dblink_connstr_check(const char *connstr)
2629+
static bool
2630+
dblink_connstr_has_pw(const char *connstr)
26152631
{
2616-
if (!superuser())
2617-
{
2618-
PQconninfoOption *options;
2619-
PQconninfoOption *option;
2620-
bool connstr_gives_password = false;
2632+
PQconninfoOption *options;
2633+
PQconninfoOption *option;
2634+
bool connstr_gives_password = false;
26212635

2622-
options = PQconninfoParse(connstr, NULL);
2623-
if (options)
2636+
options = PQconninfoParse(connstr, NULL);
2637+
if (options)
2638+
{
2639+
for (option = options; option->keyword != NULL; option++)
26242640
{
2625-
for (option = options; option->keyword != NULL; option++)
2641+
if (strcmp(option->keyword, "password") == 0)
26262642
{
2627-
if (strcmp(option->keyword, "password") == 0)
2643+
if (option->val != NULL && option->val[0] != '\0')
26282644
{
2629-
if (option->val != NULL && option->val[0] != '\0')
2630-
{
2631-
connstr_gives_password = true;
2632-
break;
2633-
}
2645+
connstr_gives_password = true;
2646+
break;
26342647
}
26352648
}
2636-
PQconninfoFree(options);
26372649
}
2638-
2639-
if (!connstr_gives_password)
2640-
ereport(ERROR,
2641-
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
2642-
errmsg("password is required"),
2643-
errdetail("Non-superusers must provide a password in the connection string.")));
2650+
PQconninfoFree(options);
26442651
}
2652+
2653+
return connstr_gives_password;
2654+
}
2655+
2656+
/*
2657+
* For non-superusers, insist that the connstr specify a password, except
2658+
* if GSSAPI credentials have been delegated (and we check that they are used
2659+
* for the connection in dblink_security_check later). This prevents a
2660+
* password or GSSAPI credentials from being picked up from .pgpass, a
2661+
* service file, the environment, etc. We don't want the postgres user's
2662+
* passwords or Kerberos credentials to be accessible to non-superusers.
2663+
*/
2664+
static void
2665+
dblink_connstr_check(const char *connstr)
2666+
{
2667+
if (superuser())
2668+
return;
2669+
2670+
if (dblink_connstr_has_pw(connstr))
2671+
return;
2672+
2673+
#ifdef ENABLE_GSS
2674+
if (be_gssapi_get_deleg(MyProcPort))
2675+
return;
2676+
#endif
2677+
2678+
ereport(ERROR,
2679+
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
2680+
errmsg("password or GSSAPI delegated credentials required"),
2681+
errdetail("Non-superusers must provide a password in the connection string or send delegated GSSAPI credentials.")));
26452682
}
26462683

26472684
/*

contrib/dblink/expected/dblink.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -903,8 +903,8 @@ GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO regress_dblink_user;
903903
SET SESSION AUTHORIZATION regress_dblink_user;
904904
-- should fail
905905
SELECT dblink_connect('myconn', 'fdtest');
906-
ERROR: password is required
907-
DETAIL: Non-superusers must provide a password in the connection string.
906+
ERROR: password or GSSAPI delegated credentials required
907+
DETAIL: Non-superusers must provide a password in the connection string or send delegated GSSAPI credentials.
908908
-- should succeed
909909
SELECT dblink_connect_u('myconn', 'fdtest');
910910
dblink_connect_u

contrib/postgres_fdw/connection.c

+56-16
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "catalog/pg_user_mapping.h"
1818
#include "commands/defrem.h"
1919
#include "funcapi.h"
20+
#include "libpq/libpq-be.h"
2021
#include "libpq/libpq-be-fe-helpers.h"
2122
#include "mb/pg_wchar.h"
2223
#include "miscadmin.h"
@@ -149,6 +150,8 @@ static void pgfdw_finish_pre_subcommit_cleanup(List *pending_entries,
149150
static void pgfdw_finish_abort_cleanup(List *pending_entries,
150151
List *cancel_requested,
151152
bool toplevel);
153+
static void pgfdw_security_check(const char **keywords, const char **values,
154+
UserMapping *user, PGconn *conn);
152155
static bool UserMappingPasswordRequired(UserMapping *user);
153156
static bool disconnect_cached_connections(Oid serverid);
154157

@@ -384,6 +387,47 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
384387
entry->conn, server->servername, user->umid, user->userid);
385388
}
386389

390+
/*
391+
* Check that non-superuser has used password or delegated credentials
392+
* to establish connection; otherwise, he's piggybacking on the
393+
* postgres server's user identity. See also dblink_security_check()
394+
* in contrib/dblink and check_conn_params.
395+
*/
396+
static void
397+
pgfdw_security_check(const char **keywords, const char **values, UserMapping *user, PGconn *conn)
398+
{
399+
/* Superusers bypass the check */
400+
if (superuser_arg(user->userid))
401+
return;
402+
403+
#ifdef ENABLE_GSS
404+
/* Connected via GSSAPI with delegated credentials- all good. */
405+
if (PQconnectionUsedGSSAPI(conn) && be_gssapi_get_deleg(MyProcPort))
406+
return;
407+
#endif
408+
409+
/* Ok if superuser set PW required false. */
410+
if (!UserMappingPasswordRequired(user))
411+
return;
412+
413+
/* Connected via PW, with PW required true, and provided non-empty PW. */
414+
if (PQconnectionUsedPassword(conn))
415+
{
416+
/* ok if params contain a non-empty password */
417+
for (int i = 0; keywords[i] != NULL; i++)
418+
{
419+
if (strcmp(keywords[i], "password") == 0 && values[i][0] != '\0')
420+
return;
421+
}
422+
}
423+
424+
ereport(ERROR,
425+
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
426+
errmsg("password or GSSAPI delegated credentials required"),
427+
errdetail("Non-superuser cannot connect if the server does not request a password or use GSSAPI with delegated credentials."),
428+
errhint("Target server's authentication method must be changed or password_required=false set in the user mapping attributes.")));
429+
}
430+
387431
/*
388432
* Connect to remote server using specified server and user mapping properties.
389433
*/
@@ -495,19 +539,8 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
495539
server->servername),
496540
errdetail_internal("%s", pchomp(PQerrorMessage(conn)))));
497541

498-
/*
499-
* Check that non-superuser has used password to establish connection;
500-
* otherwise, he's piggybacking on the postgres server's user
501-
* identity. See also dblink_security_check() in contrib/dblink and
502-
* check_conn_params.
503-
*/
504-
if (!superuser_arg(user->userid) && UserMappingPasswordRequired(user) &&
505-
!PQconnectionUsedPassword(conn))
506-
ereport(ERROR,
507-
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
508-
errmsg("password is required"),
509-
errdetail("Non-superuser cannot connect if the server does not request a password."),
510-
errhint("Target server's authentication method must be changed or password_required=false set in the user mapping attributes.")));
542+
/* Perform post-connection security checks */
543+
pgfdw_security_check(keywords, values, user, conn);
511544

512545
/* Prepare new session for use */
513546
configure_remote_session(conn);
@@ -561,7 +594,8 @@ UserMappingPasswordRequired(UserMapping *user)
561594
}
562595

563596
/*
564-
* For non-superusers, insist that the connstr specify a password. This
597+
* For non-superusers, insist that the connstr specify a password or that the
598+
* user provided their own GSSAPI delegated credentials. This
565599
* prevents a password from being picked up from .pgpass, a service file, the
566600
* environment, etc. We don't want the postgres user's passwords,
567601
* certificates, etc to be accessible to non-superusers. (See also
@@ -576,6 +610,12 @@ check_conn_params(const char **keywords, const char **values, UserMapping *user)
576610
if (superuser_arg(user->userid))
577611
return;
578612

613+
#ifdef ENABLE_GSS
614+
/* ok if the user provided their own delegated credentials */
615+
if (be_gssapi_get_deleg(MyProcPort))
616+
return;
617+
#endif
618+
579619
/* ok if params contain a non-empty password */
580620
for (i = 0; keywords[i] != NULL; i++)
581621
{
@@ -589,8 +629,8 @@ check_conn_params(const char **keywords, const char **values, UserMapping *user)
589629

590630
ereport(ERROR,
591631
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
592-
errmsg("password is required"),
593-
errdetail("Non-superusers must provide a password in the user mapping.")));
632+
errmsg("password or GSSAPI delegated credentials required"),
633+
errdetail("Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping.")));
594634
}
595635

596636
/*

contrib/postgres_fdw/expected/postgres_fdw.out

+10-9
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ ALTER SERVER testserver1 OPTIONS (
171171
sslcrl 'value',
172172
--requirepeer 'value',
173173
krbsrvname 'value',
174-
gsslib 'value'
174+
gsslib 'value',
175+
gssdeleg 'value'
175176
--replication 'value'
176177
);
177178
-- Error, invalid list syntax
@@ -9840,8 +9841,8 @@ CREATE FOREIGN TABLE pg_temp.ft1_nopw (
98409841
c8 user_enum
98419842
) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1');
98429843
SELECT 1 FROM ft1_nopw LIMIT 1;
9843-
ERROR: password is required
9844-
DETAIL: Non-superusers must provide a password in the user mapping.
9844+
ERROR: password or GSSAPI delegated credentials required
9845+
DETAIL: Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping.
98459846
-- If we add a password to the connstr it'll fail, because we don't allow passwords
98469847
-- in connstrs only in user mappings.
98479848
ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw');
@@ -9853,16 +9854,16 @@ HINT: Perhaps you meant the option "passfile".
98539854
-- This won't work with installcheck, but neither will most of the FDW checks.
98549855
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password 'dummypw');
98559856
SELECT 1 FROM ft1_nopw LIMIT 1;
9856-
ERROR: password is required
9857-
DETAIL: Non-superuser cannot connect if the server does not request a password.
9857+
ERROR: password or GSSAPI delegated credentials required
9858+
DETAIL: Non-superuser cannot connect if the server does not request a password or use GSSAPI with delegated credentials.
98589859
HINT: Target server's authentication method must be changed or password_required=false set in the user mapping attributes.
98599860
-- Unpriv user cannot make the mapping passwordless
98609861
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password_required 'false');
98619862
ERROR: password_required=false is superuser-only
98629863
HINT: User mappings with the password_required option set to false may only be created or modified by the superuser.
98639864
SELECT 1 FROM ft1_nopw LIMIT 1;
9864-
ERROR: password is required
9865-
DETAIL: Non-superuser cannot connect if the server does not request a password.
9865+
ERROR: password or GSSAPI delegated credentials required
9866+
DETAIL: Non-superuser cannot connect if the server does not request a password or use GSSAPI with delegated credentials.
98669867
HINT: Target server's authentication method must be changed or password_required=false set in the user mapping attributes.
98679868
RESET ROLE;
98689869
-- But the superuser can
@@ -9890,8 +9891,8 @@ DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw;
98909891
-- This will fail again as it'll resolve the user mapping for public, which
98919892
-- lacks password_required=false
98929893
SELECT 1 FROM ft1_nopw LIMIT 1;
9893-
ERROR: password is required
9894-
DETAIL: Non-superusers must provide a password in the user mapping.
9894+
ERROR: password or GSSAPI delegated credentials required
9895+
DETAIL: Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping.
98959896
RESET ROLE;
98969897
-- The user mapping for public is passwordless and lacks the password_required=false
98979898
-- mapping option, but will work because the current user is a superuser.

contrib/postgres_fdw/option.c

+6
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ InitPgFdwOptions(void)
288288
{"sslcert", UserMappingRelationId, true},
289289
{"sslkey", UserMappingRelationId, true},
290290

291+
/*
292+
* gssdeleg is also a libpq option but should be allowed in a user
293+
* mapping context too
294+
*/
295+
{"gssdeleg", UserMappingRelationId, true},
296+
291297
{NULL, InvalidOid, false}
292298
};
293299

contrib/postgres_fdw/sql/postgres_fdw.sql

+2-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ ALTER SERVER testserver1 OPTIONS (
185185
sslcrl 'value',
186186
--requirepeer 'value',
187187
krbsrvname 'value',
188-
gsslib 'value'
188+
gsslib 'value',
189+
gssdeleg 'value'
189190
--replication 'value'
190191
);
191192

0 commit comments

Comments
 (0)