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]> --- NEWS | 6 +++++ lib/ssl-syn.man | 2 ++ lib/ssl.man | 7 ++++++ lib/ssl.xml | 9 +++++++ lib/stream-nossl.c | 7 ++++++ lib/stream-ssl.c | 21 ++++++++++++++++- lib/stream-ssl.h | 13 +++++++--- lib/stream.c | 4 +++- ovsdb/ovsdb-server.c | 9 +++++++ tests/ovs-vsctl.at | 56 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 129 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 96bf4992c..d25be10db 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,11 @@ 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. - Userspace datapath: * Conntrack now supports the FTP commands EPSV and EPRT with IPv4 connections, instead of limiting these commands to IPv6 only. diff --git a/lib/ssl-syn.man b/lib/ssl-syn.man index 583454548..77682bade 100644 --- a/lib/ssl-syn.man +++ b/lib/ssl-syn.man @@ -4,3 +4,5 @@ [\fB\-\-certificate=\fIcert.pem\fR] .br [\fB\-\-ca\-cert=\fIcacert.pem\fR] +.br +[\fB\-\-ssl\-server\-name=\fIservername\fR] diff --git a/lib/ssl.man b/lib/ssl.man index 9bec3a786..303f0550b 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 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..4dd2938a7 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 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..c0a6bf92a 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 TLS server name if + * SSL/TLS won't be used. */ +} diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c index 99c82b6af..c647716ab 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,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 diff --git a/lib/stream-ssl.h b/lib/stream-ssl.h index abd3ba219..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,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 */ diff --git a/lib/stream.c b/lib/stream.c index aa48a973b..feaa1cb2d 100644 --- a/lib/stream.c +++ b/lib/stream.c @@ -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 } diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c index 5303a46c6..86b0dc7b8 100644 --- a/ovsdb/ovsdb-server.c +++ b/ovsdb/ovsdb-server.c @@ -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; diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at index 59245fff8..9b9d73c5f 100644 --- a/tests/ovs-vsctl.at +++ b/tests/ovs-vsctl.at @@ -1769,6 +1769,62 @@ 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_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_CAPTURE_FILE([ovsdb-server.log]) +AT_CLEANUP + dnl ---------------------------------------------------------------------- AT_BANNER([set ingress policing test]) -- 2.34.1 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
