mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
fix(gateway): don't count body-read failures toward the auth lockout
A request whose body cannot be read to hash failed through the catch-all branch, logging a malformed envelope, returning IncompleteSignature and recording an anonymous lockout strike, so ten dropped connections locked out an IP. Match sigv4.ErrReadingBody and answer InternalError without recording a failure. Bumps bluebottle for the new sentinel.
This commit is contained in:
@@ -23,7 +23,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.4.0
|
||||
github.com/miekg/dns v1.1.73
|
||||
github.com/minio/highwayhash v1.0.4
|
||||
github.com/mulgadc/bluebottle v1.20.1-0.20260916040751-5a5ca6f0fb33
|
||||
github.com/mulgadc/bluebottle v1.20.1-0.20260928004821-163f43196afd
|
||||
github.com/mulgadc/northstar v1.20.1-0.20260917032839-357bbc94201d
|
||||
github.com/mulgadc/predastore v1.20.1-0.20260918222809-019dd349cf1f
|
||||
github.com/mulgadc/viperblock v1.20.0
|
||||
@@ -74,7 +74,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 // indirect
|
||||
github.com/aws/smithy-go v1.28.1 // indirect
|
||||
github.com/aws/smithy-go v1.28.2 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
|
||||
@@ -73,6 +73,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 h1:Zpnqa6XtrNzXZnwbdCqHOXpXhMsa
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.51.0/go.mod h1:/8JRcdTt//hG0Q4BTmGbuOplT7ABe+5rdtqUHqXvYIM=
|
||||
github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ=
|
||||
github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aws/smithy-go v1.28.2 h1:myhcykQcatTul2B/zITjDk203G7t0awUAs1hVry5Bvg=
|
||||
github.com/aws/smithy-go v1.28.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -368,6 +370,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/mulgadc/bluebottle v1.20.1-0.20260916040751-5a5ca6f0fb33 h1:Rw3mnRF2YrhVZgHpCsOi6La8cOu/V3xnhB000iWcdr8=
|
||||
github.com/mulgadc/bluebottle v1.20.1-0.20260916040751-5a5ca6f0fb33/go.mod h1:4rjLL3B0xZBPmxacaXPkw6sPOZZgyuCDBFmPSOC81yE=
|
||||
github.com/mulgadc/bluebottle v1.20.1-0.20260928004821-163f43196afd h1:Ffa6OIUqHLUNn+NM4Ij4kGOLm7DEeicM16+Sn/Kulm8=
|
||||
github.com/mulgadc/bluebottle v1.20.1-0.20260928004821-163f43196afd/go.mod h1:LH0MuQcFxet0dDEISlZsuULIYj592eCN2Fv39A6EH5o=
|
||||
github.com/mulgadc/northstar v1.20.1-0.20260917032839-357bbc94201d h1:xYdO+KXrn+/aBLva3yuRly9MnKrSNSouGYleITgkoXE=
|
||||
github.com/mulgadc/northstar v1.20.1-0.20260917032839-357bbc94201d/go.mod h1:3GB49VUE9rA2idp88se5jAMv3ql1Oq9kIcT29WAZwDU=
|
||||
github.com/mulgadc/predastore v1.20.1-0.20260918222809-019dd349cf1f h1:P7echVc7fCYiU2CS/L0dLAf0i/6eWcjDqqf7AnFKpDg=
|
||||
|
||||
@@ -57,6 +57,12 @@ func (gw *GatewayConfig) SigV4AuthMiddleware() func(http.Handler) http.Handler {
|
||||
gw.writeSigV4Error(w, r, awserrors.ErrorMissingAuthenticationToken, "")
|
||||
case errors.Is(err, sigv4.ErrPayloadTooLarge):
|
||||
gw.writeSigV4Error(w, r, awserrors.ErrorRequestEntityTooLarge, "")
|
||||
case errors.Is(err, sigv4.ErrReadingBody):
|
||||
// A dropped or stalled connection, not an auth attempt: counting it
|
||||
// would lock out an IP for its network trouble.
|
||||
slog.Warn("Auth aborted: request body read failed",
|
||||
"sourceIP", clientIP, "err", err)
|
||||
gw.writeSigV4Error(w, r, awserrors.ErrorInternalError, "")
|
||||
case errors.Is(err, sigv4.ErrRequestTimeTooSkewed):
|
||||
// Skew/replay: AWS returns this as SignatureDoesNotMatch, which is
|
||||
// indistinguishable on the wire from a canonicalisation mismatch.
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
@@ -544,6 +545,31 @@ func TestSigV4Auth_RequestBodyTooLarge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A body the gateway cannot read is a transport failure: it answers InternalError
|
||||
// and leaves the lockout alone, so a flaky link cannot lock out its own IP.
|
||||
func TestSigV4Auth_BodyReadFailureDoesNotCountTowardLockout(t *testing.T) {
|
||||
handler := setupTestApp(testAccessKey, testSecretKey)
|
||||
|
||||
for range maxFailures + 1 {
|
||||
req := httptest.NewRequest(http.MethodPost, "/", iotest.ErrReader(io.ErrUnexpectedEOF))
|
||||
req.Host = "localhost:9999"
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
signTestRequest(t, req, []byte("Action=DescribeInstances"), testAccessKey, testSecretKey)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
require.Equal(t, http.StatusInternalServerError, resp.StatusCode, string(body))
|
||||
require.Contains(t, string(body), awserrors.ErrorInternalError)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = "localhost:9999"
|
||||
signTestRequest(t, req, nil, testAccessKey, testSecretKey)
|
||||
|
||||
resp := doRequest(handler, req)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "body-read failures must not lock out the IP")
|
||||
}
|
||||
|
||||
// --- Clock Skew / Replay Protection Tests ---
|
||||
|
||||
func TestSigV4Auth_ExpiredTimestamp(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user