Hi all.

I've slightly modified the patch to support "gssencmode" and added TAP tests.

Best regards,
Peifeng Qiu

________________________________
From: Peifeng Qiu
Sent: Tuesday, July 20, 2021 11:05 AM
To: pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>; 
Magnus Hagander <mag...@hagander.net>; Stephen Frost <sfr...@snowman.net>; Tom 
Lane <t...@sss.pgh.pa.us>
Subject: Kerberos delegation support in libpq and postgres_fdw

Hi hackers.

This is the patch to add kerberos delegation support in libpq, which
enables postgres_fdw to connect to another server and authenticate
as the same user to the current login user. This will obsolete my
previous patch which requires keytab file to be present on the fdw
server host.

After the backend accepts the gssapi context, it may also get a
proxy credential if permitted by policy. I previously made a hack
to pass the pointer of proxy credential directly into libpq. It turns
out that the correct way to do this is store/acquire using credential
cache within local process memory to prevent leak.

Because no password is needed when querying foreign table via
kerberos delegation, the "password_required" option in user
mapping must be set to false by a superuser. Other than this, it
should work with normal user.

I only tested it manually in a very simple configuration currently.
I will go on to work with TAP tests for this.

How do you feel about this patch? Any feature/security concerns
about this?

Best regards,
Peifeng Qiu

commit a413b020afb663b5f89722b480c1b453e4304a36
Author: Peifeng Qiu <peife...@vmware.com>
Date:   Thu Jul 22 17:27:21 2021 +0900

    kerberos delegation

diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 4593cbc540..855f9af09c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -247,6 +247,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 8cc23ef7fb..235c9b32f5 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -918,6 +918,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
@@ -947,6 +948,8 @@ pg_GSS_recvauth(Port *port)
 	 */
 	port->gss->ctx = GSS_C_NO_CONTEXT;
 
+	proxy = NULL;
+
 	/*
 	 * Loop through GSSAPI message exchange. This exchange can consist of
 	 * multiple messages sent in both directions. First message is always from
@@ -997,7 +1000,7 @@ pg_GSS_recvauth(Port *port)
 										  &port->gss->outbuf,
 										  &gflags,
 										  NULL,
-										  NULL);
+										  &proxy);
 
 		/* gbuf no longer used */
 		pfree(buf.data);
@@ -1009,6 +1012,9 @@ pg_GSS_recvauth(Port *port)
 
 		CHECK_FOR_INTERRUPTS();
 
+		if (proxy != NULL)
+			pg_store_proxy_credential(proxy);
+
 		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 38f58def25..cd243994c8 100644
--- a/src/backend/libpq/be-gssapi-common.c
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -92,3 +92,40 @@ pg_GSS_error(const char *errmsg,
 			(errmsg_internal("%s", errmsg),
 			 errdetail_internal("%s: %s", msg_major, msg_minor)));
 }
+
+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 = "MEMORY:";
+	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. */
+	putenv("KRB5CCNAME=MEMORY:");
+}
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index 316ca65db5..e27d517dea 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.
@@ -588,7 +589,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 +607,9 @@ secure_open_gssapi(Port *port)
 			complete_next = true;
 		}
 
+		if (proxy != NULL)
+			pg_store_proxy_credential(proxy);
+
 		/* Done handling the incoming packet, reset our buffer */
 		PqGSSRecvLength = 0;
 
diff --git a/src/include/libpq/be-gssapi-common.h b/src/include/libpq/be-gssapi-common.h
index c07d7e7c5a..62d60ffbd8 100644
--- a/src/include/libpq/be-gssapi-common.h
+++ b/src/include/libpq/be-gssapi-common.h
@@ -18,9 +18,11 @@
 #include <gssapi.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							/* BE_GSSAPI_COMMON_H */
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3421ed4685..e0d342124e 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -62,6 +62,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
@@ -94,12 +95,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 c783a53734..781af4227c 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 */
commit a24714cc5507ce6027eef6f923b8d24e7e1ce208
Author: Peifeng Qiu <peife...@vmware.com>
Date:   Thu Jul 22 17:27:36 2021 +0900

    TAP test

diff --git a/contrib/postgres_fdw/kerberos/.gitignore b/contrib/postgres_fdw/kerberos/.gitignore
new file mode 100644
index 0000000000..871e943d50
--- /dev/null
+++ b/contrib/postgres_fdw/kerberos/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/contrib/postgres_fdw/kerberos/Makefile b/contrib/postgres_fdw/kerberos/Makefile
new file mode 100644
index 0000000000..3efec922f0
--- /dev/null
+++ b/contrib/postgres_fdw/kerberos/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/kerberos
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/kerberos/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = contrib/postgres_fdw/kerberos
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+export with_gssapi with_krb_srvnam
+
+check:
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
+
+clean distclean maintainer-clean:
+	rm -rf tmp_check
diff --git a/contrib/postgres_fdw/kerberos/README b/contrib/postgres_fdw/kerberos/README
new file mode 100644
index 0000000000..40b8758543
--- /dev/null
+++ b/contrib/postgres_fdw/kerberos/README
@@ -0,0 +1,45 @@
+contrib/postgres_fdw/kerberos/README
+
+Tests for Kerberos/GSSAPI functionality
+=======================================
+
+This directory contains a test suite for Kerberos/GSSAPI
+functionality.  This requires a full MIT Kerberos installation,
+including server and client tools, and is therefore kept separate and
+not run by default.
+
+CAUTION: The test server run by this test is configured to listen for TCP
+connections on localhost. Any user on the same host is able to log in to the
+test server while the tests are running. Do not run this suite on a multi-user
+system where you don't trust all local users! Also, this test suite creates a
+KDC server that listens for TCP/IP connections on localhost without any real
+access control.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+    make check
+or
+    make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested.  With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops a test Postgres
+cluster, as well as a test KDC server.
+
+Requirements
+============
+
+MIT Kerberos server and client tools are required.  Heimdal is not
+supported.
+
+Debian/Ubuntu packages: krb5-admin-server krb5-kdc krb5-user
+
+RHEL/CentOS/Fedora packages: krb5-server krb5-workstation
+
+FreeBSD port: krb5 (base system has Heimdal)
diff --git a/contrib/postgres_fdw/kerberos/t/001_auth.pl b/contrib/postgres_fdw/kerberos/t/001_auth.pl
new file mode 100644
index 0000000000..79cd0bb006
--- /dev/null
+++ b/contrib/postgres_fdw/kerberos/t/001_auth.pl
@@ -0,0 +1,426 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Sets up a KDC and then runs a variety of tests to make sure that the
+# GSSAPI/Kerberos authentication and encryption are working properly,
+# that the options in pg_hba.conf and pg_ident.conf are handled correctly,
+# and that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test.
+#
+# Since this requires setting up a full KDC, it doesn't make much sense
+# to have multiple test scripts (since they'd have to also create their
+# own KDC and that could cause race conditions or other problems)- so
+# just add whatever other tests are needed to here.
+#
+# See the README for additional information.
+
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More;
+use Time::HiRes qw(usleep);
+use Cwd 'abs_path';
+
+note "start";
+if ($ENV{with_gssapi} eq 'yes')
+{
+	plan tests => 23;
+}
+else
+{
+	plan skip_all => 'GSSAPI/Kerberos not supported by this build';
+}
+
+my ($krb5_bin_dir, $krb5_sbin_dir);
+
+if ($^O eq 'darwin')
+{
+	$krb5_bin_dir  = '/usr/local/opt/krb5/bin';
+	$krb5_sbin_dir = '/usr/local/opt/krb5/sbin';
+}
+elsif ($^O eq 'freebsd')
+{
+	$krb5_bin_dir  = '/usr/local/bin';
+	$krb5_sbin_dir = '/usr/local/sbin';
+}
+elsif ($^O eq 'linux')
+{
+	$krb5_sbin_dir = '/usr/sbin';
+}
+
+my $krb5_config  = 'krb5-config';
+my $kinit        = 'kinit';
+my $kdb5_util    = 'kdb5_util';
+my $kadmin_local = 'kadmin.local';
+my $krb5kdc      = 'krb5kdc';
+
+if ($krb5_bin_dir && -d $krb5_bin_dir)
+{
+	$krb5_config = $krb5_bin_dir . '/' . $krb5_config;
+	$kinit       = $krb5_bin_dir . '/' . $kinit;
+}
+if ($krb5_sbin_dir && -d $krb5_sbin_dir)
+{
+	$kdb5_util    = $krb5_sbin_dir . '/' . $kdb5_util;
+	$kadmin_local = $krb5_sbin_dir . '/' . $kadmin_local;
+	$krb5kdc      = $krb5_sbin_dir . '/' . $krb5kdc;
+}
+
+my $host     = 'auth-test-localhost.postgresql.example.com';
+my $hostaddr = '127.0.0.1';
+my $realm    = 'EXAMPLE.COM';
+
+
+my $tmp_check = abs_path(${TestLib::tmp_check});
+my $krb5_conf   = "$tmp_check/krb5.conf";
+my $kdc_conf    = "$tmp_check/kdc.conf";
+#my $krb5_cache  = "$tmp_check/krb5cc";
+my $krb5_cache  = "MEMORY:";
+my $krb5_log    = "${TestLib::log_path}/krb5libs.log";
+my $kdc_log     = "${TestLib::log_path}/krb5kdc.log";
+my $kdc_port    = get_free_port();
+my $kdc_datadir = "$tmp_check/krb5kdc";
+my $kdc_pidfile = "$tmp_check/krb5kdc.pid";
+my $keytab_client      = "$tmp_check/client.keytab";
+my $keytab_server      = "$tmp_check/server.keytab";
+
+my $dbname      = 'testdb';
+my $testuser    = 'testuser';
+my $test1   = 'test1';
+my $application = '001_auth.pl';
+
+note "setting up Kerberos";
+
+my ($stdout, $krb5_version);
+run_log [ $krb5_config, '--version' ], '>', \$stdout
+  or BAIL_OUT("could not execute krb5-config");
+BAIL_OUT("Heimdal is not supported") if $stdout =~ m/heimdal/;
+$stdout =~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/
+  or BAIL_OUT("could not get Kerberos version");
+$krb5_version = $1;
+
+append_to_file(
+	$krb5_conf,
+	qq![logging]
+default = FILE:$krb5_log
+kdc = FILE:$kdc_log
+
+[libdefaults]
+default_realm = $realm
+forwardable = false
+
+[realms]
+$realm = {
+    kdc = $hostaddr:$kdc_port
+}!);
+
+append_to_file(
+	$kdc_conf,
+	qq![kdcdefaults]
+!);
+
+# For new-enough versions of krb5, use the _listen settings rather
+# than the _ports settings so that we can bind to localhost only.
+if ($krb5_version >= 1.15)
+{
+	append_to_file(
+		$kdc_conf,
+		qq!kdc_listen = $hostaddr:$kdc_port
+kdc_tcp_listen = $hostaddr:$kdc_port
+!);
+}
+else
+{
+	append_to_file(
+		$kdc_conf,
+		qq!kdc_ports = $kdc_port
+kdc_tcp_ports = $kdc_port
+!);
+}
+
+append_to_file(
+	$kdc_conf,
+	qq!
+[realms]
+$realm = {
+    database_name = $kdc_datadir/principal
+    admin_keytab = FILE:$kdc_datadir/kadm5.keytab
+    acl_file = $kdc_datadir/kadm5.acl
+    key_stash_file = $kdc_datadir/_k5.$realm
+}!);
+
+mkdir $kdc_datadir or die;
+
+# Ensure that we use test's config and cache files, not global ones.
+$ENV{'KRB5_CONFIG'}      = $krb5_conf;
+$ENV{'KRB5_KDC_PROFILE'} = $kdc_conf;
+$ENV{'KRB5CCNAME'}       = $krb5_cache;
+
+my $service_principal = "$ENV{with_krb_srvnam}/$host";
+
+system_or_bail $kdb5_util, 'create', '-s', '-P', 'secret0';
+
+my $test1_password = 'secret1';
+system_or_bail $kadmin_local, '-q', "addprinc -randkey $testuser";
+system_or_bail $kadmin_local, '-q', "addprinc -randkey $service_principal";
+system_or_bail $kadmin_local, '-q', "ktadd -k $keytab_client $testuser";
+system_or_bail $kadmin_local, '-q', "ktadd -k $keytab_server $service_principal";
+
+system_or_bail $krb5kdc, '-P', $kdc_pidfile;
+
+END
+{
+	kill 'INT', `cat $kdc_pidfile` if -f $kdc_pidfile;
+}
+
+note "setting up PostgreSQL instance";
+
+# server node for fdw to connect
+my $server_node = get_new_node('node');
+$server_node->init;
+$server_node->append_conf(
+	'postgresql.conf', qq{
+listen_addresses = '$hostaddr'
+krb_server_keyfile = '$keytab_server'
+log_connections = on
+lc_messages = 'C'
+});
+$server_node->start;
+
+note "setting up user on server";
+$server_node->safe_psql('postgres', "CREATE USER $testuser;");
+# there's no direct way to get the backend pid from postgres_fdw, create a VIEW at server side to provide this.
+$server_node->safe_psql('postgres', 'CREATE VIEW my_pg_stat_gssapi AS
+    SELECT  S.pid,
+            S.gss_auth AS gss_authenticated,
+            S.gss_princ AS principal,
+            S.gss_enc AS encrypted
+    FROM pg_stat_get_activity(NULL) AS S
+    WHERE S.client_port IS NOT NULL AND S.pid = pg_backend_pid();');
+$server_node->safe_psql('postgres', "GRANT ALL ON my_pg_stat_gssapi TO $testuser;");
+
+my $server_node_port = $server_node->port;
+note "setting up PostgreSQL fdw instance";
+# client node that runs postgres fdw
+my $fdw_node = get_new_node('node_fdw');
+$fdw_node->init;
+$fdw_node->append_conf(
+	'postgresql.conf', qq{
+listen_addresses = '$hostaddr'
+krb_server_keyfile = '$keytab_server'
+log_connections = on
+lc_messages = 'C'
+});
+$fdw_node->start;
+
+$fdw_node->safe_psql('postgres', "CREATE USER $testuser;");
+$fdw_node->safe_psql('postgres', "CREATE DATABASE $dbname;");
+$fdw_node->safe_psql($dbname, "CREATE EXTENSION postgres_fdw");
+$fdw_node->safe_psql($dbname, "GRANT ALL ON FOREIGN DATA WRAPPER postgres_fdw TO $testuser");
+
+note "running tests";
+
+sub set_user_mapping_option
+{
+	my ($option, $value, $action) = @_;
+	if (!defined $action)
+	{
+		$action = "SET";
+	}
+
+	my $option_str = "$option";
+	if (defined $value)
+	{
+		$option_str = "$option '$value'";
+	}
+
+	my $q = "ALTER USER MAPPING for $testuser SERVER postgres_server OPTIONS ($action $option_str);";
+	$fdw_node->safe_psql($dbname, $q);
+}
+
+# Test connection success or failure, and if success, that query returns true.
+sub test_access
+{
+	my ($query, $expected_res, $test_name,
+		$expect_output, @expect_log_msgs)
+	  = @_;
+	test_access_gssmode($query, "prefer", $expected_res, $test_name,
+		$expect_output, @expect_log_msgs)
+}
+
+sub test_access_gssmode_disable
+{
+	my ($query, $expected_res, $test_name,
+		$expect_output, @expect_log_msgs)
+	  = @_;
+	test_access_gssmode($query, "disable", $expected_res, $test_name,
+		$expect_output, @expect_log_msgs)
+}
+
+sub test_access_gssmode
+{
+	my ($query, $mode, $expected_res, $test_name,
+		$expect_output, @expect_log_msgs)
+	  = @_;
+
+	my %params = (sql => $query,);
+
+	# Check log in server node. Obtain log file size before we run any query.
+	# This way we can obtain logs for this psql connection only.
+	my $log_location = -s $server_node->logfile;
+
+	# Run psql in fdw node.
+	my ($ret, $stdout, $stderr) = $fdw_node->psql(
+		$dbname,
+		$query,
+		extra_params => ['-w'],
+		connstr      => "user=$testuser dbname=$dbname host=$host hostaddr=$hostaddr gssencmode=$mode");
+
+
+	is($ret, $expected_res, $test_name);
+	if ($ret != $expected_res)
+	{
+		note $ret;
+		note $stdout;
+		note $stderr;
+	}
+
+	if (defined $expect_output)
+	{
+		if ($expected_res eq 0)
+		{
+			like($stdout, $expect_output, "$test_name: result matches");
+		}
+		else
+		{
+			like($stderr, $expect_output, "$test_name: result matches");
+		}
+	}
+
+	if (@expect_log_msgs)
+	{
+		# Match every message literally.
+		my @regexes = map { qr/\Q$_\E/ } @expect_log_msgs;
+		my $log_contents = TestLib::slurp_file($server_node->logfile, $log_location);
+
+		while (my $regex = shift @regexes)
+		{
+			like($log_contents, $regex, "$test_name: matches");
+		}
+	}
+}
+
+# Setup pg_hba to allow superuser trust login, and testuser gss login.
+# We need to remove the KRB5_CLIENT_KTNAME environemnt first.
+delete $ENV{'KRB5_CLIENT_KTNAME'};
+unlink($fdw_node->data_dir . '/pg_hba.conf');
+$fdw_node->append_conf('pg_hba.conf',
+	qq{local all $ENV{'USER'} trust});
+$fdw_node->append_conf('pg_hba.conf',
+	qq{host all $testuser $hostaddr/32 gss map=mymap});
+$fdw_node->append_conf('pg_ident.conf', qq{mymap  /^(.*)\@$realm\$  \\1});
+$fdw_node->restart;
+
+test_access(
+	'SELECT true FROM pg_stat_gssapi',
+	2,
+	'no access without credential',
+	'/No Kerberos credentials available/'
+);
+
+$ENV{'KRB5_CLIENT_KTNAME'}       = $keytab_client;
+
+test_access(
+	'SELECT true FROM pg_stat_gssapi',
+	0,
+	'success',
+	"/^t\$/"
+);
+
+# create foreign table using unprivileged user
+
+test_access(
+	"CREATE SERVER postgres_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '$host', hostaddr '$hostaddr', port '$server_node_port', dbname 'postgres')",
+	0,
+	'create server',
+);
+test_access(
+	"CREATE FOREIGN TABLE my_pg_stat_gssapi\(pid int, gss_authenticated boolean, principal text, encrypted boolean\) SERVER postgres_server OPTIONS(table_name 'my_pg_stat_gssapi'); ",
+	0,
+	'create foreign table',
+);
+test_access(
+	"CREATE USER MAPPING for $testuser SERVER postgres_server OPTIONS (user '$testuser');",
+	0,
+	'create user mapping',
+);
+
+test_access(
+	"SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi;",
+	3,
+	'select failed due to password required',
+	"/password is required/"
+);
+
+set_user_mapping_option('password_required', 'false', 'ADD');
+
+test_access(
+	"SELECT gss_authenticated AND encrypted from my_pg_stat_gssapi;",
+	0,
+	'select fdw success, but with trust connection',
+	"/f/"
+);
+
+
+unlink($server_node->data_dir . '/pg_hba.conf');
+$server_node->append_conf('pg_hba.conf',
+	qq{host all all $hostaddr/32 gss map=mymap});
+$server_node->append_conf('pg_ident.conf', qq{mymap  /^(.*)\@$realm\$  \\1});
+$server_node->restart;
+
+test_access(
+	'SELECT gss_authenticated FROM my_pg_stat_gssapi',
+	3,
+	'fails without ticket, due to not forwardable',
+	'/No Kerberos credentials available/',
+);
+
+# Allow ticket forward in krb5.conf
+string_replace_file($krb5_conf, "forwardable = false", "forwardable = true");
+test_access(
+	'SELECT gss_authenticated FROM my_pg_stat_gssapi',
+	0,
+	'success with ticket forward',
+	"/^t\$/"
+);
+
+test_access_gssmode_disable(
+	'SELECT gss_authenticated,encrypted FROM pg_stat_gssapi',
+	0,
+	'login fdw success, encryption disabled',
+	"/^t|f\$/"
+);
+
+test_access_gssmode_disable(
+	'SELECT gss_authenticated,encrypted FROM my_pg_stat_gssapi',
+	0,
+	'success, encryption enabled for fdw even disabled by login',
+	"/^t|t\$/"
+);
+
+set_user_mapping_option('gssencmode', 'disable', 'ADD');
+
+test_access(
+	'SELECT gss_authenticated,encrypted FROM my_pg_stat_gssapi',
+	0,
+	'success, encryption disabled for fdw even enabled by login',
+	"/^t|f\$/"
+);
+
+test_access_gssmode_disable(
+	'SELECT gss_authenticated,encrypted FROM my_pg_stat_gssapi',
+	0,
+	'success, encryption disabled both by login and fdw',
+	"/^t|f\$/"
+);
\ No newline at end of file
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 15572abbea..af4245f755 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -67,6 +67,7 @@ our @EXPORT = qw(
   slurp_dir
   slurp_file
   append_to_file
+  string_replace_file
   check_mode_recursive
   chmod_recursive
   check_pg_config
@@ -539,6 +540,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,

Reply via email to