diff --git a/doc/configuration.txt b/doc/configuration.txt index b34946485..30ba03299 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -11513,6 +11513,20 @@ agent-port See also the "agent-check" and "agent-inter" parameters. +alpn + 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 + 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 This option enables health adjusting based on observing communication with the server. By default this functionality is disabled and enabling it also diff --git a/include/types/server.h b/include/types/server.h index 9586fb8c8..bca7c43f1 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -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 */ diff --git a/src/server.c b/src/server.c index 9c5f4e906..0cf5ecaf5 100644 --- a/src/server.c +++ b/src/server.c @@ -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 diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 3e54e011b..583899043 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -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 ( )*, + * so we reuse each comma to store the next 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 ( )*, + * so we reuse each comma to store the next 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 */