]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: spoe: Add support of ACLS to enable or disable sending of SPOE messages
authorChristopher Faulet <cfaulet@haproxy.com>
Mon, 4 Sep 2017 13:41:09 +0000 (15:41 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 31 Oct 2017 10:36:12 +0000 (11:36 +0100)
Now, it is possible to conditionnaly send a SPOE message by adding an ACL-based
condition on the "event" line, in a "spoe-message" section. Here is the example
coming for the SPOE documentation:

    spoe-message get-ip-reputation
        args ip=src
        event on-client-session if ! { src -f /etc/haproxy/whitelist.lst }

To avoid mixin with proxy's ACLs, each SPOE message has its private ACL list. It
possible to declare named ACLs in "spoe-message" section, using the same syntax
than for proxies. So we can rewrite the previous example to use a named ACL:

    spoe-message get-ip-reputation
        args ip=src
acl ip-whitelisted src -f /etc/haproxy/whitelist.lst
        event on-client-session if ! ip-whitelisted

ACL-based conditions are executed in the context of the stream that handle the
client and the server connections.

doc/SPOE.txt
include/types/spoe.h
src/flt_spoe.c

index 0f6ed14a512c21d553c82cc9dc44221cb977ba8a..611b22878698bb0516e17e8cce873751564a144b 100644 (file)
@@ -1,7 +1,7 @@
                 -----------------------------------------------
                    Stream Processing Offload Engine (SPOE)
-                                  Version 1.1
-                          ( Last update: 2017-02-27 )
+                                  Version 1.2
+                          ( Last update: 2017-09-22 )
                 -----------------------------------------------
                           Author : Christopher Faulet
                       Contact : cfaulet at haproxy dot com
@@ -351,12 +351,20 @@ spoe-message <name>
 
   Here you define a message that can be referenced in a "spoe-agent"
   section. Following keywords are supported :
+    - acl
     - args
     - event
 
   See also: "spoe-agent" section.
 
 
+acl <aclname> <criterion> [flags] [operator] <value> ...
+
+  Declare or complete an access list.
+
+  See section 7 about ACL usage in the HAProxy Configuration Manual.
+
+
 args [name=]<sample> ...
   Define arguments passed into the SPOE message.
 
@@ -371,11 +379,17 @@ args [name=]<sample> ...
     args frontend=fe_id src dst
 
 
-event <name>
-  Set the event that triggers sending of the message.
+event <name> [ { if | unless } <condition> ]
+  Set the event that triggers sending of the message. It may optionally be
+  followed by an ACL-based condition, in which case it will only be evaluated
+  if the condition is true.
+
+  ACL-based conditions are executed in the context of the stream that handle
+  the client and the server connections.
 
-  Argument :
-    <name>   is the event name.
+  Arguments :
+    <name>      is the event name.
+    <condition> is a standard ACL-based condition.
 
   Supported events are:
     - on-client-session
@@ -387,7 +401,8 @@ event <name>
     - on-backend-http-request
     - on-http-response
 
-  See section "Events & Messages".
+  See section "Events & Messages" for more details about supported events.
+  See section 7 about ACL usage in the HAProxy Configuration Manual.
 
 2.4. Example
 -------------
@@ -441,7 +456,7 @@ and 0 a blacklisted IP with no doubt).
 
     spoe-message get-ip-reputation
         args ip=src
-        event on-client-session
+        event on-client-session if ! { src -f /etc/haproxy/whitelist.lst }
 
 
 3. SPOP specification
index edbb459048936792b30e0a2b37cf6fbee29f9421..193c2f64b8a690d2426b80247669aab380ce4575 100644 (file)
@@ -184,6 +184,8 @@ struct spoe_message {
        struct list        args;    /* Arguments added when the SPOE messages is sent */
        struct list        list;    /* Used to chain SPOE messages */
 
+       struct list        acls;    /* ACL declared on this message */
+       struct acl_cond   *cond;    /* acl condition to meet */
        enum spoe_event    event;   /* SPOE_EV_* */
 };
 
index abaded156c26c0d664f538f63f3552b7df7e5443..dbc9d30755bf79eb998d0755306c42a0071db062 100644 (file)
@@ -23,6 +23,7 @@
 #include <types/global.h>
 #include <types/spoe.h>
 
+#include <proto/acl.h>
 #include <proto/arg.h>
 #include <proto/backend.h>
 #include <proto/filters.h>
@@ -107,18 +108,28 @@ spoe_release_msg_placeholder(struct spoe_msg_placeholder *mp)
 static void
 spoe_release_message(struct spoe_message *msg)
 {
-       struct spoe_arg *arg, *back;
+       struct spoe_arg *arg, *argback;
+       struct acl      *acl, *aclback;
 
        if (!msg)
                return;
        free(msg->id);
        free(msg->conf.file);
-       list_for_each_entry_safe(arg, back, &msg->args, list) {
+       list_for_each_entry_safe(arg, argback, &msg->args, list) {
                release_sample_expr(arg->expr);
                free(arg->name);
                LIST_DEL(&arg->list);
                free(arg);
        }
+       list_for_each_entry_safe(acl, aclback, &msg->acls, list) {
+               LIST_DEL(&acl->list);
+               prune_acl(acl);
+               free(acl);
+       }
+       if (msg->cond) {
+               prune_acl_cond(msg->cond);
+               free(msg->cond);
+       }
        free(msg);
 }
 
@@ -2070,7 +2081,8 @@ spoe_queue_context(struct spoe_context *ctx)
  **************************************************************************/
 /* Encode SPOE messages for a specific event. Info in <ctx->frag_ctx>, if any,
  * are used to handle fragmented content. On success it returns 1. If an error
- * occurred, -1 is returned. */
+ * occurred, -1 is returned. If nothing has been encoded, it returns 0 (this is
+ * only possible for unfragmented payload). */
 static int
 spoe_encode_messages(struct stream *s, struct spoe_context *ctx,
                     struct list *messages, int dir)
@@ -2099,6 +2111,19 @@ spoe_encode_messages(struct stream *s, struct spoe_context *ctx,
                ctx->frag_ctx.curoff = UINT_MAX;
 
          encode_message:
+               if (msg->cond) {
+                       int ret = 1;
+
+                       ret = acl_exec_cond(msg->cond, s->be, s->sess, s, dir|SMP_OPT_FINAL);
+                       ret = acl_pass(ret);
+                       if (msg->cond->pol == ACL_COND_UNLESS)
+                               ret = !ret;
+
+                       /* the rule does not match */
+                       if (!ret)
+                               continue;
+               }
+
                /* Resume encoding of a SPOE argument */
                if (ctx->frag_ctx.curarg != NULL) {
                        arg = ctx->frag_ctx.curarg;
@@ -2149,6 +2174,10 @@ spoe_encode_messages(struct stream *s, struct spoe_context *ctx,
                }
        }
 
+       /* nothing has been encoded for an unfragmented payload */
+       if (!(ctx->flags & SPOE_CTX_FL_FRAGMENTED) && p == ctx->buffer->p)
+               goto skip;
+
        SPOE_PRINTF(stderr, "%d.%06d [SPOE/%-15s] %s: stream=%p"
                    " - encode %s messages - spoe_appctx=%p"
                    "- max_size=%u - encoded=%ld\n",
@@ -2163,6 +2192,7 @@ spoe_encode_messages(struct stream *s, struct spoe_context *ctx,
        ctx->frag_ctx.curarg = NULL;
        ctx->frag_ctx.curoff = 0;
        ctx->frag_ctx.flags  = SPOE_FRM_FL_FIN;
+
        return 1;
 
   too_big:
@@ -2184,6 +2214,13 @@ spoe_encode_messages(struct stream *s, struct spoe_context *ctx,
        ctx->flags |= SPOE_CTX_FL_FRAGMENTED;
        ctx->frag_ctx.flags &= ~SPOE_FRM_FL_FIN;
        return 1;
+
+  skip:
+       SPOE_PRINTF(stderr, "%d.%06d [SPOE/%-15s] %s: stream=%p"
+                   " - skip the frame because nothing has been encoded\n",
+                   (int)now.tv_sec, (int)now.tv_usec,
+                   agent->id, __FUNCTION__, s);
+       return 0;
 }
 
 
@@ -2477,6 +2514,8 @@ spoe_process_event(struct stream *s, struct spoe_context *ctx,
                ret = spoe_encode_messages(s, ctx, &(ctx->messages[ev]), dir);
                if (ret < 0)
                        goto error;
+               if (!ret)
+                       goto skip;
                ctx->state = SPOE_CTX_ST_SENDING_MSGS;
        }
 
@@ -3320,6 +3359,7 @@ cfg_parse_spoe_message(const char *file, int linenum, char **args, int kwm)
                curmsg->conf.line = linenum;
                curmsg->nargs = 0;
                LIST_INIT(&curmsg->args);
+               LIST_INIT(&curmsg->acls);
                LIST_ADDQ(&curmsgs, &curmsg->list);
        }
        else if (!strcmp(args[0], "args")) {
@@ -3365,14 +3405,29 @@ cfg_parse_spoe_message(const char *file, int linenum, char **args, int kwm)
                curproxy->conf.args.file = NULL;
                curproxy->conf.args.line = 0;
        }
+       else if (!strcmp(args[0], "acl")) {
+               err = invalid_char(args[1]);
+               if (err) {
+                       Alert("parsing [%s:%d] : character '%c' is not permitted in acl name '%s'.\n",
+                             file, linenum, *err, args[1]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+               if (parse_acl((const char **)args + 1, &curmsg->acls, &errmsg, &curproxy->conf.args, file, linenum) == NULL) {
+                       Alert("parsing [%s:%d] : error detected while parsing ACL '%s' : %s.\n",
+                             file, linenum, args[1], errmsg);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+       }
        else if (!strcmp(args[0], "event")) {
                if (!*args[1]) {
                        Alert("parsing [%s:%d] : missing event name.\n", file, linenum);
                        err_code |= ERR_ALERT | ERR_FATAL;
                        goto out;
                }
-               if (alertif_too_many_args(1, file, linenum, args, &err_code))
-                       goto out;
+               /* if (alertif_too_many_args(1, file, linenum, args, &err_code)) */
+               /*      goto out; */
 
                if (!strcmp(args[1], spoe_event_str[SPOE_EV_ON_CLIENT_SESS]))
                        curmsg->event = SPOE_EV_ON_CLIENT_SESS;
@@ -3398,6 +3453,29 @@ cfg_parse_spoe_message(const char *file, int linenum, char **args, int kwm)
                        err_code |= ERR_ALERT | ERR_FATAL;
                        goto out;
                }
+
+               if (strcmp(args[2], "if") == 0 || strcmp(args[2], "unless") == 0) {
+                       struct acl_cond *cond;
+
+                       cond = build_acl_cond(file, linenum, &curmsg->acls,
+                                             curproxy, (const char **)args+2,
+                                             &errmsg);
+                       if (cond == NULL) {
+                               Alert("parsing [%s:%d] : error detected while "
+                                     "parsing an 'event %s' condition : %s.\n",
+                                     file, linenum, args[1], errmsg);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+                       curmsg->cond = cond;
+               }
+               else if (*args[2]) {
+                       Alert("parsing [%s:%d]: 'event %s' expects either 'if' "
+                             "or 'unless' followed by a condition but found '%s'.\n",
+                             file, linenum, args[1], args[2]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
        }
        else if (!*args[0]) {
                Alert("parsing [%s:%d] : unknown keyword '%s' in spoe-message section.\n",