Skip to content

Commit

Permalink
Allow logging key when the rate limit is exceeded.
Browse files Browse the repository at this point in the history
  • Loading branch information
popcorn committed Jun 28, 2024
1 parent 9f619ad commit 4595b8a
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 11 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": "",
Expand All @@ -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.
Expand Down Expand Up @@ -133,6 +136,7 @@ rate_limit {
write_interval <duration>
purge_age <duration>
}
log_key <bool>
storage <module...>
jitter <percent>
sweep_interval <duration>
Expand Down
4 changes: 2 additions & 2 deletions distributed.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
}
}
}
Expand All @@ -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 {
Expand Down
28 changes: 21 additions & 7 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -198,11 +202,21 @@ 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",
zap.String("zone", zoneName),
zap.Duration("wait", wait),
zap.String("remote_ip", remoteIP),
)

if h.LogKey {
h.logger.Info("rate limit exceeded",
zap.String("zone", zoneName),
zap.String("key", key),
zap.Duration("wait", wait),
zap.String("remote_ip", remoteIP),
)
} else {
h.logger.Info("rate limit exceeded",
zap.String("zone", zoneName),
zap.Duration("wait", wait),
zap.String("remote_ip", remoteIP),
)
}

// make some information about this rate limit available
repl.Set("http.rate_limit.exceeded.name", zoneName)
Expand Down

0 comments on commit 4595b8a

Please sign in to comment.