libpq: Set Server Name Indication (SNI) for SSL connections
authorPeter Eisentraut <peter@eisentraut.org>
Wed, 7 Apr 2021 13:11:41 +0000 (15:11 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Wed, 7 Apr 2021 13:11:41 +0000 (15:11 +0200)
By default, have libpq set the TLS extension "Server Name Indication" (SNI).

This allows an SNI-aware SSL proxy to route connections.  (This
requires a proxy that is aware of the PostgreSQL protocol, not just
any SSL proxy.)

In the future, this could also allow the server to use different SSL
certificates for different host specifications.  (That would require
new server functionality.  This would be the client-side functionality
for that.)

Since SNI makes the host name appear in cleartext in the network
traffic, this might be undesirable in some cases.  Therefore, also add
a libpq connection option "sslsni" to turn it off.

Discussion: https://www.postgresql.org/message-id/flat/7289d5eb-62a5-a732-c3b9-438cee2cb709%40enterprisedb.com

contrib/postgres_fdw/expected/postgres_fdw.out
doc/src/sgml/libpq.sgml
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-secure-openssl.c
src/interfaces/libpq/libpq-int.h

index 9d472d2d3d6a8cd97fc384cf141d846e51d0cc84..eeb6ae79d0685514ef7a5b6e645925d12ba7ac3f 100644 (file)
@@ -8917,7 +8917,7 @@ DO $d$
     END;
 $d$;
 ERROR:  invalid option "password"
-HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
+HINT:  Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, sslcrldir, sslsni, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size, async_capable, keep_connections
 CONTEXT:  SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
 PL/pgSQL function inline_code_block line 3 at EXECUTE
 -- If we add a password for our user mapping instead, we should get a different
index 3ec458ce09d59b61f15fa0414df3ee9132ae0e86..52622fe4c1ae233f6c7d628f875c9cb36349b141 100644 (file)
@@ -1777,6 +1777,27 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-sslsni" xreflabel="sslsni">
+      <term><literal>sslsni</literal><indexterm><primary>Server Name Indication</primary></indexterm></term>
+      <listitem>
+       <para>
+        By default, libpq sets the TLS extension <quote>Server Name
+        Indication</quote> (SNI) on SSL-enabled connections.  See <ulink
+        url="https://tools.ietf.org/html/rfc6066#section-3">RFC 6066</ulink>
+        for details.  By setting this parameter to 0, this is turned off.
+       </para>
+
+       <para>
+        The Server Name Indication can be used by SSL-aware proxies to route
+        connections without having to decrypt the SSL stream.  (Note that this
+        requires a proxy that is aware of the PostgreSQL protocol handshake,
+        not just any SSL proxy.)  However, SNI makes the destination host name
+        appear in cleartext in the network traffic, so it might be undesirable
+        in some cases.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-requirepeer" xreflabel="requirepeer">
       <term><literal>requirepeer</literal></term>
       <listitem>
@@ -7797,6 +7818,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGSSLSNI</envar></primary>
+      </indexterm>
+      <envar>PGSSLSNI</envar>  behaves the same as the <xref
+      linkend="libpq-connect-sslsni"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
index 56a8266bc3fc0a58c1e3fbeb45c142a57b2869c1..aa654dd6a8e48dbe6fc0a20db278888a99320303 100644 (file)
@@ -303,6 +303,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
                "SSL-Revocation-List-Dir", "", 64,
        offsetof(struct pg_conn, sslcrldir)},
 
+       {"sslsni", "PGSSLSNI", "1", NULL,
+               "SSL-SNI", "", 1,
+       offsetof(struct pg_conn, sslsni)},
+
        {"requirepeer", "PGREQUIREPEER", NULL, NULL,
                "Require-Peer", "", 10,
        offsetof(struct pg_conn, requirepeer)},
@@ -4095,6 +4099,8 @@ freePGconn(PGconn *conn)
                free(conn->sslcrldir);
        if (conn->sslcompression)
                free(conn->sslcompression);
+       if (conn->sslsni)
+               free(conn->sslsni);
        if (conn->requirepeer)
                free(conn->requirepeer);
        if (conn->ssl_min_protocol_version)
index 9c2222c1d15916b39086557732bc08337698f22a..6f357dfbfec5617361a0f0c849079a98760718a8 100644 (file)
@@ -1082,6 +1082,28 @@ initialize_SSL(PGconn *conn)
        SSL_CTX_free(SSL_context);
        SSL_context = NULL;
 
+       /*
+        * Set Server Name Indication (SNI), if enabled by connection parameters.
+        * Per RFC 6066, do not set it if the host is a literal IP address (IPv4
+        * or IPv6).
+        */
+       if (conn->sslsni && conn->sslsni[0] &&
+               !(strspn(conn->pghost, "0123456789.") == strlen(conn->pghost) ||
+                 strchr(conn->pghost, ':')))
+       {
+               if (SSL_set_tlsext_host_name(conn->ssl, conn->pghost) != 1)
+               {
+                       char       *err = SSLerrmessage(ERR_get_error());
+
+                       appendPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("could not set SSL Server Name Indication (SNI): %s\n"),
+                                                         err);
+                       SSLerrfree(err);
+                       SSL_CTX_free(SSL_context);
+                       return -1;
+               }
+       }
+
        /*
         * Read the SSL key. If a key is specified, treat it as an engine:key
         * combination if there is colon present - we don't support files with
index 3f7907127efa8cee54930dc88af05a8615165df8..e81dc37906b8bdc1f58a705e522d5c3075b62c20 100644 (file)
@@ -383,6 +383,7 @@ struct pg_conn
        char       *sslrootcert;        /* root certificate filename */
        char       *sslcrl;                     /* certificate revocation list filename */
        char       *sslcrldir;          /* certificate revocation list directory name */
+       char       *sslsni;                     /* use SSL SNI extension (0 or 1) */
        char       *requirepeer;        /* required peer credentials for local sockets */
        char       *gssencmode;         /* GSS mode (require,prefer,disable) */
        char       *krbsrvname;         /* Kerberos service name */