MEDIUM: http: add support for "http-request tarpit" rule
The "reqtarpit" rule is not very handy to use. Now that we have more
flexibility with "http-request", let's finally make the tarpit rules
usable there.
There are still semantical differences between apply_filters_to_request()
and http_req_get_intercept_rule() because the former updates the counters
while the latter does not. So we currently have almost similar code leafs
for similar conditions, but this should be cleaned up later.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index a4973ec..5c15d6a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2606,7 +2606,7 @@
See also : "option httpchk", "http-check disable-on-404"
-http-request { allow | deny | auth [realm <realm>] | redirect <rule> |
+http-request { allow | deny | tarpit | auth [realm <realm>] | redirect <rule> |
add-header <name> <fmt> | set-header <name> <fmt> }
[ { if | unless } <condition> ]
Access control for Layer 7 requests
@@ -2628,6 +2628,18 @@
the request and emits an HTTP 403 error. No further "http-request" rules
are evaluated.
+ - "tarpit" : 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 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"
+ developped robots, it can make things worse by forcing haproxy and the
+ front firewall to support insane number of concurrent connections.
+
- "auth" : this stops the evaluation of the rules and immediately responds
with an HTTP 401 or 407 error code to invite the user to present a valid
user name and password. No further "http-request" rules are evaluated. An
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index ef81a12..12e446f 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -240,6 +240,7 @@
HTTP_REQ_ACT_UNKNOWN = 0,
HTTP_REQ_ACT_ALLOW,
HTTP_REQ_ACT_DENY,
+ HTTP_REQ_ACT_TARPIT,
HTTP_REQ_ACT_AUTH,
HTTP_REQ_ACT_ADD_HDR,
HTTP_REQ_ACT_SET_HDR,
diff --git a/src/proto_http.c b/src/proto_http.c
index 7fc2dce..aaa9476 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3101,6 +3101,10 @@
txn->flags |= TX_CLDENY;
return rule;
+ case HTTP_REQ_ACT_TARPIT:
+ txn->flags |= TX_CLTARPIT;
+ return rule;
+
case HTTP_REQ_ACT_AUTH:
return rule;
@@ -3419,7 +3423,8 @@
do_stats = 0;
/* return a 403 if either rule has blocked */
- if (txn->flags & TX_CLDENY) {
+ if (txn->flags & (TX_CLDENY|TX_CLTARPIT)) {
+ if (txn->flags & TX_CLDENY) {
txn->status = 403;
s->logs.tv_request = now;
stream_int_retnclose(req->prod, http_error_message(s, HTTP_ERR_403));
@@ -3430,6 +3435,31 @@
if (s->listener->counters)
s->listener->counters->denied_req++;
goto return_prx_cond;
+ }
+ /* When a connection is tarpitted, we use the tarpit timeout,
+ * which may be the same as the connect timeout if unspecified.
+ * If unset, then set it to zero because we really want it to
+ * eventually expire. We build the tarpit as an analyser.
+ */
+ if (txn->flags & TX_CLTARPIT) {
+ channel_erase(s->req);
+ /* wipe the request out so that we can drop the connection early
+ * if the client closes first.
+ */
+ channel_dont_connect(req);
+ req->analysers = 0; /* remove switching rules etc... */
+ req->analysers |= AN_REQ_HTTP_TARPIT;
+ req->analyse_exp = tick_add_ifset(now_ms, s->be->timeout.tarpit);
+ if (!req->analyse_exp)
+ req->analyse_exp = tick_add(now_ms, 0);
+ session_inc_http_err_ctr(s);
+ s->fe->fe_counters.denied_req++;
+ if (s->fe != s->be)
+ s->be->be_counters.denied_req++;
+ if (s->listener->counters)
+ s->listener->counters->denied_req++;
+ return 1;
+ }
}
/* try headers filters */
@@ -8059,6 +8089,9 @@
} else if (!strcmp(args[0], "deny")) {
rule->action = HTTP_REQ_ACT_DENY;
cur_arg = 1;
+ } else if (!strcmp(args[0], "tarpit")) {
+ rule->action = HTTP_REQ_ACT_TARPIT;
+ cur_arg = 1;
} else if (!strcmp(args[0], "auth")) {
rule->action = HTTP_REQ_ACT_AUTH;
cur_arg = 1;