varnish / varnish-modules

Collection of Varnish Cache modules (vmods) by Varnish Software
Other
185 stars 86 forks source link

Add a DURATION param to is_denied() to block everything during that period. #79

Closed slimhazard closed 7 years ago

slimhazard commented 7 years ago

This changes the signature of is_denied() to add the block parameter:

BOOL is_denied(STRING key, INT limit, DURATION period, DURATION block=0)

When block > 0s, then after the threshold is reached, always return true for the duration of block. This is a way to completely lock out a particularly troublesome client for a period of time, rather than letting them slip back in occasionally when the request rate slips below the threshold.

The default value of block is 0s. So existing deployments of the VMOD can continue using it without changes.

block becomes one of the hashed elements that identify a token bucket. (Otherwise, different invocations with the same values for the other three params but with different blocks could pick up someone else's block, and get blocked spuriously.) So block is also a new optional param for remaining(), also with the default value 0s.

Further add a new function blocked():

DURATION blocked(STRING key, INT limit, DURATION period, DURATION block)

For the token bucket identified by the four params, return the amount of time remaining in a pending block, or 0s if no block is pending.

We had to cook this up in a pinch to help a customer who was facing a DOS attack. The VMOD with these additions is running in production now and is doing what it's expected to do.

This PR contains a bit more commits than it needs to, and I'm willing to squash a few of them if you decide to accept it. You could also pick what parts you like.

The first commit adds the new param to is_denied() and remaining(), and the third commit is a bugfix for it. These can be squashed.

The second commit adds the blocked() function, and the 5th and 6th commits are bugfixes that can be squashed with it.

The fourth commit changes the algorithm when a block is in effect. Originally, I had it just stop counting down tokens during that time, so the token countdown began anew when the block expired. With this change, the token countdown goes on during the block, so that if the request rate is high enough, a client may be immediately denied and blocked again after a block expires. So this aggressively locks out a client who keeps up a high request rate.

If you like it that way, the fourth commit can be squashed with the other ones for is_denied() and remaining().

So your choices are:

But I hope you like it. %^)

slimhazard commented 7 years ago

Ugh, the test for blocking has some timing dependencies, which probably accounts for the travis fail. I'll try to build in longer delays -- it'll make the test last longer, but I'm guessing that's what it needs.

I don't suppose there's any way to look inside the test log after a Travis run?

slimhazard commented 7 years ago

OK that did it, I hadn't been testing against Varnish 4.1.

daghf commented 7 years ago

Merged.

Thanks @slimhazard