]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: sample: extend the "when" converter to support an ACL
authorWilly Tarreau <w@1wt.eu>
Mon, 18 Nov 2024 14:27:28 +0000 (15:27 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 18 Nov 2024 15:11:55 +0000 (16:11 +0100)
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)]}"

doc/configuration.txt
src/sample.c

index 09340fdc2a2514dd62e104aa29bb5f4b271efc20..5cfdb2ee37cb8591f0447bb45ffd71748a3f8a93 100644 (file)
@@ -21485,14 +21485,15 @@ utime(<format>[,<offset>])
       # e.g.  20140710162350 127.0.0.1:57325
       log-format %[date,utime(%Y%m%d%H%M%S)]\ %ci:%cp
 
-when(<condition>)
+when(<condition>[,<args>...])
   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(<condition>)
 
     - "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(<condition>)
     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 \
index 52760d916eb2d1be8205377c2759c82ab8e3721b..ab44bfecc2d2a9adda7e19d6790281fba4970674 100644 (file)
 #include <import/mjson.h>
 #include <import/sha1.h>
 
+#include <haproxy/acl.h>
 #include <haproxy/api.h>
 #include <haproxy/arg.h>
 #include <haproxy/auth.h>
 #include <haproxy/base64.h>
 #include <haproxy/buf.h>
+#include <haproxy/cfgparse.h>
 #include <haproxy/chunk.h>
 #include <haproxy/clock.h>
 #include <haproxy/errors.h>
@@ -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 },
 }};