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 */

Reply via email to