mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-06 15:17:01 +02:00
MEDIUM: http: add a new "http-response" ruleset
Some actions were clearly missing to process response headers. This patch adds a new "http-response" ruleset which provides the following actions : - allow : stop evaluating http-response rules - deny : stop and reject the response with a 502 - add-header : add a header in log-format mode - set-header : set a header in log-format mode
This commit is contained in:
parent
2b57cb8f30
commit
e365c0b92b
@ -1138,6 +1138,7 @@ http-check disable-on-404 X - X X
|
||||
http-check expect - - X X
|
||||
http-check send-state X - X X
|
||||
http-request - X X X
|
||||
http-response - X X X
|
||||
id - X X X
|
||||
ignore-persist - X X X
|
||||
log (*) X X X X
|
||||
@ -2760,6 +2761,52 @@ http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
|
||||
See also : "stats http-request", section 3.4 about userlists and section 7
|
||||
about ACL usage.
|
||||
|
||||
http-response { allow | deny | add-header <name> <fmt> |
|
||||
set-header <name> <fmt> } [ { if | unless } <condition> ]
|
||||
Access control for Layer 7 responses
|
||||
|
||||
May be used in sections: defaults | frontend | listen | backend
|
||||
no | yes | yes | yes
|
||||
|
||||
The http-response statement defines a set of rules which apply to layer 7
|
||||
processing. The rules are evaluated in their declaration order when they are
|
||||
met in a frontend, listen or backend section. Any rule may optionally be
|
||||
followed by an ACL-based condition, in which case it will only be evaluated
|
||||
if the condition is true. Since these rules apply on responses, the backend
|
||||
rules are applied first, followed by the frontend's rules.
|
||||
|
||||
The first keyword is the rule's action. Currently supported actions include :
|
||||
- "allow" : this stops the evaluation of the rules and lets the response
|
||||
pass the check. No further "http-response" rules are evaluated for the
|
||||
current section.
|
||||
|
||||
- "deny" : this stops the evaluation of the rules and immediately rejects
|
||||
the response and emits an HTTP 502 error. No further "http-response"
|
||||
rules are evaluated.
|
||||
|
||||
- "add-header" appends an HTTP header field whose name is specified in
|
||||
<name> and whose value is defined by <fmt> which follows the log-format
|
||||
rules (see Custom Log Format in section 8.2.4). This may be used to send
|
||||
a cookie to a client for example, or to pass some internal information.
|
||||
This rule is not final, so it is possible to add other similar rules.
|
||||
Note that header addition is performed immediately, so one rule might
|
||||
reuse the resulting header from a previous rule.
|
||||
|
||||
- "set-header" does the same as "add-header" except that the header name
|
||||
is first removed if it existed. This is useful when passing security
|
||||
information to the server, where the header must not be manipulated by
|
||||
external users.
|
||||
|
||||
There is no limit to the number of http-response statements per instance.
|
||||
|
||||
It is important to know that http-reqsponse rules are processed very early in
|
||||
the HTTP processing, before "reqdel" or "reqrep" rules. That way, headers
|
||||
added by "add-header"/"set-header" are visible by almost all further ACL
|
||||
rules.
|
||||
|
||||
See also : "http-request", section 3.4 about userlists and section 7 about
|
||||
ACL usage.
|
||||
|
||||
http-send-name-header [<header>]
|
||||
Add the server name to a request. Use the header string given by <header>
|
||||
|
||||
|
@ -111,6 +111,7 @@ void http_end_txn(struct session *s);
|
||||
void http_reset_txn(struct session *s);
|
||||
|
||||
struct http_req_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy);
|
||||
struct http_res_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy);
|
||||
void free_http_req_rules(struct list *r);
|
||||
struct chunk *http_error_message(struct session *s, int msgnum);
|
||||
struct redirect_rule *http_parse_redirect_rule(const char *file, int line, struct proxy *curproxy,
|
||||
|
@ -236,6 +236,7 @@ enum {
|
||||
HTTP_AUTH_DIGEST,
|
||||
};
|
||||
|
||||
/* actions for "http-request" */
|
||||
enum {
|
||||
HTTP_REQ_ACT_UNKNOWN = 0,
|
||||
HTTP_REQ_ACT_ALLOW,
|
||||
@ -248,6 +249,16 @@ enum {
|
||||
HTTP_REQ_ACT_MAX /* must always be last */
|
||||
};
|
||||
|
||||
/* actions for "http-response" */
|
||||
enum {
|
||||
HTTP_RES_ACT_UNKNOWN = 0,
|
||||
HTTP_RES_ACT_ALLOW,
|
||||
HTTP_RES_ACT_DENY,
|
||||
HTTP_RES_ACT_ADD_HDR,
|
||||
HTTP_RES_ACT_SET_HDR,
|
||||
HTTP_RES_ACT_MAX /* must always be last */
|
||||
};
|
||||
|
||||
/*
|
||||
* All implemented return codes
|
||||
*/
|
||||
@ -360,6 +371,19 @@ struct http_req_rule {
|
||||
} arg; /* arguments used by some actions */
|
||||
};
|
||||
|
||||
struct http_res_rule {
|
||||
struct list list;
|
||||
struct acl_cond *cond; /* acl condition to meet */
|
||||
unsigned int action; /* HTTP_RES_* */
|
||||
union {
|
||||
struct {
|
||||
char *name; /* header name */
|
||||
int name_len; /* header name's length */
|
||||
struct list fmt; /* log-format compatible expression */
|
||||
} hdr_add; /* args used by "add-header" and "set-header" */
|
||||
} arg; /* arguments used by some actions */
|
||||
};
|
||||
|
||||
/* This is an HTTP transaction. It contains both a request message and a
|
||||
* response message (which can be empty).
|
||||
*/
|
||||
|
@ -211,7 +211,8 @@ struct proxy {
|
||||
char *name; /* default backend name during config parse */
|
||||
} defbe;
|
||||
struct list acl; /* ACL declared on this proxy */
|
||||
struct list http_req_rules; /* HTTP request rules: allow/deny/http-auth */
|
||||
struct list http_req_rules; /* HTTP request rules: allow/deny/... */
|
||||
struct list http_res_rules; /* HTTP response rules: allow/deny/... */
|
||||
struct list block_cond; /* early blocking conditions (chained) */
|
||||
struct list redirect_rules; /* content redirecting rules (chained) */
|
||||
struct list switching_rules; /* content switching rules (chained) */
|
||||
|
@ -2635,6 +2635,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
|
||||
!LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->cond &&
|
||||
(LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_ALLOW ||
|
||||
LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_DENY ||
|
||||
LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_REDIR ||
|
||||
LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_AUTH)) {
|
||||
Warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n",
|
||||
file, linenum, args[0]);
|
||||
@ -2654,6 +2655,37 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
|
||||
|
||||
LIST_ADDQ(&curproxy->http_req_rules, &rule->list);
|
||||
}
|
||||
else if (!strcmp(args[0], "http-response")) { /* response access control */
|
||||
struct http_res_rule *rule;
|
||||
|
||||
if (curproxy == &defproxy) {
|
||||
Alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!LIST_ISEMPTY(&curproxy->http_res_rules) &&
|
||||
!LIST_PREV(&curproxy->http_res_rules, struct http_res_rule *, list)->cond &&
|
||||
(LIST_PREV(&curproxy->http_res_rules, struct http_res_rule *, list)->action == HTTP_RES_ACT_ALLOW ||
|
||||
LIST_PREV(&curproxy->http_res_rules, struct http_res_rule *, list)->action == HTTP_RES_ACT_DENY)) {
|
||||
Warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n",
|
||||
file, linenum, args[0]);
|
||||
err_code |= ERR_WARN;
|
||||
}
|
||||
|
||||
rule = parse_http_res_cond((const char **)args + 1, file, linenum, curproxy);
|
||||
|
||||
if (!rule) {
|
||||
err_code |= ERR_ALERT | ERR_ABORT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err_code |= warnif_cond_conflicts(rule->cond,
|
||||
(curproxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
|
||||
file, linenum);
|
||||
|
||||
LIST_ADDQ(&curproxy->http_res_rules, &rule->list);
|
||||
}
|
||||
else if (!strcmp(args[0], "http-send-name-header")) { /* send server name in request header */
|
||||
/* set the header name and length into the proxy structure */
|
||||
if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
|
||||
|
139
src/proto_http.c
139
src/proto_http.c
@ -3232,6 +3232,71 @@ http_req_get_intercept_rule(struct proxy *px, struct list *rules, struct session
|
||||
}
|
||||
|
||||
|
||||
/* Executes the http-response rules <rules> for session <s>, proxy <px> and
|
||||
* transaction <txn>. Returns the first rule that prevents further processing
|
||||
* of the response (deny, ...) or NULL if it executed all rules or stopped
|
||||
* on an allow. It may set the TX_SVDENY on txn->flags if it encounters a deny
|
||||
* rule.
|
||||
*/
|
||||
static struct http_res_rule *
|
||||
http_res_get_intercept_rule(struct proxy *px, struct list *rules, struct session *s, struct http_txn *txn)
|
||||
{
|
||||
struct http_res_rule *rule;
|
||||
struct hdr_ctx ctx;
|
||||
|
||||
list_for_each_entry(rule, rules, list) {
|
||||
if (rule->action >= HTTP_RES_ACT_MAX)
|
||||
continue;
|
||||
|
||||
/* check optional condition */
|
||||
if (rule->cond) {
|
||||
int ret;
|
||||
|
||||
ret = acl_exec_cond(rule->cond, px, s, txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL);
|
||||
ret = acl_pass(ret);
|
||||
|
||||
if (rule->cond->pol == ACL_COND_UNLESS)
|
||||
ret = !ret;
|
||||
|
||||
if (!ret) /* condition not matched */
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
switch (rule->action) {
|
||||
case HTTP_RES_ACT_ALLOW:
|
||||
return NULL; /* "allow" rules are OK */
|
||||
|
||||
case HTTP_RES_ACT_DENY:
|
||||
txn->flags |= TX_SVDENY;
|
||||
return rule;
|
||||
|
||||
case HTTP_RES_ACT_SET_HDR:
|
||||
ctx.idx = 0;
|
||||
/* remove all occurrences of the header */
|
||||
while (http_find_header2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len,
|
||||
txn->rsp.chn->buf->p, &txn->hdr_idx, &ctx)) {
|
||||
http_remove_header2(&txn->rsp, &txn->hdr_idx, &ctx);
|
||||
}
|
||||
/* now fall through to header addition */
|
||||
|
||||
case HTTP_RES_ACT_ADD_HDR:
|
||||
chunk_printf(&trash, "%s: ", rule->arg.hdr_add.name);
|
||||
memcpy(trash.str, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len);
|
||||
trash.len = rule->arg.hdr_add.name_len;
|
||||
trash.str[trash.len++] = ':';
|
||||
trash.str[trash.len++] = ' ';
|
||||
trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt);
|
||||
http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* we reached the end of the rules, nothing to report */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Perform an HTTP redirect based on the information in <rule>. The function
|
||||
* returns non-zero on success, or zero in case of a, irrecoverable error such
|
||||
* as too large a request to build a valid response.
|
||||
@ -5494,6 +5559,7 @@ int http_process_res_common(struct session *t, struct channel *rep, int an_bit,
|
||||
struct http_msg *msg = &txn->rsp;
|
||||
struct proxy *cur_proxy;
|
||||
struct cond_wordlist *wl;
|
||||
struct http_res_rule *http_res_last_rule = NULL;
|
||||
|
||||
DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%d analysers=%02x\n",
|
||||
now_ms, __FUNCTION__,
|
||||
@ -5591,6 +5657,10 @@ int http_process_res_common(struct session *t, struct channel *rep, int an_bit,
|
||||
while (1) {
|
||||
struct proxy *rule_set = cur_proxy;
|
||||
|
||||
/* evaluate http-response rules */
|
||||
if (!http_res_last_rule)
|
||||
http_res_last_rule = http_res_get_intercept_rule(cur_proxy, &cur_proxy->http_res_rules, t, txn);
|
||||
|
||||
/* try headers filters */
|
||||
if (rule_set->rsp_exp != NULL) {
|
||||
if (apply_filters_to_response(t, rep, rule_set) < 0) {
|
||||
@ -8283,6 +8353,7 @@ void free_http_req_rules(struct list *r) {
|
||||
}
|
||||
}
|
||||
|
||||
/* parse an "http-request" rule */
|
||||
struct http_req_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
|
||||
{
|
||||
struct http_req_rule *rule;
|
||||
@ -8384,6 +8455,74 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* parse an "http-respose" rule */
|
||||
struct http_res_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
|
||||
{
|
||||
struct http_res_rule *rule;
|
||||
int cur_arg;
|
||||
|
||||
rule = calloc(1, sizeof(*rule));
|
||||
if (!rule) {
|
||||
Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
if (!strcmp(args[0], "allow")) {
|
||||
rule->action = HTTP_RES_ACT_ALLOW;
|
||||
cur_arg = 1;
|
||||
} else if (!strcmp(args[0], "deny")) {
|
||||
rule->action = HTTP_RES_ACT_DENY;
|
||||
cur_arg = 1;
|
||||
} else if (strcmp(args[0], "add-header") == 0 || strcmp(args[0], "set-header") == 0) {
|
||||
rule->action = *args[0] == 'a' ? HTTP_RES_ACT_ADD_HDR : HTTP_RES_ACT_SET_HDR;
|
||||
cur_arg = 1;
|
||||
|
||||
if (!*args[cur_arg] || !*args[cur_arg+1] ||
|
||||
(*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
|
||||
Alert("parsing [%s:%d]: 'http-response %s' expects exactly 2 arguments.\n",
|
||||
file, linenum, args[0]);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
rule->arg.hdr_add.name = strdup(args[cur_arg]);
|
||||
rule->arg.hdr_add.name_len = strlen(rule->arg.hdr_add.name);
|
||||
LIST_INIT(&rule->arg.hdr_add.fmt);
|
||||
|
||||
proxy->conf.args.ctx = ARGC_HDR;
|
||||
parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, 0,
|
||||
(proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR);
|
||||
cur_arg += 2;
|
||||
} else {
|
||||
Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'set-header', but got '%s'%s.\n",
|
||||
file, linenum, args[0], *args[0] ? "" : " (missing argument)");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
|
||||
struct acl_cond *cond;
|
||||
char *errmsg = NULL;
|
||||
|
||||
if ((cond = build_acl_cond(file, linenum, proxy, args+cur_arg, &errmsg)) == NULL) {
|
||||
Alert("parsing [%s:%d] : error detected while parsing an 'http-response %s' condition : %s.\n",
|
||||
file, linenum, args[0], errmsg);
|
||||
free(errmsg);
|
||||
goto out_err;
|
||||
}
|
||||
rule->cond = cond;
|
||||
}
|
||||
else if (*args[cur_arg]) {
|
||||
Alert("parsing [%s:%d]: 'http-response %s' expects"
|
||||
" either 'if' or 'unless' followed by a condition but found '%s'.\n",
|
||||
file, linenum, args[0], args[cur_arg]);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
return rule;
|
||||
out_err:
|
||||
free(rule);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parses a redirect rule. Returns the redirect rule on success or NULL on error,
|
||||
* with <err> filled with the error message.
|
||||
*/
|
||||
|
@ -433,6 +433,7 @@ void init_new_proxy(struct proxy *p)
|
||||
LIST_INIT(&p->pendconns);
|
||||
LIST_INIT(&p->acl);
|
||||
LIST_INIT(&p->http_req_rules);
|
||||
LIST_INIT(&p->http_res_rules);
|
||||
LIST_INIT(&p->block_cond);
|
||||
LIST_INIT(&p->redirect_rules);
|
||||
LIST_INIT(&p->mon_fail_cond);
|
||||
|
Loading…
Reference in New Issue
Block a user