From 732eac41f4a8b2b81db5152c633a504e5de37b6f Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 9 Jul 2015 11:40:25 +0200 Subject: [PATCH] MEDIUM: ssl: add sni support on the server lines The new "sni" server directive takes a sample fetch expression and uses its return value as a hostname sent as the TLS SNI extension. A typical use case consists in forwarding the front connection's SNI value to the server in a bridged HTTPS forwarder : sni ssl_fc_sni --- doc/configuration.txt | 9 +++++++++ include/types/server.h | 1 + src/backend.c | 34 ++++++++++++++++++++++++++++++++++ src/ssl_sock.c | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index 03388e368..a8063127a 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -10183,6 +10183,15 @@ slowstart Supported in default-server: Yes +sni + The "sni" parameter evaluates the sample fetch expression, converts it to a + string and uses the result as the host name sent in the SNI TLS extension to + the server. A typical use case is to send the SNI received from the client in + a bridged HTTPS scenario, using the "ssl_fc_sni" sample fetch for the + expression, though alternatives such as req.hdr(host) can also make sense. + + Supported in default-server: no + source [:[-]] [usesrc { [:] | client | clientip } ] source [:] [usesrc { [:] | hdr_ip([,]) } ] source [:[-]] [interface ] ... diff --git a/include/types/server.h b/include/types/server.h index 4b44f22eb..6a7e5586c 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -222,6 +222,7 @@ struct server { char *ca_file; /* CAfile to use on verify */ char *crl_file; /* CRLfile to use on verify */ char *client_crt; /* client certificate to send */ + struct sample_expr *sni; /* sample expression for SNI */ } ssl_ctx; #endif struct { diff --git a/src/backend.c b/src/backend.c index 26f558971..48c5e79d3 100644 --- a/src/backend.c +++ b/src/backend.c @@ -55,6 +55,10 @@ #include #include +#ifdef USE_OPENSSL +#include +#endif /* USE_OPENSSL */ + int be_lastsession(const struct proxy *be) { if (be->be_counters.last_sess) @@ -1116,6 +1120,36 @@ int connect_server(struct stream *s) srv->counters.cur_sess_max = srv->cur_sess; if (s->be->lbprm.server_take_conn) s->be->lbprm.server_take_conn(srv); + +#ifdef USE_OPENSSL + if (srv->ssl_ctx.sni) { + struct sample *smp; + int rewind; + + /* Tricky case : we have already scheduled the pending + * HTTP request or TCP data for leaving. So in HTTP we + * rewind exactly the headers, otherwise we rewind the + * output data. + */ + rewind = s->txn ? http_hdr_rewind(&s->txn->req) : s->req.buf->o; + b_rew(s->req.buf, rewind); + + smp = sample_fetch_as_type(s->be, s->sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, srv->ssl_ctx.sni, SMP_T_STR); + + /* restore the pointers */ + b_adv(s->req.buf, rewind); + + if (smp) { + /* get write access to terminate with a zero */ + smp_dup(smp); + if (smp->data.str.len >= smp->data.str.size) + smp->data.str.len = smp->data.str.size - 1; + smp->data.str.str[smp->data.str.len] = 0; + ssl_sock_set_servername(srv_conn, smp->data.str.str); + } + } +#endif /* USE_OPENSSL */ + } return SF_ERR_NONE; /* connection is OK */ diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 7f5d2ae20..4e7554942 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -4952,6 +4952,42 @@ static int srv_parse_send_proxy_cn(char **args, int *cur_arg, struct proxy *px, return 0; } +/* parse the "sni" server keyword */ +static int srv_parse_sni(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) +{ +#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME + memprintf(err, "'%s' : the current SSL library doesn't support the SNI TLS extension", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; +#else + struct sample_expr *expr; + + if (!*args[*cur_arg + 1]) { + memprintf(err, "'%s' : missing sni expression", args[*cur_arg]); + return ERR_ALERT | ERR_FATAL; + } + + (*cur_arg)++; + proxy->conf.args.ctx = ARGC_SRV; + + expr = sample_parse_expr((char **)args, cur_arg, px->conf.file, px->conf.line, err, &proxy->conf.args); + if (!expr) { + memprintf(err, "error detected while parsing sni expression : %s", *err); + return ERR_ALERT | ERR_FATAL; + } + + if (!(expr->fetch->val & SMP_VAL_BE_SRV_CON)) { + memprintf(err, "error detected while parsing sni expression : " + " fetch method '%s' extracts information from '%s', none of which is available here.\n", + args[*cur_arg-1], sample_src_names(expr->fetch->use)); + return ERR_ALERT | ERR_FATAL; + } + + px->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY); + newsrv->ssl_ctx.sni = expr; + return 0; +#endif +} + /* parse the "ssl" server keyword */ static int srv_parse_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err) { @@ -5225,6 +5261,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, { { "no-tls-tickets", srv_parse_no_tls_tickets, 0, 0 }, /* disable session resumption tickets */ { "send-proxy-v2-ssl", srv_parse_send_proxy_ssl, 0, 0 }, /* send PROXY protocol header v2 with SSL info */ { "send-proxy-v2-ssl-cn", srv_parse_send_proxy_cn, 0, 0 }, /* send PROXY protocol header v2 with CN */ + { "sni", srv_parse_sni, 1, 0 }, /* send SNI extension */ { "ssl", srv_parse_ssl, 0, 0 }, /* enable SSL processing */ { "verify", srv_parse_verify, 1, 0 }, /* set SSL verify method */ { "verifyhost", srv_parse_verifyhost, 1, 0 }, /* require that SSL cert verifies for hostname */