fix(api): bound concurrent login bcrypt to shed CPU-pin floods
The public /auth/login route runs a full-cost bcrypt compare on every request — including the anti-enumeration dummy-hash compare for an unknown user — with no bound on how many run at once. A flood of concurrent logins therefore pins every core in bcrypt, starving the rest of the API. Cap the simultaneous compares with a small non-blocking concurrency limiter (a buffered-channel semaphore): a login that cannot take a slot is shed with 429 auth_busy before the compare, rather than piling more work onto the scheduler. The slot guards only the hash and is released the instant the compare returns. It is a concurrency cap, not a per-account lockout, so it never fences out the one admin trying to break-glass in, and the 429 lands before any credential distinction so it leaks nothing about the username. The cap follows the existing "zero disables" lever idiom (WakeCooldown, MaxRunningServers); cmd/felis wires it to the core count (floored at 4).
This commit is contained in:
4 files changed
+133
-3
No files matched your search
+12
-2
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
goruntime "runtime"
|
||||
"time"
|
||||
|
||||
"felis.lolicon.best/internal/api"
|
||||
@@ -137,6 +138,14 @@ func cmdAPI(args []string, stdout, stderr io.Writer) int {
|
||||
// agree on what local auth knows.
|
||||
repo := api.NewPGRepo(drv.DB())
|
||||
|
||||
// Bound concurrent login bcrypt to roughly the core count (floored so even a 1–2
|
||||
// vCPU demo box tolerates a handful of simultaneous staff logins). bcrypt is
|
||||
// CPU-costly and the public login route runs a full compare on every request, so
|
||||
// this caps the work a login flood can pile on the scheduler; the excess is shed
|
||||
// as a cheap 429. Staff password logins are rare (players never use this path), so
|
||||
// the cap never bites legitimate use.
|
||||
loginBcryptCap := max(goruntime.NumCPU(), 4)
|
||||
|
||||
a := &api.API{
|
||||
Repo: repo,
|
||||
Cluster: api.NewK8sCluster(cl, cfg.K8s.Namespace),
|
||||
@@ -161,8 +170,9 @@ func cmdAPI(args []string, stdout, stderr io.Writer) int {
|
||||
RootDomain: cfg.Server.RootDomain,
|
||||
AdminHostname: cfg.Auth.AdminHostname,
|
||||
},
|
||||
RootDomain: cfg.Server.RootDomain,
|
||||
WakeCooldown: 30 * time.Second,
|
||||
RootDomain: cfg.Server.RootDomain,
|
||||
WakeCooldown: 30 * time.Second,
|
||||
MaxConcurrentLogins: loginBcryptCap,
|
||||
}
|
||||
fmt.Fprintln(stderr, "felis api: external face fails closed (Access JWKS key function not configured)")
|
||||
|
||||
|
||||
Reference in new issue
Block a user