mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
Audit review (rounds 1-31): fix confirmed issues, remove dead code, harden tests
Security/correctness fixes: - fail-open token validation preserved with lenient-audience; access token that fails verification and has no ID token to corroborate now fails closed - token_manager blacklist-expiry: fix impossible tautology (x>24h && x<24h) - constant-time OAuth state compare; clear state/nonce/code_verifier on callback token-exchange failure to prevent replay of half-failed flows - StopAllTasks gated on last-instance close - background-task panic recovery; remove task-registry counter leak - circuit breaker half-open: release probe slot; CB + per-session outcome recorded once per refresh operation, not once per waiter - Redis connection pool: handle nil receive when pool closed concurrently - FileStore: atomic temp-write + rename - errors_recovery health-check data race -> atomic Test-isolation / hardening: - reset helpers for Once-pinned global cache manager and transport pool - coverage: utils 70->93% (redirect URL security contract); fuzz targets for bearer parsers; concurrent ServeHTTP race tests for the shared request path Dead-code removal (verified orphaned, build+suite green): internal/providers (30 files, 0 importers), internal/compat, internal/features, input_validation. Verified: govulncheck, go mod tidy, yaegi-validate, cross-compile (darwin+ linux), golangci-lint (new-from-rev clean), 3x full race+shuffle green.
This commit is contained in:
+10
@@ -83,6 +83,11 @@ func (p *ConnectionPool) Get(ctx context.Context) (*RedisConn, error) {
|
||||
|
||||
select {
|
||||
case conn = <-p.connections:
|
||||
if conn == nil {
|
||||
// Pool channel was closed concurrently (Close) - receiving from a
|
||||
// closed channel yields the zero value.
|
||||
return nil, ErrBackendClosed
|
||||
}
|
||||
// Reuse existing connection - validate if health check enabled
|
||||
if p.config.EnableHealthCheck && !p.isConnectionHealthy(conn) {
|
||||
// Connection is stale, close it and try again
|
||||
@@ -118,6 +123,11 @@ func (p *ConnectionPool) Get(ctx context.Context) (*RedisConn, error) {
|
||||
// Pool exhausted, wait for a connection with timeout
|
||||
select {
|
||||
case conn = <-p.connections:
|
||||
if conn == nil {
|
||||
// Pool channel was closed concurrently (Close) - receiving from a
|
||||
// closed channel yields the zero value.
|
||||
return nil, ErrBackendClosed
|
||||
}
|
||||
// Validate connection if health check enabled
|
||||
if p.config.EnableHealthCheck && !p.isConnectionHealthy(conn) {
|
||||
_ = conn.Close()
|
||||
|
||||
+41
-1
@@ -367,7 +367,47 @@ func TestConnectionPool_Close(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestConnectionPool_Timeouts tests various timeout scenarios
|
||||
// TestConnectionPool_GetDuringClose_NoNilRegression ensures a Get racing with
|
||||
// Close never yields a nil *RedisConn with a nil error (which previously
|
||||
// happened when the receive from the closed p.connections channel returned
|
||||
// the zero value), and never panics by calling Close on a nil receiver.
|
||||
func TestConnectionPool_GetDuringClose_NoNilRegression(t *testing.T) {
|
||||
mr := NewMiniredisServer(t)
|
||||
|
||||
config := &PoolConfig{
|
||||
Address: mr.GetAddr(),
|
||||
MaxConnections: 1,
|
||||
ConnectTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
pool, err := NewConnectionPool(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fill the pool so a concurrent Get blocks waiting for a connection.
|
||||
held, err := pool.Get(context.Background())
|
||||
require.NoError(t, err)
|
||||
defer pool.Put(held)
|
||||
|
||||
type result struct {
|
||||
conn *RedisConn
|
||||
err error
|
||||
}
|
||||
resCh := make(chan result, 1)
|
||||
go func() {
|
||||
c, e := pool.Get(context.Background())
|
||||
resCh <- result{c, e}
|
||||
}()
|
||||
|
||||
// Give the goroutine time to reach the blocking receive on p.connections,
|
||||
// then close the pool. Closing the channel unblocks the receive with the
|
||||
// zero value; Get must surface ErrBackendClosed rather than (nil, nil).
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
require.NoError(t, pool.Close())
|
||||
|
||||
res := <-resCh
|
||||
require.Error(t, res.err, "Get after Close must return an error, not (nil, nil)")
|
||||
assert.True(t, errors.Is(res.err, ErrBackendClosed), "want ErrBackendClosed, got %v", res.err)
|
||||
}
|
||||
func TestConnectionPool_Timeouts(t *testing.T) {
|
||||
mr := NewMiniredisServer(t)
|
||||
|
||||
|
||||
Vendored
+7
@@ -1223,6 +1223,10 @@ func TestCacheManagerCompat(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("CacheManagerCompat Close should not error: %v", err)
|
||||
}
|
||||
// Close() shuts down the process-global manager. Recreate it so later
|
||||
// tests using the compat wrappers see an open cache (order-independent;
|
||||
// the singleton is otherwise pinned closed for the process lifetime).
|
||||
resetGlobalManagerForTest()
|
||||
}
|
||||
|
||||
// TestUniversalCacheManagerCompat tests UniversalCacheManagerCompat methods
|
||||
@@ -1262,6 +1266,9 @@ func TestUniversalCacheManagerCompat(t *testing.T) {
|
||||
if err != nil && err.Error() != "cache already closed" {
|
||||
t.Errorf("UniversalCacheManagerCompat Close should not error (unless already closed): %v", err)
|
||||
}
|
||||
// Close() shuts down the process-global manager. Recreate it so later
|
||||
// tests using the compat wrappers see an open cache (order-independent).
|
||||
resetGlobalManagerForTest()
|
||||
}
|
||||
|
||||
// TestTypedCacheWrapper tests TypedCache methods
|
||||
|
||||
Vendored
+9
@@ -33,6 +33,15 @@ func GetGlobalManager(logger Logger) *Manager {
|
||||
return globalManager
|
||||
}
|
||||
|
||||
// resetGlobalManagerForTest clears the process-global manager so the next call to
|
||||
// GetGlobalManager returns a fresh instance. The singleton is otherwise pinned
|
||||
// for the process lifetime; tests that Close() the global manager must reset it,
|
||||
// or any later test using the compat wrappers fails with "cache already closed".
|
||||
func resetGlobalManagerForTest() {
|
||||
globalManagerOnce = sync.Once{}
|
||||
globalManager = nil
|
||||
}
|
||||
|
||||
// NewManager creates a new cache manager
|
||||
func NewManager(logger Logger) *Manager {
|
||||
if logger == nil {
|
||||
|
||||
Reference in New Issue
Block a user