The three felis-api http.Servers (internal, external, https) were built with only Addr and Handler, leaving ReadHeaderTimeout, IdleTimeout, and ReadTimeout at zero. A zero ReadHeaderTimeout is a Slowloris hole — a client trickling header bytes pins a connection indefinitely — and a zero IdleTimeout lets kept-alive connections accumulate (gosec G112). Route all three listeners through a newAPIServer factory that sets a 10s ReadHeaderTimeout and a 120s IdleTimeout. WriteTimeout and ReadTimeout are left unset on purpose: the external and https faces stream Server-Sent Events (console / build logs) for the lifetime of a client attachment, and a WriteTimeout would sever a healthy long-lived stream. Slowloris is closed by ReadHeaderTimeout, which bounds only the header phase.
29 lines
1.1 KiB
Go
29 lines
1.1 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
)
|
|
|
|
// TestNewAPIServerSetsHardenedTimeouts pins the gosec-G112 hardening on every
|
|
// felis-api listener: the shared factory must bound the header and idle phases
|
|
// (Slowloris + idle-connection exhaustion) while leaving WriteTimeout UNSET, because
|
|
// the external and https faces stream Server-Sent Events for the life of a client's
|
|
// console/build-log attachment and a WriteTimeout would sever a healthy long stream.
|
|
func TestNewAPIServerSetsHardenedTimeouts(t *testing.T) {
|
|
srv := newAPIServer(":0", http.NewServeMux())
|
|
|
|
if srv.ReadHeaderTimeout <= 0 {
|
|
t.Errorf("ReadHeaderTimeout = %v, want a positive Slowloris bound", srv.ReadHeaderTimeout)
|
|
}
|
|
if srv.IdleTimeout <= 0 {
|
|
t.Errorf("IdleTimeout = %v, want a positive idle-connection bound", srv.IdleTimeout)
|
|
}
|
|
if srv.WriteTimeout != 0 {
|
|
t.Errorf("WriteTimeout = %v, want 0 (unset) so long-lived SSE streams are not severed", srv.WriteTimeout)
|
|
}
|
|
if srv.ReadTimeout != 0 {
|
|
t.Errorf("ReadTimeout = %v, want 0 (unset) so a slow SSE attach is not capped", srv.ReadTimeout)
|
|
}
|
|
}
|