Greetings, (Dropping the original poster as their email address apparently no longer works)
* Peter Eisentraut (peter.eisentr...@enterprisedb.com) wrote: > On 22.07.21 10:39, Peifeng Qiu wrote: > >I've slightly modified the patch to support "gssencmode" and added TAP > >tests. > > For the TAP tests, please put then under src/test/kerberos/, instead of > copying the whole infrastructure to contrib/postgres_fdw/. Just make a new > file, for example t/002_postgres_fdw_proxy.pl, and put your tests there. I've incorporated the tests into the existing kerberos/001_auth.pl as there didn't seem any need to create another file. > Also, you can put code and tests in one patch, no need to separate. Done. Also rebased and updated for the changes in the TAP testing infrastructure and other changes. Also added code to track if credentials were forwarded or not and to log that information. > I wonder if this feature would also work in dblink. Since there is no > substantial code changes in postgres_fdw itself as part of this patch, I > would suspect yes. Can you check? Yup, this should work fine. I didn't include any explicit testing of postgres_fdw or dblink in this, yet. Instead, for the moment at least, I've added to the connection log message an indiciation of if credentials were passed along with the connection along with tests of both the negative case and the positive case. Not sure if that's useful information to have in pg_stat_gssapi, but if so, then we could add it there pretty easily. I'm happy to try and get testing with postgres_fdw and dblink working soon though, assuming there aren't any particular objections to moving this forward. Will add to the CF for consideration. Thanks, Stephen
From c8aff4ae7595647fb5c82ea2f726c2d5b866765c Mon Sep 17 00:00:00 2001 From: Stephen Frost <sfr...@snowman.net> Date: Mon, 28 Feb 2022 20:17:55 -0500 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. Original patch by: Peifeng Qiu, whacked around some by me. --- .../postgres_fdw/expected/postgres_fdw.out | 2 +- contrib/postgres_fdw/option.c | 3 ++ src/backend/libpq/auth.c | 12 ++++- src/backend/libpq/be-gssapi-common.c | 44 +++++++++++++++++++ src/backend/libpq/be-secure-gssapi.c | 26 ++++++++++- src/backend/utils/init/postinit.c | 8 ++-- src/include/libpq/be-gssapi-common.h | 3 ++ src/include/libpq/libpq-be.h | 2 + src/interfaces/libpq/fe-auth.c | 9 +++- src/interfaces/libpq/fe-secure-gssapi.c | 2 +- src/test/kerberos/t/001_auth.pl | 25 +++++++---- src/test/perl/PostgreSQL/Test/Utils.pm | 27 ++++++++++++ 12 files changed, 146 insertions(+), 17 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index f210f91188..a73a468d89 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/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index efc53f3135..6f820a34f1 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -920,6 +920,7 @@ pg_GSS_recvauth(Port *port) int mtype; StringInfoData buf; gss_buffer_desc gbuf; + gss_cred_id_t proxy; /* * Use the configured keytab, if there is one. Unfortunately, Heimdal @@ -949,6 +950,9 @@ pg_GSS_recvauth(Port *port) */ port->gss->ctx = GSS_C_NO_CONTEXT; + 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 +1003,7 @@ pg_GSS_recvauth(Port *port) &port->gss->outbuf, &gflags, NULL, - NULL); + &proxy); /* gbuf no longer used */ pfree(buf.data); @@ -1011,6 +1015,12 @@ pg_GSS_recvauth(Port *port) CHECK_FOR_INTERRUPTS(); + if (proxy != NULL) + { + pg_store_proxy_credential(proxy); + port->gss->proxy_creds = true; + } + if (port->gss->outbuf.length != 0) { /* diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index 71b796d5a2..d649d86dbf 100644 --- a/src/backend/libpq/be-gssapi-common.c +++ b/src/backend/libpq/be-gssapi-common.c @@ -92,3 +92,47 @@ 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); + } + + /* quite strange that gss_store_cred doesn't work with "KRB5CCNAME=MEMORY:", + * we have to use gss_store_cred_into instead and set the env for later + * gss_acquire_cred calls. */ + 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..bece580f61 100644 --- a/src/backend/libpq/be-secure-gssapi.c +++ b/src/backend/libpq/be-secure-gssapi.c @@ -497,6 +497,7 @@ secure_open_gssapi(Port *port) bool complete_next = false; OM_uint32 major, minor; + gss_cred_id_t proxy; /* * Allocate subsidiary Port data for GSSAPI operations. @@ -504,6 +505,9 @@ secure_open_gssapi(Port *port) port->gss = (pg_gssinfo *) MemoryContextAllocZero(TopMemoryContext, sizeof(pg_gssinfo)); + 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 +592,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, &proxy); + if (GSS_ERROR(major)) { pg_GSS_error(_("could not accept GSSAPI security context"), @@ -605,6 +610,12 @@ secure_open_gssapi(Port *port) complete_next = true; } + if (proxy != NULL) + { + pg_store_proxy_credential(proxy); + port->gss->proxy_creds = true; + } + /* Done handling the incoming packet, reset our buffer */ PqGSSRecvLength = 0; @@ -731,3 +742,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/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 86d193c89f..3b850fc9ef 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -284,15 +284,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/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/libpq-be.h b/src/include/libpq/libpq-be.h index dd3e5efba3..d9d70b9e1d 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -95,6 +95,7 @@ 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 */ #endif } pg_gssinfo; #endif @@ -321,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/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 6fceff561b..3f75834eb8 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, diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c index 6ea52ed866..566c89f52f 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 */ diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl index 62e0542639..7cc07f982d 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 = { @@ -240,6 +243,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 +266,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 +277,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 +287,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 +316,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 +329,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 +339,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 +357,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,7 +369,7 @@ 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)" ); truncate($node->data_dir . '/pg_ident.conf', 0); @@ -377,7 +386,7 @@ 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)" ); # Reset pg_hba.conf, and cause a usermap failure with an authentication diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm index 46cd746796..e49ee3662c 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 @@ -532,6 +533,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