Greetings, * Stephen Frost (sfr...@snowman.net) wrote: > The new krb_user_ccache is a lot closer to 'global', though it's > specifically for user-authenticated backends (allowing the postmaster > and other things like replication connections to use whatever the > credential cache is set to by the administrator on startup), but that > seems like it makes sense to me- generally you're not going to want > regular user backends to be accessing the credential cache of the > 'postgres' unix account on the server.
Added an explicit 'environment' option to allow for, basically, existing behavior, where we don't mess with the environment variable at all, though I kept the default as MEMORY since I don't think it's really typical that folks actually want regular user backends to inherit the credential cache of the server. Added a few more tests and updated the documentation too. Sadly, seems we've missed the deadline for v15 though for lack of feedback on these. Would really like to get some other folks commenting as these are new pg_hba and postgresql.conf options being added. Thanks! Stephen
From bd248c3fd82d04d3c12bf6c777f861134a45a101 Mon Sep 17 00:00:00 2001 From: Stephen Frost <sfr...@snowman.net> Date: Thu, 7 Apr 2022 15:34:39 -0400 Subject: [PATCH] Add support for Kerberos credential delegation Accept GSSAPI/Kerberos delegated credentials. With this, a user could authenticate to PostgreSQL using Kerberos credentials, delegate credentials to the PostgreSQL server, and then the PostgreSQL server could use those credentials to connect to another service, such as with postgres_fdw or dblink or theoretically any other authenticated connection which is able to use delegated credentials. If an administrator prefers to not allow credentials to be delegated to the server, they can be disallowed using a new pg_hba option for gss called 'allow_cred_delegation'. A new server GUC has also been introduced to allow an administrator to control what the kerberos credential cache is configured to for user authenticated backends, krb_user_ccache. This defaults to MEMORY:, which is where delegated credentials are stored (and is otherwise empty, avoiding the risk of an administrator's credentials on the server being mistakenly picked up and used). Original patch by: Peifeng Qiu, whacked around some by me. Reviewed-by: Jacob Champion Discussion: https://postgr.es/m/co1pr05mb8023cc2cb575e0faad7df4f8a8...@co1pr05mb8023.namprd05.prod.outlook.com --- contrib/dblink/dblink.c | 6 +- .../postgres_fdw/expected/postgres_fdw.out | 2 +- contrib/postgres_fdw/option.c | 3 + doc/src/sgml/client-auth.sgml | 13 ++ doc/src/sgml/config.sgml | 28 ++++ doc/src/sgml/libpq.sgml | 19 +++ src/backend/libpq/auth.c | 27 +++- src/backend/libpq/be-gssapi-common.c | 51 +++++++ src/backend/libpq/be-secure-gssapi.c | 19 ++- src/backend/libpq/hba.c | 19 +++ src/backend/utils/adt/hbafuncs.c | 4 + src/backend/utils/init/postinit.c | 8 +- src/backend/utils/misc/guc.c | 15 ++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/libpq/auth.h | 1 + src/include/libpq/be-gssapi-common.h | 3 + src/include/libpq/hba.h | 1 + src/include/libpq/libpq-be.h | 3 + src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-auth.c | 12 +- src/interfaces/libpq/fe-connect.c | 12 ++ src/interfaces/libpq/fe-secure-gssapi.c | 3 +- src/interfaces/libpq/libpq-fe.h | 1 + src/interfaces/libpq/libpq-int.h | 1 + src/test/kerberos/Makefile | 3 + src/test/kerberos/t/001_auth.pl | 128 ++++++++++++++++-- src/test/perl/PostgreSQL/Test/Utils.pm | 27 ++++ 27 files changed, 391 insertions(+), 20 deletions(-) diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index a06d4bd12d..e5b70e084e 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -2643,7 +2643,7 @@ dblink_security_check(PGconn *conn, remoteConn *rconn) { if (!superuser()) { - if (!PQconnectionUsedPassword(conn)) + if (!(PQconnectionUsedPassword(conn) || PQconnectionUsedGSSAPI(conn))) { PQfinish(conn); ReleaseExternalFD(); @@ -2652,8 +2652,8 @@ dblink_security_check(PGconn *conn, remoteConn *rconn) ereport(ERROR, (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), - errmsg("password is required"), - errdetail("Non-superuser cannot connect if the server does not request a password."), + errmsg("password or GSSAPI is required"), + errdetail("Non-superuser cannot connect if the server does not request a password or use GSSAPI."), errhint("Target server's authentication method must be changed."))); } } diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 30e95f585f..d472c24601 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -188,7 +188,7 @@ ALTER USER MAPPING FOR public SERVER testserver1 ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (ADD sslmode 'require'); ERROR: invalid option "sslmode" -HINT: Valid options in this context are: user, password, sslpassword, password_required, sslcert, sslkey +HINT: Valid options in this context are: user, password, sslpassword, password_required, sslcert, sslkey, gssencmode -- But we can add valid ones fine ALTER USER MAPPING FOR public SERVER testserver1 OPTIONS (ADD sslpassword 'dummy'); diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index 572591a558..05922cfe6d 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -262,6 +262,9 @@ InitPgFdwOptions(void) {"sslcert", UserMappingRelationId, true}, {"sslkey", UserMappingRelationId, true}, + /* gssencmode is also libpq option, same to above. */ + {"gssencmode", UserMappingRelationId, true}, + {NULL, InvalidOid, false} }; diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 142b0affcb..9d35aa3c67 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1369,6 +1369,19 @@ omicron bryanh guest1 </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>allow_cred_delegation</literal></term> + <listitem> + <para> + If set to 0, credential delegation will not be allowed. The default + setting (1) allows an authenticated client to delegate credentials to the + server which will allow the server to then use those credentials to + authenticate when connecting to other systems such as with dblink or when + using a foreign data wrapper (FDW). + </para> + </listitem> + </varlistentry> </variablelist> </para> diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 6e3e27bed7..ee5c506d64 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1129,6 +1129,34 @@ include_dir 'conf.d' </listitem> </varlistentry> + <varlistentry id="guc-krb-user-ccache" xreflabel="krb_user_ccache"> + <term><varname>krb_user_ccache</varname> (<type>string</type>) + <indexterm> + <primary><varname>krb_user_ccache</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the location of the Kerberos credential cache to be used for + regular user backends which go through authentication. The default is + <filename>MEMORY:</filename>, which is where delegated credentials + are stored (and is otherwise empty). Care should be used when changing + this value- setting it to a file-based credential cache will mean that + user backends could potentially use any credentials stored to access + other systems. + If this parameter is set to an empty string, then the variable will be + explicit un-set and the system-dependent default is used, which may be a + file-based credential cache with the same caveats as previously + mentioned. If the special value 'environment' is used, then the variable + is left untouched and will be whatever was set in the environment at + startup time. + This parameter can only be set in the + <filename>postgresql.conf</filename> file or on the server command line. + See <xref linkend="gssapi-auth"/> for more information. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-db-user-namespace" xreflabel="db_user_namespace"> <term><varname>db_user_namespace</varname> (<type>boolean</type>) <indexterm> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 1c20901c3c..22ba41ea10 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2489,6 +2489,25 @@ int PQconnectionUsedPassword(const PGconn *conn); </para> </listitem> </varlistentry> + + <varlistentry id="libpq-PQconnectionUsedGSSAPI"> + <term><function>PQconnectionUsedGSSAPI</function><indexterm><primary>PQconnectionUsedGSSAPI</primary></indexterm></term> + <listitem> + <para> + Returns true (1) if the connection authentication method + used GSSAPI. Returns false (0) if not. + +<synopsis> +int PQconnectionUsedGSSAPI(const PGconn *conn); +</synopsis> + </para> + + <para> + This function can be applied to detect whether the connection was + authenticated with GSSAPI. + </para> + </listitem> + </varlistentry> </variablelist> </para> diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index efc53f3135..c3cfe9de00 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -170,6 +170,7 @@ static int CheckCertAuth(Port *port); */ char *pg_krb_server_keyfile; bool pg_krb_caseins_users; +char *pg_krb_user_ccache; /*---------------------------------------------------------------- @@ -422,6 +423,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + /* + * Set the credential cache to use for user backends which go through + * regular authentication. + */ + if (!pg_krb_user_ccache || pg_krb_user_ccache[0] == '\0') + unsetenv("KRB5CCNAME"); + else if (pg_strcasecmp(pg_krb_user_ccache, "environment") != 0) + setenv("KRB5CCNAME", pg_krb_user_ccache, 1); +#endif + /* * Now proceed to do the actual authentication check */ @@ -564,6 +576,16 @@ ClientAuthentication(Port *port) sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); status = pg_GSS_recvauth(port); } + + /* + * If the HBA line allows and we were delegated credentials then + * store them. + */ + if (port->hba->allow_cred_delegation && port->gss->proxy) + { + pg_store_proxy_credential(port->gss->proxy); + port->gss->proxy_creds = true; + } #else Assert(false); #endif @@ -949,6 +971,9 @@ pg_GSS_recvauth(Port *port) */ port->gss->ctx = GSS_C_NO_CONTEXT; + port->gss->proxy = NULL; + port->gss->proxy_creds = false; + /* * Loop through GSSAPI message exchange. This exchange can consist of * multiple messages sent in both directions. First message is always from @@ -999,7 +1024,7 @@ pg_GSS_recvauth(Port *port) &port->gss->outbuf, &gflags, NULL, - NULL); + &port->gss->proxy); /* gbuf no longer used */ pfree(buf.data); diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index 71b796d5a2..f439cac4b6 100644 --- a/src/backend/libpq/be-gssapi-common.c +++ b/src/backend/libpq/be-gssapi-common.c @@ -92,3 +92,54 @@ pg_GSS_error(const char *errmsg, (errmsg_internal("%s", errmsg), errdetail_internal("%s: %s", msg_major, msg_minor))); } + +/* + * Store the credentials passed in into the memory cache for later usage. + * + * This allows credentials to be delegated to us for us to use to connect + * to other systems with, using, e.g. postgres_fdw or dblink. + */ +#define GSS_MEMORY_CACHE "MEMORY:" +void +pg_store_proxy_credential(gss_cred_id_t cred) +{ + OM_uint32 major, minor; + gss_OID_set mech; + gss_cred_usage_t usage; + gss_key_value_element_desc cc; + gss_key_value_set_desc ccset; + + cc.key = "ccache"; + cc.value = GSS_MEMORY_CACHE; + ccset.count = 1; + ccset.elements = &cc; + + /* Make the proxy credential only available to current process */ + major = gss_store_cred_into(&minor, + cred, + GSS_C_INITIATE, /* credential only used for starting libpq connection */ + GSS_C_NULL_OID, /* store all */ + true, /* overwrite */ + true, /* make default */ + &ccset, + &mech, + &usage); + + if (major != GSS_S_COMPLETE) + { + pg_GSS_error("gss_store_cred", major, minor); + } + + /* Credential stored, so we can release our credential handle. */ + major = gss_release_cred(&minor, &cred); + if (major != GSS_S_COMPLETE) + { + pg_GSS_error("gss_release_cred", major, minor); + } + + /* + * Be sure that KRB5CCNAME is set to MEMORY: for this backend, so that later + * calls to gss_acquire_cred will find the proxied credentials we stored. + */ + setenv("KRB5CCNAME", GSS_MEMORY_CACHE, 1); +} diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c index 2844c5aa4b..8e7abff849 100644 --- a/src/backend/libpq/be-secure-gssapi.c +++ b/src/backend/libpq/be-secure-gssapi.c @@ -504,6 +504,9 @@ secure_open_gssapi(Port *port) port->gss = (pg_gssinfo *) MemoryContextAllocZero(TopMemoryContext, sizeof(pg_gssinfo)); + port->gss->proxy = NULL; + port->gss->proxy_creds = false; + /* * Allocate buffers and initialize state variables. By malloc'ing the * buffers at this point, we avoid wasting static data space in processes @@ -588,7 +591,8 @@ secure_open_gssapi(Port *port) GSS_C_NO_CREDENTIAL, &input, GSS_C_NO_CHANNEL_BINDINGS, &port->gss->name, NULL, &output, NULL, - NULL, NULL); + NULL, &port->gss->proxy); + if (GSS_ERROR(major)) { pg_GSS_error(_("could not accept GSSAPI security context"), @@ -731,3 +735,16 @@ be_gssapi_get_princ(Port *port) return port->gss->princ; } + +/* + * Return if GSSAPI delegated/proxy credentials were included on this + * connection. + */ +bool +be_gssapi_get_proxy(Port *port) +{ + if (!port || !port->gss) + return NULL; + + return port->gss->proxy_creds; +} diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index f8393ca8ed..d286ab59b9 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1471,6 +1471,16 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) parsedline->upn_username = false; } + /* + * For GSS, set the default value of allow_cred_delegation to true. + * This should generally be safe as the delegated credentials are those of + * the user who has been authorized, and will only happen if the user chooses + * to authenticate with a credential that can be delegated, but admins are + * able to disable it if they wish to. + */ + if (parsedline->auth_method == uaGSS) + parsedline->allow_cred_delegation = true; + /* Parse remaining arguments */ while ((field = lnext(tok_line->fields, field)) != NULL) { @@ -1941,6 +1951,15 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, else hbaline->upn_username = false; } + else if (strcmp(name, "allow_cred_delegation") == 0) + { + if (hbaline->auth_method != uaGSS) + INVALID_AUTH_OPTION("allow_cred_delegation", gettext_noop("gssapi")); + if (strcmp(val, "1") == 0) + hbaline->allow_cred_delegation = true; + else + hbaline->allow_cred_delegation = false; + } else if (strcmp(name, "radiusservers") == 0) { struct addrinfo *gai_result; diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c index 9fe7b62c9a..2167c7ba9e 100644 --- a/src/backend/utils/adt/hbafuncs.c +++ b/src/backend/utils/adt/hbafuncs.c @@ -64,6 +64,10 @@ get_hba_options(HbaLine *hba) if (hba->krb_realm) options[noptions++] = CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm)); + + if (hba->allow_cred_delegation) + options[noptions++] = + CStringGetTextDatum("allow_cred_delegation=true"); } if (hba->usermap) diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index a85c2e0260..1d8b831615 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -285,15 +285,17 @@ PerformAuthentication(Port *port) if (princ) appendStringInfo(&logmsg, - _(" GSS (authenticated=%s, encrypted=%s, principal=%s)"), + _(" GSS (authenticated=%s, encrypted=%s, proxy_credentials=%s, principal=%s)"), be_gssapi_get_auth(port) ? _("yes") : _("no"), be_gssapi_get_enc(port) ? _("yes") : _("no"), + be_gssapi_get_proxy(port) ? _("yes") : _("no"), princ); else appendStringInfo(&logmsg, - _(" GSS (authenticated=%s, encrypted=%s)"), + _(" GSS (authenticated=%s, encrypted=%s, proxy_credentials=%s)"), be_gssapi_get_auth(port) ? _("yes") : _("no"), - be_gssapi_get_enc(port) ? _("yes") : _("no")); + be_gssapi_get_enc(port) ? _("yes") : _("no"), + be_gssapi_get_proxy(port) ? _("yes") : _("no")); } #endif diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 22b5571a70..56cd788869 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -119,6 +119,10 @@ #define PG_KRB_SRVTAB "" #endif +#ifndef PG_KRB_USER_CCACHE +#define PG_KRB_USER_CCACHE "MEMORY:" +#endif + #define CONFIG_FILENAME "postgresql.conf" #define HBA_FILENAME "pg_hba.conf" #define IDENT_FILENAME "pg_ident.conf" @@ -4190,6 +4194,17 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"krb_user_ccache", PGC_SIGHUP, CONN_AUTH_AUTH, + gettext_noop("Sets the Kerberos credential cache location for user authenticated backends."), + NULL, + GUC_SUPERUSER_ONLY + }, + &pg_krb_user_ccache, + PG_KRB_USER_CCACHE, + NULL, NULL, NULL + }, + { {"bonjour_name", PGC_POSTMASTER, CONN_AUTH_SETTINGS, gettext_noop("Sets the Bonjour service name."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 94270eb0ec..b8d69cd80c 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -99,6 +99,7 @@ # GSSAPI using Kerberos #krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' #krb_caseins_users = off +#krb_user_ccache = MEMORY: # - SSL - diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h index 6d7ee1acb9..c032e4224c 100644 --- a/src/include/libpq/auth.h +++ b/src/include/libpq/auth.h @@ -19,6 +19,7 @@ extern char *pg_krb_server_keyfile; extern bool pg_krb_caseins_users; extern char *pg_krb_realm; +extern char *pg_krb_user_ccache; extern void ClientAuthentication(Port *port); extern void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, diff --git a/src/include/libpq/be-gssapi-common.h b/src/include/libpq/be-gssapi-common.h index ae8411245d..6953157f05 100644 --- a/src/include/libpq/be-gssapi-common.h +++ b/src/include/libpq/be-gssapi-common.h @@ -18,13 +18,16 @@ #if defined(HAVE_GSSAPI_H) #include <gssapi.h> +#include <gssapi_ext.h> #else #include <gssapi/gssapi.h> +#include <gssapi/gssapi_ext.h> #endif extern void pg_GSS_error(const char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat); +extern void pg_store_proxy_credential(gss_cred_id_t cred); #endif /* ENABLE_GSS */ #endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 90036f7bcd..99affd91fe 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -112,6 +112,7 @@ typedef struct HbaLine bool include_realm; bool compat_realm; bool upn_username; + bool allow_cred_delegation; List *radiusservers; char *radiusservers_s; List *radiussecrets; diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index c3bf514652..30afe5a591 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -94,6 +94,8 @@ typedef struct * GSSAPI auth was not used */ bool auth; /* GSSAPI Authentication used */ bool enc; /* GSSAPI encryption in use */ + bool proxy_creds; /* GSSAPI Delegated/proxy credentials? */ + gss_cred_id_t proxy; /* GSSAPI Proxy credentials */ #endif } pg_gssinfo; #endif @@ -320,6 +322,7 @@ extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook; extern bool be_gssapi_get_auth(Port *port); extern bool be_gssapi_get_enc(Port *port); extern const char *be_gssapi_get_princ(Port *port); +extern bool be_gssapi_get_proxy(Port *port); /* Read and write to a GSSAPI-encrypted connection. */ extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index e8bcc88370..7ded77aff3 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -186,3 +186,4 @@ PQpipelineStatus 183 PQsetTraceFlags 184 PQmblenBounded 185 PQsendFlushRequest 186 +PQconnectionUsedGSSAPI 187 diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 6fceff561b..943db5c722 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -61,6 +61,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) lmin_s; gss_buffer_desc ginbuf; gss_buffer_desc goutbuf; + gss_cred_id_t proxy; /* * On first call, there's no input token. On subsequent calls, read the @@ -93,12 +94,16 @@ pg_GSS_continue(PGconn *conn, int payloadlen) ginbuf.value = NULL; } + /* Check if we can aquire a proxy credential. */ + if (!pg_GSS_have_cred_cache(&proxy)) + proxy = GSS_C_NO_CREDENTIAL; + maj_stat = gss_init_sec_context(&min_stat, - GSS_C_NO_CREDENTIAL, + proxy, &conn->gctx, conn->gtarg_nam, GSS_C_NO_OID, - GSS_C_MUTUAL_FLAG, + GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, (ginbuf.value == NULL) ? GSS_C_NO_BUFFER : &ginbuf, @@ -138,7 +143,10 @@ pg_GSS_continue(PGconn *conn, int payloadlen) } if (maj_stat == GSS_S_COMPLETE) + { gss_release_name(&lmin_s, &conn->gtarg_nam); + conn->gssapi_used = true; + } return STATUS_OK; } diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index cf554d389f..a296c82d1d 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -600,6 +600,7 @@ pqDropServerData(PGconn *conn) conn->last_sqlstate[0] = '\0'; conn->auth_req_received = false; conn->password_needed = false; + conn->gssapi_used = false; conn->write_failed = false; if (conn->write_err_msg) free(conn->write_err_msg); @@ -6981,6 +6982,17 @@ PQconnectionUsedPassword(const PGconn *conn) return false; } +int +PQconnectionUsedGSSAPI(const PGconn *conn) +{ + if (!conn) + return false; + if (conn->gssapi_used) + return true; + else + return false; +} + int PQclientEncoding(const PGconn *conn) { diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c index 6ea52ed866..5eeaca542d 100644 --- a/src/interfaces/libpq/fe-secure-gssapi.c +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -631,7 +631,7 @@ pqsecure_open_gss(PGconn *conn) */ major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, conn->gtarg_nam, GSS_C_NO_OID, - GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + GSS_REQUIRED_FLAGS | GSS_C_DELEG_FLAG, 0, 0, &input, NULL, &output, NULL, NULL); /* GSS Init Sec Context uses the whole packet, so clear it */ @@ -651,6 +651,7 @@ pqsecure_open_gss(PGconn *conn) * to do GSS wrapping/unwrapping. */ conn->gssenc = true; + conn->gssapi_used = true; /* Clean up */ gss_release_cred(&minor, &conn->gcred); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 7986445f1a..bdd073c645 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -354,6 +354,7 @@ extern int PQbackendPID(const PGconn *conn); extern PGpipelineStatus PQpipelineStatus(const PGconn *conn); extern int PQconnectionNeedsPassword(const PGconn *conn); extern int PQconnectionUsedPassword(const PGconn *conn); +extern int PQconnectionUsedGSSAPI(const PGconn *conn); extern int PQclientEncoding(const PGconn *conn); extern int PQsetClientEncoding(PGconn *conn, const char *encoding); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index e0cee4b142..5dd9a52305 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -449,6 +449,7 @@ struct pg_conn int sversion; /* server version, e.g. 70401 for 7.4.1 */ bool auth_req_received; /* true if any type of auth req received */ bool password_needed; /* true if server demanded a password */ + bool gssapi_used; /* true if authenticated via gssapi */ bool sigpipe_so; /* have we masked SIGPIPE via SO_NOSIGPIPE? */ bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */ bool write_failed; /* have we had a write failure on sock? */ diff --git a/src/test/kerberos/Makefile b/src/test/kerberos/Makefile index c531998835..67dfaae901 100644 --- a/src/test/kerberos/Makefile +++ b/src/test/kerberos/Makefile @@ -13,6 +13,9 @@ subdir = src/test/kerberos top_builddir = ../../.. include $(top_builddir)/src/Makefile.global +EXTRA_INSTALL += contrib/postgres_fdw +EXTRA_INSTALL += contrib/dblink + export with_gssapi with_krb_srvnam check: diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl index 62e0542639..7e6de8d458 100644 --- a/src/test/kerberos/t/001_auth.pl +++ b/src/test/kerberos/t/001_auth.pl @@ -45,6 +45,7 @@ elsif ($^O eq 'linux') my $krb5_config = 'krb5-config'; my $kinit = 'kinit'; +my $klist = 'klist'; my $kdb5_util = 'kdb5_util'; my $kadmin_local = 'kadmin.local'; my $krb5kdc = 'krb5kdc'; @@ -53,6 +54,7 @@ if ($krb5_bin_dir && -d $krb5_bin_dir) { $krb5_config = $krb5_bin_dir . '/' . $krb5_config; $kinit = $krb5_bin_dir . '/' . $kinit; + $klist = $krb5_bin_dir . '/' . $klist; } if ($krb5_sbin_dir && -d $krb5_sbin_dir) { @@ -97,6 +99,7 @@ kdc = FILE:$kdc_log [libdefaults] default_realm = $realm +forwardable = false [realms] $realm = { @@ -174,7 +177,21 @@ lc_messages = 'C' }); $node->start; +my $port = $node->port(); + $node->safe_psql('postgres', 'CREATE USER test1;'); +$node->safe_psql('postgres', 'CREATE EXTENSION postgres_fdw;'); +$node->safe_psql('postgres', 'CREATE EXTENSION dblink;'); +$node->safe_psql('postgres', "CREATE SERVER s1 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '$host', hostaddr '$hostaddr', port '$port', dbname 'postgres');"); + +$node->safe_psql('postgres', 'GRANT USAGE ON FOREIGN SERVER s1 TO test1;'); + +$node->safe_psql('postgres', "CREATE USER MAPPING FOR test1 SERVER s1 OPTIONS (user 'test1', password_required 'false');"); +$node->safe_psql('postgres', "CREATE TABLE t1 (c1 int);"); +$node->safe_psql('postgres', "INSERT INTO t1 VALUES (1);"); +$node->safe_psql('postgres', "CREATE FOREIGN TABLE tf1 (c1 int) SERVER s1 OPTIONS (schema_name 'public', table_name 't1');"); +$node->safe_psql('postgres', "GRANT SELECT ON t1 TO test1;"); +$node->safe_psql('postgres', "GRANT SELECT ON tf1 TO test1;"); note "running tests"; @@ -240,6 +257,7 @@ $node->restart; test_access($node, 'test1', 'SELECT true', 2, '', 'fails without ticket'); run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?); +run_log [ $klist, '-f' ] or BAIL_OUT($?); test_access( $node, @@ -262,7 +280,7 @@ test_access( '', 'succeeds with mapping with default gssencmode and host hba', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, proxy_credentials=no, principal=test1\@$realm)" ); test_access( @@ -273,7 +291,7 @@ test_access( 'gssencmode=prefer', 'succeeds with GSS-encrypted access preferred with host hba', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, proxy_credentials=no, principal=test1\@$realm)" ); test_access( $node, @@ -283,7 +301,7 @@ test_access( 'gssencmode=require', 'succeeds with GSS-encrypted access required with host hba', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, proxy_credentials=no, principal=test1\@$realm)" ); # Test that we can transport a reasonable amount of data. @@ -312,6 +330,11 @@ $node->append_conf('pg_hba.conf', qq{hostgssenc all all $hostaddr/32 gss map=mymap}); $node->restart; +string_replace_file($krb5_conf, "forwardable = false", "forwardable = true"); + +run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?); +run_log [ $klist, '-f' ] or BAIL_OUT($?); + test_access( $node, 'test1', @@ -320,7 +343,7 @@ test_access( 'gssencmode=prefer', 'succeeds with GSS-encrypted access preferred and hostgssenc hba', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, proxy_credentials=yes, principal=test1\@$realm)" ); test_access( $node, @@ -330,7 +353,7 @@ test_access( 'gssencmode=require', 'succeeds with GSS-encrypted access required and hostgssenc hba', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, proxy_credentials=yes, principal=test1\@$realm)" ); test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=disable', 'fails with GSS encryption disabled and hostgssenc hba'); @@ -348,7 +371,7 @@ test_access( 'gssencmode=prefer', 'succeeds with GSS-encrypted access preferred and hostnogssenc hba, but no encryption', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, proxy_credentials=yes, principal=test1\@$realm)" ); test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=require', 'fails with GSS-encrypted access required and hostnogssenc hba'); @@ -360,9 +383,25 @@ test_access( 'gssencmode=disable', 'succeeds with GSS encryption disabled and hostnogssenc hba', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, proxy_credentials=yes, principal=test1\@$realm)" ); +test_query( + $node, + 'test1', + "SELECT * FROM dblink('user=test1 dbname=$dbname host=$host hostaddr=$hostaddr port=$port password=1234','select 1') as t1(c1 int);", + qr/^1$/s, + 'gssencmode=prefer', + 'dblink works not-encrypted'); + +test_query( + $node, + 'test1', + "TABLE tf1;", + qr/^1$/s, + 'gssencmode=prefer', + 'postgres_fdw works not-encrypted'); + truncate($node->data_dir . '/pg_ident.conf', 0); unlink($node->data_dir . '/pg_hba.conf'); $node->append_conf('pg_hba.conf', @@ -377,9 +416,25 @@ test_access( '', 'succeeds with include_realm=0 and defaults', "connection authenticated: identity=\"test1\@$realm\" method=gss", - "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)" + "connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, proxy_credentials=yes, principal=test1\@$realm)" ); +test_query( + $node, + 'test1', + "SELECT * FROM dblink('user=test1 dbname=$dbname host=$host hostaddr=$hostaddr port=$port password=1234','select 1') as t1(c1 int);", + qr/^1$/s, + 'gssencmode=require', + 'dblink works encrypted'); + +test_query( + $node, + 'test1', + "TABLE tf1;", + qr/^1$/s, + 'gssencmode=require', + 'postgres_fdw works encrypted'); + # Reset pg_hba.conf, and cause a usermap failure with an authentication # that has passed. unlink($node->data_dir . '/pg_hba.conf'); @@ -396,4 +451,61 @@ test_access( 'fails with wrong krb_realm, but still authenticates', "connection authenticated: identity=\"test1\@$realm\" method=gss"); +# Reset pg_hba.conf, and have the server refuse delegated credentials. +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{host all all $hostaddr/32 gss include_realm=0 allow_cred_delegation=0}); +$node->restart; + +my ($psql_stdout, $psql_stderr, $psql_timed_out); +my $psql_cmdret = $node->psql('postgres', + "SELECT * FROM dblink('user=test1 dbname=$dbname host=$host hostaddr=$hostaddr port=$port password=1234','select 1') as t1(c1 int);", + stdout => \$psql_stdout, stderr => \$psql_stderr, + connstr => $node->connstr('postgres') . " user=test1 host=$host hostaddr=$hostaddr gssencmode=require"); + +ok ($psql_cmdret == 3, 'error result from dblink failing without delegated credentials'); +like ($psql_stderr, qr/could not establish connection/, 'dblink fails due to missing delegated credentials'); + +$node->append_conf( + 'postgresql.conf', qq{ +krb_user_ccache = 'blah' +}); +$node->restart; + +$psql_cmdret = $node->psql('postgres', + "SELECT * FROM dblink('user=test1 dbname=$dbname host=$host hostaddr=$hostaddr port=$port password=1234','select 1') as t1(c1 int);", + stdout => \$psql_stdout, stderr => \$psql_stderr, + connstr => $node->connstr('postgres') . " user=test1 host=$host hostaddr=$hostaddr gssencmode=require"); + +ok ($psql_cmdret == 3, 'error result from dblink failing due to invalid credential cache'); +like ($psql_stderr, qr/could not establish connection/, 'dblink fails due to invalid credential cache'); + +$node->append_conf( + 'postgresql.conf', qq{ +krb_user_ccache = '' +}); +$node->restart; + +$psql_cmdret = $node->psql('postgres', + "SELECT * FROM dblink('user=test1 dbname=$dbname host=$host hostaddr=$hostaddr port=$port password=1234','select 1') as t1(c1 int);", + stdout => \$psql_stdout, stderr => \$psql_stderr, + connstr => $node->connstr('postgres') . " user=test1 host=$host hostaddr=$hostaddr gssencmode=require"); + +ok ($psql_cmdret == 3, 'error result from dblink failing with un-set credential cache'); +like ($psql_stderr, qr/could not establish connection/, 'dblink fails with un-set credential cache'); + +$node->append_conf( + 'postgresql.conf', qq{ +krb_user_ccache = 'environment' +}); +$node->restart; + +test_query( + $node, + 'test1', + "SELECT * FROM dblink('user=test1 dbname=$dbname host=$host hostaddr=$hostaddr port=$port password=1234','select 1') as t1(c1 int);", + qr/^1$/s, + 'gssencmode=require', + 'dblink works with environment ccache and not delegated credentials'); + done_testing(); diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm index dca1b3b17c..0f94a628a5 100644 --- a/src/test/perl/PostgreSQL/Test/Utils.pm +++ b/src/test/perl/PostgreSQL/Test/Utils.pm @@ -65,6 +65,7 @@ our @EXPORT = qw( slurp_dir slurp_file append_to_file + string_replace_file check_mode_recursive chmod_recursive check_pg_config @@ -544,6 +545,32 @@ sub append_to_file =pod +=item string_replace_file(filename, find, replace) + +Find and replace string of a given file. + +=cut + +sub string_replace_file +{ + my ($filename, $find, $replace) = @_; + open(my $in, '<', $filename); + my $content; + while(<$in>) + { + $_ =~ s/$find/$replace/; + $content = $content.$_; + } + close $in; + open(my $out, '>', $filename); + print $out $content; + close($out); + + return; +} + +=pod + =item check_mode_recursive(dir, expected_dir_mode, expected_file_mode, ignore_list) Check that all file/dir modes in a directory match the expected values, -- 2.30.2
signature.asc
Description: PGP signature