Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log key when rate limit has been exceeded. #57

Merged
merged 2 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": {},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is in the wrong place.

"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
20 changes: 17 additions & 3 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,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)

Expand Down
Loading