From: Willy Tarreau Date: Mon, 18 Nov 2024 14:27:28 +0000 (+0100) Subject: MINOR: sample: extend the "when" converter to support an ACL X-Git-Tag: v3.1-dev14~131 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=45f9e95f2276dd712b07a241e22ef15d5e65de95;p=thirdparty%2Fhaproxy.git MINOR: sample: extend the "when" converter to support an ACL Sometimes conditions to decide of an anomaly are not as easy to define as just an error or a success. One example use case would be to monitor the transfer time and fix a threshold. An idea suggested by Tristan would be to make permit the "when" converter to refer to a more variable or dynamic condition. Here we make this possible by making "when" rely on a named ACL. The ACL then needs to be specified in either the proxy or the defaults section. Since it is evaluated inline, it may even refer to information available at the end (at log time) such as the data transfer time. If the ACL evalutates to true, the converter passes the data. Example: log "dbg={-}" when fine, or "dbg={... debug info ...}" on slow transfers: acl slow_xfer res.timer.data ge 10000 # more than 10s is slow log-format "$HAPROXY_HTTP_LOG_FMT \ fsdbg={%[fs.debug_str,when(acl,slow_xfer)]} \ bsdbg={%[bs.debug_str,when(acl,slow_xfer)]}" --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 09340fdc2a..5cfdb2ee37 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -21485,14 +21485,15 @@ utime([,]) # e.g. 20140710162350 127.0.0.1:57325 log-format %[date,utime(%Y%m%d%H%M%S)]\ %ci:%cp -when() +when([,...]) Evaluates the condition and when true, passes the input sample as-is to the output, otherwise return nothing. This is designed specifically to produce some rarely needed data that should only be emitted under certain conditions, such as debugging information when an error is met. The condition is made of a keyword among the list below, optionally preceeded - by an exclamation mark ('!') to negate it: + by an exclamation mark ('!') to negate it, and optionally suffixed by some + arguments specific to that condition: - "error" returns true when an error was encountered during the processing of the request or stream. It uses the same rules as "dontlog-normal" @@ -21512,6 +21513,12 @@ when() - "toapplet" returns true when the request was processed by an applet. + - "acl" returns true when the ACL designated by the next argument evaluates + to true. Note that the ACL is evaluated inline by the converter, so that + what it refers to must be valid in that context. A particular use case + consists in evaluating if the total transfer time is too long or not + before deciding to log detauls from abnormally long transfers. + Note that the content is evaluated in any case, so doing this does not avoid the generation of that information. It's only meant to avoid producing that information. @@ -21528,6 +21535,13 @@ when() indicate a missing content when the rule is not validated, and will emit a whole debugging block when it is. + Example + # log "dbg={-}" when fine, or "dbg={... debug info ...}" on slow transfers + acl slow_xfer res.timer.data ge 10000 # more than 10s is slow + log-format "$HAPROXY_HTTP_LOG_FMT \ + fsdbg={%[fs.debug_str,when(acl,slow_xfer)]} \ + bsdbg={%[bs.debug_str,when(acl,slow_xfer)]}" + Example # only emit the backend src/port when a real connection was issued: log-format "$HAPROXY_HTTP_LOG_FMT \ diff --git a/src/sample.c b/src/sample.c index 52760d916e..ab44bfecc2 100644 --- a/src/sample.c +++ b/src/sample.c @@ -19,11 +19,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -3825,6 +3827,7 @@ enum { WHEN_COND_FORWARDED, WHEN_COND_TOAPPLET, WHEN_COND_PROCESSED, + WHEN_COND_ACL, WHEN_COND_CONDITIONS }; @@ -3835,6 +3838,7 @@ const char *when_cond_kw[WHEN_COND_CONDITIONS] = { [WHEN_COND_FORWARDED] = "forwarded", [WHEN_COND_TOAPPLET] = "toapplet", [WHEN_COND_PROCESSED] = "processed", + [WHEN_COND_ACL] = "acl", }; /* Evaluates a condition and decides whether or not to pass the input sample @@ -3850,6 +3854,7 @@ static int sample_conv_when(const struct arg *arg_p, struct sample *smp, void *p struct stream *strm = smp->strm; int neg = arg_p[0].data.sint; int cond = arg_p[1].data.sint; + struct acl_sample *acl_sample; int ret = 0; switch (cond) { @@ -3881,6 +3886,11 @@ static int sample_conv_when(const struct arg *arg_p, struct sample *smp, void *p case WHEN_COND_PROCESSED: // true if forwarded or appctx ret = sc_conn(smp->strm->scb) || sc_appctx(smp->strm->scb); break; + + case WHEN_COND_ACL: // true if the ACL pointed to by args[2] evaluates to true + acl_sample = arg_p[2].data.ptr; + ret = acl_exec_cond(&acl_sample->cond, smp->px, smp->sess, smp->strm, smp->opt) == ACL_TEST_PASS; + break; } ret = !!ret ^ !!neg; @@ -3895,11 +3905,13 @@ static int sample_conv_when(const struct arg *arg_p, struct sample *smp, void *p /* checks and resolves the type of the argument passed to when(). * It supports an optional '!' to negate the condition, followed by - * a keyword among the list above. + * a keyword among the list above. Note that we're purposely declaring + * one extra arg because the first one will be split into two. */ static int check_when_cond(struct arg *args, struct sample_conv *conv, const char *file, int line, char **err) { + struct acl_sample *acl_sample; const char *kw; int neg = 0; int i; @@ -3923,7 +3935,52 @@ static int check_when_cond(struct arg *args, struct sample_conv *conv, return 0; } + if (i == WHEN_COND_ACL) { + if (args[1].type != ARGT_STR || !*args[1].data.str.area) { + memprintf(err, "'acl' selector requires an extra argument with the ACL name"); + return 0; + } + + if (!curproxy) { + memprintf(err, "'acl' selector may only be used in the context of a proxy"); + return 0; + } + + acl_sample = calloc(1, sizeof(struct acl_sample) + sizeof(struct acl_term)); + if (!acl_sample) { + memprintf(err, "not enough memory for 'acl' selector"); + return 0; + } + + LIST_INIT(&acl_sample->suite.terms); + LIST_INIT(&acl_sample->cond.suites); + LIST_APPEND(&acl_sample->cond.suites, &acl_sample->suite.list); + LIST_APPEND(&acl_sample->suite.terms, &acl_sample->terms[0].list); + acl_sample->cond.val = ~0U; // the keyword is valid everywhere for now. + + /* build one term based on the ACL kw */ + if (!(acl_sample->terms[0].acl = find_acl_by_name(args[1].data.str.area, &curproxy->acl)) && + !(acl_sample->terms[0].acl = find_acl_default(args[1].data.str.area, &curproxy->acl, err, NULL, NULL, 0))) { + memprintf(err, "ACL '%s' not found", args[1].data.str.area); + return 0; + } + + acl_sample->cond.use |= acl_sample->terms[0].acl->use; + acl_sample->cond.val &= acl_sample->terms[0].acl->val; + + args[2].type = ARGT_PTR; + args[2].unresolved = 0; + args[2].resolve_ptr = NULL; + args[2].data.ptr = acl_sample; + } + chunk_destroy(&args[0].data.str); + if (args[1].type == ARGT_STR) + chunk_destroy(&args[1].data.str); + + if (args[2].type == ARGT_STR) + chunk_destroy(&args[2].data.str); + // store condition args[0].type = ARGT_SINT; args[0].data.sint = neg; // '!' present @@ -5386,7 +5443,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, { { "jwt_payload_query", sample_conv_jwt_payload_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY }, { "jwt_verify", sample_conv_jwt_verify, ARG2(2,STR,STR), sample_conv_jwt_verify_check, SMP_T_BIN, SMP_T_SINT }, #endif - { "when", sample_conv_when, ARG1(1,STR), check_when_cond, SMP_T_ANY, SMP_T_ANY }, + { "when", sample_conv_when, ARG3(1,STR,STR,STR), check_when_cond, SMP_T_ANY, SMP_T_ANY }, { NULL, NULL, 0, 0, 0 }, }};