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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index d12a3ae..93b9c65 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4412,12 +4412,17 @@
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 @@
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 @@
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 @@
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> ]
diff --git a/include/proto/http_htx.h b/include/proto/http_htx.h
index 0b25d41..4b980e2 100644
--- a/include/proto/http_htx.h
+++ b/include/proto/http_htx.h
@@ -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);
diff --git a/include/types/action.h b/include/types/action.h
index 63dcfcf..6ec3f01 100644
--- a/include/types/action.h
+++ b/include/types/action.h
@@ -123,6 +123,10 @@
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 218b1c4..774df14 100644
--- a/src/http_act.c
+++ b/src/http_act.c
@@ -785,52 +785,118 @@
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;
}
diff --git a/src/http_ana.c b/src/http_ana.c
index 10ee241..82a9e14 100644
--- a/src/http_ana.c
+++ b/src/http_ana.c
@@ -2896,13 +2896,17 @@
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 @@
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;