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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index b349464..30ba032 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -11513,6 +11513,20 @@
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 @@
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
diff --git a/include/types/server.h b/include/types/server.h
index 9586fb8..bca7c43 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -299,6 +299,14 @@
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 9c5f4e9..0cf5eca 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1486,6 +1486,27 @@
#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 3e54e01..5838990 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1635,6 +1635,20 @@
}
#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 @@
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 @@
/* 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 @@
/************** "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 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 @@
{ "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 */