mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-04-16 20:15:49 +00:00
[MEDIUM] http: add support for conditional response header rewriting
Just as for the req* rules, we can now condition rsp* rules with ACLs. ACLs match on response, so volatile request information cannot be used. A warning is emitted if a configuration contains such an anomaly.
This commit is contained in:
parent
8abd4cd526
commit
fdb563c06f
@ -3857,7 +3857,7 @@ retries <value>
|
||||
See also : "option redispatch"
|
||||
|
||||
|
||||
rspadd <string>
|
||||
rspadd <string> [{if | unless} <cond>]
|
||||
Add a header at the end of the HTTP response
|
||||
May be used in sections : defaults | frontend | listen | backend
|
||||
no | yes | yes | yes
|
||||
@ -3866,6 +3866,9 @@ rspadd <string>
|
||||
must be escaped using a backslash ('\'). Please refer to section
|
||||
6 about HTTP header manipulation for more information.
|
||||
|
||||
<cond> is an optional matching condition built from ACLs. It makes it
|
||||
possible to ignore this rule when other conditions are not met.
|
||||
|
||||
A new line consisting in <string> followed by a line feed will be added after
|
||||
the last header of an HTTP response.
|
||||
|
||||
@ -3873,11 +3876,12 @@ rspadd <string>
|
||||
and not to traffic generated by HAProxy, such as health-checks or error
|
||||
responses.
|
||||
|
||||
See also: "reqadd" and section 6 about HTTP header manipulation
|
||||
See also: "reqadd", section 6 about HTTP header manipulation, and section 7
|
||||
about ACLs.
|
||||
|
||||
|
||||
rspdel <search>
|
||||
rspidel <search> (ignore case)
|
||||
rspdel <search> [{if | unless} <cond>]
|
||||
rspidel <search> [{if | unless} <cond>] (ignore case)
|
||||
Delete all headers matching a regular expression in an HTTP response
|
||||
May be used in sections : defaults | frontend | listen | backend
|
||||
no | yes | yes | yes
|
||||
@ -3890,6 +3894,9 @@ rspidel <search> (ignore case)
|
||||
The "rspdel" keyword strictly matches case while "rspidel"
|
||||
ignores case.
|
||||
|
||||
<cond> is an optional matching condition built from ACLs. It makes it
|
||||
possible to ignore this rule when other conditions are not met.
|
||||
|
||||
Any header line matching extended regular expression <search> in the response
|
||||
will be completely deleted. Most common use of this is to remove unwanted
|
||||
and/or sensible headers or cookies from a response before passing it to the
|
||||
@ -3903,12 +3910,12 @@ rspidel <search> (ignore case)
|
||||
# remove the Server header from responses
|
||||
reqidel ^Server:.*
|
||||
|
||||
See also: "rspadd", "rsprep", "reqdel" and section 6 about HTTP header
|
||||
manipulation
|
||||
See also: "rspadd", "rsprep", "reqdel", section 6 about HTTP header
|
||||
manipulation, and section 7 about ACLs.
|
||||
|
||||
|
||||
rspdeny <search>
|
||||
rspideny <search> (ignore case)
|
||||
rspdeny <search> [{if | unless} <cond>]
|
||||
rspideny <search> [{if | unless} <cond>] (ignore case)
|
||||
Block an HTTP response if a line matches a regular expression
|
||||
May be used in sections : defaults | frontend | listen | backend
|
||||
no | yes | yes | yes
|
||||
@ -3921,6 +3928,9 @@ rspideny <search> (ignore case)
|
||||
The "rspdeny" keyword strictly matches case while "rspideny"
|
||||
ignores case.
|
||||
|
||||
<cond> is an optional matching condition built from ACLs. It makes it
|
||||
possible to ignore this rule when other conditions are not met.
|
||||
|
||||
A response containing any line which matches extended regular expression
|
||||
<search> will mark the request as denied. The test applies both to the
|
||||
response line and to response headers. Keep in mind that header names are not
|
||||
@ -3938,12 +3948,12 @@ rspideny <search> (ignore case)
|
||||
# Ensure that no content type matching ms-word will leak
|
||||
rspideny ^Content-type:\.*/ms-word
|
||||
|
||||
See also: "reqdeny", "acl", "block" and section 6 about HTTP header
|
||||
manipulation
|
||||
See also: "reqdeny", "acl", "block", section 6 about HTTP header manipulation
|
||||
and section 7 about ACLs.
|
||||
|
||||
|
||||
rsprep <search> <string>
|
||||
rspirep <search> <string> (ignore case)
|
||||
rsprep <search> <string> [{if | unless} <cond>]
|
||||
rspirep <search> <string> [{if | unless} <cond>] (ignore case)
|
||||
Replace a regular expression with a string in an HTTP response line
|
||||
May be used in sections : defaults | frontend | listen | backend
|
||||
no | yes | yes | yes
|
||||
@ -3962,6 +3972,9 @@ rspirep <search> <string> (ignore case)
|
||||
being a single digit between 0 and 9. Please refer to section
|
||||
6 about HTTP header manipulation for more information.
|
||||
|
||||
<cond> is an optional matching condition built from ACLs. It makes it
|
||||
possible to ignore this rule when other conditions are not met.
|
||||
|
||||
Any line matching extended regular expression <search> in the response (both
|
||||
the response line and header lines) will be completely replaced with
|
||||
<string>. Most common use of this is to rewrite Location headers.
|
||||
@ -3976,8 +3989,8 @@ rspirep <search> <string> (ignore case)
|
||||
# replace "Location: 127.0.0.1:8080" with "Location: www.mydomain.com"
|
||||
rspirep ^Location:\ 127.0.0.1:8080 Location:\ www.mydomain.com
|
||||
|
||||
See also: "rspadd", "rspdel", "reqrep" and section 6 about HTTP header
|
||||
manipulation
|
||||
See also: "rspadd", "rspdel", "reqrep", section 6 about HTTP header
|
||||
manipulation, and section 7 about ACLs.
|
||||
|
||||
|
||||
server <name> <address>[:port] [param*]
|
||||
|
@ -2,7 +2,7 @@
|
||||
* include/proto/proto_http.h
|
||||
* This file contains HTTP protocol definitions.
|
||||
*
|
||||
* Copyright (C) 2000-2009 Willy Tarreau - w@1wt.eu
|
||||
* Copyright (C) 2000-2010 Willy Tarreau - w@1wt.eu
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -76,7 +76,7 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len);
|
||||
int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hdr_exp *exp);
|
||||
int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_exp *exp);
|
||||
int apply_filters_to_request(struct session *s, struct buffer *req, struct proxy *px);
|
||||
int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp);
|
||||
int apply_filters_to_response(struct session *t, struct buffer *rtr, struct proxy *px);
|
||||
void manage_client_side_appsession(struct session *t, const char *buf, int len);
|
||||
void manage_client_side_cookies(struct session *t, struct buffer *req);
|
||||
void manage_server_side_cookies(struct session *t, struct buffer *rtr);
|
||||
|
@ -405,6 +405,23 @@ static int warnif_cond_requires_resp(const struct acl_cond *cond, const char *fi
|
||||
return ERR_WARN;
|
||||
}
|
||||
|
||||
/* Report it if a request ACL condition uses some request-only volatile parameters.
|
||||
* It returns either 0 or ERR_WARN so that its result can be or'ed with err_code.
|
||||
* Note that <cond> may be NULL and then will be ignored.
|
||||
*/
|
||||
static int warnif_cond_requires_req(const struct acl_cond *cond, const char *file, int line)
|
||||
{
|
||||
struct acl *acl;
|
||||
|
||||
if (!cond || !(cond->requires & ACL_USE_REQ_VOLATILE))
|
||||
return 0;
|
||||
|
||||
acl = cond_find_require(cond, ACL_USE_REQ_VOLATILE);
|
||||
Warning("parsing [%s:%d] : acl '%s' involves some volatile request-only criteria which will be ignored.\n",
|
||||
file, line, acl ? acl->name : "(unknown)");
|
||||
return ERR_WARN;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* parse a line in a <global> section. Returns the error code, 0 if OK, or
|
||||
@ -947,6 +964,8 @@ static int create_cond_regex_rule(const char *file, int line,
|
||||
|
||||
if (dir == ACL_DIR_REQ)
|
||||
err_code |= warnif_cond_requires_resp(cond, file, line);
|
||||
else
|
||||
err_code |= warnif_cond_requires_req(cond, file, line);
|
||||
|
||||
preg = calloc(1, sizeof(regex_t));
|
||||
if (!preg) {
|
||||
@ -3770,21 +3789,21 @@ stats_error_parsing:
|
||||
|
||||
err_code |= create_cond_regex_rule(file, linenum, curproxy,
|
||||
ACL_DIR_RTR, ACT_REPLACE, 0,
|
||||
args[0], args[1], args[2], NULL);
|
||||
args[0], args[1], args[2], (const char **)args+3);
|
||||
if (err_code & ERR_FATAL)
|
||||
goto out;
|
||||
}
|
||||
else if (!strcmp(args[0], "rspdel")) { /* delete response header from a regex */
|
||||
err_code |= create_cond_regex_rule(file, linenum, curproxy,
|
||||
ACL_DIR_RTR, ACT_REMOVE, 0,
|
||||
args[0], args[1], NULL, NULL);
|
||||
args[0], args[1], NULL, (const char **)args+2);
|
||||
if (err_code & ERR_FATAL)
|
||||
goto out;
|
||||
}
|
||||
else if (!strcmp(args[0], "rspdeny")) { /* block response header from a regex */
|
||||
err_code |= create_cond_regex_rule(file, linenum, curproxy,
|
||||
ACL_DIR_RTR, ACT_DENY, 0,
|
||||
args[0], args[1], NULL, NULL);
|
||||
args[0], args[1], NULL, (const char **)args+2);
|
||||
if (err_code & ERR_FATAL)
|
||||
goto out;
|
||||
}
|
||||
@ -3798,21 +3817,21 @@ stats_error_parsing:
|
||||
|
||||
err_code |= create_cond_regex_rule(file, linenum, curproxy,
|
||||
ACL_DIR_RTR, ACT_REPLACE, REG_ICASE,
|
||||
args[0], args[1], args[2], NULL);
|
||||
args[0], args[1], args[2], (const char **)args+3);
|
||||
if (err_code & ERR_FATAL)
|
||||
goto out;
|
||||
}
|
||||
else if (!strcmp(args[0], "rspidel")) { /* delete response header from a regex ignoring case */
|
||||
err_code |= create_cond_regex_rule(file, linenum, curproxy,
|
||||
ACL_DIR_RTR, ACT_REMOVE, REG_ICASE,
|
||||
args[0], args[1], NULL, NULL);
|
||||
args[0], args[1], NULL, (const char **)args+2);
|
||||
if (err_code & ERR_FATAL)
|
||||
goto out;
|
||||
}
|
||||
else if (!strcmp(args[0], "rspideny")) { /* block response header from a regex ignoring case */
|
||||
err_code |= create_cond_regex_rule(file, linenum, curproxy,
|
||||
ACL_DIR_RTR, ACT_DENY, REG_ICASE,
|
||||
args[0], args[1], NULL, NULL);
|
||||
args[0], args[1], NULL, (const char **)args+2);
|
||||
if (err_code & ERR_FATAL)
|
||||
goto out;
|
||||
}
|
||||
@ -3833,7 +3852,24 @@ stats_error_parsing:
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((strcmp(args[2], "if") == 0 || strcmp(args[2], "unless") == 0)) {
|
||||
if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args+2)) == NULL) {
|
||||
Alert("parsing [%s:%d] : error detected while parsing a '%s' condition.\n",
|
||||
file, linenum, args[0]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
err_code |= warnif_cond_requires_req(cond, file, linenum);
|
||||
}
|
||||
else if (*args[2]) {
|
||||
Alert("parsing [%s:%d] : '%s' : Expecting nothing, 'if', or 'unless', got '%s'.\n",
|
||||
file, linenum, args[0], args[2]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
wl = calloc(1, sizeof(*wl));
|
||||
wl->cond = cond;
|
||||
wl->s = strdup(args[1]);
|
||||
LIST_ADDQ(&curproxy->rsp_add, &wl->list);
|
||||
}
|
||||
|
@ -4582,7 +4582,7 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
|
||||
|
||||
/* try headers filters */
|
||||
if (rule_set->rsp_exp != NULL) {
|
||||
if (apply_filters_to_response(t, rep, rule_set->rsp_exp) < 0) {
|
||||
if (apply_filters_to_response(t, rep, rule_set) < 0) {
|
||||
return_bad_resp:
|
||||
if (t->srv) {
|
||||
t->srv->counters.failed_resp++;
|
||||
@ -4618,6 +4618,14 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
|
||||
list_for_each_entry(wl, &rule_set->rsp_add, list) {
|
||||
if (txn->status < 200)
|
||||
break;
|
||||
if (wl->cond) {
|
||||
int ret = acl_exec_cond(wl->cond, px, t, txn, ACL_DIR_RTR);
|
||||
ret = acl_pass(ret);
|
||||
if (((struct acl_cond *)wl->cond)->pol == ACL_COND_UNLESS)
|
||||
ret = !ret;
|
||||
if (!ret)
|
||||
continue;
|
||||
}
|
||||
if (unlikely(http_header_add_tail(rep, &txn->rsp, &txn->hdr_idx, wl->s) < 0))
|
||||
goto return_bad_resp;
|
||||
}
|
||||
@ -5806,15 +5814,16 @@ int apply_filter_to_sts_line(struct session *t, struct buffer *rtr, struct hdr_e
|
||||
|
||||
|
||||
/*
|
||||
* Apply all the resp filters <exp> to all headers in buffer <rtr> of session <t>.
|
||||
* Apply all the resp filters of proxy <px> to all headers in buffer <rtr> of session <s>.
|
||||
* Returns 0 if everything is alright, or -1 in case a replacement lead to an
|
||||
* unparsable response.
|
||||
*/
|
||||
int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp)
|
||||
int apply_filters_to_response(struct session *s, struct buffer *rtr, struct proxy *px)
|
||||
{
|
||||
struct http_txn *txn = &t->txn;
|
||||
/* iterate through the filters in the outer loop */
|
||||
while (exp && !(txn->flags & TX_SVDENY)) {
|
||||
struct http_txn *txn = &s->txn;
|
||||
struct hdr_exp *exp;
|
||||
|
||||
for (exp = px->rsp_exp; exp; exp = exp->next) {
|
||||
int ret;
|
||||
|
||||
/*
|
||||
@ -5823,6 +5832,9 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
|
||||
* the evaluation.
|
||||
*/
|
||||
|
||||
if (txn->flags & TX_SVDENY)
|
||||
break;
|
||||
|
||||
if ((txn->flags & TX_SVALLOW) &&
|
||||
(exp->action == ACT_ALLOW || exp->action == ACT_DENY ||
|
||||
exp->action == ACT_PASS)) {
|
||||
@ -5830,8 +5842,20 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if this filter had a condition, evaluate it now and skip to
|
||||
* next filter if the condition does not match.
|
||||
*/
|
||||
if (exp->cond) {
|
||||
ret = acl_exec_cond(exp->cond, px, s, txn, ACL_DIR_RTR);
|
||||
ret = acl_pass(ret);
|
||||
if (((struct acl_cond *)exp->cond)->pol == ACL_COND_UNLESS)
|
||||
ret = !ret;
|
||||
if (!ret)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Apply the filter to the status line. */
|
||||
ret = apply_filter_to_sts_line(t, rtr, exp);
|
||||
ret = apply_filter_to_sts_line(s, rtr, exp);
|
||||
if (unlikely(ret < 0))
|
||||
return -1;
|
||||
|
||||
@ -5839,9 +5863,8 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
|
||||
/* The filter did not match the response, it can be
|
||||
* iterated through all headers.
|
||||
*/
|
||||
apply_filter_to_resp_headers(t, rtr, exp);
|
||||
apply_filter_to_resp_headers(s, rtr, exp);
|
||||
}
|
||||
exp = exp->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user