[MEDIUM] http: add support for conditional request filter execution

All the req* rules except the reqadd rules can now be specified with
an if/unless condition. If a condition is specified and does not match,
the filter is ignored. This is particularly useful with reqidel, reqirep
and reqtarpit.
This commit is contained in:
Willy Tarreau 2010-01-28 20:35:13 +01:00
parent 6c123b15cb
commit 5321c42722
2 changed files with 82 additions and 40 deletions

View File

@ -3597,8 +3597,8 @@ reqadd <string>
See also: "rspadd" and section 6 about HTTP header manipulation See also: "rspadd" and section 6 about HTTP header manipulation
reqallow <search> reqallow <search> [{if | unless} <cond>]
reqiallow <search> (ignore case) reqiallow <search> [{if | unless} <cond>] (ignore case)
Definitely allow an HTTP request if a line matches a regular expression Definitely allow an HTTP request 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
@ -3611,6 +3611,9 @@ reqiallow <search> (ignore case)
"reqallow" keyword strictly matches case while "reqiallow" "reqallow" keyword strictly matches case while "reqiallow"
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 request containing any line which matches extended regular expression A request containing any line which matches extended regular expression
<search> will mark the request as allowed, even if any later test would <search> will mark the request as allowed, even if any later test would
result in a deny. The test applies both to the request line and to request result in a deny. The test applies both to the request line and to request
@ -3625,12 +3628,12 @@ reqiallow <search> (ignore case)
reqiallow ^Host:\ www\. reqiallow ^Host:\ www\.
reqideny ^Host:\ .*\.local reqideny ^Host:\ .*\.local
See also: "reqdeny", "acl", "block" and section 6 about HTTP header See also: "reqdeny", "block", section 6 about HTTP header manipulation, and
manipulation section 7 about ACLs.
reqdel <search> reqdel <search> [{if | unless} <cond>]
reqidel <search> (ignore case) reqidel <search> [{if | unless} <cond>] (ignore case)
Delete all headers matching a regular expression in an HTTP request Delete all headers matching a regular expression in an HTTP request
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
@ -3642,6 +3645,9 @@ reqidel <search> (ignore case)
('\'). The pattern applies to a full line at a time. The "reqdel" ('\'). The pattern applies to a full line at a time. The "reqdel"
keyword strictly matches case while "reqidel" ignores case. keyword strictly matches case while "reqidel" 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 request Any header line matching extended regular expression <search> in the request
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 dangerous headers or cookies from a request before passing it to the and/or dangerous headers or cookies from a request before passing it to the
@ -3656,12 +3662,12 @@ reqidel <search> (ignore case)
reqidel ^X-Forwarded-For:.* reqidel ^X-Forwarded-For:.*
reqidel ^Cookie:.*SERVER= reqidel ^Cookie:.*SERVER=
See also: "reqadd", "reqrep", "rspdel" and section 6 about HTTP header See also: "reqadd", "reqrep", "rspdel", section 6 about HTTP header
manipulation manipulation, and section 7 about ACLs.
reqdeny <search> reqdeny <search> [{if | unless} <cond>]
reqideny <search> (ignore case) reqideny <search> [{if | unless} <cond>] (ignore case)
Deny an HTTP request if a line matches a regular expression Deny an HTTP request 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
@ -3674,6 +3680,9 @@ reqideny <search> (ignore case)
"reqdeny" keyword strictly matches case while "reqideny" ignores "reqdeny" keyword strictly matches case while "reqideny" ignores
case. 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 request containing any line which matches extended regular expression A request containing any line which matches extended regular expression
<search> will mark the request as denied, even if any later test would <search> will mark the request as denied, even if any later test would
result in an allow. The test applies both to the request line and to request result in an allow. The test applies both to the request line and to request
@ -3692,12 +3701,12 @@ reqideny <search> (ignore case)
reqideny ^Host:\ .*\.local reqideny ^Host:\ .*\.local
reqiallow ^Host:\ www\. reqiallow ^Host:\ www\.
See also: "reqallow", "rspdeny", "acl", "block" and section 6 about HTTP See also: "reqallow", "rspdeny", "block", section 6 about HTTP header
header manipulation manipulation, and section 7 about ACLs.
reqpass <search> reqpass <search> [{if | unless} <cond>]
reqipass <search> (ignore case) reqipass <search> [{if | unless} <cond>] (ignore case)
Ignore any HTTP request line matching a regular expression in next rules Ignore any HTTP request line matching a regular expression in next rules
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
@ -3710,6 +3719,9 @@ reqipass <search> (ignore case)
"reqpass" keyword strictly matches case while "reqipass" ignores "reqpass" keyword strictly matches case while "reqipass" ignores
case. 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 request containing any line which matches extended regular expression A request containing any line which matches extended regular expression
<search> will skip next rules, without assigning any deny or allow verdict. <search> will skip next rules, without assigning any deny or allow verdict.
The test applies both to the request line and to request headers. Keep in The test applies both to the request line and to request headers. Keep in
@ -3724,12 +3736,12 @@ reqipass <search> (ignore case)
reqideny ^Host:\ .*\.local reqideny ^Host:\ .*\.local
reqiallow ^Host:\ www\. reqiallow ^Host:\ www\.
See also: "reqallow", "reqdeny", "acl", "block" and section 6 about HTTP See also: "reqallow", "reqdeny", "block", section 6 about HTTP header
header manipulation manipulation, and section 7 about ACLs.
reqrep <search> <string> reqrep <search> <string> [{if | unless} <cond>]
reqirep <search> <string> (ignore case) reqirep <search> <string> [{if | unless} <cond>] (ignore case)
Replace a regular expression with a string in an HTTP request line Replace a regular expression with a string in an HTTP request 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
@ -3747,6 +3759,9 @@ reqirep <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 request (both Any line matching extended regular expression <search> in the request (both
the request line and header lines) will be completely replaced with <string>. the request line and header lines) will be completely replaced with <string>.
Most common use of this is to rewrite URLs or domain names in "Host" headers. Most common use of this is to rewrite URLs or domain names in "Host" headers.
@ -3763,12 +3778,12 @@ reqirep <search> <string> (ignore case)
# replace "www.mydomain.com" with "www" in the host name. # replace "www.mydomain.com" with "www" in the host name.
reqirep ^Host:\ www.mydomain.com Host:\ www reqirep ^Host:\ www.mydomain.com Host:\ www
See also: "reqadd", "reqdel", "rsprep" and section 6 about HTTP header See also: "reqadd", "reqdel", "rsprep", section 6 about HTTP header
manipulation manipulation, and section 7 about ACLs.
reqtarpit <search> reqtarpit <search> [{if | unless} <cond>]
reqitarpit <search> (ignore case) reqitarpit <search> [{if | unless} <cond>] (ignore case)
Tarpit an HTTP request containing a line matching a regular expression Tarpit an HTTP request containing a line matching 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
@ -3781,6 +3796,9 @@ reqitarpit <search> (ignore case)
"reqtarpit" keyword strictly matches case while "reqitarpit" "reqtarpit" keyword strictly matches case while "reqitarpit"
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 request containing any line which matches extended regular expression A request containing any line which matches extended regular expression
<search> will be tarpitted, which means that it will connect to nowhere, will <search> will be tarpitted, which means that it will connect to nowhere, will
be kept open for a pre-defined time, then will return an HTTP error 500 so be kept open for a pre-defined time, then will return an HTTP error 500 so
@ -3795,14 +3813,18 @@ reqitarpit <search> (ignore case)
come. Depending on the environment and attack, it may be particularly come. Depending on the environment and attack, it may be particularly
efficient at reducing the load on the network and firewalls. efficient at reducing the load on the network and firewalls.
Example : Examples :
# ignore user-agents reporting any flavour of "Mozilla" or "MSIE", but # ignore user-agents reporting any flavour of "Mozilla" or "MSIE", but
# block all others. # block all others.
reqipass ^User-Agent:\.*(Mozilla|MSIE) reqipass ^User-Agent:\.*(Mozilla|MSIE)
reqitarpit ^User-Agent: reqitarpit ^User-Agent:
See also: "reqallow", "reqdeny", "reqpass", and section 6 about HTTP header # block bad guys
manipulation acl badguys src 10.1.0.3 172.16.13.20/28
reqitarpit . if badguys
See also: "reqallow", "reqdeny", "reqpass", section 6 about HTTP header
manipulation, and section 7 about ACLs.
retries <value> retries <value>

View File

@ -912,6 +912,7 @@ static int create_cond_regex_rule(const char *file, int line,
regex_t *preg = NULL; regex_t *preg = NULL;
const char *err; const char *err;
int err_code = 0; int err_code = 0;
struct acl_cond *cond = NULL;
if (px == &defproxy) { if (px == &defproxy) {
Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, line, cmd); Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, line, cmd);
@ -928,6 +929,25 @@ static int create_cond_regex_rule(const char *file, int line,
if (warnifnotcap(px, PR_CAP_RS, file, line, cmd, NULL)) if (warnifnotcap(px, PR_CAP_RS, file, line, cmd, NULL))
err_code |= ERR_WARN; err_code |= ERR_WARN;
if (cond_start &&
(strcmp(*cond_start, "if") == 0 || strcmp(*cond_start, "unless") == 0)) {
if ((cond = build_acl_cond(file, line, px, cond_start)) == NULL) {
Alert("parsing [%s:%d] : error detected while parsing a '%s' condition.\n",
file, line, cmd);
err_code |= ERR_ALERT | ERR_FATAL;
goto err;
}
}
else if (cond_start && **cond_start) {
Alert("parsing [%s:%d] : '%s' : Expecting nothing, 'if', or 'unless', got '%s'.\n",
file, line, cmd, *cond_start);
err_code |= ERR_ALERT | ERR_FATAL;
goto err;
}
if (dir == ACL_DIR_REQ)
err_code |= warnif_cond_requires_resp(cond, file, line);
preg = calloc(1, sizeof(regex_t)); preg = calloc(1, sizeof(regex_t));
if (!preg) { if (!preg) {
Alert("parsing [%s:%d] : '%s' : not enough memory to build regex.\n", file, line, cmd); Alert("parsing [%s:%d] : '%s' : not enough memory to build regex.\n", file, line, cmd);
@ -942,7 +962,7 @@ static int create_cond_regex_rule(const char *file, int line,
} }
err = chain_regex((dir == ACL_DIR_REQ) ? &px->req_exp : &px->rsp_exp, err = chain_regex((dir == ACL_DIR_REQ) ? &px->req_exp : &px->rsp_exp,
preg, action, repl ? strdup(repl) : NULL, NULL); preg, action, repl ? strdup(repl) : NULL, cond);
if (repl && err) { if (repl && err) {
Alert("parsing [%s:%d] : '%s' : invalid character or unterminated sequence in replacement string near '%c'.\n", Alert("parsing [%s:%d] : '%s' : invalid character or unterminated sequence in replacement string near '%c'.\n",
file, line, cmd, *err); file, line, cmd, *err);
@ -3599,56 +3619,56 @@ stats_error_parsing:
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_REPLACE, 0, ACL_DIR_REQ, 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], "reqdel")) { /* delete request header from a regex */ else if (!strcmp(args[0], "reqdel")) { /* delete request header from a regex */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_REMOVE, 0, ACL_DIR_REQ, 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], "reqdeny")) { /* deny a request if a header matches this regex */ else if (!strcmp(args[0], "reqdeny")) { /* deny a request if a header matches this regex */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_DENY, 0, ACL_DIR_REQ, 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;
} }
else if (!strcmp(args[0], "reqpass")) { /* pass this header without allowing or denying the request */ else if (!strcmp(args[0], "reqpass")) { /* pass this header without allowing or denying the request */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_PASS, 0, ACL_DIR_REQ, ACT_PASS, 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], "reqallow")) { /* allow a request if a header matches this regex */ else if (!strcmp(args[0], "reqallow")) { /* allow a request if a header matches this regex */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_ALLOW, 0, ACL_DIR_REQ, ACT_ALLOW, 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], "reqtarpit")) { /* tarpit a request if a header matches this regex */ else if (!strcmp(args[0], "reqtarpit")) { /* tarpit a request if a header matches this regex */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_TARPIT, 0, ACL_DIR_REQ, ACT_TARPIT, 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], "reqsetbe")) { /* switch the backend from a regex, respecting case */ else if (!strcmp(args[0], "reqsetbe")) { /* switch the backend from a regex, respecting case */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_SETBE, 0, ACL_DIR_REQ, ACT_SETBE, 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], "reqisetbe")) { /* switch the backend from a regex, ignoring case */ else if (!strcmp(args[0], "reqisetbe")) { /* switch the backend 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_REQ, ACT_SETBE, REG_ICASE, ACL_DIR_REQ, ACT_SETBE, 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;
} }
@ -3662,42 +3682,42 @@ stats_error_parsing:
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_REPLACE, REG_ICASE, ACL_DIR_REQ, 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], "reqidel")) { /* delete request header from a regex ignoring case */ else if (!strcmp(args[0], "reqidel")) { /* delete request 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_REQ, ACT_REMOVE, REG_ICASE, ACL_DIR_REQ, 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], "reqideny")) { /* deny a request if a header matches this regex ignoring case */ else if (!strcmp(args[0], "reqideny")) { /* deny a request if a header matches this regex ignoring case */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_DENY, REG_ICASE, ACL_DIR_REQ, 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;
} }
else if (!strcmp(args[0], "reqipass")) { /* pass this header without allowing or denying the request */ else if (!strcmp(args[0], "reqipass")) { /* pass this header without allowing or denying the request */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_PASS, REG_ICASE, ACL_DIR_REQ, ACT_PASS, 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], "reqiallow")) { /* allow a request if a header matches this regex ignoring case */ else if (!strcmp(args[0], "reqiallow")) { /* allow a request if a header matches this regex ignoring case */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_ALLOW, REG_ICASE, ACL_DIR_REQ, ACT_ALLOW, 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], "reqitarpit")) { /* tarpit a request if a header matches this regex ignoring case */ else if (!strcmp(args[0], "reqitarpit")) { /* tarpit a request if a header matches this regex ignoring case */
err_code |= create_cond_regex_rule(file, linenum, curproxy, err_code |= create_cond_regex_rule(file, linenum, curproxy,
ACL_DIR_REQ, ACT_TARPIT, REG_ICASE, ACL_DIR_REQ, ACT_TARPIT, 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;
} }