From b9aaf1705d9de6ac9e95d97980551ac27343bcab Mon Sep 17 00:00:00 2001 From: Stephen Frost 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. --- contrib/dblink/dblink.c | 6 +- .../postgres_fdw/expected/postgres_fdw.out | 2 +- contrib/postgres_fdw/option.c | 3 + doc/src/sgml/libpq.sgml | 19 +++++ src/backend/libpq/auth.c | 12 +++- src/backend/libpq/be-gssapi-common.c | 51 +++++++++++++ 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/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 | 71 ++++++++++++++++--- src/test/perl/PostgreSQL/Test/Utils.pm | 27 +++++++ 19 files changed, 243 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/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); + + + PQconnectionUsedGSSAPIPQconnectionUsedGSSAPI + + + Returns true (1) if the connection authentication method + used GSSAPI. Returns false (0) if not. + + +int PQconnectionUsedGSSAPI(const PGconn *conn); + + + + + This function can be applied to detect whether the connection was + authenticated with GSSAPI. + + + 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..9e7e7b5c85 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); + } + + /* + * Set KRB5CCNAME 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..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 342169b195..27b3404b86 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 +#include #else #include +#include #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 c3bf514652..5cc221617e 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -94,6 +94,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 @@ -320,6 +321,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..c5dc552d15 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'); 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.32.0