0

I've been trying to implement rate limiting for some HTTP POST requests on my website. It works great, except for one detail: the expiration of my entry in my stick-table is always reset to 30 seconds, which means that if the client mistakenly makes a request 29 seconds after being blocked, it will be blocked, again, for 30 seconds.

Here's my config, stripped down to the bare minimal for ease of reading:

frontend http-in
        mode    http
        bind *:80

        ### Request limiting
        # Declare stick table
        stick-table type string size 100k expire 30s store gpc0

        # Inspect layer 7
        tcp-request inspect-delay 15s

        # Declare ACLs
        acl source_is_abuser sc0_get_gpc0 gt 0

        tcp-request content track-sc0 req.cook(frontend) if !source_is_abuser
        ### End Request limiting

        use_backend rate-limit if source_is_abuser

        default_backend mybackend

backend mybackend
        mode   http

        stick-table type string size 100k expire 30s store http_req_rate(30s)
        tcp-request content track-sc1 req.cook(frontend) if METH_POST

        acl post_req_rate_abuse sc1_http_req_rate gt 30
        acl mark_as_abuser sc0_inc_gpc0 gt 0

        tcp-request content accept if post_req_rate_abuse mark_as_abuser

        server myLocalhost 127.0.0.1:8081

backend rate-limit
        mode http
        errorfile 503 /usr/local/etc/haproxy/rate-limit.http

With this config, as soon as a client makes more than 1 request per second over 30 seconds, this client is marked as an abuser by mybackend. The following request are then, as expected, blocked by the http-in frontend.

However, every time the currently marked source_is_abuser client sends a request, the expiration counter of http-in 's stick-table is reset to 30 seconds. I would expect the expiration counter to keep going down, since the connection is supposedly only tracked when !source_is_abuser.

Any insight into what I am doing wrong?

liquidity
  • 418
  • 1
  • 7
  • 22
  • I can't actually provide an answer, because in all honesty the whole `gpc0`, `scX_YYY_gpc0` thing makes my head hurt, but it seems to me you're not *just* counting POSTs here (`acl post_req_rate_abuse sc1_http_req_rate gt 30') and are accepting when you should be blocking (`tcp-request content accept if post_req_rate_abuse mark_as_abuser`. Then again, maybe I'm not understanding how this whole thing works. Have you read [these](http://blog.serverfault.com/2010/08/26/1016491873/) [two](http://blog.haproxy.com/2012/02/27/use-a-load-balancer-as-a-first-row-of-defense-against-ddos/) articles? – GregL Aug 20 '15 at 18:54
  • @GregL: thanks for the 2 links. I did read them indeed. The POST tracking happens in `mybackend`. What the configuration does, however, is that once you've abused POST, any type of request (GET,HEAD,POST,etc) from the same client gets blocked. This wasn't on purpose, although I could see this functionality being useful. – liquidity Aug 21 '15 at 16:54
  • Oh, that case, just change `use_backend rate-limit if source_is_abuser` to `use_backend rate-limit if source_is_abuser METH_POST`, so that they only get sent to the `rate-limit` backend if they're an abuser *AND* they're POSTing. – GregL Aug 21 '15 at 16:58
  • @GregL That unfortunately doesn't solve my issue. The issue is that "the expiration of my entry in my stick-table is always reset to 30 seconds". – liquidity Aug 21 '15 at 19:50

1 Answers1

2

To check the counter without updating its value, you can use table_http_req_rate. In your case you can put the below config in your frontend, so that you can deny the request before reaching the stick-table.

http-request deny if { key,table_http_req_rate(name_of_your_table) gt 30 }

More reference: Stick table doc