Hi all.

I've come up with a proof-of-concept patch using the delegation/proxy approach.

Let's say we have two DB, one for FDW and one for the real server. When client
connects to FDW server using kerberos authentication, we can obtain a "proxy"
credential and store it in the global variable "MyProcPort->gss->proxy". This 
can
be then passed to gssapi calls during libpq kerberos setup when the foreign 
table
is queried.

This will mitigate the need for keytab file on FDW server. We will also have to
relax the password requirement for user mapping.

The big problem here is how to pass proxy credential from backend to libpq-fe
safely. Because libpq called in postgres_fdw is compiled as frontend binary, 
we'd
better not include any backend related stuff in libpq-fe.
In this patch I use a very ugly hack to work around this. First take pointer 
address
of the variable MyProcPort->gss->proxy, convert it to hex string, and then pass
it as libpq option "gss_proxy_cred". Any idea about how to do this in a more
elegant way?

Best regards,
Peifeng

diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index c1b0cad453..2113fa8028 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -7,7 +7,8 @@ OBJS = \
 	deparse.o \
 	option.o \
 	postgres_fdw.o \
-	shippable.o
+	shippable.o \
+	gss_proxy.o
 PGFILEDESC = "postgres_fdw - foreign data wrapper for PostgreSQL"
 
 PG_CPPFLAGS = -I$(libpq_srcdir)
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 82aa14a65d..e5e5f534ae 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -333,6 +333,9 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
 		 entry->conn, server->servername, user->umid, user->userid);
 }
 
+extern char* get_gss_proxy_cred();
+extern bool has_gss_proxy_cred();
+
 /*
  * Connect to remote server using specified server and user mapping properties.
  */
@@ -349,14 +352,16 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
 		const char **keywords;
 		const char **values;
 		int			n;
+		char *gss_proxy_cred_addr;
 
 		/*
 		 * Construct connection params from generic options of ForeignServer
 		 * and UserMapping.  (Some of them might not be libpq options, in
-		 * which case we'll just waste a few array slots.)  Add 3 extra slots
-		 * for fallback_application_name, client_encoding, end marker.
+		 * which case we'll just waste a few array slots.)  Add 4 extra slots
+		 * for fallback_application_name, client_encoding, end marker,
+		 * gss_proxy_cred.
 		 */
-		n = list_length(server->options) + list_length(user->options) + 3;
+		n = list_length(server->options) + list_length(user->options) + 4;
 		keywords = (const char **) palloc(n * sizeof(char *));
 		values = (const char **) palloc(n * sizeof(char *));
 
@@ -376,6 +381,14 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
 		values[n] = GetDatabaseEncodingName();
 		n++;
 
+		gss_proxy_cred_addr = get_gss_proxy_cred();
+		if (gss_proxy_cred_addr != NULL)
+		{
+			keywords[n] = "gss_proxy_cred";
+			values[n] = gss_proxy_cred_addr;
+			n++;
+		}
+
 		keywords[n] = values[n] = NULL;
 
 		/* verify the set of connection parameters */
@@ -476,6 +489,9 @@ UserMappingPasswordRequired(UserMapping *user)
 {
 	ListCell   *cell;
 
+	if (has_gss_proxy_cred())
+		return false;
+
 	foreach(cell, user->options)
 	{
 		DefElem    *def = (DefElem *) lfirst(cell);
diff --git a/contrib/postgres_fdw/gss_proxy.c b/contrib/postgres_fdw/gss_proxy.c
new file mode 100644
index 0000000000..1f016c54fc
--- /dev/null
+++ b/contrib/postgres_fdw/gss_proxy.c
@@ -0,0 +1,25 @@
+#include <gssapi/gssapi.h>
+
+#include "postgres.h"
+#include "miscadmin.h"
+#include "libpq/libpq-be.h"
+
+bool has_gss_proxy_cred()
+{
+	return MyProcPort->gss != NULL && MyProcPort->gss->proxy != NULL;
+}
+
+/* ugly hack to pass the gss proxy credential from backend Port to frontend libpq. */
+char* get_gss_proxy_cred()
+{
+	char* addr = NULL;
+	if (MyProcPort->gss)
+	{
+		if (MyProcPort->gss->proxy)
+		{
+			addr = palloc(sizeof(void*) + 3);
+			sprintf(addr, "%p", MyProcPort->gss->proxy);
+		}
+	}
+	return addr;
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 8cc23ef7fb..2cb3e3b435 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -935,12 +935,17 @@ pg_GSS_recvauth(Port *port)
 	}
 
 	/*
-	 * We accept any service principal that's present in our keytab. This
-	 * increases interoperability between kerberos implementations that see
-	 * for example case sensitivity differently, while not really opening up
-	 * any vector of attack.
+	 * Acquire default credential with GSS_C_BOTH. Comprare to using
+	 * GSS_C_NO_CREDENTIAL, this allows us to also acquire a proxy
+	 * credential, which can be used in postgres_fdw as for delegation.
 	 */
-	port->gss->cred = GSS_C_NO_CREDENTIAL;
+	maj_stat = gss_acquire_cred(&min_stat, GSS_C_NO_NAME, 0,
+			NULL, GSS_C_BOTH, &port->gss->cred, NULL, NULL);
+	if (maj_stat != GSS_S_COMPLETE)
+	{
+		pg_GSS_error(_("gss_acquire_cred failed"), maj_stat, min_stat);
+		return STATUS_ERROR;
+	}
 
 	/*
 	 * Initialize sequence with an empty context
@@ -997,7 +1002,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-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index 316ca65db5..4a130f8d0f 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -536,6 +536,19 @@ secure_open_gssapi(Port *port)
 		}
 	}
 
+	/*
+	 * Acquire default credential with GSS_C_BOTH. Comprare to using
+	 * GSS_C_NO_CREDENTIAL, this allows us to also acquire a proxy
+	 * credential, which can be used in postgres_fdw as for delegation.
+	 */
+	major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0,
+			NULL, GSS_C_BOTH, &port->gss->cred, NULL, NULL);
+	if (major != GSS_S_COMPLETE)
+	{
+		pg_GSS_error(_("gss_acquire_cred failed"), major, minor);
+		return -1;
+	}
+
 	while (true)
 	{
 		ssize_t		ret;
@@ -585,10 +598,10 @@ secure_open_gssapi(Port *port)
 
 		/* Process incoming data.  (The client sends first.) */
 		major = gss_accept_sec_context(&minor, &port->gss->ctx,
-									   GSS_C_NO_CREDENTIAL, &input,
+									   port->gss->cred, &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"),
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 02015efe13..6efaf75bfd 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -95,6 +95,8 @@ typedef struct
 								 * GSSAPI auth was not used */
 	bool		auth;			/* GSSAPI Authentication used */
 	bool		enc;			/* GSSAPI encryption in use */
+
+	gss_cred_id_t proxy;
 #endif
 } pg_gssinfo;
 #endif
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3421ed4685..70da371366 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
@@ -93,9 +94,15 @@ pg_GSS_continue(PGconn *conn, int payloadlen)
 		ginbuf.length = 0;
 		ginbuf.value = NULL;
 	}
+	proxy = GSS_C_NO_CREDENTIAL;
+	if (conn->gssproxycred)
+	{
+		if (1 != sscanf(conn->gssproxycred, "%p", &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,
@@ -1307,4 +1314,4 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
 							 libpq_gettext("out of memory\n"));
 
 	return crypt_pwd;
-}
+}
\ No newline at end of file
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e950b41374..25ac862dc6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -335,6 +335,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"GSS-library", "", 7,	/* sizeof("gssapi") == 7 */
 	offsetof(struct pg_conn, gsslib)},
 
+	{"gss_proxy_cred", "PGGSSPROXYCRED", NULL, NULL,
+		"GSS proxy credential", "", 19, /* sizeof("0x0123456789abcdef") */
+	offsetof(struct pg_conn, gssproxycred)},
+
 	{"replication", NULL, NULL, NULL,
 		"Replication", "D", 5,
 	offsetof(struct pg_conn, replication)},
@@ -4116,6 +4120,8 @@ freePGconn(PGconn *conn)
 		free(conn->krbsrvname);
 	if (conn->gsslib)
 		free(conn->gsslib);
+	if (conn->gssproxycred)
+		free(conn->gssproxycred);
 	if (conn->connip)
 		free(conn->connip);
 	/* Note that conn->Pfdebug is not ours to close or free */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e9f214b61b..120d13f03f 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -390,6 +390,7 @@ struct pg_conn
 	char	   *krbsrvname;		/* Kerberos service name */
 	char	   *gsslib;			/* What GSS library to use ("gssapi" or
 								 * "sspi") */
+	char       *gssproxycred;   /* proxy/delegation credential address(hex string) */
 	char	   *ssl_min_protocol_version;	/* minimum TLS protocol version */
 	char	   *ssl_max_protocol_version;	/* maximum TLS protocol version */
 	char	   *target_session_attrs;	/* desired session properties */

Reply via email to