]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: acl: add acl() sample fetch
authorPatrick Hemmer <patrick.hemmer@gmail.com>
Mon, 24 Jul 2023 18:10:13 +0000 (14:10 -0400)
committerWilly Tarreau <w@1wt.eu>
Tue, 1 Aug 2023 08:49:06 +0000 (10:49 +0200)
This provides a sample fetch which returns the evaluation result
of the conjunction of named ACLs.

doc/configuration.txt
include/haproxy/acl-t.h
include/haproxy/defaults.h
src/acl.c

index 99133a53db839165f42c65d3c39c98fdf22649e5..2e255bfd78169ee1538d3e5c5c348bfaf7d82b40 100644 (file)
@@ -19393,6 +19393,15 @@ The sample fetch methods described in this section are usable anywhere.
 act_conn : integer
   Returns the total number of active concurrent connections on the process.
 
+acl([!]<name>[,...]) : boolean
+  Returns true if the evaluation of all the named ACL(s) is true, otherwise
+  returns false. Up to 12 ACLs may be provided, each delimited by comma. Each
+  named ACL may be prefixed with a "!" to invert the result. If any evaluation
+  produces an error then the sample also returns an error.
+  Note that HAProxy does not perform any validation checks on the referenced
+  ACLs, such as whether an ACL which uses a http request sample is used in
+  response context. This behavior may be changed in the future.
+
 always_false : boolean
   Always returns the boolean "false" value. It may be used with ACLs as a
   temporary replacement for another one when adjusting configurations.
index f832137b84be87ac64ed4049476e702f0a047987..34b7e40ca3010eecf36a3bf684a0ea9f4d7da459 100644 (file)
@@ -144,6 +144,12 @@ struct acl_cond {
        int line;                   /* line in the config file where the condition is declared */
 };
 
+struct acl_sample {
+       struct acl_cond cond;
+       struct acl_term_suite suite;
+       struct acl_term terms[];
+};
+
 #endif /* _HAPROXY_ACL_T_H */
 
 /*
index 0dc25818d833a7683979ffffc8504ba467d650bf..03c4d2c2261141cf9a51afa2335b79d060feafa2 100644 (file)
 #define COOKIE_DELIM_DATE       '|'
 #endif
 
+// Max number of acl() sample fetch recursive evaluations, to avoid deep tree
+// loops.
+#ifndef ACL_MAX_RECURSE
+#define ACL_MAX_RECURSE 1000
+#endif
+
 #define CONN_RETRIES    3
 
 #define        CHK_CONNTIME    2000
index 198e1bfdddc2d2caf06ba0873f659f7076b6651d..f75e6ac89673ebba1011e9265b4f3541357f0a11 100644 (file)
--- a/src/acl.c
+++ b/src/acl.c
@@ -28,6 +28,7 @@
 #include <haproxy/sample.h>
 #include <haproxy/stick_table.h>
 #include <haproxy/tools.h>
+#include <haproxy/cfgparse.h>
 
 /* List head of all known ACL keywords */
 static struct acl_kw_list acl_keywords = {
@@ -576,6 +577,35 @@ struct acl *prune_acl(struct acl *acl) {
        return acl;
 }
 
+/* Walk the ACL tree, following nested acl() sample fetches, for no more than
+ * max_recurse evaluations. Returns -1 if a recursive loop is detected, 0 if
+ * the max_recurse was reached, otherwise the number of max_recurse left.
+ */
+static int parse_acl_recurse(struct acl *acl, struct acl_expr *expr, int max_recurse)
+{
+       struct acl_term *term;
+       struct acl_sample *sample;
+
+       if (strcmp(expr->smp->fetch->kw, "acl") != 0)
+               return max_recurse;
+
+       if (--max_recurse <= 0)
+               return 0;
+
+       sample = (struct acl_sample *)expr->smp->arg_p->data.ptr;
+       list_for_each_entry(term, &sample->suite.terms, list) {
+               if (term->acl == acl)
+                       return -1;
+               list_for_each_entry(expr, &term->acl->expr, list) {
+                       max_recurse = parse_acl_recurse(acl, expr, max_recurse);
+                       if (max_recurse <= 0)
+                               return max_recurse;
+               }
+       }
+
+       return max_recurse;
+}
+
 /* Parse an ACL with the name starting at <args>[0], and with a list of already
  * known ACLs in <acl>. If the ACL was not in the list, it will be added.
  * A pointer to that ACL is returned. If the ACL has an empty name, then it's
@@ -623,7 +653,16 @@ struct acl *parse_acl(const char **args, struct list *known_acl, char **err, str
        else
                cur_acl = NULL;
 
-       if (!cur_acl) {
+       if (cur_acl) {
+               int ret = parse_acl_recurse(cur_acl, acl_expr, ACL_MAX_RECURSE);
+               if (ret <= 0) {
+                       if (ret < 0)
+                               memprintf(err, "have a recursive loop");
+                       else
+                               memprintf(err, "too deep acl() tree");
+                       goto out_free_acl_expr;
+               }
+       } else {
                name = strdup(args[0]);
                if (!name) {
                        memprintf(err, "out of memory when parsing ACL");
@@ -1254,6 +1293,61 @@ void free_acl_cond(struct acl_cond *cond)
        free(cond);
 }
 
+
+static int smp_fetch_acl(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+       struct acl_sample *acl_sample = (struct acl_sample *)args->data.ptr;
+       enum acl_test_res ret;
+
+       ret = acl_exec_cond(&acl_sample->cond, smp->px, smp->sess, smp->strm, smp->opt);
+       if (ret == ACL_TEST_MISS)
+               return 0;
+       smp->data.u.sint = ret == ACL_TEST_PASS;
+       smp->data.type = SMP_T_BOOL;
+       return 1;
+}
+
+int smp_fetch_acl_parse(struct arg *args, char **err_msg)
+{
+       struct acl_sample *acl_sample;
+       char *name;
+       int i;
+
+       for (i = 0; args[i].type != ARGT_STOP; i++)
+               ;
+       acl_sample = calloc(1, sizeof(struct acl_sample) + sizeof(struct acl_term) * i);
+       LIST_INIT(&acl_sample->suite.terms);
+       LIST_INIT(&acl_sample->cond.suites);
+       LIST_APPEND(&acl_sample->cond.suites, &acl_sample->suite.list);
+       acl_sample->cond.val = ~0U; // the keyword is valid everywhere for now.
+
+       args->data.ptr = acl_sample;
+
+       for (i = 0; args[i].type != ARGT_STOP; i++) {
+               name = args[i].data.str.area;
+               if (name[0] == '!') {
+                       acl_sample->terms[i].neg = 1;
+                       name++;
+               }
+
+               if (!(acl_sample->terms[i].acl = find_acl_by_name(name, &curproxy->acl))) {
+                       memprintf(err_msg, "ACL '%s' not found", name);
+                       goto err;
+               }
+
+               acl_sample->cond.use |= acl_sample->terms[i].acl->use;
+               acl_sample->cond.val &= acl_sample->terms[i].acl->val;
+
+               LIST_APPEND(&acl_sample->suite.terms, &acl_sample->terms[i].list);
+       }
+
+       return 1;
+
+err:
+       free(acl_sample);
+       return 0;
+}
+
 /************************************************************************/
 /*      All supported sample and ACL keywords must be declared here.    */
 /************************************************************************/
@@ -1267,6 +1361,13 @@ static struct acl_kw_list acl_kws = {ILH, {
 
 INITCALL1(STG_REGISTER, acl_register_keywords, &acl_kws);
 
+static struct sample_fetch_kw_list smp_kws = {ILH, {
+       { "acl", smp_fetch_acl, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), smp_fetch_acl_parse, SMP_T_BOOL, SMP_USE_CONST },
+       { /* END */ },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
+
 /*
  * Local variables:
  *  c-indent-level: 8