fix(api): cap concurrent SSE streams per principal
Console and build-log relays hold a Server-Sent Event connection open for the life of a client's attachment; a stalled reader pins the relay goroutine plus its upstream kube-apiserver follow. Without a bound, one authenticated principal could open these repeatedly and accumulate leaked control-plane connections. Add a per-principal stream cap (streamLimiter) enforced before either relay opens its follow stream, returning 429 too_many_streams past the limit. cmd/felis wires it to 16; zero disables it, matching the "zero disables" idiom of the other levers. This bounds the blast radius of the stalled-stream leak; it does not close the leak itself -- the per-write deadline that severs a stalled stream is a separate change.
This commit is contained in:
5 files changed
+175
No files matched your search
@@ -118,6 +118,19 @@ func (a *API) handleBuildLogs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
p := principalFromContext(r.Context())
|
||||
|
||||
// Bound concurrent SSE streams per principal (shared with the console relay): a
|
||||
// stalled reader pins this relay and its upstream build-pod follow, so cap how many
|
||||
// one principal may hold at once. Acquired before opening the stream and released on
|
||||
// every return path. See streamGate — this bounds blast radius, not the leak itself.
|
||||
release, ok := a.streamGate().acquire(streamKey(p))
|
||||
if !ok {
|
||||
writeError(w, r, newError(http.StatusTooManyRequests, "too_many_streams",
|
||||
"too many open build-log streams; close one and retry"))
|
||||
return
|
||||
}
|
||||
defer release()
|
||||
|
||||
src, err := a.BuildLogs.StreamLogs(r.Context(), id)
|
||||
switch {
|
||||
case errors.Is(err, ErrNotFound):
|
||||
|
||||
Reference in new issue
Block a user