[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:
Willy Tarreau 2010-01-31 15:43:27 +01:00
parent 8abd4cd526
commit fdb563c06f
4 changed files with 103 additions and 31 deletions

View File

@ -3857,7 +3857,7 @@ retries <value>
See also : "option redispatch" See also : "option redispatch"
rspadd <string> rspadd <string> [{if | unless} <cond>]
Add a header at the end of the HTTP response Add a header at the end of the HTTP response
May be used in sections : defaults | frontend | listen | backend May be used in sections : defaults | frontend | listen | backend
no | yes | yes | yes no | yes | yes | yes
@ -3866,6 +3866,9 @@ rspadd <string>
must be escaped using a backslash ('\'). Please refer to section must be escaped using a backslash ('\'). Please refer to section
6 about HTTP header manipulation for more information. 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 A new line consisting in <string> followed by a line feed will be added after
the last header of an HTTP response. 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 and not to traffic generated by HAProxy, such as health-checks or error
responses. 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> rspdel <search> [{if | unless} <cond>]
rspidel <search> (ignore case) rspidel <search> [{if | unless} <cond>] (ignore case)
Delete all headers matching a regular expression in an HTTP response Delete all headers matching a regular expression in an HTTP response
May be used in sections : defaults | frontend | listen | backend May be used in sections : defaults | frontend | listen | backend
no | yes | yes | yes no | yes | yes | yes
@ -3890,6 +3894,9 @@ rspidel <search> (ignore case)
The "rspdel" keyword strictly matches case while "rspidel" The "rspdel" keyword strictly matches case while "rspidel"
ignores case. 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 Any header line matching extended regular expression <search> in the response
will be completely deleted. Most common use of this is to remove unwanted 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 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 # remove the Server header from responses
reqidel ^Server:.* reqidel ^Server:.*
See also: "rspadd", "rsprep", "reqdel" and section 6 about HTTP header See also: "rspadd", "rsprep", "reqdel", section 6 about HTTP header
manipulation manipulation, and section 7 about ACLs.
rspdeny <search> rspdeny <search> [{if | unless} <cond>]
rspideny <search> (ignore case) rspideny <search> [{if | unless} <cond>] (ignore case)
Block an HTTP response if a line matches a regular expression Block an HTTP response if a line matches a regular expression
May be used in sections : defaults | frontend | listen | backend May be used in sections : defaults | frontend | listen | backend
no | yes | yes | yes no | yes | yes | yes
@ -3921,6 +3928,9 @@ rspideny <search> (ignore case)
The "rspdeny" keyword strictly matches case while "rspideny" The "rspdeny" keyword strictly matches case while "rspideny"
ignores case. 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 A response containing any line which matches extended regular expression
<search> will mark the request as denied. The test applies both to the <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 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 # Ensure that no content type matching ms-word will leak
rspideny ^Content-type:\.*/ms-word rspideny ^Content-type:\.*/ms-word
See also: "reqdeny", "acl", "block" and section 6 about HTTP header See also: "reqdeny", "acl", "block", section 6 about HTTP header manipulation
manipulation and section 7 about ACLs.
rsprep <search> <string> rsprep <search> <string> [{if | unless} <cond>]
rspirep <search> <string> (ignore case) rspirep <search> <string> [{if | unless} <cond>] (ignore case)
Replace a regular expression with a string in an HTTP response line Replace a regular expression with a string in an HTTP response line
May be used in sections : defaults | frontend | listen | backend May be used in sections : defaults | frontend | listen | backend
no | yes | yes | yes 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 being a single digit between 0 and 9. Please refer to section
6 about HTTP header manipulation for more information. 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 Any line matching extended regular expression <search> in the response (both
the response line and header lines) will be completely replaced with the response line and header lines) will be completely replaced with
<string>. Most common use of this is to rewrite Location headers. <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" # replace "Location: 127.0.0.1:8080" with "Location: www.mydomain.com"
rspirep ^Location:\ 127.0.0.1:8080 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 See also: "rspadd", "rspdel", "reqrep", section 6 about HTTP header
manipulation manipulation, and section 7 about ACLs.
server <name> <address>[:port] [param*] server <name> <address>[:port] [param*]

View File

@ -2,7 +2,7 @@
* include/proto/proto_http.h * include/proto/proto_http.h
* This file contains HTTP protocol definitions. * 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 * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * 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_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_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_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_appsession(struct session *t, const char *buf, int len);
void manage_client_side_cookies(struct session *t, struct buffer *req); void manage_client_side_cookies(struct session *t, struct buffer *req);
void manage_server_side_cookies(struct session *t, struct buffer *rtr); void manage_server_side_cookies(struct session *t, struct buffer *rtr);

View File

@ -405,6 +405,23 @@ static int warnif_cond_requires_resp(const struct acl_cond *cond, const char *fi
return ERR_WARN; 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 * 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) if (dir == ACL_DIR_REQ)
err_code |= warnif_cond_requires_resp(cond, file, line); 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)); preg = calloc(1, sizeof(regex_t));
if (!preg) { if (!preg) {
@ -3770,21 +3789,21 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_RTR, ACT_REPLACE, 0, 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) if (err_code & ERR_FATAL)
goto out; goto out;
} }
else if (!strcmp(args[0], "rspdel")) { /* delete response header from a regex */ else if (!strcmp(args[0], "rspdel")) { /* delete response header from a regex */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_RTR, ACT_REMOVE, 0, 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) if (err_code & ERR_FATAL)
goto out; goto out;
} }
else if (!strcmp(args[0], "rspdeny")) { /* block response header from a regex */ else if (!strcmp(args[0], "rspdeny")) { /* block response header from a regex */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_RTR, ACT_DENY, 0, 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) if (err_code & ERR_FATAL)
goto out; goto out;
} }
@ -3798,21 +3817,21 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_RTR, ACT_REPLACE, REG_ICASE, 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) if (err_code & ERR_FATAL)
goto out; goto out;
} }
else if (!strcmp(args[0], "rspidel")) { /* delete response header from a regex ignoring case */ else if (!strcmp(args[0], "rspidel")) { /* delete response header from a regex ignoring case */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_RTR, ACT_REMOVE, REG_ICASE, 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) if (err_code & ERR_FATAL)
goto out; goto out;
} }
else if (!strcmp(args[0], "rspideny")) { /* block response header from a regex ignoring case */ else if (!strcmp(args[0], "rspideny")) { /* block response header from a regex ignoring case */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_RTR, ACT_DENY, REG_ICASE, 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) if (err_code & ERR_FATAL)
goto out; goto out;
} }
@ -3833,7 +3852,24 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
goto out; 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 = calloc(1, sizeof(*wl));
wl->cond = cond;
wl->s = strdup(args[1]); wl->s = strdup(args[1]);
LIST_ADDQ(&curproxy->rsp_add, &wl->list); LIST_ADDQ(&curproxy->rsp_add, &wl->list);
} }

View File

@ -4582,7 +4582,7 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
/* try headers filters */ /* try headers filters */
if (rule_set->rsp_exp != NULL) { 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: return_bad_resp:
if (t->srv) { if (t->srv) {
t->srv->counters.failed_resp++; 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) { list_for_each_entry(wl, &rule_set->rsp_add, list) {
if (txn->status < 200) if (txn->status < 200)
break; 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)) if (unlikely(http_header_add_tail(rep, &txn->rsp, &txn->hdr_idx, wl->s) < 0))
goto return_bad_resp; 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 * Returns 0 if everything is alright, or -1 in case a replacement lead to an
* unparsable response. * 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; struct http_txn *txn = &s->txn;
/* iterate through the filters in the outer loop */ struct hdr_exp *exp;
while (exp && !(txn->flags & TX_SVDENY)) {
for (exp = px->rsp_exp; exp; exp = exp->next) {
int ret; int ret;
/* /*
@ -5823,6 +5832,9 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
* the evaluation. * the evaluation.
*/ */
if (txn->flags & TX_SVDENY)
break;
if ((txn->flags & TX_SVALLOW) && if ((txn->flags & TX_SVALLOW) &&
(exp->action == ACT_ALLOW || exp->action == ACT_DENY || (exp->action == ACT_ALLOW || exp->action == ACT_DENY ||
exp->action == ACT_PASS)) { exp->action == ACT_PASS)) {
@ -5830,8 +5842,20 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
continue; 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. */ /* 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)) if (unlikely(ret < 0))
return -1; 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 /* The filter did not match the response, it can be
* iterated through all headers. * 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; return 0;
} }