]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: checks: Implement ssl-hello check using tcp-check rules
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 1 Apr 2020 09:10:27 +0000 (11:10 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Apr 2020 07:39:38 +0000 (09:39 +0200)
A shared tcp-check ruleset is now created to support ssl-hello check. This way
no extra memory is used if several backends use a ssl-hello check.

The following sequence is used :

    tcp-check send-binary SSLV3_CLIENT_HELLO log-format

    tcp-check expect rbinary "^1[56]" min-recv 5 \
        error-status "L6RSP" tout-status "L6TOUT"

SSLV3_CLIENT_HELLO is a log-format hexa string representing a SSLv3 CLIENT HELLO
packet. It is the same than the one used by the old ssl-hello except the sample
expression "%[date(),htonl,hex]" is used to set the date field.

include/proto/checks.h
include/types/checks.h
include/types/proxy.h
src/cfgparse-listen.c
src/cfgparse.c
src/checks.c

index 1c76a896845ccc3654c6a7c9394ad601b0608d12..33ed492c46096e01156a415282a91ca70ec5f8ac 100644 (file)
@@ -71,6 +71,9 @@ int spoe_handle_healthcheck_response(char *frame, size_t size, char *err, int er
 
 int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
                                const char *file, int line);
+int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                                 const char *file, int line);
+
 #endif /* _PROTO_CHECKS_H */
 
 /*
index b5f4649e226127464ce01ac932ea1b9f0176bd79..4830f0d945fe0f5f52b58cd04ede3a3d26bb2d38 100644 (file)
@@ -313,6 +313,7 @@ struct tcpcheck_rule {
 #define TCPCHK_RULES_DEF         0x00000002 /* Ruleset inherited from the default section */
 
 #define TCPCHK_RULES_REDIS_CHK   0x00000020
+#define TCPCHK_RULES_SSL3_CHK    0x00000070
 
 /* A list of tcp-check vars, to be registered before executing a ruleset */
 struct tcpcheck_var {
index 792c25225ce2553a941c03b2400e4e539f59baa9..9ace915d9ab0c0770dcab41fc349c6ed4773852a 100644 (file)
@@ -175,7 +175,7 @@ enum PR_SRV_STATE_FILE {
 #define PR_O2_HTTP_CHK  0x40000000      /* use HTTP 'OPTIONS' method to check server health */
 #define PR_O2_MYSQL_CHK 0x50000000      /* use MYSQL check for server health */
 #define PR_O2_LDAP_CHK  0x60000000      /* use LDAP check for server health */
-#define PR_O2_SSL3_CHK  0x70000000      /* use SSLv3 CLIENT_HELLO packets for server health */
+/* unused: 0x70000000 */
 #define PR_O2_LB_AGENT_CHK 0x80000000   /* use a TCP connection to obtain a metric of server health */
 #define PR_O2_TCPCHK_CHK 0x90000000     /* use TCPCHK check for server health */
 #define PR_O2_EXT_CHK   0xA0000000      /* use external command for server health */
index fdcf68e83111281a464f269adfd4b08b16c50e86..291dca80548b28945dd6383ef3f9101d810db093 100644 (file)
@@ -2397,16 +2397,8 @@ stats_error_parsing:
                                goto out;
                }
                else if (!strcmp(args[1], "ssl-hello-chk")) {
-                       /* use SSLv3 CLIENT HELLO to check servers' health */
-                       if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL))
-                               err_code |= ERR_WARN;
-
-                       free(curproxy->check_req);
-                       curproxy->check_req = NULL;
-                       curproxy->options2 &= ~PR_O2_CHK_ANY;
-                       curproxy->options2 |= PR_O2_SSL3_CHK;
-
-                       if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
+                       err_code |= proxy_parse_ssl_hello_chk_opt(args, 0, curproxy, &defproxy, file, linenum);
+                       if (err_code & ERR_FATAL)
                                goto out;
                }
                else if (!strcmp(args[1], "smtpchk")) {
index 6bd816017e448cfb5f71debac0894625762ef851..707046e4734ef2deaf413c4afc6f91dc52c4de8c 100644 (file)
 #include <proto/connection.h>
 
 
-/* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
- * ssl-hello-chk option to ensure that the remote server speaks SSL.
- *
- * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
- */
-const char sslv3_client_hello_pkt[] = {
-       "\x16"                /* ContentType         : 0x16 = Handshake          */
-       "\x03\x00"            /* ProtocolVersion     : 0x0300 = SSLv3            */
-       "\x00\x79"            /* ContentLength       : 0x79 bytes after this one */
-       "\x01"                /* HanshakeType        : 0x01 = CLIENT HELLO       */
-       "\x00\x00\x75"        /* HandshakeLength     : 0x75 bytes after this one */
-       "\x03\x00"            /* Hello Version       : 0x0300 = v3               */
-       "\x00\x00\x00\x00"    /* Unix GMT Time (s)   : filled with <now> (@0x0B) */
-       "HAPROXYSSLCHK\nHAPROXYSSLCHK\n" /* Random   : must be exactly 28 bytes  */
-       "\x00"                /* Session ID length   : empty (no session ID)     */
-       "\x00\x4E"            /* Cipher Suite Length : 78 bytes after this one   */
-       "\x00\x01" "\x00\x02" "\x00\x03" "\x00\x04" /* 39 most common ciphers :  */
-       "\x00\x05" "\x00\x06" "\x00\x07" "\x00\x08" /* 0x01...0x1B, 0x2F...0x3A  */
-       "\x00\x09" "\x00\x0A" "\x00\x0B" "\x00\x0C" /* This covers RSA/DH,       */
-       "\x00\x0D" "\x00\x0E" "\x00\x0F" "\x00\x10" /* various bit lengths,      */
-       "\x00\x11" "\x00\x12" "\x00\x13" "\x00\x14" /* SHA1/MD5, DES/3DES/AES... */
-       "\x00\x15" "\x00\x16" "\x00\x17" "\x00\x18"
-       "\x00\x19" "\x00\x1A" "\x00\x1B" "\x00\x2F"
-       "\x00\x30" "\x00\x31" "\x00\x32" "\x00\x33"
-       "\x00\x34" "\x00\x35" "\x00\x36" "\x00\x37"
-       "\x00\x38" "\x00\x39" "\x00\x3A"
-       "\x01"                /* Compression Length  : 0x01 = 1 byte for types   */
-       "\x00"                /* Compression Type    : 0x00 = NULL compression   */
-};
-
 /* Used to chain configuration sections definitions. This list
  * stores struct cfg_section
  */
@@ -3127,12 +3097,6 @@ out_uri_auth_compat:
                        }
                }
 
-               if ((curproxy->options2 & PR_O2_CHK_ANY) == PR_O2_SSL3_CHK) {
-                       curproxy->check_len = sizeof(sslv3_client_hello_pkt) - 1;
-                       curproxy->check_req = malloc(curproxy->check_len);
-                       memcpy(curproxy->check_req, sslv3_client_hello_pkt, curproxy->check_len);
-               }
-
                if (curproxy->tcpcheck_rules.list != NULL &&
                    (curproxy->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
                        ha_warning("config : %s '%s' uses tcp-check rules without 'option tcp-check', so the rules are ignored.\n",
index c7f156becba3dfdc92ad3b0fa430f5a26a9e1552..0c62a03b05fa980c8835eee9b1b0a87e6892df6c 100644 (file)
@@ -741,13 +741,9 @@ static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
                enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
 
                /* connection established but expired check */
-               if (check->type == PR_O2_SSL3_CHK)
-                       set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
-               else {  /* HTTP, SMTP, ... */
-                       if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
-                               tout = check->current_step->expect.tout_status;
-                       set_server_check_status(check, tout, err_msg);
-               }
+               if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
+                       tout = check->current_step->expect.tout_status;
+               set_server_check_status(check, tout, err_msg);
        }
 
        return;
@@ -953,17 +949,6 @@ static void __event_srv_chk_r(struct conn_stream *cs)
                }
                break;
 
-       case PR_O2_SSL3_CHK:
-               if (!done && b_data(&check->bi) < 5)
-                       goto wait_more_data;
-
-               /* Check for SSLv3 alert or handshake */
-               if ((b_data(&check->bi) >= 5) && (*b_head(&check->bi) == 0x15 || *b_head(&check->bi) == 0x16))
-                       set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
-               else
-                       set_server_check_status(check, HCHK_STATUS_L6RSP, NULL);
-               break;
-
        case PR_O2_SMTP_CHK:
                if (!done && b_data(&check->bi) < strlen("000\r"))
                        goto wait_more_data;
@@ -1623,15 +1608,10 @@ static int connect_conn_chk(struct task *t)
        if (check->type && check->type != PR_O2_TCPCHK_CHK && !(check->state & CHK_ST_AGENT)) {
                b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len);
 
-               /* we want to check if this host replies to HTTP or SSLv3 requests
+               /* we want to check if this host replies to HTTP requests
                 * so we'll send the request, and won't wake the checker up now.
                 */
-               if ((check->type) == PR_O2_SSL3_CHK) {
-                       /* SSL requires that we put Unix time in the request */
-                       int gmt_time = htonl(date.tv_sec);
-                       memcpy(b_head(&check->bo) + 11, &gmt_time, 4);
-               }
-               else if ((check->type) == PR_O2_HTTP_CHK) {
+               if ((check->type) == PR_O2_HTTP_CHK) {
                        /* prevent HTTP keep-alive when "http-check expect" is used */
                        if (s->proxy->options2 & PR_O2_EXP_TYPE)
                                b_putist(&check->bo, ist("Connection: close\r\n"));
@@ -5194,6 +5174,115 @@ int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, s
        goto out;
 }
 
+
+/* Parses the "option ssl-hello-chk" proxy keyword */
+int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                                 const char *file, int line)
+{
+       /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
+        * ssl-hello-chk option to ensure that the remote server speaks SSL.
+        *
+        * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
+        */
+       static char sslv3_client_hello[] = {
+               "16"                        /* ContentType         : 0x16 = Hanshake           */
+               "0300"                      /* ProtocolVersion     : 0x0300 = SSLv3            */
+               "0079"                      /* ContentLength       : 0x79 bytes after this one */
+               "01"                        /* HanshakeType        : 0x01 = CLIENT HELLO       */
+               "000075"                    /* HandshakeLength     : 0x75 bytes after this one */
+               "0300"                      /* Hello Version       : 0x0300 = v3               */
+               "%[date(),htonl,hex]"       /* Unix GMT Time (s)   : filled with <now> (@0x0B) */
+               "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random   : must be exactly 28 bytes  */
+               "00"                        /* Session ID length   : empty (no session ID)     */
+               "004E"                      /* Cipher Suite Length : 78 bytes after this one   */
+               "0001" "0002" "0003" "0004" /* 39 most common ciphers :  */
+               "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A  */
+               "0009" "000A" "000B" "000C" /* This covers RSA/DH,       */
+               "000D" "000E" "000F" "0010" /* various bit lengths,      */
+               "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
+               "0015" "0016" "0017" "0018"
+               "0019" "001A" "001B" "002F"
+               "0030" "0031" "0032" "0033"
+               "0034" "0035" "0036" "0037"
+               "0038" "0039" "003A"
+               "01"                       /* Compression Length  : 0x01 = 1 byte for types   */
+               "00"                       /* Compression Type    : 0x00 = NULL compression   */
+       };
+
+       struct tcpcheck_ruleset *rs = NULL;
+       struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
+       struct tcpcheck_rule *chk;
+       char *errmsg = NULL;
+       int err_code = 0;
+
+       if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
+               err_code |= ERR_WARN;
+
+       if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
+               goto out;
+
+       if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
+               ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
+                        file, line);
+               err_code |= ERR_ALERT | ERR_FATAL;
+               goto out;
+       }
+
+       curpx->options2 &= ~PR_O2_CHK_ANY;
+       curpx->options2 |= PR_O2_TCPCHK_CHK;
+
+       free_tcpcheck_vars(&rules->preset_vars);
+       rules->list  = NULL;
+       rules->flags = 0;
+
+       rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
+       if (rs)
+               goto ruleset_found;
+
+       rs = tcpcheck_ruleset_create("*ssl-hello-check");
+       if (rs == NULL) {
+               ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+               goto error;
+       }
+
+       chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
+                                 1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 0;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
+                                               "min-recv", "5",
+                                               "error-status", "L6RSP", "tout-status", "L6TOUT",
+                                               ""},
+                                   1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 1;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       LIST_ADDQ(&tcpchecks_list, &rs->list);
+
+  ruleset_found:
+       rules->list = &rs->rules;
+       rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SSL3_CHK);
+
+  out:
+       free(errmsg);
+       return err_code;
+
+  error:
+       tcpcheck_ruleset_release(rs);
+       err_code |= ERR_ALERT | ERR_FATAL;
+       goto out;
+}
+
+
 static struct cfg_kw_list cfg_kws = {ILH, {
         { CFG_LISTEN, "tcp-check",  proxy_parse_tcpcheck },
         { 0, NULL, NULL },