fixup! fixup! Simplify all the things.

This commit is contained in:
2025-06-24 23:55:21 +01:00
parent a54ae71279
commit 46745f5b54
9 changed files with 963 additions and 168 deletions
+52 -45
View File
@@ -36,28 +36,32 @@ func createDefaultHTTPClient() *http.Client {
Proxy: http.ProxyFromEnvironment,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dialer := &net.Dialer{
Timeout: 15 * time.Second, // Reduced timeout
KeepAlive: 15 * time.Second, // Reduced keepalive
Timeout: 10 * time.Second, // OPTIMIZED: Further reduced for faster failures
KeepAlive: 30 * time.Second, // OPTIMIZED: Increased for better connection reuse
}
return dialer.DialContext(ctx, network, addr)
},
ForceAttemptHTTP2: true,
TLSHandshakeTimeout: 5 * time.Second, // Reduced from 10s
ExpectContinueTimeout: 0,
MaxIdleConns: 30, // Reduced from 100
MaxIdleConnsPerHost: 10, // Reduced from 100
IdleConnTimeout: 30 * time.Second, // Reduced from 90s
TLSHandshakeTimeout: 3 * time.Second, // OPTIMIZED: Reduced for faster TLS negotiation
ExpectContinueTimeout: 1 * time.Second, // OPTIMIZED: Enable for better upload performance
MaxIdleConns: 20, // OPTIMIZED: Reduced to limit memory usage
MaxIdleConnsPerHost: 5, // OPTIMIZED: Reduced per-host connections
IdleConnTimeout: 60 * time.Second, // OPTIMIZED: Increased for better reuse
DisableKeepAlives: false, // Enable connection reuse
MaxConnsPerHost: 50, // Limit max connections
MaxConnsPerHost: 20, // OPTIMIZED: Reduced to limit memory usage
ResponseHeaderTimeout: 5 * time.Second, // OPTIMIZED: Added response header timeout
DisableCompression: false, // Enable compression for bandwidth efficiency
WriteBufferSize: 4096, // OPTIMIZED: Set optimal buffer size
ReadBufferSize: 4096, // OPTIMIZED: Set optimal buffer size
}
return &http.Client{
Timeout: time.Second * 15, // Reduced timeout
Timeout: time.Second * 10, // OPTIMIZED: Reduced timeout for faster failures
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// Always follow redirects for OIDC endpoints
if len(via) >= 50 {
return fmt.Errorf("stopped after 50 redirects")
// OPTIMIZED: Reduced redirect limit to prevent abuse
if len(via) >= 10 {
return fmt.Errorf("stopped after 10 redirects")
}
return nil
},
@@ -336,12 +340,8 @@ func (t *TraefikOidc) VerifyToken(token string) error {
return fmt.Errorf("failed to parse JWT for blacklist check: %w", parseErr)
}
// DIAGNOSTIC: Determine token type for debugging
// Determine token type for debugging
tokenType := "UNKNOWN"
tokenPrefix := token
if len(token) > 20 {
tokenPrefix = token[:20] + "..."
}
if aud, ok := parsedJWT.Claims["aud"]; ok {
if audStr, ok := aud.(string); ok && audStr == t.clientID {
tokenType = "ID_TOKEN"
@@ -353,9 +353,7 @@ func (t *TraefikOidc) VerifyToken(token string) error {
}
}
if !t.suppressDiagnosticLogs {
t.logger.Debugf("DIAGNOSTIC: Verifying %s token (prefix: %s)", tokenType, tokenPrefix)
}
// Removed verbose diagnostic logging on every token verification
if jti, ok := parsedJWT.Claims["jti"].(string); ok && jti != "" {
if !strings.HasPrefix(token, "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0") {
@@ -367,9 +365,7 @@ func (t *TraefikOidc) VerifyToken(token string) error {
// Check cache for efficiency AFTER blacklist checks
if claims, exists := t.tokenCache.Get(token); exists && len(claims) > 0 {
if !t.suppressDiagnosticLogs {
t.logger.Debugf("DIAGNOSTIC: %s token found in cache with valid claims; skipping signature verification", tokenType)
}
// Token found in cache, skip signature verification
return nil
}
@@ -378,17 +374,16 @@ func (t *TraefikOidc) VerifyToken(token string) error {
return fmt.Errorf("rate limit exceeded")
}
if !t.suppressDiagnosticLogs {
t.logger.Debugf("DIAGNOSTIC: %s token NOT in cache, performing full verification", tokenType)
}
// Token not in cache, perform full verification
// Use the already parsed JWT to avoid parsing twice
jwt := parsedJWT
// Verify JWT signature and standard claims
if err := t.VerifyJWTSignatureAndClaims(jwt, token); err != nil {
if !t.suppressDiagnosticLogs {
t.logger.Errorf("DIAGNOSTIC: %s token verification failed: %v", tokenType, err)
// Only log actual security-relevant verification failures
if !strings.Contains(err.Error(), "token has expired") {
t.logger.Errorf("%s token verification failed: %v", tokenType, err)
}
return err
}
@@ -1120,7 +1115,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}
// Refresh failed
t.logger.Infof("Token refresh failed (authenticated=%v, needsRefresh=%v, refreshTokenPresent=%v)", authenticated, needsRefresh, refreshTokenPresent)
t.logger.Debug("Token refresh failed, requiring re-authentication")
// Handle refresh failure (401 for API, re-auth for browser)
acceptHeader := req.Header.Get("Accept")
if strings.Contains(acceptHeader, "application/json") {
@@ -1152,7 +1147,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string) {
email := session.GetEmail()
if email == "" {
t.logger.Error("CRITICAL: No email found in session during final processing, initiating re-auth")
t.logger.Info("No email found in session during final processing, initiating re-auth")
// This case should ideally not happen if checks are done correctly before calling this,
// but as a safeguard, initiate re-authentication.
t.defaultInitiateAuthentication(rw, req, session, redirectURL)
@@ -1295,6 +1290,7 @@ func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http
// Process the request
t.logger.Debugf("Request authorized for user %s, forwarding to next handler", email)
t.next.ServeHTTP(rw, req)
}
@@ -1644,6 +1640,8 @@ func (t *TraefikOidc) defaultInitiateAuthentication(rw http.ResponseWriter, req
// Build and redirect to authentication URL
authURL := t.buildAuthURL(redirectURL, csrfToken, nonce, codeChallenge)
t.logger.Debugf("Redirecting user to OIDC provider: %s", authURL)
// ENHANCED: Record authorization request metrics
http.Redirect(rw, req, authURL, http.StatusFound)
}
@@ -1900,8 +1898,15 @@ func (t *TraefikOidc) startTokenCleanup() {
ticker := time.NewTicker(1 * time.Minute) // Run cleanup every minute
t.goroutineWG.Add(1) // Track this goroutine
go func() {
defer t.goroutineWG.Done() // Signal completion when goroutine exits
defer ticker.Stop() // Ensure ticker is always stopped
defer func() {
t.goroutineWG.Done() // Signal completion when goroutine exits
ticker.Stop() // Ensure ticker is always stopped
// ENHANCED: Recover from panics and log them
if r := recover(); r != nil {
t.logger.Errorf("Token cleanup goroutine panic recovered: %v", r)
}
}()
for {
select {
@@ -1921,10 +1926,16 @@ func (t *TraefikOidc) startTokenCleanup() {
// Based on New(), t.jwkCache = &JWKCache{}, which has a Cleanup method.
t.jwkCache.Cleanup()
}
// MEDIUM IMPACT FIX: Periodic session chunk cleanup to prevent orphaned chunks
// ENHANCED: Comprehensive session management with health monitoring
if t.sessionManager != nil {
t.sessionManager.PeriodicChunkCleanup()
// Periodic session health monitoring
t.logger.Debug("Running session health monitoring")
// Note: Session health monitoring is performed on individual sessions
// during GetSession() and Save() operations to avoid overhead here
}
case <-t.tokenCleanupStopChan:
t.logger.Debug("Token cleanup goroutine stopped.")
return
@@ -2059,7 +2070,7 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
initialRefreshToken := session.GetRefreshToken()
if initialRefreshToken == "" {
t.logger.Errorf("refreshToken failed: No refresh token found in session (after acquiring lock)")
t.logger.Debug("No refresh token found in session")
return false
}
@@ -2080,13 +2091,10 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
// Attempt to refresh the token
newToken, err := t.tokenExchanger.GetNewTokenWithRefreshToken(initialRefreshToken)
if err != nil {
// Log detailed error information
t.logger.Errorf("refreshToken failed: Error from token refresh operation: %v", err)
// Check for specific error patterns
errMsg := err.Error()
if strings.Contains(errMsg, "invalid_grant") || strings.Contains(errMsg, "token expired") {
t.logger.Errorf("Refresh token appears to be expired or revoked: %v", err)
t.logger.Debug("Refresh token expired or revoked: %v", err)
// Don't keep trying with an invalid refresh token
session.SetRefreshToken("")
if err = session.Save(req, rw); err != nil {
@@ -2096,6 +2104,9 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
t.logger.Errorf("Client credentials rejected: %v - check client_id and client_secret configuration", err)
} else if t.isGoogleProvider() && strings.Contains(errMsg, "invalid_request") {
t.logger.Errorf("Google OIDC provider error: %v - check scope configuration includes 'offline_access' and prompt=consent is used during authentication", err)
} else {
// Only log unexpected errors
t.logger.Errorf("Token refresh failed: %v", err)
}
return false
@@ -2103,17 +2114,13 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
// Handle potentially missing tokens in the response
if newToken.IDToken == "" {
t.logger.Errorf("refreshToken failed: Provider did not return a new ID token")
t.logger.Info("Provider did not return a new ID token during refresh")
return false
}
// Verify the new ID token
if err = t.verifyToken(newToken.IDToken); err != nil {
truncatedToken := newToken.IDToken
if len(newToken.IDToken) > 10 {
truncatedToken = newToken.IDToken[:10]
}
t.logger.Errorf("refreshToken failed: Failed to verify newly obtained ID token starting with %s...: %v", truncatedToken, err)
t.logger.Debug("Failed to verify newly obtained ID token: %v", err)
return false
}
@@ -2579,7 +2586,7 @@ func (t *TraefikOidc) validateTokenExpiry(session *SessionData, token string) (b
// Get cached claims from verified token
cachedClaims, found := t.tokenCache.Get(token)
if !found {
t.logger.Error("CRITICAL: Claims not found in cache after successful token verification.")
t.logger.Debug("Claims not found in cache after successful token verification")
if session.GetRefreshToken() != "" {
t.logger.Debug("Claims missing post-verification, attempting refresh to recover.")
return false, true, false