]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: checks: Add matching on log-format string for expect rules
authorChristopher Faulet <cfaulet@haproxy.com>
Tue, 5 May 2020 13:54:22 +0000 (15:54 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Wed, 6 May 2020 06:31:29 +0000 (08:31 +0200)
It is now possible to use log-format string (or hexadecimal string for the
binary version) to match a content in tcp-check based expect rules. For
hexadecimal log-format string, the conversion in binary is performed after the
string evaluation, during health check execution. The pattern keywords to use
are "string-lf" for the log-format string and "binary-lf" for the hexadecimal
log-format string.

doc/configuration.txt
include/types/checks.h
src/checks.c

index b227742ce217696e65994b8e4f58da223dfbee2c..8018d907e5101b501c52d9eda2285a85e4f029d3 100644 (file)
@@ -4629,6 +4629,13 @@ http-check expect [min-recv <int>] [comment <msg>]
                       of a dynamic page, or to detect a failure when a specific
                       error appears on the check page (e.g. a stack trace).
 
+    string-lf <fmt> : test a log-format string match in the HTTP response body.
+                      A health check response will be considered valid if the
+                      response's body contains the  string resulting of the
+                      evaluation of <fmt>, which follows the log-format rules.
+                      If prefixed with "!", then the response will be
+                      considered invalid if the body contains the string.
+
   It is important to note that the responses will be limited to a certain size
   defined by the global "tune.chksize" option, which defaults to 16384 bytes.
   Thus, too large responses may not contain the mandatory pattern when using
@@ -10244,6 +10251,13 @@ tcp-check expect [min-recv <int>] [comment <msg>]
                       will be considered invalid if the body matches the
                       expression.
 
+    string-lf <fmt> : test a log-format string match in the response's buffer.
+                      A health check response will be considered valid if the
+                      response's buffer contains the  string resulting of the
+                      evaluation of <fmt>, which follows the log-format rules.
+                      If prefixed with "!", then the response will be
+                      considered invalid if the buffer contains the string.
+
     binary <hexstring> : test the exact string in its hexadecimal form matches
                          in the response buffer. A health check response will
                          be considered valid if the response's buffer contains
@@ -10259,6 +10273,17 @@ tcp-check expect [min-recv <int>] [comment <msg>]
                       pattern should work on at-most half the response buffer
                       size.
 
+    binary-lf <hexfmt> : test a log-format string in its hexadecimal form
+                         match in the response's buffer. A health check response
+                         will be considered valid if the response's buffer
+                         contains the hexadecimal string resulting of the
+                         evaluation of <fmt>, which follows the log-format
+                         rules. If prefixed with "!", then the response will be
+                         considered invalid if the buffer contains the
+                         hexadecimal string. The hexadecimal string is converted
+                         in a binary string before matching the response's
+                         buffer.
+
   It is important to note that the responses will be limited to a certain size
   defined by the global "tune.chksize" option, which defaults to 16384 bytes.
   Thus, too large responses may not contain the mandatory pattern when using
index 927dada33be263ab4815227b56e0af0db1970835..a59fb165c775ca2e869d327f868726bd619bb525 100644 (file)
@@ -247,14 +247,17 @@ enum tcpcheck_expect_type {
        TCPCHK_EXPECT_UNDEF = 0,         /* Match is not used. */
        TCPCHK_EXPECT_STRING,            /* Matches a string. */
        TCPCHK_EXPECT_REGEX,             /* Matches a regular pattern. */
+       TCPCHK_EXPECT_STRING_LF,         /* Matches a log-format string. */
        TCPCHK_EXPECT_REGEX_BINARY,      /* Matches a regular pattern on a hex-encoded text. */
        TCPCHK_EXPECT_BINARY,            /* Matches a binary sequence on a hex-encoded text. */
+       TCPCHK_EXPECT_BINARY_LF,         /* Matches a log-format binary sequence on a hex-encoded text. */
        TCPCHK_EXPECT_CUSTOM,            /* Execute a custom function. */
        TCPCHK_EXPECT_HTTP_STATUS,       /* Matches a list of codes on the HTTP status */
        TCPCHK_EXPECT_HTTP_REGEX_STATUS, /* Matches a regular pattern on the HTTP status */
        TCPCHK_EXPECT_HTTP_HEADER,       /* Matches on HTTP headers */
        TCPCHK_EXPECT_HTTP_BODY,         /* Matches a string oa the HTTP payload */
        TCPCHK_EXPECT_HTTP_REGEX_BODY,   /* Matches a regular pattern on a HTTP payload */
+       TCPCHK_EXPECT_HTTP_BODY_LF,      /* Matches a log-format string on the HTTP payload */
 };
 
 /* tcp-check expect flags */
@@ -276,6 +279,7 @@ enum tcpcheck_expect_type {
 
 #define TCPCHK_EXPT_FL_HTTP_HNAME_TYPE 0x003E /* Mask to get matching method on header name */
 #define TCPCHK_EXPT_FL_HTTP_HVAL_TYPE  0x1F00 /* Mask to get matching method on header value */
+
 struct tcpcheck_expect {
        enum tcpcheck_expect_type type;   /* Type of pattern used for matching. */
        unsigned int flags;               /* TCPCHK_EXPT_FL_* */
@@ -283,6 +287,7 @@ struct tcpcheck_expect {
                struct ist data;             /* Matching a literal string / binary anywhere in the response. */
                struct my_regex *regex;      /* Matching a regex pattern. */
                struct tcpcheck_codes codes; /* Matching a list of codes */
+               struct list fmt;             /* Matching a log-format string / binary */
                struct {
                        union {
                                struct ist name;
index 8b4cff28bdd2e80c44544eadb197f87e9e787ebb..e19964a8930c0581d17ba7cedfa18a4cff38b0c3 100644 (file)
@@ -600,6 +600,12 @@ static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
                                case TCPCHK_EXPECT_REGEX_BINARY:
                                        chunk_appendf(chk, " (expect binary regex)");
                                        break;
+                               case TCPCHK_EXPECT_STRING_LF:
+                                       chunk_appendf(chk, " (expect log-format string)");
+                                       break;
+                               case TCPCHK_EXPECT_BINARY_LF:
+                                       chunk_appendf(chk, " (expect log-format binary)");
+                                       break;
                                case TCPCHK_EXPECT_HTTP_STATUS:
                                        chunk_appendf(chk, " (expect HTTP status codes)");
                                        break;
@@ -615,6 +621,9 @@ static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
                                case TCPCHK_EXPECT_HTTP_REGEX_BODY:
                                        chunk_appendf(chk, " (expect HTTP body regex)");
                                        break;
+                               case TCPCHK_EXPECT_HTTP_BODY_LF:
+                                       chunk_appendf(chk, " (expect log-format HTTP body)");
+                                       break;
                                case TCPCHK_EXPECT_CUSTOM:
                                        chunk_appendf(chk, " (expect custom function)");
                                        break;
@@ -799,6 +808,11 @@ static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
                case TCPCHK_EXPECT_HTTP_REGEX_BODY:
                        regex_free(rule->expect.regex);
                        break;
+               case TCPCHK_EXPECT_STRING_LF:
+               case TCPCHK_EXPECT_BINARY_LF:
+               case TCPCHK_EXPECT_HTTP_BODY_LF:
+                       free_tcpcheck_fmt(&rule->expect.fmt);
+                       break;
                case TCPCHK_EXPECT_HTTP_HEADER:
                        if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
                                regex_free(rule->expect.hdr.name_re);
@@ -1084,6 +1098,13 @@ static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *ch
        case TCPCHK_EXPECT_REGEX_BINARY:
                chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
                break;
+       case TCPCHK_EXPECT_STRING_LF:
+       case TCPCHK_EXPECT_HTTP_BODY_LF:
+               chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
+               break;
+       case TCPCHK_EXPECT_BINARY_LF:
+               chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
+               break;
        case TCPCHK_EXPECT_CUSTOM:
                chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
                break;
@@ -2136,7 +2157,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
        struct htx_blk *blk;
        enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
        struct tcpcheck_expect *expect = &rule->expect;
-       struct buffer *msg = NULL, *nbuf = NULL, *vbuf = NULL;
+       struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
        enum healthcheck_status status = HCHK_STATUS_L7RSP;
        struct ist desc = IST_NULL;
        int i, match, inverse;
@@ -2319,6 +2340,8 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
 
        case TCPCHK_EXPECT_HTTP_BODY:
        case TCPCHK_EXPECT_HTTP_REGEX_BODY:
+       case TCPCHK_EXPECT_HTTP_BODY_LF:
+               match = 0;
                chunk_reset(&trash);
                for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
                        enum htx_blk_type type = htx_get_blk_type(blk);
@@ -2340,8 +2363,24 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
                        goto error;
                }
 
+               if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
+                       tmp = alloc_trash_chunk();
+                       if (!tmp) {
+                               status = HCHK_STATUS_L7RSP;
+                               desc = ist("Failed to allocate buffer to eval log-format string");
+                               goto error;
+                       }
+                       tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
+                       if (!b_data(tmp)) {
+                               status = HCHK_STATUS_L7RSP;
+                               desc = ist("log-format string evaluated to an empty string");
+                               goto error;
+                       }
+               }
+
                if (!last_read &&
                    ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
+                    ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
                     (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
                        ret = TCPCHK_EVAL_WAIT;
                        goto out;
@@ -2381,6 +2420,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
                goto error;
 
   out:
+       free_trash_chunk(tmp);
        free_trash_chunk(nbuf);
        free_trash_chunk(vbuf);
        free_trash_chunk(msg);
@@ -2407,7 +2447,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t
 {
        enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
        struct tcpcheck_expect *expect = &rule->expect;
-       struct buffer *msg = NULL;
+       struct buffer *msg = NULL, *tmp = NULL;
        struct ist desc = IST_NULL;
        enum healthcheck_status status;
        int match, inverse;
@@ -2448,6 +2488,41 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t
                dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
                match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
                break;
+
+       case TCPCHK_EXPECT_STRING_LF:
+       case TCPCHK_EXPECT_BINARY_LF:
+               match = 0;
+               tmp = alloc_trash_chunk();
+               if (!tmp) {
+                       status = HCHK_STATUS_L7RSP;
+                       desc = ist("Failed to allocate buffer to eval format string");
+                       goto error;
+               }
+               tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
+               if (!b_data(tmp)) {
+                       status = HCHK_STATUS_L7RSP;
+                       desc = ist("log-format string evaluated to an empty string");
+                       goto error;
+               }
+               if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
+                       int len = tmp->data;
+                       if (parse_binary(b_orig(tmp),  &tmp->area, &len, NULL) == 0) {
+                               status = HCHK_STATUS_L7RSP;
+                               desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
+                               goto error;
+                       }
+                       tmp->data = len;
+               }
+               if (b_data(&check->bi) < tmp->data) {
+                       if (!last_read) {
+                               ret = TCPCHK_EVAL_WAIT;
+                               goto out;
+                       }
+                       break;
+               }
+               match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
+               break;
+
        case TCPCHK_EXPECT_CUSTOM:
                if (expect->custom)
                        ret = expect->custom(check, rule, last_read);
@@ -2471,7 +2546,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t
        if (match ^ inverse)
                goto out;
 
-
+  error:
        /* From this point on, we matched something we did not want, this is an error state. */
        ret = TCPCHK_EVAL_STOP;
        msg = alloc_trash_chunk();
@@ -2481,6 +2556,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct t
        free_trash_chunk(msg);
 
   out:
+       free_trash_chunk(tmp);
        return ret;
 }
 
@@ -4194,6 +4270,26 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                        cur_arg++;
                        pattern = args[cur_arg];
                }
+               else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
+                       if (type != TCPCHK_EXPECT_UNDEF) {
+                               memprintf(errmsg, "only on pattern expected");
+                               goto error;
+                       }
+                       if (proto != TCPCHK_RULES_HTTP_CHK)
+                               type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
+                       else {
+                               if (*(args[cur_arg]) != 's')
+                                       goto bad_http_kw;
+                               type = TCPCHK_EXPECT_HTTP_BODY_LF;
+                       }
+
+                       if (!*(args[cur_arg+1])) {
+                               memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
+                               goto error;
+                       }
+                       cur_arg++;
+                       pattern = args[cur_arg];
+               }
                else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
                        if (proto != TCPCHK_RULES_HTTP_CHK)
                                goto bad_tcp_kw;
@@ -4475,13 +4571,13 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                else {
                        if (proto == TCPCHK_RULES_HTTP_CHK) {
                          bad_http_kw:
-                               memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
-                                         "[!]header or comment but got '%s' as argument.", args[cur_arg]);
+                               memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
+                                         "'[!]rstatus', [!]header or comment but got '%s' as argument.", args[cur_arg]);
                        }
                        else {
                          bad_tcp_kw:
-                               memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
-                                         " or comment but got '%s' as argument.", args[cur_arg]);
+                               memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
+                                         "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
                        }
                        goto error;
                }
@@ -4585,6 +4681,18 @@ static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, str
                if (!chk->expect.regex)
                        goto error;
                break;
+
+       case TCPCHK_EXPECT_STRING_LF:
+       case TCPCHK_EXPECT_BINARY_LF:
+       case TCPCHK_EXPECT_HTTP_BODY_LF:
+               LIST_INIT(&chk->expect.fmt);
+               px->conf.args.ctx = ARGC_SRV;
+               if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
+                       memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
+                       goto error;
+               }
+               break;
+
        case TCPCHK_EXPECT_HTTP_HEADER:
                if (!npat) {
                        memprintf(errmsg, "unexpected error, undefined header name pattern");