mholt / caddy-ratelimit

HTTP rate limiting module for Caddy 2
Apache License 2.0
255 stars 17 forks source link

Dynamic zone key for network block of {http.request.remote.host} with certain prefix #12

Closed kellytk closed 2 years ago

kellytk commented 2 years ago

When used as the key of a dynamic zone, can {http.request.remote.host} be reduced to its network block for a certain prefix?

Assuming http.request.remote.host is 1.2.3.4, and a function is used reducing it to /24, requests (and rate-limiting) to any address in the range 1.2.3.0-255 would be grouped.

"key": "reduce_to_network_block({http.request.remote.host}, '/24')",
mholt commented 2 years ago

Interesting, let me think about this.

mholt commented 2 years ago

What if there was a placeholder:

{http.request.remote.host/N} where N could be any bit length that mapped the IP (if an IP, otherwise it would ignore the /N) to, say, 1.2.3.0 for anything in the 1.2.3.0-255 range?

I can implement that upstream in Caddy if that's sufficient for you.

francislavoie commented 2 years ago

What if it's IPv6 though? Need ranges for both cases I think. Kinda tricky.

There must be a way we can set up a var with the right value.

mholt commented 2 years ago

@francislavoie I don't think the https://pkg.go.dev/net/netip package cares. It's just more bits, right?

francislavoie commented 2 years ago

My point is, you might want to configure 16 bits for IPv4 but 32 bits for IPv6, like we have in the log ip_mask filter https://caddyserver.com/docs/caddyfile/directives/log#ip-mask

mholt commented 2 years ago

True; maybe we could support a /N,M syntax or something; let's wait for a feature request.

mholt commented 2 years ago

Oh what the hey. I did it anyway: https://github.com/caddyserver/caddy/commit/9873ff9918224f56604048ea0ed3d3ae3953939e

@kellytk Here's what you want: {http.request.remote.host/24,32} (for example; the ,32 is optional if you want both bit lengths for IPv4 and IPv6)

kellytk commented 2 years ago

Currently traveling. Will ponder and respond within a day or two!

kellytk commented 2 years ago

@mholt This is very exciting; especially the IPv6 support!

I wish to employ this feature in a layered fashion, ala contrived and simplified example: (IPv6 omitted as I don't yet have experience with it)

One request from 1.2.3.4 would:

  1. Apply a +1 event to the 1.2.3.0-255 zone key bucket.
  2. Another +1 event to the 1.2.0-255.0-255 bucket.
  3. And finally another +1 event to the 1.0-255.0-255.0-255 bucket.

Is that how this feature, as implemented, is designed to function?

This would enable me to allow burst traffic from individual IPs and small blocks, such as CGNAT, while still applying the events to the quotas of the larger containing blocks. The rationale is that individual IPs and small blocks can legitimately be hot, however amortizing events across larger blocks should yield more moderate figures. If large blocks are also hot, it's possibly an attack scenario.

To thwart my thinking an attacker would need to utilize more evenly distributed addresses from the global space. That can be 'easily' mitigated with active monitoring and dynamic updating of these rate limit policies.

mholt commented 2 years ago

@kellytk Yes, I believe that should work. The placeholder {http.request.remote.host/24} for example would reduce 192.168.55.123 to a key of 192.168.55.0/24 -- /16 would become 192.168.0.0/16 and /8 would output 192.0.0.0/8.

You could hard-code those zone keys to test, if you know the IP range you'll be testing with. Then use the placeholder and observe the same behavior.

kellytk commented 2 years ago

@mholt Excellent, thank you! Will tests be added to verify the behavior and ensure there're no regressions in the future?

mholt commented 2 years ago

@kellytk I did write unit tests: https://github.com/caddyserver/caddy/commit/9873ff9918224f56604048ea0ed3d3ae3953939e#diff-f7d3f5ac378699eb31c965343929663ac9504606500b9b4f81f10d371bfc9677

We can always add more later if needed.

What are you using this for? Just curious.

kellytk commented 2 years ago

@mholt Thank you! I'm using Caddy as the user-facing reverse proxy to Rust-based 'apps' I write.

With enhancements such as this and https://github.com/caddyserver/caddy/issues/4558 Caddy is proving pleasantly mature. Thank you once again.

mholt commented 2 years ago

You're welcome! Glad it is working for you.