]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: server: Add "alpn" and "npn" keywords.
authorOlivier Houchard <cognet@ci0.org>
Tue, 20 Nov 2018 22:33:50 +0000 (23:33 +0100)
committerWilly Tarreau <w@1wt.eu>
Thu, 22 Nov 2018 18:50:08 +0000 (19:50 +0100)
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.

doc/configuration.txt
include/types/server.h
src/server.c
src/ssl_sock.c

index b34946485173c04325bcb6c558f18430a8b20020..30ba032995845d1b81e382aac12e234839064499 100644 (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
index 9586fb8c8fc2dbe13b3c75ec7c21bd34e77e7a63..bca7c43f11081b6e463f63af6c1f40e7ece0a1a4 100644 (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 */
index 9c5f4e90670e75625f4b9017059e3519413d4c4c..0cf5ecaf56e89e52b79f06fb86ff007533a97124 100644 (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
 
index 3e54e011bbb5c6f5a3e3282317a9b040ca59fe3a..583899043adbbb7e25e15a6306102744208b60d3 100644 (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 */