caddyserver / caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
https://caddyserver.com
Apache License 2.0
57.19k stars 4k forks source link

on_demand ask cache instead of rate_limit #6245

Closed Jimbolino closed 5 months ago

Jimbolino commented 5 months ago

Every now and then we get traffic from an enthusiastic scanner, resulting in "a few" calls to our ask endpoint:

172.31.31.68 - - [16/Apr/2024:11:06:23 +0000] "GET /api/caddy/domain/check?domain=172.18.0.4 HTTP/1.1" 400 -
172.31.31.68 - - [16/Apr/2024:11:06:23 +0000] "GET /api/caddy/domain/check?domain=172.18.0.4 HTTP/1.1" 400 -
172.31.31.68 - - [16/Apr/2024:11:06:23 +0000] "GET /api/caddy/domain/check?domain=172.18.0.4 HTTP/1.1" 400 -
172.31.8.240 - - [16/Apr/2024:11:06:23 +0000] "GET /api/caddy/domain/check?domain=52.57.163.17 HTTP/1.1" 400 -
172.31.8.240 - - [16/Apr/2024:11:06:23 +0000] "GET /api/caddy/domain/check?domain=52.57.163.17 HTTP/1.1" 400 -
172.31.47.81 - - [16/Apr/2024:11:06:23 +0000] "GET /api/caddy/domain/check?domain=52.57.163.17 HTTP/1.1" 400 -

I've read in the docs that rate_limit is deprecated.

Currently we have rate limiting implemented on the "/api/caddy/domain/check" endpoint, however the rate limiter is ip based, and shared with other api calls used by our application. Before disabling our rate limiter, i was wondering if an alternative solution would be possible.

What if the ask endpoint is allowed to return a http cache header like: Cache-Control: max-age=60 And caddy would keep the result in memory for 60 seconds. This would greatly reduce the number of calls done to the ask endpoint.

mholt commented 5 months ago

I like that idea. But, naively implemented, this could use a lot of memory. We'd basically start a goroutine for each domain that is requested in order to "forget" the rejection after the designated amount of time. So clients enumerating domains rapidly could easily leak goroutines.

mohammed90 commented 5 months ago

The Ask functionality was recently changed from being strictly HTTP-endpoint to be modular, i.e. fulfilled by plugins. I think it's best to have a 3rd party module implementing the caching, rate-limiting, etc., than bundling them into the current implementation. It becomes a conscious choice to add the overhead to your deployment and for the implementation to mature before bringing it into standard distro.

That said, it can be resolved without the development of extra modules. You can add another site managed by Caddy to respond to the ask calls. The additional site listen internally, e.g. localhost:9000, and uses the cache-handler, rate-limit handler, and reverse-proxy to your real ask endpoint that's shared across a fleet. Matt's rate-limit module can use distributed storage and use any part of the request as key, so it can be the remote address.

mholt commented 5 months ago

Hmm, yes yes, that is very wise. We can always start with a module and then if it turns out to be super useful and still very efficient we can move it into the standard distribution.

@Jimbolino A Caddy module in the tls.permission.* namespace, one which implements a simple interface, CertificateAllowed(ctx context.Context, name string) error, could do this. Instructions here: https://caddyserver.com/docs/extending-caddy

If it works well maybe we can consider it for everyone!

I'll close the issue now since I think that's a much better idea -- and anyone can implement it much more quickly than we'd be able to right now -- and feel free to continue discussion as needed. :)

PS. I'd be able to prioritize this development with a sufficient sponsorship :100: