MEDIUM: httpcheck/ssl: Base the SNI value on the HTTP host header by default

Similarly to the automic SNI selection for regulat SSL traffic, the SNI of
health-checks HTTPS connection is now automatically set by default by using
the host header value. "check-sni-auto" and "no-check-sni-auto" server
settings were added to change this behavior.

Only implicit HTTPS health-checks can take advantage of this feature. In
this case, the host header value from the "option httpchk" directive is used
to extract the SNI. It is disabled if http-check rules are used. So, the SNI
must still be explicitly specified via a "http-check connect" rule.

This patch with should paritally fix the issue #3081.
This commit is contained in:
Christopher Faulet 2025-09-04 12:13:54 +02:00
parent 668916c1a2
commit ffc1f096e0
5 changed files with 123 additions and 1 deletions

View File

@ -17635,6 +17635,28 @@ check-proto <name>
protocol for health-check connections established to this server. protocol for health-check connections established to this server.
If not defined, the server one will be used, if set. If not defined, the server one will be used, if set.
check-sni-auto
May be used in the following contexts: tcp, http, log
This option enables the automatic SNI selection when doing health checks over
SSL, if no value was already set. It is enabled by default but this parameter
may be used as "server" setting to reset any "no-check-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-check-sni-auto" setting.
For HTTPS connections, the SNI is automatically selected but only if there is
no "http-check connect" rule. In that case, the selected SNI is based on the
host header value, specified via the "option httpchk" directive or a
"http-check send" rule. There is no automatic selection for "http-check
connect" rules. For other protocols, the option is ignored.
If the automatic selection of the SNI is used for health-checks, the value is
assigned to the connection name if "check-reuse-pool" setting is set, unless
overridden by the "check-pool-conn-name" server keyword.
See "sni-auto" option to enable automatic SNI selection for proxied traffic.
check-sni <sni> check-sni <sni>
May be used in the following contexts: tcp, http, log May be used in the following contexts: tcp, http, log
@ -18129,6 +18151,15 @@ no-check-reuse-pool
This option reverts any previous "check-reuse-pool" possibly inherited from a This option reverts any previous "check-reuse-pool" possibly inherited from a
"default-server". Any checks will be conducted on its dedicated connection. "default-server". Any checks will be conducted on its dedicated connection.
no-check-sni-auto
May be used in the following contexts: tcp, http, log
This option may be used as "server" setting to disable the automatic SNI
selection for SSL health checks which is enabled by default.
See "no-sni-auto" option to disable automatic SNI selection for proxied
traffic.
no-check-ssl no-check-ssl
May be used in the following contexts: tcp, http, log May be used in the following contexts: tcp, http, log
@ -18195,6 +18226,9 @@ no-sni-auto
This option may be used as "server" setting to disable the automatic SNI This option may be used as "server" setting to disable the automatic SNI
selection which is enabled by default. selection which is enabled by default.
See "no-check-sni-auto" option to disable automatic SNI selection for SSL
health checks.
no-ssl no-ssl
May be used in the following contexts: tcp, http, log, peers, ring May be used in the following contexts: tcp, http, log, peers, ring
@ -18780,6 +18814,9 @@ sni-auto
connection name for "http-reuse", unless overridden by the "pool-conn-name" connection name for "http-reuse", unless overridden by the "pool-conn-name"
server keyword. server keyword.
See "check-sni-auto" option to enable automatic SNI selection for SSL health
checks.
source <addr>[:<pl>[-<ph>]] [usesrc { <addr2>[:<port2>] | client | clientip } ] source <addr>[:<pl>[-<ph>]] [usesrc { <addr2>[:<port2>] | client | clientip } ]
source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ] source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ]
source <addr>[:<pl>[-<ph>]] [interface <name>] ... source <addr>[:<pl>[-<ph>]] [interface <name>] ...

View File

@ -171,7 +171,7 @@ enum srv_init_state {
#define SRV_F_DEFSRV_USE_SSL 0x4000 /* default-server uses SSL */ #define SRV_F_DEFSRV_USE_SSL 0x4000 /* default-server uses SSL */
#define SRV_F_DELETED 0x8000 /* srv is deleted but not yet purged */ #define SRV_F_DELETED 0x8000 /* srv is deleted but not yet purged */
#define SRV_F_STRICT_MAXCONN 0x10000 /* maxconn is to be strictly enforced, as a limit of outbound connections */ #define SRV_F_STRICT_MAXCONN 0x10000 /* maxconn is to be strictly enforced, as a limit of outbound connections */
/* unused: 0x20000 */ #define SRV_F_CHK_NO_AUTO_SNI 0x20000 /* disable automatic SNI selection for healthcheck */
/* configured server options for send-proxy (server->pp_opts) */ /* configured server options for send-proxy (server->pp_opts) */
#define SRV_PP_V1 0x0001 /* proxy protocol version 1 */ #define SRV_PP_V1 0x0001 /* proxy protocol version 1 */

View File

@ -125,6 +125,7 @@ enum tcpcheck_rule_type {
struct check; struct check;
struct tcpcheck_connect { struct tcpcheck_connect {
char *sni; /* server name to use for SSL connections */ char *sni; /* server name to use for SSL connections */
struct lf_expr *sni_fmt; /* log-format string used for SNI. if defined, point on the following HTTP host header value */
char *alpn; /* ALPN to use for the SSL connection */ char *alpn; /* ALPN to use for the SSL connection */
int alpn_len; /* ALPN string length */ int alpn_len; /* ALPN string length */
const struct mux_proto_list *mux_proto; /* the mux to use for all outgoing connections (specified by the "proto" keyword) */ const struct mux_proto_list *mux_proto; /* the mux to use for all outgoing connections (specified by the "proto" keyword) */

View File

@ -1878,6 +1878,20 @@ static int srv_parse_crt(char **args, int *cur_arg, struct proxy *px, struct ser
return 0; return 0;
} }
/* parse the "check-sni-auto" server keyword */
static int srv_parse_check_sni_auto(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
{
newsrv->flags &= ~SRV_F_CHK_NO_AUTO_SNI;
return 0;
}
/* parse the "no-check-sni-auto" server keyword */
static int srv_parse_no_check_sni_auto(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
{
newsrv->flags |= SRV_F_CHK_NO_AUTO_SNI;
return 0;
}
/* parse the "no-check-ssl" server keyword */ /* parse the "no-check-ssl" server keyword */
static int srv_parse_no_check_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) static int srv_parse_no_check_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
{ {
@ -2594,6 +2608,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
{ "ca-file", srv_parse_ca_file, 1, 1, 1 }, /* set CAfile to process verify server cert */ { "ca-file", srv_parse_ca_file, 1, 1, 1 }, /* set CAfile to process verify server cert */
{ "check-alpn", srv_parse_check_alpn, 1, 1, 1 }, /* Set ALPN used for checks */ { "check-alpn", srv_parse_check_alpn, 1, 1, 1 }, /* Set ALPN used for checks */
{ "check-sni", srv_parse_check_sni, 1, 1, 1 }, /* set SNI */ { "check-sni", srv_parse_check_sni, 1, 1, 1 }, /* set SNI */
{ "check-sni-auto", srv_parse_check_sni_auto, 0, 1, 0 }, /* enable automatic SNI selection for health checks */
{ "check-ssl", srv_parse_check_ssl, 0, 1, 1 }, /* enable SSL for health checks */ { "check-ssl", srv_parse_check_ssl, 0, 1, 1 }, /* enable SSL for health checks */
{ "ciphers", srv_parse_ciphers, 1, 1, 1 }, /* select the cipher suite */ { "ciphers", srv_parse_ciphers, 1, 1, 1 }, /* select the cipher suite */
{ "ciphersuites", srv_parse_ciphersuites, 1, 1, 1 }, /* select the cipher suite */ { "ciphersuites", srv_parse_ciphersuites, 1, 1, 1 }, /* select the cipher suite */
@ -2607,6 +2622,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
{ "force-tlsv12", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv12 */ { "force-tlsv12", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv12 */
{ "force-tlsv13", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv13 */ { "force-tlsv13", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv13 */
{ "ktls", srv_parse_ktls, 1, 1, 1 }, /* enable or disable kTLS */ { "ktls", srv_parse_ktls, 1, 1, 1 }, /* enable or disable kTLS */
{ "no-check-sni-auto", srv_parse_no_check_sni_auto, 0, 1, 0 }, /* disable automatic SNI selection for health checks */
{ "no-check-ssl", srv_parse_no_check_ssl, 0, 1, 0 }, /* disable SSL for health checks */ { "no-check-ssl", srv_parse_no_check_ssl, 0, 1, 0 }, /* disable SSL for health checks */
{ "no-renegotiate", srv_parse_renegotiate, 0, 1, 1 }, /* Disable renegotiation */ { "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", srv_parse_no_send_proxy_ssl, 0, 1, 0 }, /* do not send PROXY protocol header v2 with SSL info */

View File

@ -1232,6 +1232,27 @@ static inline int tcpcheck_connect_use_ssl(const struct check *check,
return 0; return 0;
} }
static inline void tcpcheck_connect_auto_sni(const struct check *check,
const struct tcpcheck_connect *connect,
struct buffer *chk)
{
chunk_reset(chk);
chk->data = sess_build_logline(check->sess, NULL, b_orig(chk), b_size(chk), connect->sni_fmt);
if (b_data(chk)) {
char *beg = b_orig(chk);
char *end = b_tail(chk) - 1;
char *p;
for (p = end; p >= beg; p--) {
if (*p == ':' || *p == ']')
break;
}
if (p >= beg && *p == ':')
b_set_data(chk, p - beg);
*b_tail(chk) = 0;
}
}
/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the /* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
* connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
* TCPCHK_EVAL_STOP if an error occurred. * TCPCHK_EVAL_STOP if an error occurred.
@ -1247,6 +1268,7 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
struct protocol *proto; struct protocol *proto;
struct xprt_ops *xprt; struct xprt_ops *xprt;
struct tcpcheck_rule *next; struct tcpcheck_rule *next;
struct buffer *auto_sni = NULL;
int status, port; int status, port;
TRACE_ENTER(CHK_EV_TCPCHK_CONN, check); TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
@ -1275,6 +1297,19 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
check_release_buf(check, &check->bi); check_release_buf(check, &check->bi);
check_release_buf(check, &check->bo); check_release_buf(check, &check->bo);
/* Deal with automatic SNI selection now because it can be used as connection name */
if (tcpcheck_connect_use_ssl(check, connect) && s && !(s->flags & SRV_F_CHK_NO_AUTO_SNI) && connect->sni_fmt) {
auto_sni = alloc_trash_chunk();
if (auto_sni) {
tcpcheck_connect_auto_sni(check, connect, auto_sni);
if (!b_data(auto_sni)) {
free_trash_chunk(auto_sni);
auto_sni = NULL;
}
}
}
if (!(check->state & CHK_ST_AGENT) && check->reuse_pool && if (!(check->state & CHK_ST_AGENT) && check->reuse_pool &&
!tcpcheck_use_nondefault_connect(check, connect) && !tcpcheck_use_nondefault_connect(check, connect) &&
!srv_is_transparent(s)) { !srv_is_transparent(s)) {
@ -1292,6 +1327,8 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
pool_conn_name = ist(connect->sni); pool_conn_name = ist(connect->sni);
else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->sni) else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->sni)
pool_conn_name = ist(check->sni); pool_conn_name = ist(check->sni);
else if (auto_sni)
pool_conn_name = ist2(b_orig(auto_sni), b_data(auto_sni));
} }
if (!(s->flags & SRV_F_RHTTP)) { if (!(s->flags & SRV_F_RHTTP)) {
@ -1444,6 +1481,8 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
ssl_sock_set_servername(conn, connect->sni); ssl_sock_set_servername(conn, connect->sni);
else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni) else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
ssl_sock_set_servername(conn, s->check.sni); ssl_sock_set_servername(conn, s->check.sni);
else if (auto_sni)
ssl_sock_set_servername(conn, b_orig(auto_sni));
if (connect->alpn) if (connect->alpn)
ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len); ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
@ -1551,6 +1590,9 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check) if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check); check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
if (auto_sni)
free_trash_chunk(auto_sni);
TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret}); TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
return ret; return ret;
} }
@ -3959,6 +4001,32 @@ static int check_proxy_tcpcheck(struct proxy *px)
LIST_INSERT(px->tcpcheck_rules.list, &chk->list); LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
} }
/* Now, back again on HTTP ruleset. Try to resolve the sni log-format
* string if necessary, but onlu for implicit connect rules, by getting
* it from the following send rule.
*/
if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
struct tcpcheck_connect *connect = NULL;
list_for_each_entry(chk, px->tcpcheck_rules.list, list) {
if (chk->action == TCPCHK_ACT_CONNECT && !chk->connect.sni &&
(chk->connect.options & TCPCHK_OPT_IMPLICIT)) {
/* Only eval connect rule with no explici SNI */
connect = &chk->connect;
}
else if (connect && chk->action == TCPCHK_ACT_SEND) {
struct tcpcheck_http_hdr *hdr;
list_for_each_entry(hdr, &chk->send.http.hdrs, list) {
if (isteqi(hdr->name, ist("host")))
connect->sni_fmt = &hdr->value;
}
connect = NULL;
}
}
}
/* Remove all comment rules. To do so, when a such rule is found, the /* Remove all comment rules. To do so, when a such rule is found, the
* comment is assigned to the following rule(s). * comment is assigned to the following rule(s).
*/ */