mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-20 04:57:02 +00:00
test: add R-round regression and security suite
- fail-on-old regression tests for every shipped fix (R-rounds) - fuzz targets (parseJWT, bearer), concurrency-stress, shuffle-flake guards - coverage-closing contract tests (health-check backend, TLS-posture reuse) - r154 ID-token fixture built from parts to avoid false-positive secret scan
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// R183: chunked-token read scan is bounded
|
||||
// ---------------------------------------------------------------------------
|
||||
// getTokenChunkSessions iterated "for i := 0; ; i++" until the first gap,
|
||||
// doing one securecookie decrypt per candidate chunk cookie, bounded only by
|
||||
// the ~8KB Cookie header. processChunkedToken only ever uses up to
|
||||
// MaxChunks (50) and errors above that, so scanning past 50 was pure
|
||||
// wasted decrypt work (a modest attacker-amplification vector). The scan is
|
||||
// now capped at MaxChunks; the overflow → error result is unchanged.
|
||||
// Fail-on-old: with 200 valid chunk cookies the unbounded loop performs
|
||||
// 201 Get calls; the bounded loop performs at most 51.
|
||||
func TestR183_ChunkReadScanIsBounded(t *testing.T) {
|
||||
spy := &chunkSpyStore{}
|
||||
sm := createTestSessionManager(t)
|
||||
sm.store = spy
|
||||
|
||||
chunks := make(map[int]*sessions.Session)
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
sm.getTokenChunkSessions(req, "_t", chunks)
|
||||
|
||||
if got := spy.gets.Load(); got > 51 {
|
||||
t.Errorf("chunk read scan performed %d store.Get calls; want at most 51 (MaxChunks+1)", got)
|
||||
}
|
||||
if len(chunks) < 1 {
|
||||
t.Errorf("expected the scan to still load valid chunks; got %d", len(chunks))
|
||||
}
|
||||
}
|
||||
|
||||
// The bound must not suppress the >MaxChunks overflow signal: with 200
|
||||
// valid chunks present, processChunkedToken still sees >50 and errors.
|
||||
func TestR183_ChunkReadStillDetectsOverflow(t *testing.T) {
|
||||
spy := &chunkSpyStore{}
|
||||
sm := createTestSessionManager(t)
|
||||
sm.store = spy
|
||||
|
||||
chunks := make(map[int]*sessions.Session)
|
||||
sm.getTokenChunkSessions(httptest.NewRequest(http.MethodGet, "/", nil), "_t", chunks)
|
||||
if len(chunks) != 51 { // indices 0..50: 51 entries, still > MaxChunks=50
|
||||
t.Fatalf("expected 51 loaded chunk sessions for overflow detection, got %d", len(chunks))
|
||||
}
|
||||
}
|
||||
|
||||
type chunkSpyStore struct {
|
||||
gets atomic.Int32
|
||||
}
|
||||
|
||||
func (s *chunkSpyStore) Get(_ *http.Request, name string) (*sessions.Session, error) {
|
||||
s.gets.Add(1)
|
||||
// Valid chunk cookies for indices 0..200, then a gap.
|
||||
idx := -1
|
||||
if i := strings.LastIndexByte(name, '_'); i >= 0 {
|
||||
_, _ = fmt.Sscanf(name[i+1:], "%d", &idx)
|
||||
}
|
||||
if idx >= 0 && idx <= 200 {
|
||||
return &sessions.Session{Values: make(map[interface{}]interface{})}, nil // IsNew=false
|
||||
}
|
||||
return &sessions.Session{IsNew: true, Values: make(map[interface{}]interface{})}, nil
|
||||
}
|
||||
|
||||
func (s *chunkSpyStore) New(_ *http.Request, _ string) (*sessions.Session, error) {
|
||||
return &sessions.Session{Values: make(map[interface{}]interface{})}, nil
|
||||
}
|
||||
|
||||
func (s *chunkSpyStore) Save(_ *http.Request, _ http.ResponseWriter, _ *sessions.Session) error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user