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>] [ { errorfile | errorfiles } <err> ]
- [ { if | unless } <condition> ]
+http-request deny [deny_status <status>] [ { if | unless } <condition> ]
+http-request deny [ { status | deny_status } <code>] [content-type <type>]
+ [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+ file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+ [ hdr <name> <fmt> ]*
+ [ { 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. 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.
+ This stops the evaluation of the rules and immediately rejects the request.
+ By default an HTTP 403 error is returned. But the response may be customized
+ using same syntax than "http-request return" rules. Thus, see "http-request
+ return" for details. For compatiblity purpose, when no argument is defined,
+ or only "deny_status", the argument "default-errorfiles" is implied. It means
+ "http-request deny [deny_status <status>]" is an alias of
+ "http-request deny [status <status>] default-errorfiles".
No further "http-request" rules are evaluated.
+ See also "http-request return".
http-request disable-l7-retry [ { if | unless } <condition> ]
This disables any attempt to retry the request if it fails for any other
the frontend, the default mode is restored when HAProxy starts the backend
rules evaluation.
-http-request tarpit [deny_status <status>] [ { errorfile | errorfiles } <err> ]
- [ { if | unless } <condition> ]
+http-request tarpit [deny_status <status>] [ { if | unless } <condition> ]
+http-request tarpit [ { status | deny_status } <code>] [content-type <type>]
+ [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+ file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+ [ hdr <name> <fmt> ]*
+ [ { 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
"timeout connect" if the former is not set. After that delay, if the client
- is still connected, an HTTP error 500 (or optionally the status code
- specified as an argument to "deny_status") is returned so that the client
- does not suspect it has been tarpitted. Logs will report the flags "PT".
- The goal of the tarpit rule is to slow down robots during an attack when
- they're limited on the number of concurrent requests. It can be very
- 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. 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.
+ is still connected, a response is returned so that the client does not
+ suspect it has been tarpitted. Logs will report the flags "PT". The goal of
+ the tarpit rule is to slow down robots during an attack when they're limited
+ on the number of concurrent requests. It can be very 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. By default an HTTP error 500 is returned.
+ But the response may be customized using same syntax than
+ "http-request return" rules. Thus, see "http-request return" for details.
+ For compatiblity purpose, when no argument is defined, or only "deny_status",
+ the argument "default-errorfiles" is implied. It means
+ "http-request tarpit [deny_status <status>]" is an alias of
+ "http-request tarpit [status <status>] default-errorfiles".
+ No further "http-request" rules are evaluated.
+ See also "http-request return" and "http-request silent-drop".
http-request track-sc0 <key> [table <table>] [ { if | unless } <condition> ]
http-request track-sc1 <key> [table <table>] [ { 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>] [ { errorfile | errorfiles } <err> ]
- [ { if | unless } <condition> ]
+http-response deny [deny_status <status>] [ { if | unless } <condition> ]
+http-response deny [ { status | deny_status } <code>] [content-type <type>]
+ [ { default-errorfiles | errorfile <file> | errorfiles <name> |
+ file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]
+ [ hdr <name> <fmt> ]*
+ [ { 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. 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.
+ This stops the evaluation of the rules and immediately rejects the response.
+ By default an HTTP 502 error is returned. But the response may be customized
+ using same syntax than "http-response return" rules. Thus, see
+ "http-response return" for details. For compatiblity purpose, when no
+ argument is defined, or only "deny_status", the argument "default-errorfiles"
+ is implied. It means "http-response deny [deny_status <status>]" is an alias
+ of "http-response deny [status <status>] default-errorfiles".
No further "http-response" rules are evaluated.
+ See also "http-response return".
http-response redirect <rule> [ { if | unless } <condition> ]
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 http_reply *http_reply; /* HTTP response to be used by return rules */
+ struct http_reply *http_reply; /* HTTP response to be used by return/deny/tarpit rules */
struct redirect_rule *redir; /* redirect rule or "http-request redirect" */
struct {
char *ref; /* MAP or ACL file name to update */
#include <common/http.h>
#include <types/channel.h>
+#include <types/http_htx.h>
/* These are the flags that are found in txn->flags */
/* 1 unused byte here */
short status; /* HTTP status from the server, negative if from proxy */
struct buffer *errmsg; /* custom HTTP error message to use as reply */
+ struct http_reply *http_reply; /* The HTTP reply to use as reply */
char cache_hash[20]; /* Store the cache hash */
char *uri; /* first line if log needed, NULL otherwise */
--- /dev/null
+The path "%[path]" is forbidden
http-request deny deny_status 500 errorfile /dev/null if { path /500-1 }
http-request deny deny_status 500 errorfiles errors-1 if { path /500-2 }
+ http-request deny status 500 hdr x-err-info "path=%[path]" content-type "text/plain" string "Internal Error" if { path /int-err }
+ http-request deny status 403 hdr x-err-info "path=%[path]" content-type "text/plain" lf-file ${testdir}/errors/lf-403.txt if { path /forbidden }
+
} -start
client c1r1 -connect ${h1_fe1_sock} {
txreq -req GET -url /500-2
expect_close
} -run
+client c1r6 -connect ${h1_fe1_sock} {
+ txreq -req GET -url /int-err
+ rxresp
+ expect resp.status == 500
+ expect resp.http.x-err-info == "path=/int-err"
+ expect resp.http.content-type == "text/plain"
+ expect resp.http.content-length == 14
+ expect resp.body == "Internal Error"
+} -run
+client c1r7 -connect ${h1_fe1_sock} {
+ txreq -req GET -url /forbidden
+ rxresp
+ expect resp.status == 403
+ expect resp.http.x-err-info == "path=/forbidden"
+ expect resp.http.content-type == "text/plain"
+ expect resp.body == "The path \"/forbidden\" is forbidden\n"
+} -run
}
}
+/* Release memory allocated by HTTP actions relying on an http reply. Concretly,
+ * it releases <.arg.http_reply>
+ */
+static void release_act_http_reply(struct act_rule *rule)
+{
+ release_http_reply(rule->arg.http_reply);
+ rule->arg.http_reply = NULL;
+}
+
+
+/* Check function for HTTP actions relying on an http reply. The function
+ * returns 1 in success case, otherwise, it returns 0 and err is filled.
+ */
+static int check_act_http_reply(struct act_rule *rule, struct proxy *px, char **err)
+{
+ struct http_reply *reply = rule->arg.http_reply;
+
+ if (!http_check_http_reply(reply, px, err)) {
+ release_act_http_reply(rule);
+ return 0;
+ }
+ return 1;
+}
+
/* This function executes one of the set-{method,path,query,uri} actions. It
* builds a string in the trash from the specified format string. It finds
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 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.
+ * response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
+ * error. It relies on http_parse_http_reply() to set
+ * <.arg.http_reply>.
*/
static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
struct act_rule *rule, char **err)
{
- int default_status, status, hc, cur_arg;
-
+ int default_status;
+ int cur_arg, arg = 0;
cur_arg = *orig_arg;
if (rule->from == ACT_F_HTTP_REQ) {
if (!strcmp(args[cur_arg-1], "tarpit")) {
rule->action = ACT_HTTP_REQ_TARPIT;
- default_status = status = 500;
+ default_status = 500;
}
else {
rule->action = ACT_ACTION_DENY;
- default_status = status = 403;
+ default_status = 403;
}
}
else {
rule->action = ACT_ACTION_DENY;
- default_status = status = 502;
+ default_status = 502;
}
- rule->flags |= ACT_FLAG_FINAL;
-
- if (strcmp(args[cur_arg], "deny_status") == 0) {
- cur_arg++;
- if (!*args[cur_arg]) {
- memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
- return ACT_RET_PRS_ERR;
- }
- status = atol(args[cur_arg]);
- cur_arg++;
- for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
- if (http_err_codes[hc] == status)
- break;
- }
- 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 no args or only a deny_status specified, fallback on the legacy
+ * mode and use default error files despite the fact that
+ * default-errorfiles is not used. Otherwise, parse an http reply.
+ */
- 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;
- }
+ /* Prepare parsing of log-format strings */
+ px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
- 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++;
+ if (!*(args[cur_arg])) {
+ rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
+ goto end;
}
- 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;
+
+ if (strcmp(args[cur_arg], "deny_status") == 0) {
+ if (!*(args[cur_arg+2]) ||
+ (strcmp(args[cur_arg+2], "errorfile") != 0 && strcmp(args[cur_arg+2], "errorfiles") != 0)) {
+ rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
+ &arg, px, default_status, err);
+ *orig_arg += 2;
+ goto end;
}
- /* 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;
+ args[cur_arg] += 5; /* skip "deny_" for the parsing */
}
- rule->arg.http_deny.status = status;
+ rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
- out:
- *orig_arg = cur_arg;
+ end:
+ if (!rule->arg.http_reply)
+ return ACT_RET_PRS_ERR;
+
+ rule->flags |= ACT_FLAG_FINAL;
+ rule->check_ptr = check_act_http_reply;
+ rule->release_ptr = release_act_http_reply;
return ACT_RET_PRS_OK;
}
return ACT_RET_PRS_OK;
}
-/* Release <.arg.http_reply> */
-static void release_http_return(struct act_rule *rule)
-{
- release_http_reply(rule->arg.http_reply);
- rule->arg.http_reply = NULL;
-}
-
/* This function executes a return action. It builds an HTX message from an
* errorfile, an raw file or a log-format string, depending on <.action>
* value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
return ACT_RET_ABRT;
}
-/* Check an "http-request return" action. The function returns 1 in success
- * case, otherwise, it returns 0 and err is filled.
- */
-static int check_http_return_action(struct act_rule *rule, struct proxy *px, char **err)
-{
- struct http_reply *reply = rule->arg.http_reply;
-
- if (!http_check_http_reply(reply, px, err)) {
- release_http_return(rule);
- return 0;
- }
- return 1;
-}
-
/* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
* ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
* <.arg.http_reply>.
rule->flags |= ACT_FLAG_FINAL;
rule->action = ACT_CUSTOM;
- rule->check_ptr = check_http_return_action;
+ rule->check_ptr = check_act_http_reply;
rule->action_ptr = http_action_return;
- rule->release_ptr = release_http_return;
+ rule->release_ptr = release_act_http_reply;
return ACT_RET_PRS_OK;
}
_HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
if (sess->listener->counters)
_HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
+
+ if (txn->http_reply) {
+ if (http_reply_message(s, txn->http_reply) == -1)
+ goto return_int_err;
+ goto return_prx_cond;
+ }
goto return_prx_err;
return_int_err:
*/
s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now);
- http_reply_and_close(s, txn->status, (!(req->flags & CF_READ_ERROR) ? http_error_message(s) : NULL));
+ if (req->flags & CF_READ_ERROR) {
+ http_reply_and_close(s, txn->status, NULL);
+ goto end;
+ }
+
+ if (txn->http_reply) {
+ if (!http_reply_message(s, txn->http_reply))
+ goto end;
+ txn->status = 500;
+ if (!(s->flags & SF_ERR_MASK))
+ s->flags |= SF_ERR_INTERNAL;
+ _HA_ATOMIC_ADD(&s->sess->fe->fe_counters.internal_errors, 1);
+ if (s->flags & SF_BE_ASSIGNED)
+ _HA_ATOMIC_ADD(&s->be->be_counters.internal_errors, 1);
+ if (s->sess->listener->counters)
+ _HA_ATOMIC_ADD(&s->sess->listener->counters->internal_errors, 1);
+ }
+
+ http_reply_and_close(s, txn->status, http_error_message(s));
+
+ end:
req->analysers &= AN_REQ_FLT_END;
req->analyse_exp = TICK_ETERNITY;
_HA_ATOMIC_ADD(&sess->listener->counters->denied_resp, 1);
if (objt_server(s->target))
_HA_ATOMIC_ADD(&__objt_server(s->target)->counters.denied_resp, 1);
+
+ if (txn->http_reply) {
+ if (http_reply_message(s, txn->http_reply) == -1)
+ goto return_int_err;
+ goto return_prx_cond;
+ }
goto return_prx_err;
return_int_err:
goto end;
case ACT_ACTION_DENY:
- txn->status = rule->arg.http_deny.status;
- if (rule->arg.http_deny.errmsg)
- txn->errmsg = rule->arg.http_deny.errmsg;
+ txn->status = rule->arg.http_reply->status;
+ txn->http_reply = rule->arg.http_reply;
rule_ret = HTTP_RULE_RES_DENY;
goto end;
case ACT_HTTP_REQ_TARPIT:
txn->flags |= TX_CLTARPIT;
- txn->status = rule->arg.http_deny.status;
- if (rule->arg.http_deny.errmsg)
- txn->errmsg = rule->arg.http_deny.errmsg;
+ txn->status = rule->arg.http_reply->status;
+ txn->http_reply = rule->arg.http_reply;
rule_ret = HTTP_RULE_RES_DENY;
goto end;
goto end;
case ACT_ACTION_DENY:
- txn->status = rule->arg.http_deny.status;
- if (rule->arg.http_deny.errmsg)
- txn->errmsg = rule->arg.http_deny.errmsg;
+ txn->status = rule->arg.http_reply->status;
+ txn->http_reply = rule->arg.http_reply;
rule_ret = HTTP_RULE_RES_DENY;
goto end;
/* get default error message */
errmsg = http_error_message(s);
}
- if (b_is_null(errmsg))
- goto leave;
- if (!channel_htx_copy_msg(res, htx, errmsg))
+ if (!b_is_null(errmsg) && !channel_htx_copy_msg(res, htx, errmsg))
goto fail;
}
else {
: 0);
txn->status = -1;
txn->errmsg = NULL;
+ txn->http_reply = NULL;
write_u32(txn->cache_hash, 0);
txn->cookie_first_date = 0;