From 7fccccccead650373de04d416754d47ed1b5c6ba Mon Sep 17 00:00:00 2001 From: Patrick Hemmer Date: Mon, 24 Jul 2023 14:10:13 -0400 Subject: [PATCH] MINOR: acl: add acl() sample fetch This provides a sample fetch which returns the evaluation result of the conjunction of named ACLs. --- doc/configuration.txt | 9 ++++ include/haproxy/acl-t.h | 6 +++ include/haproxy/defaults.h | 6 +++ src/acl.c | 103 ++++++++++++++++++++++++++++++++++++- 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 99133a53d..2e255bfd7 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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([!][,...]) : 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. diff --git a/include/haproxy/acl-t.h b/include/haproxy/acl-t.h index f832137b8..34b7e40ca 100644 --- a/include/haproxy/acl-t.h +++ b/include/haproxy/acl-t.h @@ -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 */ /* diff --git a/include/haproxy/defaults.h b/include/haproxy/defaults.h index 0dc25818d..03c4d2c22 100644 --- a/include/haproxy/defaults.h +++ b/include/haproxy/defaults.h @@ -250,6 +250,12 @@ #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 diff --git a/src/acl.c b/src/acl.c index 198e1bfdd..f75e6ac89 100644 --- a/src/acl.c +++ b/src/acl.c @@ -28,6 +28,7 @@ #include #include #include +#include /* 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 [0], and with a list of already * known ACLs in . 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