From fdb563c06fd57b509efed5532c7449ee25e2bc4b Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 31 Jan 2010 15:43:27 +0100 Subject: [PATCH] [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. --- doc/configuration.txt | 41 +++++++++++++++++++++----------- include/proto/proto_http.h | 4 ++-- src/cfgparse.c | 48 +++++++++++++++++++++++++++++++++----- src/proto_http.c | 41 +++++++++++++++++++++++++------- 4 files changed, 103 insertions(+), 31 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index e6cffa977..3c27e8eb9 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3857,7 +3857,7 @@ retries See also : "option redispatch" -rspadd +rspadd [{if | unless} ] 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 must be escaped using a backslash ('\'). Please refer to section 6 about HTTP header manipulation for more information. + 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 followed by a line feed will be added after the last header of an HTTP response. @@ -3873,11 +3876,12 @@ rspadd 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 -rspidel (ignore case) +rspdel [{if | unless} ] +rspidel [{if | unless} ] (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 (ignore case) The "rspdel" keyword strictly matches case while "rspidel" ignores case. + 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 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 (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 -rspideny (ignore case) +rspdeny [{if | unless} ] +rspideny [{if | unless} ] (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 (ignore case) The "rspdeny" keyword strictly matches case while "rspideny" ignores case. + 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 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 (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 -rspirep (ignore case) +rsprep [{if | unless} ] +rspirep [{if | unless} ] (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 (ignore case) being a single digit between 0 and 9. Please refer to section 6 about HTTP header manipulation for more information. + 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 in the response (both the response line and header lines) will be completely replaced with . Most common use of this is to rewrite Location headers. @@ -3976,8 +3989,8 @@ rspirep (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
[:port] [param*] diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 6a424c0f0..68a771ede 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -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); diff --git a/src/cfgparse.c b/src/cfgparse.c index c7f549d3e..77e61d864 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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 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 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); } diff --git a/src/proto_http.c b/src/proto_http.c index 1ee230712..b2dd232ce 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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 to all headers in buffer of session . + * Apply all the resp filters of proxy to all headers in buffer of session . * 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; }