MINOR: server: Add "alpn" and "npn" keywords.

Add new keywords to "server" lines, alpn and npn.
If set, when connecting through SSL, those alpn/npn will be negociated
during the SSL handshake.
This commit is contained in:
Olivier Houchard 2018-11-20 23:33:50 +01:00 committed by Willy Tarreau
parent beb859abce
commit c756600103
4 changed files with 191 additions and 0 deletions

View File

@ -11513,6 +11513,20 @@ agent-port <port>
See also the "agent-check" and "agent-inter" parameters.
alpn <protocols>
This enables the TLS ALPN extension and advertises the specified protocol
list as supported on top of ALPN. The protocol list consists in a comma-
delimited list of protocol names, for instance: "http/1.1,http/1.0" (without
quotes). This requires that the SSL library is build with support for TLS
extensions enabled (check with haproxy -vv). The ALPN extension replaces the
initial NPN extension. ALPN is required to connect to HTTP/2 servers.
Versions of OpenSSL prior to 1.0.2 didn't support ALPN and only supposed the
now obsolete NPN extension.
If both HTTP/2 and HTTP/1.1 are expected to be supported, both versions can
be advertised, in order of preference, like below :
server 127.0.0.1:443 ssl crt pub.pem alpn h2,http/1.1
backup
When "backup" is present on a server line, the server is only used in load
balancing when all other non-backup servers are unavailable. Requests coming
@ -11890,6 +11904,15 @@ non-stick
This may be used in conjunction with backup to ensure that
stick-table persistence is disabled for backup servers.
npn <protocols>
This enables the NPN TLS extension and advertises the specified protocol list
as supported on top of NPN. The protocol list consists in a comma-delimited
list of protocol names, for instance: "http/1.1,http/1.0" (without quotes).
This requires that the SSL library is build with support for TLS extensions
enabled (check with haproxy -vv). Note that the NPN extension has been
replaced with the ALPN extension (see the "alpn" keyword), though this one is
only available starting with OpenSSL 1.0.2.
observe <mode>
This option enables health adjusting based on observing communication with
the server. By default this functionality is disabled and enabling it also

View File

@ -299,6 +299,14 @@ struct server {
char *crl_file; /* CRLfile to use on verify */
char *client_crt; /* client certificate to send */
struct sample_expr *sni; /* sample expression for SNI */
#ifdef OPENSSL_NPN_NEGOTIATED
char *npn_str; /* NPN protocol string */
int npn_len; /* NPN protocol string length */
#endif
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
char *alpn_str; /* ALPN protocol string */
int alpn_len; /* ALPN protocol string length */
#endif
} ssl_ctx;
#endif
struct dns_srvrq *srvrq; /* Pointer representing the DNS SRV requeest, if any */

View File

@ -1486,6 +1486,27 @@ static void srv_ssl_settings_cpy(struct server *srv, struct server *src)
#endif
if (src->sni_expr != NULL)
srv->sni_expr = strdup(src->sni_expr);
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
if (src->ssl_ctx.alpn_str) {
srv->ssl_ctx.alpn_str = malloc(src->ssl_ctx.alpn_len);
if (srv->ssl_ctx.alpn_str) {
memcpy(srv->ssl_ctx.alpn_str, src->ssl_ctx.alpn_str,
src->ssl_ctx.alpn_len);
srv->ssl_ctx.alpn_len = src->ssl_ctx.alpn_len;
}
}
#endif
#ifdef OPENSSL_NPN_NEGOTIATED
if (src->ssl_ctx.npn_str) {
srv->ssl_ctx.npn_str = malloc(src->ssl_ctx.npn_len);
if (srv->ssl_ctx.npn_str) {
memcpy(srv->ssl_ctx.npn_str, src->ssl_ctx.npn_str,
src->ssl_ctx.npn_len);
srv->ssl_ctx.npn_len = src->ssl_ctx.npn_len;
}
}
#endif
}
#endif

View File

@ -1634,6 +1634,20 @@ void ssl_sock_msgcbk(int write_p, int version, int content_type, const void *buf
ssl_sock_parse_clienthello(write_p, version, content_type, buf, len, ssl);
}
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
static int ssl_sock_srv_select_protos(SSL *s, unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen,
void *arg)
{
struct server *srv = arg;
if (SSL_select_next_proto(out, outlen, in, inlen, (unsigned char *)srv->ssl_ctx.npn_str,
srv->ssl_ctx.npn_len) == OPENSSL_NPN_NEGOTIATED)
return SSL_TLSEXT_ERR_OK;
return SSL_TLSEXT_ERR_NOACK;
}
#endif
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
/* This callback is used so that the server advertises the list of
* negociable protocols for NPN.
@ -4701,6 +4715,15 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
cfgerr++;
}
#endif
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
if (srv->ssl_ctx.npn_str)
SSL_CTX_set_next_proto_select_cb(ctx, ssl_sock_srv_select_protos, srv);
#endif
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
if (srv->ssl_ctx.alpn_str)
SSL_CTX_set_alpn_protos(ctx, (unsigned char *)srv->ssl_ctx.alpn_str, srv->ssl_ctx.alpn_len);
#endif
return cfgerr;
}
@ -4815,8 +4838,16 @@ int ssl_sock_prepare_bind_conf(struct bind_conf *bind_conf)
/* release ssl context allocated for servers. */
void ssl_sock_free_srv_ctx(struct server *srv)
{
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
if (srv->ssl_ctx.alpn_str)
free(srv->ssl_ctx.alpn_str);
#endif
if (srv->ssl_ctx.npn_str)
free(srv->ssl_ctx.npn_str);
#ifdef OPENSSL_NPN_NEGOTIATED
if (srv->ssl_ctx.ctx)
SSL_CTX_free(srv->ssl_ctx.ctx);
#endif
}
/* Walks down the two trees in bind_conf and frees all the certs. The pointer may
@ -7853,6 +7884,112 @@ static int bind_parse_no_ca_names(char **args, int cur_arg, struct proxy *px, st
/************** "server" keywords ****************/
/* parse the "npn" bind keyword */
static int srv_parse_npn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
{
#if defined(OPENSSL_NPN_NEGOTIATED) && !defined(OPENSSL_NO_NEXTPROTONEG)
char *p1, *p2;
if (!*args[*cur_arg + 1]) {
memprintf(err, "'%s' : missing the comma-delimited NPN protocol suite", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
free(newsrv->ssl_ctx.npn_str);
/* the NPN string is built as a suite of (<len> <name>)*,
* so we reuse each comma to store the next <len> and need
* one more for the end of the string.
*/
newsrv->ssl_ctx.npn_len = strlen(args[*cur_arg + 1]) + 1;
newsrv->ssl_ctx.npn_str = calloc(1, newsrv->ssl_ctx.npn_len + 1);
memcpy(newsrv->ssl_ctx.npn_str + 1, args[*cur_arg + 1],
newsrv->ssl_ctx.npn_len);
/* replace commas with the name length */
p1 = newsrv->ssl_ctx.npn_str;
p2 = p1 + 1;
while (1) {
p2 = memchr(p1 + 1, ',', newsrv->ssl_ctx.npn_str +
newsrv->ssl_ctx.npn_len - (p1 + 1));
if (!p2)
p2 = p1 + 1 + strlen(p1 + 1);
if (p2 - (p1 + 1) > 255) {
*p2 = '\0';
memprintf(err, "'%s' : NPN protocol name too long : '%s'", args[*cur_arg], p1 + 1);
return ERR_ALERT | ERR_FATAL;
}
*p1 = p2 - (p1 + 1);
p1 = p2;
if (!*p2)
break;
*(p2++) = '\0';
}
return 0;
#else
if (err)
memprintf(err, "'%s' : library does not support TLS NPN extension", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
#endif
}
/* parse the "alpn" bind keyword */
static int srv_parse_alpn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
{
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
char *p1, *p2;
if (!*args[*cur_arg + 1]) {
memprintf(err, "'%s' : missing the comma-delimited ALPN protocol suite", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
free(newsrv->ssl_ctx.alpn_str);
/* the ALPN string is built as a suite of (<len> <name>)*,
* so we reuse each comma to store the next <len> and need
* one more for the end of the string.
*/
newsrv->ssl_ctx.alpn_len = strlen(args[*cur_arg + 1]) + 1;
newsrv->ssl_ctx.alpn_str = calloc(1, newsrv->ssl_ctx.alpn_len + 1);
memcpy(newsrv->ssl_ctx.alpn_str + 1, args[*cur_arg + 1],
newsrv->ssl_ctx.alpn_len);
/* replace commas with the name length */
p1 = newsrv->ssl_ctx.alpn_str;
p2 = p1 + 1;
while (1) {
p2 = memchr(p1 + 1, ',', newsrv->ssl_ctx.alpn_str +
newsrv->ssl_ctx.alpn_len - (p1 + 1));
if (!p2)
p2 = p1 + 1 + strlen(p1 + 1);
if (p2 - (p1 + 1) > 255) {
*p2 = '\0';
memprintf(err, "'%s' : ALPN protocol name too long : '%s'", args[*cur_arg], p1 + 1);
return ERR_ALERT | ERR_FATAL;
}
*p1 = p2 - (p1 + 1);
p1 = p2;
if (!*p2)
break;
*(p2++) = '\0';
}
return 0;
#else
if (err)
memprintf(err, "'%s' : library does not support TLS ALPN extension", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
#endif
}
/* parse the "ca-file" server keyword */
static int srv_parse_ca_file(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
{
@ -8949,6 +9086,7 @@ static struct bind_kw_list bind_kws = { "SSL", { }, {
*/
static struct srv_kw_list srv_kws = { "SSL", { }, {
{ "allow-0rtt", srv_parse_allow_0rtt, 0, 1 }, /* Allow using early data on this server */
{ "alpn", srv_parse_alpn, 1, 1 }, /* Set ALPN supported protocols */
{ "ca-file", srv_parse_ca_file, 1, 1 }, /* set CAfile to process verify server cert */
{ "check-sni", srv_parse_check_sni, 1, 1 }, /* set SNI */
{ "check-ssl", srv_parse_check_ssl, 0, 1 }, /* enable SSL for health checks */
@ -8974,6 +9112,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
{ "no-tlsv12", srv_parse_tls_method_options, 0, 0 }, /* disable TLSv12 */
{ "no-tlsv13", srv_parse_tls_method_options, 0, 0 }, /* disable TLSv13 */
{ "no-tls-tickets", srv_parse_no_tls_tickets, 0, 1 }, /* disable session resumption tickets */
{ "npn", srv_parse_npn, 1, 1 }, /* Set NPN supported protocols */
{ "send-proxy-v2-ssl", srv_parse_send_proxy_ssl, 0, 1 }, /* send PROXY protocol header v2 with SSL info */
{ "send-proxy-v2-ssl-cn", srv_parse_send_proxy_cn, 0, 1 }, /* send PROXY protocol header v2 with CN */
{ "sni", srv_parse_sni, 1, 1 }, /* send SNI extension */