MEDIUM: http-rules: Support an optional error message in http deny rules

It is now possible to set the error message to use when a deny rule is
executed. It may be a specific error file, adding "errorfile <file>" :

  http-request deny deny_status 400 errorfile /etc/haproxy/errorfiles/400badreq.http

It may also be an error file from an http-errors section, adding "errorfiles
<name>" :

  http-request deny errorfiles my-errors  # use 403 error from "my-errors" section

When defined, this error message is set in the HTTP transaction. The tarpit rule
is also concerned by this change.
This commit is contained in:
Christopher Faulet 2020-01-14 12:00:28 +01:00
parent 473e880a25
commit 554c0ebffd
5 changed files with 116 additions and 24 deletions

View File

@ -4412,12 +4412,17 @@ http-request del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
It takes one argument: "file name" It is the equivalent of the "del map"
command from the stats socket, but can be triggered by an HTTP request.
http-request deny [deny_status <status>] [ { if | unless } <condition> ]
http-request deny [deny_status <status>] [ { errorfile | errorfiles } <err> ]
[ { if | unless } <condition> ]
This stops the evaluation of the rules and immediately rejects the request
and emits an HTTP 403 error, or optionally the status code specified as an
argument to "deny_status". The list of permitted status codes is limited to
those that can be overridden by the "errorfile" directive.
those that can be overridden by the "errorfile" directive. A specific error
message may be specified. It may be an error file, using the "errorfile"
keyword followed by the file containing the full HTTP response. It may also
be an error from an http-errors section, using the "errorfiles" keyword
followed by the section name.
No further "http-request" rules are evaluated.
http-request disable-l7-retry [ { if | unless } <condition> ]
@ -4912,7 +4917,8 @@ http-request strict-mode { on | off }
the frontend, the default mode is restored when HAProxy starts the backend
rules evaluation.
http-request tarpit [deny_status <status>] [ { if | unless } <condition> ]
http-request tarpit [deny_status <status>] [ { errorfile | errorfiles } <err> ]
[ { if | unless } <condition> ]
This stops the evaluation of the rules and immediately blocks the request
without responding for a delay specified by "timeout tarpit" or
@ -4925,7 +4931,11 @@ http-request tarpit [deny_status <status>] [ { if | unless } <condition> ]
efficient against very dumb robots, and will significantly reduce the load
on firewalls compared to a "deny" rule. But when facing "correctly"
developed robots, it can make things worse by forcing haproxy and the front
firewall to support insane number of concurrent connections.
firewall to support insane number of concurrent connections. A specific error
message may be specified. It may be an error file, using the "errorfile"
keyword followed by the file containing the full HTTP response. It may also
be an error from an http-errors section, using the "errorfiles" keyword
followed by the section name.
See also the "silent-drop" action.
http-request track-sc0 <key> [table <table>] [ { if | unless } <condition> ]
@ -5107,12 +5117,17 @@ http-response del-map(<file-name>) <key fmt> [ { if | unless } <condition> ]
It takes one argument: "file name" It is the equivalent of the "del map"
command from the stats socket, but can be triggered by an HTTP response.
http-response deny [deny_status <status>] [ { if | unless } <condition> ]
http-response deny [deny_status <status>] [ { errorfile | errorfiles } <err> ]
[ { if | unless } <condition> ]
This stops the evaluation of the rules and immediately rejects the response
and emits an HTTP 502 error, or optionally the status code specified as an
argument to "deny_status". The list of permitted status codes is limited to
those that can be overridden by the "errorfile" directive.
those that can be overridden by the "errorfile" directive. A specific error
message may be specified. It may be an error file, using the "errorfile"
keyword followed by the file containing the full HTTP response. It may also
be an error from an http-errors section, using the "errorfiles" keyword
followed by the section name.
No further "http-response" rules are evaluated.
http-response redirect <rule> [ { if | unless } <condition> ]

View File

@ -29,6 +29,7 @@
#include <types/http_htx.h>
extern struct buffer http_err_chunks[HTTP_ERR_SIZE];
extern struct list http_errors_list;
struct htx_sl *http_get_stline(struct htx *htx);
int http_find_header(const struct htx *htx, const struct ist name, struct http_hdr_ctx *ctx, int full);

View File

@ -123,6 +123,10 @@ struct act_rule {
struct list fmt; /* log-format compatible expression */
struct my_regex *re; /* used by replace-header/value/uri/path */
} http; /* args used by some HTTP rules */
struct {
int status; /* status code */
struct buffer *errmsg; /* HTTP error message, may be NULL */
} http_deny; /* args used by HTTP deny rules */
struct redirect_rule *redir; /* redirect rule or "http-request redirect" */
struct {
char *ref; /* MAP or ACL file name to update */

View File

@ -785,52 +785,118 @@ static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, str
return ACT_RET_PRS_OK;
}
/* Check an "http-request deny" action when an http-errors section is referenced.
*
* The function returns 1 in success case, otherwise, it returns 0 and err is
* filled.
*/
static int check_http_deny_action(struct act_rule *rule, struct proxy *px, char **err)
{
struct http_errors *http_errs;
int status = (intptr_t)(rule->arg.act.p[0]);
int ret = 1;
list_for_each_entry(http_errs, &http_errors_list, list) {
if (strcmp(http_errs->id, (char *)rule->arg.act.p[1]) == 0) {
free(rule->arg.act.p[1]);
rule->arg.http_deny.status = status;
rule->arg.http_deny.errmsg = http_errs->errmsg[http_get_status_idx(status)];
if (!rule->arg.http_deny.errmsg)
ha_warning("Proxy '%s': status '%d' referenced by http deny rule "
"not declared in http-errors section '%s'.\n",
px->id, status, http_errs->id);
break;
}
}
if (&http_errs->list == &http_errors_list) {
memprintf(err, "unknown http-errors section '%s' referenced by http deny rule",
(char *)rule->arg.act.p[1]);
free(rule->arg.act.p[1]);
ret = 0;
}
return ret;
}
/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
* response rule. It may take 2 optional arguments to define the status code. It
* returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
* response rule. It may take optional arguments to define the status code, the
* error file or the http-errors section to use. It returns ACT_RET_PRS_OK on
* success, ACT_RET_PRS_ERR on error.
*/
static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
struct act_rule *rule, char **err)
{
int code, hc, cur_arg;
int default_status, status, hc, cur_arg;
cur_arg = *orig_arg;
if (rule->from == ACT_F_HTTP_REQ) {
if (!strcmp(args[cur_arg-1], "tarpit")) {
rule->action = ACT_HTTP_REQ_TARPIT;
rule->arg.http.i = HTTP_ERR_500;
default_status = status = 500;
}
else {
rule->action = ACT_ACTION_DENY;
rule->arg.http.i = HTTP_ERR_403;
default_status = status = 403;
}
}
else {
rule->action = ACT_ACTION_DENY;;
rule->arg.http.i = HTTP_ERR_502;
rule->action = ACT_ACTION_DENY;
default_status = status = 502;
}
rule->flags |= ACT_FLAG_FINAL;
if (strcmp(args[cur_arg], "deny_status") == 0) {
cur_arg++;
if (!*args[cur_arg]) {
memprintf(err, "missing status code.\n");
memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
return ACT_RET_PRS_ERR;
}
code = atol(args[cur_arg]);
status = atol(args[cur_arg]);
cur_arg++;
for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
if (http_err_codes[hc] == code) {
rule->arg.http.i = hc;
if (http_err_codes[hc] == status)
break;
}
}
if (hc >= HTTP_ERR_SIZE)
memprintf(err, "status code %d not handled, using default code %d",
code, http_err_codes[rule->arg.http.i]);
if (hc >= HTTP_ERR_SIZE) {
memprintf(err, "status code '%d' not handled, using default code '%d'",
status, default_status);
status = default_status;
hc = http_get_status_idx(status);
}
}
if (strcmp(args[cur_arg], "errorfile") == 0) {
cur_arg++;
if (!*args[cur_arg]) {
memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
return ACT_RET_PRS_ERR;
}
rule->arg.http_deny.errmsg = http_load_errorfile(args[cur_arg], err);
if (!rule->arg.http_deny.errmsg)
return ACT_RET_PRS_ERR;
cur_arg++;
}
else if (strcmp(args[cur_arg], "errorfiles") == 0) {
cur_arg++;
if (!*args[cur_arg]) {
memprintf(err, "'%s' expects <http_errors_name> as argument", args[cur_arg-1]);
return ACT_RET_PRS_ERR;
}
/* Must be resolved during the config validity check */
rule->arg.act.p[0] = (void *)((intptr_t)status);
rule->arg.act.p[1] = strdup(args[cur_arg]);
rule->check_ptr = check_http_deny_action;
cur_arg++;
goto out;
}
rule->arg.http_deny.status = status;
out:
*orig_arg = cur_arg;
return ACT_RET_PRS_OK;
}

View File

@ -2896,13 +2896,17 @@ static enum rule_result http_req_get_intercept_rule(struct proxy *px, struct lis
case ACT_ACTION_DENY:
txn->flags |= TX_CLDENY;
txn->status = http_err_codes[rule->arg.http.i];
txn->status = rule->arg.http_deny.status;
if (rule->arg.http_deny.errmsg)
txn->errmsg = rule->arg.http_deny.errmsg;
rule_ret = HTTP_RULE_RES_DENY;
goto end;
case ACT_HTTP_REQ_TARPIT:
txn->flags |= TX_CLTARPIT;
txn->status = http_err_codes[rule->arg.http.i];
txn->status = rule->arg.http_deny.status;
if (rule->arg.http_deny.errmsg)
txn->errmsg = rule->arg.http_deny.errmsg;
rule_ret = HTTP_RULE_RES_DENY;
goto end;
@ -3073,7 +3077,9 @@ resume_execution:
case ACT_ACTION_DENY:
txn->flags |= TX_CLDENY;
txn->status = http_err_codes[rule->arg.http.i];
txn->status = rule->arg.http_deny.status;
if (rule->arg.http_deny.errmsg)
txn->errmsg = rule->arg.http_deny.errmsg;
rule_ret = HTTP_RULE_RES_DENY;
goto end;