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