MEDIUM: checks: Add matching on log-format string for expect rules

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.
This commit is contained in:
Christopher Faulet 2020-05-05 15:54:22 +02:00
parent 0d6909b33b
commit aaab0836d9
3 changed files with 145 additions and 7 deletions

View 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

View 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;

View 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");