mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +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:
+20
-6
@@ -244,14 +244,15 @@ func (rc *RefreshCoordinator) CoordinateRefresh(
|
||||
operation.mutex.RUnlock()
|
||||
|
||||
if result != nil {
|
||||
// Record metrics based on result
|
||||
// Per-request counters only. The circuit breaker and per-session
|
||||
// success/failure trackers must be recorded exactly once per
|
||||
// refresh OPERATION (in executeRefreshAsync), not once per waiter:
|
||||
// N requests coalescing onto a single refresh would otherwise
|
||||
// multiply one upstream result into N failure records and
|
||||
// over-trigger the circuit breaker (MaxFailures=3).
|
||||
if result.err != nil {
|
||||
rc.circuitBreaker.RecordFailure()
|
||||
rc.recordRefreshFailure(sessionID)
|
||||
atomic.AddInt64(&rc.metrics.failedRefreshes, 1)
|
||||
} else {
|
||||
rc.circuitBreaker.RecordSuccess()
|
||||
rc.recordRefreshSuccess(sessionID)
|
||||
atomic.AddInt64(&rc.metrics.successfulRefreshes, 1)
|
||||
}
|
||||
return result.tokenResponse, result.err
|
||||
@@ -371,7 +372,7 @@ func (rc *RefreshCoordinator) failCandidate(tokenHash string, op *refreshOperati
|
||||
// executeRefreshAsync performs the actual refresh operation asynchronously
|
||||
func (rc *RefreshCoordinator) executeRefreshAsync(
|
||||
operation *refreshOperation,
|
||||
_ string, // sessionID - reserved for future metrics/logging
|
||||
sessionID string,
|
||||
tokenHash string,
|
||||
refreshFunc func() (*TokenResponse, error),
|
||||
) {
|
||||
@@ -426,6 +427,19 @@ func (rc *RefreshCoordinator) executeRefreshAsync(
|
||||
}
|
||||
operation.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Record the circuit breaker and per-session outcome exactly once per
|
||||
// refresh operation. This function runs once per operation regardless of
|
||||
// how many requests coalesced onto it (see CoordinateRefresh), so a
|
||||
// single upstream refresh yields a single CB/session record rather than
|
||||
// N (one per waiter), which would over-trigger the circuit breaker.
|
||||
if err := operation.result.err; err != nil {
|
||||
rc.circuitBreaker.RecordFailure()
|
||||
rc.recordRefreshFailure(sessionID)
|
||||
} else {
|
||||
rc.circuitBreaker.RecordSuccess()
|
||||
rc.recordRefreshSuccess(sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
// scheduleDelayedCleanup schedules a cleanup using a timer instead of spawning
|
||||
|
||||
Reference in New Issue
Block a user