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 <[email protected]>
---
 lib/ssl.man           |  7 ++++
 lib/ssl.xml           |  9 +++++
 lib/stream-nossl.c    |  7 ++++
 lib/stream-ssl.c      | 20 +++++++++-
 lib/stream-ssl.h      | 14 +++++--
 tests/ovsdb-server.at | 86 ++++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 138 insertions(+), 5 deletions(-)

diff --git a/lib/ssl.man b/lib/ssl.man
index 9bec3a786..d67c91a8f 100644
--- a/lib/ssl.man
+++ b/lib/ssl.man
@@ -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 SSL/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.
diff --git a/lib/ssl.xml b/lib/ssl.xml
index bd2502898..c7c0bddd4 100644
--- a/lib/ssl.xml
+++ b/lib/ssl.xml
@@ -33,4 +33,13 @@
     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 SSL/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>
diff --git a/lib/stream-nossl.c b/lib/stream-nossl.c
index 105ac377a..665f65b51 100644
--- a/lib/stream-nossl.c
+++ b/lib/stream-nossl.c
@@ -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 SSL/TLS server name if
+     * SSL/TLS won't be used. */
+}
diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c
index 99c82b6af..f97bfd739 100644
--- a/lib/stream-ssl.c
+++ b/lib/stream-ssl.c
@@ -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,10 @@ 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 +1259,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 = server_name ? xstrdup(server_name) : NULL;
+}
+
 /* Set SSL/TLS protocols based on the string input. Aborts with an error
  * message if 'arg' is invalid. */
 void
diff --git a/lib/stream-ssl.h b/lib/stream-ssl.h
index 7036b176d..6127d6fb1 100644
--- a/lib/stream-ssl.h
+++ b/lib/stream-ssl.h
@@ -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,9 +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 'p': case 'c': case 'C': case OPT_SSL_PROTOCOLS: \
+    case OPT_SSL_CIPHERS: case OPT_SSL_CIPHERSUITES: case OPT_SSL_SERVER_NAME:
 
 #endif /* stream-ssl.h */
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index ca6e931be..245ef01ca 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -1043,7 +1043,91 @@ OVSDB_SERVER_SHUTDOWN(["
   /Protocol error/d
 "])
 AT_CLEANUP
-
+
+AT_SETUP([SSL/TLS db: SNI (Server Name Indication)])
+AT_KEYWORDS([ovsdb server positive ssl tls sni])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+# For this test, we pass PKIDIR through a ovsdb-tool transact and
+# msys on Windows does not convert the path style automatically.
+# So, do that forcefully with a 'pwd -W' (called through pwd() function).
+PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+AT_SKIP_IF([expr "$PKIDIR" : ".*[[       '\"
+\\]]"])
+
+# Create additional certificates for SNI testing in the current directory
+OVS_PKI="sh $abs_top_srcdir/utilities/ovs-pki.in --dir=./sni-pki 
--log=./sni-pki.log"
+# Remove any existing PKI directory to ensure clean state for test
+rm -rf ./sni-pki
+AT_CHECK([$OVS_PKI init], [0], [ignore], [ignore])
+
+# Create a certificate for a hostname that differs from the connection IP
+AT_CHECK([$OVS_PKI req+sign backend-service.test switch], [0], [ignore], 
[ignore])
+
+AT_DATA([schema],
+  [[{"name": "mydb",
+     "tables": {
+       "SSL": {
+         "columns": {
+           "private_key": {"type": "string"},
+           "certificate": {"type": "string"},
+           "ca_cert": {"type": "string"}
+         }}}}
+]])
+AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+
+# Use direct file paths for SSL configuration
+on_exit 'kill `cat *.pid`'
+AT_CHECK(
+  [ovsdb-server --log-file -vstream_ssl:dbg --detach --no-chdir --pidfile \
+        --private-key=backend-service.test-privkey.pem \
+        --certificate=backend-service.test-cert.pem \
+        --ca-cert=sni-pki/switchca/cacert.pem \
+        --remote=pssl:0:127.0.0.1 db],
+  [0], [ignore], [ignore])
+PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
+
+# SSL_OVSDB_CLIENT_SNI(SNI_HOSTNAME)
+m4_define([SSL_OVSDB_CLIENT_SNI], [dnl
+  ovsdb-client -vconsole:stream_ssl:dbg \
+    --private-key=backend-service.test-privkey.pem \
+    --certificate=backend-service.test-cert.pem \
+    --ca-cert=sni-pki/switchca/cacert.pem \
+    --ssl-server-name=[$1] \
+    list-dbs ssl:127.0.0.1:$SSL_PORT])
+
+# SSL_OVSDB_CLIENT_NO_SNI() - test default behavior without SNI override
+m4_define([SSL_OVSDB_CLIENT_NO_SNI], [dnl
+  ovsdb-client -vconsole:stream_ssl:dbg \
+    --private-key=backend-service.test-privkey.pem \
+    --certificate=backend-service.test-cert.pem \
+    --ca-cert=sni-pki/switchca/cacert.pem \
+    list-dbs ssl:127.0.0.1:$SSL_PORT])
+
+# SNI Test Flow:
+# 1. Client sends SNI extension with --ssl-server-name=HOSTNAME during SSL 
handshake
+# 2. Server receives SNI extension and logs "connection indicated server name 
HOSTNAME"
+# 3. We verify SNI transmission by checking server log for the expected message
+# This validates that the client-side SNI implementation correctly sends SNI 
to the server
+
+# Test 1: SNI matches server certificate - should succeed
+AT_CHECK(SSL_OVSDB_CLIENT_SNI([backend-service.test]), [0], [stdout], [stderr])
+AT_CHECK([grep -q "connection indicated server name backend-service.test" 
ovsdb-server.log])
+
+# Save the current log size to check only new messages in Test 2
+LOG_SIZE=$(wc -l < ovsdb-server.log)
+
+# Test 2: Default behavior without SNI override - should work
+# and NOT show SNI server name in debug log (since no --ssl-server-name was 
used)
+AT_CHECK(SSL_OVSDB_CLIENT_NO_SNI(), [0], [stdout], [stderr])
+# Check only new log entries after Test 1 - should have no SNI messages
+AT_CHECK([tail -n +$(($LOG_SIZE + 1)) ovsdb-server.log | grep -q "connection 
indicated server name"], [1])
+
+OVSDB_SERVER_SHUTDOWN(["
+  /stream_ssl|WARN/d
+  /Protocol error/d
+"])
+AT_CLEANUP
+
 OVS_START_SHELL_HELPERS
 # ovsdb_check_online_compaction MODEL
 #
-- 
2.34.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to