mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-19 05:47:48 +00:00
fix(auth): throttle repeated auth failures, rate-limit failure log
Fixes finding 3 from the 2026-08-22 security audit: authStore.identify had no counter, no backoff, and no lockout on failed attempts, and logger.go wrote one unbuffered stderr line per attempt including the caller's RemoteAddr. An unauthenticated attacker got unlimited digest guesses at line rate and could drive synchronous stderr writes on the shared Traefik ingress. - Add per-source-IP failure throttling to identify, reusing the existing in-process memoryStore (limits.go) as authStore's own dedicated instance rather than a new dependency: authFailureLimit (20) failed attempts per authFailureWindow (1 minute) throttles further attempts from that IP outright, including a subsequently valid key, and resets once the window elapses. - clientIP reads r.RemoteAddr only, never a client-supplied header, matching the same trust boundary the rest of this package already assumes (routes_passthrough.go strips X-Forwarded-*/X-Auth-* before any outbound request). - Rate-limit logAuthEvent's failure line via authStore.shouldLogAuthFailure, the identical technique limiter.logStoreError already applies to store errors. Successful-auth logging stays unbounded (bounded by legitimate, already-authenticated traffic elsewhere).
This commit is contained in:
@@ -25,9 +25,21 @@ func (g *Gateway) errorf(format string, args ...any) {
|
||||
// line ever includes the presented API key itself — identify resolves a
|
||||
// key to a *user and hands this function only that user's name, never the
|
||||
// key it authenticated.
|
||||
//
|
||||
// The failure line is rate-limited via g.auth.shouldLogAuthFailure
|
||||
// (auth.go), the same technique limiter.logStoreError (limits.go) already
|
||||
// applies to store-error lines (security audit finding 3, 2026-08-22): an
|
||||
// unauthenticated caller driving unlimited failed attempts must never be
|
||||
// able to drive unlimited synchronous stderr writes on the shared Traefik
|
||||
// ingress — a slow log pipe would otherwise stall request handling for
|
||||
// every tenant, not just the one failing auth. The success line stays
|
||||
// unbounded — see shouldLogAuthFailure's own doc comment for why that is
|
||||
// safe.
|
||||
func (g *Gateway) logAuthEvent(ok bool, userName string, r *http.Request) {
|
||||
if !ok {
|
||||
g.logf("auth failed: %s %q from %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||||
if g.auth.shouldLogAuthFailure() {
|
||||
g.logf("auth failed: %s %q from %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||||
}
|
||||
return
|
||||
}
|
||||
g.logf("auth ok: user %q %s %q", userName, r.Method, r.URL.Path)
|
||||
|
||||
Reference in New Issue
Block a user