diff --git a/README.md b/README.md index 2920a8c..93c2a7c 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,11 @@ This is an HTTP handler module, so it can be used wherever `http.handlers` modul "window": "", "max_events": 0 }, - "storage": {}, "jitter": 0.0, "sweep_interval": "" - } + }, + "log_key": false, + "storage": {}, "distributed": { "write_interval": "", "read_interval": "", @@ -99,6 +100,8 @@ All fields are optional, but to be useful, you'll need to define at least one zo To enable distributed RL, set `distributed` to a non-null object. The default read and write intervals are 5s, but you should tune these for your individual deployments. +To log the key when a rate limit is hit, set `log_key` to `true`. + Storage customizes the storage module that is used. Like normal Caddy convention, all instances with the same storage configuration are considered to be part of a cluster. Jitter is an optional percentage that adds random variance to the Retry-After time to avoid stampeding herds. @@ -133,6 +136,7 @@ rate_limit { write_interval purge_age } + log_key storage jitter sweep_interval diff --git a/distributed.go b/distributed.go index d44eeb0..f54e24c 100644 --- a/distributed.go +++ b/distributed.go @@ -206,7 +206,7 @@ func (h Handler) distributedRateLimiting(w http.ResponseWriter, r *http.Request, // no point in counting more if we're already over if totalCount >= maxAllowed { - return h.rateLimitExceeded(w, r, repl, zoneName, oldestEvent.Add(window).Sub(now())) + return h.rateLimitExceeded(w, r, repl, zoneName, rlKey, oldestEvent.Add(window).Sub(now())) } } } @@ -228,7 +228,7 @@ func (h Handler) distributedRateLimiting(w http.ResponseWriter, r *http.Request, limiter.mu.Unlock() // otherwise, it appears limit has been exceeded - return h.rateLimitExceeded(w, r, repl, zoneName, oldestEvent.Add(window).Sub(now())) + return h.rateLimitExceeded(w, r, repl, zoneName, rlKey, oldestEvent.Add(window).Sub(now())) } type rlStateValue struct { diff --git a/handler.go b/handler.go index 7ad283e..6441506 100644 --- a/handler.go +++ b/handler.go @@ -65,6 +65,10 @@ type Handler struct { // the global or default storage configuration will be used. StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` + // LogKey, if true, will log the key used for rate limiting. + // Defaults to `false` because keys can contain sensitive information. + LogKey bool `json:"log_key,omitempty"` + rateLimits []*RateLimit storage certmagic.Storage random *weakrand.Rand @@ -170,7 +174,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt if h.Distributed == nil { // internal rate limiter only if dur := limiter.When(); dur > 0 { - return h.rateLimitExceeded(w, r, repl, rl.zoneName, dur) + return h.rateLimitExceeded(w, r, repl, rl.zoneName, key, dur) } } else { // distributed rate limiting; add last known state of other instances @@ -183,7 +187,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt return next.ServeHTTP(w, r) } -func (h *Handler) rateLimitExceeded(w http.ResponseWriter, r *http.Request, repl *caddy.Replacer, zoneName string, wait time.Duration) error { +func (h *Handler) rateLimitExceeded(w http.ResponseWriter, r *http.Request, repl *caddy.Replacer, zoneName string, key string, wait time.Duration) error { // add jitter, if configured if h.random != nil { jitter := h.randomFloatInRange(0, float64(wait)*h.Jitter) @@ -198,12 +202,22 @@ func (h *Handler) rateLimitExceeded(w http.ResponseWriter, r *http.Request, repl if err != nil { remoteIP = r.RemoteAddr // assume there was no port, I guess } - h.logger.Info("rate limit exceeded", + + // Create logger with common fields + logger := h.logger.With( zap.String("zone", zoneName), zap.Duration("wait", wait), zap.String("remote_ip", remoteIP), ) + // Conditionally add the key field + if h.LogKey { + logger = logger.With(zap.String("key", key)) + } + + // Log the rate limit exceeded message + logger.Info("rate limit exceeded") + // make some information about this rate limit available repl.Set("http.rate_limit.exceeded.name", zoneName)