MEDIUM: spoe: Add support of ACLS to enable or disable sending of SPOE messages

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.
This commit is contained in:
Christopher Faulet 2017-09-04 15:41:09 +02:00 committed by Willy Tarreau
parent 1b421eab87
commit 57583e474e
3 changed files with 108 additions and 13 deletions

View 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.
Argument :
<name> is the event name.
ACL-based conditions are executed in the context of the stream that handle
the client and the server connections.
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

View 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_* */
};

View 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",