]> git.feebdaed.xyz Git - 0xmirror/ovs.git/commitdiff
ssl: Support for SNI extension in clients for ssl/tls.
authorGurucharan Shetty <guru@ovn.org>
Wed, 15 Oct 2025 21:28:19 +0000 (14:28 -0700)
committerIlya Maximets <i.maximets@ovn.org>
Mon, 20 Oct 2025 18:01:04 +0000 (20:01 +0200)
This commit introduces Server Name Indication (SNI) support for all OVS
SSL/TLS clients, enabling clients to specify which hostname they are
attempting to reach during the SSL handshake. This is essential for
connecting through proxies, load balancers, and service meshes where
the connection endpoint differs from the intended server name.

An example use case is trying to connect to a ovsdb-server through
a istio proxy gateway in kubernetes environments.

Signed-off-by: Gurucharan Shetty <guru@ovn.org>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
NEWS
lib/ssl-syn.man
lib/ssl.man
lib/ssl.xml
lib/stream-nossl.c
lib/stream-ssl.c
lib/stream-ssl.h
lib/stream.c
ovsdb/ovsdb-server.c
tests/ovs-vsctl.at

diff --git a/NEWS b/NEWS
index d24513b461dd68694e6cf2583e4c37bb23f8c171..f9a74df1ad52aaadf94925b34c12b3dbc028bf7d 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,12 @@
 Post-v3.6.0
 --------------------
+   - TLS:
+     * Added support for TLS Server Name Indication (SNI) with the new
+       --ssl-server-name option.  This allows specifying the server name
+       for SNI, which is useful when connecting through proxies or service
+       meshes where the connection endpoint differs from the intended
+       server name.  Without this option, the hostname from the connection
+       string is used for SNI, as before.
    - Userspace datapath:
      * Conntrack now supports the FTP commands EPSV and EPRT with IPv4
        connections, instead of limiting these commands to IPv6 only.
index 583454548b22e9782d5ff9fa02ce69635015488a..77682bade46b5ad5faa0c615e5fc4eb816c2dedd 100644 (file)
@@ -4,3 +4,5 @@
 [\fB\-\-certificate=\fIcert.pem\fR]
 .br
 [\fB\-\-ca\-cert=\fIcacert.pem\fR]
+.br
+[\fB\-\-ssl\-server\-name=\fIservername\fR]
index 9bec3a7863c579c06a80b4af633f9de99cdffc63..303f0550bc6044bace43d500401a450be28331b0 100644 (file)
@@ -24,3 +24,10 @@ be a different one, depending on the PKI design in use.)
 Disables verification of certificates presented by SSL/TLS peers.  This
 introduces a security risk, because it means that certificates cannot
 be verified to be those of known trusted hosts.
+.
+.IP "\fB\-\-ssl\-server\-name=\fIservername\fR"
+Specifies the server name to use for TLS Server Name Indication (SNI).
+By default, the hostname from the connection string is used for SNI.
+This option allows overriding the SNI hostname, which is useful when
+connecting through proxies or service meshes where the connection endpoint
+differs from the intended server name.
index bd2502898f9eb2a7305841e05534e19ae7e8e60a..4dd2938a732a5e47426f3bfcf549d01e6c25fb4a 100644 (file)
     introduces a security risk, because it means that certificates cannot
     be verified to be those of known trusted hosts.
   </dd>
+
+  <dt><code>--ssl-server-name=</code><var>servername</var></dt>
+  <dd>
+    Specifies the server name to use for TLS Server Name Indication (SNI).
+    By default, the hostname from the connection string is used for SNI.
+    This option allows overriding the SNI hostname, which is useful when
+    connecting through proxies or service meshes where the connection endpoint
+    differs from the intended server name.
+  </dd>
 </dl>
index 105ac377a9784bc39fe904c645ee7a59a4c8cfc0..c0a6bf92a4f7bd41fbeeceea95099bdb3325132b 100644 (file)
@@ -96,3 +96,10 @@ stream_ssl_set_ciphersuites(const char *arg OVS_UNUSED)
     /* Ignore this option since it seems harmless to set TLS ciphersuites if
      * SSL/TLS won't be used. */
 }
+
+void
+stream_ssl_set_server_name(const char *server_name OVS_UNUSED)
+{
+    /* Ignore this option since it seems harmless to set TLS server name if
+     * SSL/TLS won't be used. */
+}
index 99c82b6af5210fb56ba905b81f35f4065dc8b62b..4470bca543c5d7b1e3c159b48f0a3c9f329d627a 100644 (file)
@@ -168,6 +168,11 @@ static char *ssl_protocols = "TLSv1.2+";
 static char *ssl_ciphers = "DEFAULT:@SECLEVEL=2";
 static char *ssl_ciphersuites = ""; /* Using default ones, unless specified. */
 
+/* Server name override for SNI (Server Name Indication).
+ * If set, this name will be used for SNI instead of the hostname
+ * extracted from the connection string. */
+static char *ssl_server_name_override;
+
 /* Ordinarily, the SSL client and server verify each other's certificates using
  * a CA certificate.  Setting this to false disables this behavior.  (This is a
  * security risk.) */
@@ -372,7 +377,11 @@ ssl_open(const char *name, char *suffix, struct stream **streamp, uint8_t dscp)
                              dscp);
     if (fd >= 0) {
         int state = error ? STATE_TCP_CONNECTING : STATE_SSL_CONNECTING;
-        return new_ssl_stream(xstrdup(name), get_server_name(suffix),
+        char *server_name = ssl_server_name_override
+                            ? xstrdup(ssl_server_name_override)
+                            : get_server_name(suffix);
+
+        return new_ssl_stream(xstrdup(name), server_name,
                               fd, CLIENT, state, streamp);
     } else {
         VLOG_ERR("%s: connect: %s", name, ovs_strerror(error));
@@ -1251,6 +1260,16 @@ stream_ssl_set_ciphersuites(const char *arg)
     ssl_ciphersuites = xstrdup(arg);
 }
 
+/* Sets the server name override for SNI (Server Name Indication).
+ * If 'server_name' is NULL, clears any existing override and SNI will
+ * use the hostname from the connection string. */
+void
+stream_ssl_set_server_name(const char *server_name)
+{
+    free(ssl_server_name_override);
+    ssl_server_name_override = nullable_xstrdup(server_name);
+}
+
 /* Set SSL/TLS protocols based on the string input. Aborts with an error
  * message if 'arg' is invalid. */
 void
index abd3ba21951bd5a12c6f3833617193708de0b839..6127d6fb1a2d9f9ee206c985a2ed116e957d1f81 100644 (file)
@@ -28,11 +28,13 @@ void stream_ssl_set_key_and_cert(const char *private_key_file,
 void stream_ssl_set_protocols(const char *arg);
 void stream_ssl_set_ciphers(const char *arg);
 void stream_ssl_set_ciphersuites(const char *arg);
+void stream_ssl_set_server_name(const char *server_name);
 
 #define SSL_OPTION_ENUMS \
         OPT_SSL_PROTOCOLS, \
         OPT_SSL_CIPHERS, \
-        OPT_SSL_CIPHERSUITES
+        OPT_SSL_CIPHERSUITES, \
+        OPT_SSL_SERVER_NAME
 
 #define STREAM_SSL_LONG_OPTIONS                     \
         {"private-key", required_argument, NULL, 'p'}, \
@@ -40,7 +42,8 @@ void stream_ssl_set_ciphersuites(const char *arg);
         {"ca-cert",     required_argument, NULL, 'C'}, \
         {"ssl-protocols", required_argument, NULL, OPT_SSL_PROTOCOLS}, \
         {"ssl-ciphers", required_argument, NULL, OPT_SSL_CIPHERS}, \
-        {"ssl-ciphersuites", required_argument, NULL, OPT_SSL_CIPHERSUITES}
+        {"ssl-ciphersuites", required_argument, NULL, OPT_SSL_CIPHERSUITES}, \
+        {"ssl-server-name", required_argument, NULL, OPT_SSL_SERVER_NAME}
 
 #define STREAM_SSL_OPTION_HANDLERS                      \
         case 'p':                                       \
@@ -65,10 +68,14 @@ void stream_ssl_set_ciphersuites(const char *arg);
                                                         \
         case OPT_SSL_CIPHERSUITES:                      \
             stream_ssl_set_ciphersuites(optarg);        \
+            break;                                      \
+                                                        \
+        case OPT_SSL_SERVER_NAME:                       \
+            stream_ssl_set_server_name(optarg);         \
             break;
 
 #define STREAM_SSL_CASES \
     case 'p': case 'c': case 'C': case OPT_SSL_PROTOCOLS: \
-    case OPT_SSL_CIPHERS: case OPT_SSL_CIPHERSUITES:
+    case OPT_SSL_CIPHERS: case OPT_SSL_CIPHERSUITES: case OPT_SSL_SERVER_NAME:
 
 #endif /* stream-ssl.h */
index aa48a973bad3756698b96a49164370274b82d2cf..feaa1cb2d18ebbd67e2ef717a6f863e5ef75f169 100644 (file)
@@ -163,7 +163,9 @@ stream_usage(const char *name, bool active, bool passive,
            "  --ssl-ciphers=CIPHERS     list of SSL/TLS ciphers to enable\n"
            "                            with TLSv1.2\n"
            "  --ssl-ciphersuites=SUITES list of SSL/TLS ciphersuites to\n"
-           "                            enable with TLSv1.3 and later\n");
+           "                            enable with TLSv1.3 and later\n"
+           "  --ssl-server-name=NAME    server name for TLS Server Name\n"
+           "                            Indication (SNI)\n");
 #endif
 }
 
index 5303a46c66ab9a3fc654327129f3cfa7b2607d20..86b0dc7b81aee66086e698a3ec5cf585d1fcb374 100644 (file)
@@ -74,6 +74,7 @@ static char *ca_cert_file;
 static char *ssl_protocols;
 static char *ssl_ciphers;
 static char *ssl_ciphersuites;
+static char *ssl_server_name;
 static bool bootstrap_ca_cert;
 
 /* Try to reclaim heap memory back to system after DB compaction. */
@@ -1793,6 +1794,7 @@ reconfigure_ssl(const struct shash *all_dbs)
     const char *resolved_ssl_protocols;
     const char *resolved_ssl_ciphers;
     const char *resolved_ssl_ciphersuites;
+    const char *resolved_ssl_server_name;
 
     resolved_private_key = query_db_string(all_dbs, private_key_file, &errors);
     resolved_certificate = query_db_string(all_dbs, certificate_file, &errors);
@@ -1801,12 +1803,15 @@ reconfigure_ssl(const struct shash *all_dbs)
     resolved_ssl_ciphers = query_db_string(all_dbs, ssl_ciphers, &errors);
     resolved_ssl_ciphersuites = query_db_string(all_dbs, ssl_ciphersuites,
                                                 &errors);
+    resolved_ssl_server_name = query_db_string(all_dbs, ssl_server_name,
+                                               &errors);
 
     stream_ssl_set_key_and_cert(resolved_private_key, resolved_certificate);
     stream_ssl_set_ca_cert_file(resolved_ca_cert, bootstrap_ca_cert);
     stream_ssl_set_protocols(resolved_ssl_protocols);
     stream_ssl_set_ciphers(resolved_ssl_ciphers);
     stream_ssl_set_ciphersuites(resolved_ssl_ciphersuites);
+    stream_ssl_set_server_name(resolved_ssl_server_name);
 
     return errors.string;
 }
@@ -2707,6 +2712,10 @@ parse_options(int argc, char *argv[],
             ssl_ciphersuites = optarg;
             break;
 
+        case OPT_SSL_SERVER_NAME:
+            ssl_server_name = optarg;
+            break;
+
         case OPT_BOOTSTRAP_CA_CERT:
             ca_cert_file = optarg;
             bootstrap_ca_cert = true;
index 59245fff82a10c9b4d1ef4916694f761d40fbb05..abc102510b444adf908ce8fa38dbce884512b491 100644 (file)
@@ -1769,6 +1769,63 @@ AT_CHECK([grep "server name" ovsdb-server.log], [0],
 OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
+AT_SETUP([TLS server name indication (SNI) with --ssl-server-name])
+AT_KEYWORDS([ovs-vsctl ssl tls sni client])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+OVSDB_INIT([conf.db])
+PKIDIR=$abs_top_builddir/tests
+
+# This test validates the --ssl-server-name option for SNI.
+# Test 1: Connect to IP with --ssl-server-name to verify SNI override.
+# Test 2: Connect to same IP without --ssl-server-name (no SNI sent).
+
+AT_CAPTURE_FILE([ovsdb-server.log])
+on_exit 'kill $(cat ovsdb-server.pid)'
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile \
+    --private-key=$PKIDIR/testpki-privkey.pem \
+    --certificate=$PKIDIR/testpki-cert.pem \
+    --ca-cert=$PKIDIR/testpki-cacert.pem \
+    --remote=pssl:0:127.0.0.1 \
+    -vstream_ssl:file:dbg conf.db], [0], [ignore], [ignore])
+PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
+
+# Test 1: SNI override - connect to IP but specify server name.
+# This validates that --ssl-server-name overrides connection hostname.
+AT_CHECK([ovs-vsctl -t 5 --no-wait \
+    --db=ssl:127.0.0.1:$SSL_PORT \
+    --private-key=$PKIDIR/testpki-privkey.pem \
+    --certificate=$PKIDIR/testpki-cert.pem \
+    --ca-cert=$PKIDIR/testpki-cacert.pem \
+    --ssl-server-name=sni-test.example \
+    add-br br0])
+
+# Verify SNI was sent with the overridden name.
+OVS_WAIT_UNTIL([grep -q \
+    "connection indicated server name sni-test.example" \
+    ovsdb-server.log])
+
+# Save current log size for Test 2.
+LOG_SIZE=$(wc -l < ovsdb-server.log)
+
+# Test 2: Default behavior without SNI override - should NOT show SNI
+# connecting to IP address (no hostname to extract).
+AT_CHECK([ovs-vsctl -t 5 --no-wait \
+    --db=ssl:127.0.0.1:$SSL_PORT \
+    --private-key=$PKIDIR/testpki-privkey.pem \
+    --certificate=$PKIDIR/testpki-cert.pem \
+    --ca-cert=$PKIDIR/testpki-cacert.pem \
+    add-br br1])
+
+# Stop server to ensure logs are flushed before checking.
+OVS_VSCTL_CLEANUP
+
+# Check that no new SNI messages appeared in Test 2 (connecting to IP
+# without --ssl-server-name should not generate SNI).
+AT_CHECK([tail -n +$(($LOG_SIZE + 1)) ovsdb-server.log | \
+    grep -q "connection indicated server name"], [1])
+
+AT_CLEANUP
+
 dnl ----------------------------------------------------------------------
 AT_BANNER([set ingress policing test])