From: Christopher Faulet Date: Tue, 14 Jan 2020 11:00:28 +0000 (+0100) Subject: MEDIUM: http-rules: Support an optional error message in http deny rules X-Git-Tag: v2.2-dev1~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=554c0ebffd63884079b3ad865ab2329f5b575def;p=thirdparty%2Fhaproxy.git 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 " : 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 " : 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. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index d12a3ae1c0..93b9c65434 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4412,12 +4412,17 @@ http-request del-map() [ { if | unless } ] 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 ] [ { if | unless } ] +http-request deny [deny_status ] [ { errorfile | errorfiles } ] + [ { if | unless } ] 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 } ] @@ -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 ] [ { if | unless } ] +http-request tarpit [deny_status ] [ { errorfile | errorfiles } ] + [ { if | unless } ] 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 ] [ { if | unless } ] 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 [table ] [ { if | unless } ] @@ -5107,12 +5117,17 @@ http-response del-map() [ { if | unless } ] 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 ] [ { if | unless } ] +http-response deny [deny_status ] [ { errorfile | errorfiles } ] + [ { if | unless } ] 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 [ { if | unless } ] diff --git a/include/proto/http_htx.h b/include/proto/http_htx.h index 0b25d41ff1..4b980e2cf5 100644 --- a/include/proto/http_htx.h +++ b/include/proto/http_htx.h @@ -29,6 +29,7 @@ #include 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); diff --git a/include/types/action.h b/include/types/action.h index 63dcfcf94d..6ec3f018d0 100644 --- a/include/types/action.h +++ b/include/types/action.h @@ -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 */ diff --git a/src/http_act.c b/src/http_act.c index 218b1c4917..774df140ad 100644 --- a/src/http_act.c +++ b/src/http_act.c @@ -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 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 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 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; } diff --git a/src/http_ana.c b/src/http_ana.c index 10ee241115..82a9e14a26 100644 --- a/src/http_ana.c +++ b/src/http_ana.c @@ -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;