Hi, I try to host multiple postgresql-servers on the same ip and the same port through SNI-based load-balancing. Currently this is not possible because of two issues: 1. The psql client won't set the tls-sni-extension correctly (https://www.postgresql.org/message-id/20181211145240.GL20222%40redhat.com) 2. The psql connection protocol implements a SSLRequest in plain text before actually opening a connection.
The first issue is easily solvable by calling `SSL_set_tlsext_host_name(conn->ssl, conn->connhost[conn->whichhost].host)` before opening the connection. The second issue is also solvable through a new parameter "ssltermination" which if set to "proxy" will skip the initial SSLRequest and connects directly through ssl. The default value would be "server" which changes nothing on the existing behaviour. I compiled the psql-client with these changes and was able to connect to 2 different databases through the same ip and port just by changing the hostname. This fix is important to allow multiple postgres instances on one ip without having to add a port number. I implemented this change on a fork of the postgres mirror on github: https://github.com/klg71/mayope_postgres The affected files are: - src/interfaces/libpq/fe-connect.c (added ssltermination parameter) - src/interfaces/libpq/libpq-int.h (added ssltermination parameter) - src/interfaces/libpq/fe-secure-openssl.c (added tls-sni-extension) I appended the relevant diff. Best Regards Lukas
diff --git a/src/interfaces/libpq/fe-connect.c
b/src/interfaces/libpq/fe-connect.c
index 7d04d3664e..43fcfc2274 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -131,6 +131,7 @@ static int ldapServiceLookup(const char *purl,
PQconninfoOption *options,
#define DefaultTargetSessionAttrs "any"
#ifdef USE_SSL
#define DefaultSSLMode "prefer"
+#define DefaultSSLTermination "server"
#else
#define DefaultSSLMode "disable"
#endif
@@ -293,6 +294,11 @@ static const internalPQconninfoOption PQconninfoOptions[]
= {
"SSL-Mode", "", 12, /* sizeof("verify-full") == 12
*/
offsetof(struct pg_conn, sslmode)},
+ {"ssltermination", "PGSSLTERMINATION", DefaultSSLMode, NULL,
+ "SSL-Termination-Mode", "", 6, /* sizeof("server") ==
6 */
+ offsetof(struct pg_conn, ssltermination)},
+
+
{"sslcompression", "PGSSLCOMPRESSION", "0", NULL,
"SSL-Compression", "", 1,
offsetof(struct pg_conn, sslcompression)},
@@ -1278,6 +1284,16 @@ connectOptions2(PGconn *conn)
return false;
}
+ if (strcmp(conn->ssltermination, "server") != 0
+ && strcmp(conn->ssltermination, "proxy") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+
libpq_gettext("invalid %s value: \"%s\"\n"),
+ "ssltermination",
conn->ssltermination);
+ return false;
+ }
+
#ifndef USE_SSL
switch (conn->sslmode[0])
{
@@ -2915,6 +2931,13 @@ keep_going:
/* We will come back to here until there is
if (conn->allow_ssl_try && !conn->wait_ssl_try
&&
!conn->ssl_in_use)
{
+ /*
+ * SSL termination is handled by
proxy/load-balancer, no need to send SSLRequest
+ */
+ if(conn->ssltermination[0]=='p'){
+ conn->status =
CONNECTION_SSL_STARTUP;
+ return PGRES_POLLING_WRITING;
+ }
ProtocolVersion pv;
/*
@@ -2995,77 +3018,89 @@ keep_going:
/* We will come back to here until there is
if (!conn->ssl_in_use)
{
/*
- * We use pqReadData here since it has
the logic to
- * distinguish no-data-yet from
connection closure. Since
- * conn->ssl isn't set, a plain recv()
will occur.
+ * Skip SSLRequest package and
initialize ssl directly with proxy
*/
- char SSLok;
- int rdresult;
-
- rdresult = pqReadData(conn);
- if (rdresult < 0)
- {
- /* errorMessage is already
filled in */
- goto error_return;
- }
- if (rdresult == 0)
- {
- /* caller failed to wait for
data */
- return PGRES_POLLING_READING;
- }
- if (pqGetc(&SSLok, conn) < 0)
- {
- /* should not happen really */
- return PGRES_POLLING_READING;
- }
- if (SSLok == 'S')
+ if (conn->ssltermination[0]=='p')
{
/* mark byte consumed */
conn->inStart = conn->inCursor;
/* Set up global SSL state if
required */
if (pqsecure_initialize(conn)
!= 0)
goto error_return;
- }
- else if (SSLok == 'N')
- {
- /* mark byte consumed */
- conn->inStart = conn->inCursor;
- /* OK to do without SSL? */
- if (conn->sslmode[0] == 'r' ||
/* "require" */
- conn->sslmode[0] ==
'v') /* "verify-ca" or
-
* "verify-full" */
+ } else {
+ /*
+ * We use pqReadData here since
it has the logic to
+ * distinguish no-data-yet from
connection closure. Since
+ * conn->ssl isn't set, a plain
recv() will occur.
+ */
+ char SSLok;
+ int
rdresult;
+
+ rdresult = pqReadData(conn);
+ if (rdresult < 0)
{
- /* Require SSL, but
server does not want it */
-
appendPQExpBufferStr(&conn->errorMessage,
-
libpq_gettext("server does not support SSL, but SSL was
required\n"));
+ /* errorMessage is
already filled in */
+ goto error_return;
+ }
+ if (rdresult == 0)
+ {
+ /* caller failed to
wait for data */
+ return
PGRES_POLLING_READING;
+ }
+ if (pqGetc(&SSLok, conn) < 0)
+ {
+ /* should not happen
really */
+ return
PGRES_POLLING_READING;
+ }
+ if (SSLok == 'S')
+ {
+ /* mark byte consumed */
+ conn->inStart =
conn->inCursor;
+ /* Set up global SSL
state if required */
+ if
(pqsecure_initialize(conn) != 0)
+ goto
error_return;
+ }
+ else if (SSLok == 'N')
+ {
+ /* mark byte consumed */
+ conn->inStart =
conn->inCursor;
+ /* OK to do without
SSL? */
+ if (conn->sslmode[0] ==
'r' || /* "require" */
+
conn->sslmode[0] == 'v') /* "verify-ca" or
+
* "verify-full" */
+ {
+ /* Require SSL,
but server does not want it */
+
appendPQExpBufferStr(&conn->errorMessage,
+
libpq_gettext("server does not support SSL, but SSL was
required\n"));
+ goto
error_return;
+ }
+ /* Otherwise, proceed
with normal startup */
+ conn->allow_ssl_try =
false;
+ conn->status =
CONNECTION_MADE;
+ return
PGRES_POLLING_WRITING;
+ }
+ else if (SSLok == 'E')
+ {
+ /*
+ * Server failure of
some sort, such as failure to
+ * fork a backend
process. We need to process and
+ * report the error
message, which might be formatted
+ * according to either
protocol 2 or protocol 3.
+ * Rather than duplicate
the code for that, we flip
+ * into
AWAITING_RESPONSE state and let the code there
+ * deal with it. Note
we have *not* consumed the "E"
+ * byte here.
+ */
+ conn->status =
CONNECTION_AWAITING_RESPONSE;
+ goto keep_going;
+ }
+ else
+ {
+
appendPQExpBuffer(&conn->errorMessage,
+
libpq_gettext("received invalid response to SSL negotiation: %c\n"),
+
SSLok);
goto error_return;
}
- /* Otherwise, proceed with
normal startup */
- conn->allow_ssl_try = false;
- conn->status = CONNECTION_MADE;
- return PGRES_POLLING_WRITING;
- }
- else if (SSLok == 'E')
- {
- /*
- * Server failure of some sort,
such as failure to
- * fork a backend process. We
need to process and
- * report the error message,
which might be formatted
- * according to either protocol
2 or protocol 3.
- * Rather than duplicate the
code for that, we flip
- * into AWAITING_RESPONSE state
and let the code there
- * deal with it. Note we have
*not* consumed the "E"
- * byte here.
- */
- conn->status =
CONNECTION_AWAITING_RESPONSE;
- goto keep_going;
- }
- else
- {
-
appendPQExpBuffer(&conn->errorMessage,
-
libpq_gettext("received invalid response to SSL negotiation: %c\n"),
-
SSLok);
- goto error_return;
}
}
diff --git a/src/interfaces/libpq/fe-secure-openssl.c
b/src/interfaces/libpq/fe-secure-openssl.c
index d609a38bbe..d05d8a78f4 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -1044,6 +1044,7 @@ initialize_SSL(PGconn *conn)
* it doesn't really matter.)
*/
if (!(conn->ssl = SSL_new(SSL_context)) ||
+ !SSL_set_tlsext_host_name(conn->ssl,
conn->connhost[conn->whichhost].host) ||
!SSL_set_app_data(conn->ssl, conn) ||
!my_SSL_set_fd(conn, conn->sock))
{
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..7f5b03d518 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -356,6 +356,7 @@ struct pg_conn
char *keepalives_count; /* maximum number of TCP keepalive
*
retransmits */
char *sslmode; /* SSL mode
(require,prefer,allow,disable) */
+ char *ssltermination; /* SSL termination
(proxy,server) */
char *sslcompression; /* SSL compression (0 or 1) */
char *sslkey; /* client key filename */
char *sslcert; /* client certificate filename */
