]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: server/ssl: Base the SNI value to the HTTP host header by default
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 4 Sep 2025 08:12:19 +0000 (10:12 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 5 Sep 2025 13:56:42 +0000 (15:56 +0200)
For HTTPS outgoing connections, the SNI is now automatically set using the
Host header value if no other value is already set (via the "sni" server
keyword). It is now the default behavior. It could be disabled with the
"no-sni-auto" server keyword. And eventually "sni-auto" server keyword may
be used to reset any previous "no-sni-auto" setting. This option can be
inherited from "default-server" settings. Finally, if no connection name is
set via "pool-conn-name" setting, the selected value is used.

The automatic selection of the SNI is enabled by default for all outgoing
connections. But it is concretely used for HTTPS connections only. The
expression used is "req.hdr(host),host_only".

This patch should paritally fix the issue #3081. It only covers the server
part. Another patch will add the feature for HTTP health-checks.

doc/configuration.txt
include/haproxy/server-t.h
include/haproxy/server.h
reg-tests/ssl/set_ssl_cafile.vtc
reg-tests/ssl/set_ssl_crlfile.vtc
src/cfgparse-ssl.c
src/cfgparse.c
src/server.c

index 507c2da03e1f7494b95235d9a8e724139d2bdf1d..4c18eb7f616b08f18822703a9a9d690c4b3cfabc 100644 (file)
@@ -18189,6 +18189,12 @@ no-send-proxy-v2-ssl-cn
   It may also be used as "default-server" setting to reset any previous
   "default-server" "send-proxy-v2-ssl-cn" setting.
 
+no-sni-auto
+  May be used in the following contexts: tcp, http, log, peers, ring
+
+  This option may be used as "server" setting to disable the automatic SNI
+  selection which is enabled by default.
+
 no-ssl
   May be used in the following contexts: tcp, http, log, peers, ring
 
@@ -18756,6 +18762,24 @@ sni <expression>
   By default, the SNI is assigned to the connection name for "http-reuse",
   unless overridden by the "pool-conn-name" server keyword.
 
+sni-auto
+  May be used in the following contexts: tcp, http, log, peers, ring
+
+  The "sni-auto" parameter enables the automatic SNI selection, if no value was
+  already set. It is enabled by default but this parameter may be used as
+  "server" setting to reset any "no-sni-auto" setting which would have been
+  inherited from "default-server" directive as default value.  It may also be
+  used as "default-server" setting to reset any previous "default-server"
+  "no-sni-auto" setting.
+
+  For HTTPS connections, the selected SNI is based on the request host header
+  value, if found. Otherwise it remains unset. For other protocols, the option
+  is ignored.
+
+  If the automatic selection of the SNI is used, the value is assigned to the
+  connection name for "http-reuse", unless overridden by the "pool-conn-name"
+  server keyword.
+
 source <addr>[:<pl>[-<ph>]] [usesrc { <addr2>[:<port2>] | client | clientip } ]
 source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ]
 source <addr>[:<pl>[-<ph>]] [interface <name>] ...
index dd76ffac6aeecb82de3641645c43072cdcb40897..3cd1f7bdd3622025689040c1212b4e1836af9a13 100644 (file)
@@ -201,6 +201,7 @@ enum srv_init_state {
 
 /* server ssl options */
 #define SRV_SSL_O_NONE           0x0000
+#define SRV_SSL_O_NO_AUTO_SNI   0x0001  /* disable automatic SNI */
 #define SRV_SSL_O_NO_TLS_TICKETS 0x0100 /* disable session resumption tickets */
 #define SRV_SSL_O_NO_REUSE       0x200  /* disable session reuse */
 #define SRV_SSL_O_EARLY_DATA     0x400  /* Allow using early data */
index 7f38cd53bd853c4eb4244dd9f8c35532e363868a..79cf880b34b44803c42851e2007faeefbaecc474 100644 (file)
@@ -52,6 +52,7 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
 int srv_update_addr(struct server *s, void *ip, int ip_sin_family, struct server_inetaddr_updater updater);
 struct sample_expr *_parse_srv_expr(char *expr, struct arg_list *args_px,
                                     const char *file, int linenum, char **err);
+int server_parse_exprs(struct server *srv, struct proxy *px, char **err);
 int server_set_inetaddr(struct server *s, const struct server_inetaddr *inetaddr, struct server_inetaddr_updater updater, struct buffer *msg);
 int server_set_inetaddr_warn(struct server *s, const struct server_inetaddr *inetaddr, struct server_inetaddr_updater updater);
 void server_get_inetaddr(struct server *s, struct server_inetaddr *inetaddr);
index 897fe5e407c7aaca1ba11e2f1aa381d964c061f0..66511ded8a6e8b4b92b02b1f9095eab5fa3b2287 100644 (file)
@@ -51,11 +51,11 @@ haproxy h1 -conf {
         bind "fd@${clearlst}"
        # dummy bind used to test a change when the same crt is used as server and bind
         bind "fd@${foobarlst}" ssl crt ${testdir}/set_cafile_client.pem ca-file ${testdir}/set_cafile_interCA1.crt verify none
-        server s1 "${tmpdir}/ssl.sock" ssl crt ${testdir}/set_cafile_client.pem ca-file ${testdir}/set_cafile_interCA1.crt verify none
+        server s1 "${tmpdir}/ssl.sock" ssl crt ${testdir}/set_cafile_client.pem ca-file ${testdir}/set_cafile_interCA1.crt verify none no-sni-auto
 
     listen clear-verified-lst
         bind "fd@${clearverifiedlst}"
-        server s1 "${tmpdir}/ssl.sock" ssl crt ${testdir}/set_cafile_client.pem ca-file ${testdir}/set_cafile_interCA1.crt verify required
+        server s1 "${tmpdir}/ssl.sock" ssl crt ${testdir}/set_cafile_client.pem ca-file ${testdir}/set_cafile_interCA1.crt verify required no-sni-auto
 
     listen ssl-lst
         # crt: certificate of the server
index 05ff673821b70faa78834e72ba4b17dc970bc8cf..23537918c4a612bc6eebb125bcb9f1db34d6308d 100644 (file)
@@ -52,7 +52,7 @@ haproxy h1 -conf {
 
     listen clear-lst
         bind "fd@${clearlst}"
-        server s1 "${tmpdir}/ssl.sock" ssl crt ${testdir}/set_cafile_client.pem ca-file ${testdir}/set_cafile_interCA2.crt crl-file ${testdir}/interCA2_crl_empty.pem verify required
+        server s1 "${tmpdir}/ssl.sock" ssl crt ${testdir}/set_cafile_client.pem ca-file ${testdir}/set_cafile_interCA2.crt crl-file ${testdir}/interCA2_crl_empty.pem verify required no-sni-auto
 
     listen ssl-lst
         # crt: certificate of the server
index 583461d6a2d120d78a84d5dc57b9c90766198635..141da23573719e9f4d4c811d45d73c01a78f65dd 100644 (file)
@@ -2033,6 +2033,20 @@ static int srv_parse_sni(char **args, int *cur_arg, struct proxy *px, struct ser
 #endif
 }
 
+/* parse the "sni-auto" server keyword */
+static int srv_parse_sni_auto(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+       newsrv->ssl_ctx.options &= ~SRV_SSL_O_NO_AUTO_SNI;
+       return 0;
+}
+
+/* parse the "no-sni-auto" server keyword */
+static int srv_parse_no_sni_auto(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+       newsrv->ssl_ctx.options |= SRV_SSL_O_NO_AUTO_SNI;
+       return 0;
+}
+
 /* parse the "ssl" server keyword */
 static int srv_parse_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
 {
@@ -2597,6 +2611,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
        { "no-renegotiate",          srv_parse_renegotiate,        0, 1, 1 }, /* Disable renegotiation */
        { "no-send-proxy-v2-ssl",    srv_parse_no_send_proxy_ssl,  0, 1, 0 }, /* do not send PROXY protocol header v2 with SSL info */
        { "no-send-proxy-v2-ssl-cn", srv_parse_no_send_proxy_cn,   0, 1, 0 }, /* do not send PROXY protocol header v2 with CN */
+       { "no-sni-auto",             srv_parse_no_sni_auto,        0, 1, 0 }, /* disable automatic SNI selection */
        { "no-ssl",                  srv_parse_no_ssl,             0, 1, 0 }, /* disable SSL processing */
        { "no-ssl-reuse",            srv_parse_no_ssl_reuse,       0, 1, 1 }, /* disable session reuse */
        { "no-sslv3",                srv_parse_tls_method_options, 0, 0, 1 }, /* disable SSLv3 */
@@ -2611,6 +2626,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
        { "send-proxy-v2-ssl-cn",    srv_parse_send_proxy_cn,      0, 1, 1 }, /* send PROXY protocol header v2 with CN */
        { "sigalgs",                 srv_parse_sigalgs,            1, 1, 1 }, /* signature algorithms */
        { "sni",                     srv_parse_sni,                1, 1, 1 }, /* send SNI extension */
+       { "sni-auto",                srv_parse_sni_auto,           0, 1, 0 }, /* enable automatic SNI selection */
        { "ssl",                     srv_parse_ssl,                0, 1, 1 }, /* enable SSL processing */
        { "ssl-min-ver",             srv_parse_tls_method_minmax,  1, 1, 1 }, /* minimum version */
        { "ssl-max-ver",             srv_parse_tls_method_minmax,  1, 1, 1 }, /* maximum version */
index e1b4933dca31c7c490fef1c2998bf3e8e1228736..0bd29feb01c9e945adf15c04f711bea38c962204 100644 (file)
@@ -3761,6 +3761,25 @@ out_uri_auth_compat:
                                        cfgerr += xprt_get(XPRT_QUIC)->prepare_srv(newsrv);
                        }
 
+                       if (newsrv->use_ssl == 1 || ((newsrv->flags & SRV_F_DEFSRV_USE_SSL) && newsrv->use_ssl != 1)) {
+                               /* In HTTP only, if the SNI not set and we can realy on the host
+                                * header value, fill the sni expression accordingly
+                                */
+                               if (newsrv->proxy->mode == PR_MODE_HTTP && !(newsrv->ssl_ctx.options & SRV_SSL_O_NO_AUTO_SNI)) {
+                                       newsrv->sni_expr = strdup("req.hdr(host),field(1,:)");
+
+                                       err = NULL;
+                                       if (server_parse_exprs(newsrv, curproxy, &err)) {
+                                               ha_alert("parsing [%s:%d]: failed to parse auto SNI expression: %s\n",
+                                                        newsrv->conf.file, newsrv->conf.line, err);
+                                               free(err);
+                                               ++cfgerr;
+                                               goto next_srv;
+                                       }
+                               }
+                       }
+
+
                        if ((newsrv->flags & SRV_F_FASTOPEN) &&
                            ((curproxy->retry_type & (PR_RE_DISCONNECTED | PR_RE_TIMEOUT)) !=
                             (PR_RE_DISCONNECTED | PR_RE_TIMEOUT)))
index 441b7f8314456ed23842e05d03d0d261a4f4628e..83257e1511bfdbda65e7b0e9d19b0d55312bc08e 100644 (file)
@@ -54,7 +54,6 @@
 #include <haproxy/xxhash.h>
 #include <haproxy/event_hdl.h>
 
-static inline int _srv_parse_exprs(struct server *srv, struct proxy *px, char **errmsg);
 static void srv_update_status(struct server *s, int type, int cause);
 static int srv_apply_lastaddr(struct server *srv, int *err_code);
 static void srv_cleanup_connections(struct server *srv);
@@ -2768,7 +2767,7 @@ int srv_set_ssl(struct server *s, int use_ssl)
 
        s->use_ssl = use_ssl;
        if (s->use_ssl) {
-               if (_srv_parse_exprs(s, s->proxy, NULL))
+               if (server_parse_exprs(s, s->proxy, NULL))
                        return -1;
                s->xprt = xprt_get(XPRT_SSL);
        }
@@ -3303,7 +3302,7 @@ static inline void _srv_parse_set_id_from_prefix(struct server *srv,
 
 /* Parse the sni and pool-conn-name expressions. Returns 0 on success and non-zero on
  * error. */
-static inline int _srv_parse_exprs(struct server *srv, struct proxy *px, char **errmsg)
+int server_parse_exprs(struct server *srv, struct proxy *px, char **errmsg)
 {
        int ret = 0;
 
@@ -3369,7 +3368,7 @@ static int _srv_parse_tmpl_init(struct server *srv, struct proxy *px)
                srv_settings_cpy(newsrv, srv, 1);
                srv_prepare_for_resolution(newsrv, srv->hostname);
 
-               if (_srv_parse_exprs(newsrv, px, NULL))
+               if (server_parse_exprs(newsrv, px, NULL))
                        goto err;
 
                /* append to list of servers available to receive an hostname */
@@ -3876,7 +3875,7 @@ static int _srv_parse_finalize(char **args, int cur_arg,
                return ERR_ALERT | ERR_FATAL;
        }
 
-       if ((ret = _srv_parse_exprs(srv, px, &errmsg))) {
+       if ((ret = server_parse_exprs(srv, px, &errmsg))) {
                if (errmsg) {
                        ha_alert("error detected while parsing sni or pool-conn-name expressions : %s.\n", errmsg);
                        free(errmsg);