Improvements targetting possible memory usage spikes.

This commit is contained in:
2025-06-18 00:50:12 +01:00
parent 488264028b
commit 84d7cd3d76
3 changed files with 301 additions and 80 deletions
+85 -15
View File
@@ -1,6 +1,7 @@
package traefikoidc
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
@@ -16,18 +17,82 @@ import (
)
var (
replayCacheMu sync.Mutex
replayCache *Cache // Replace unbounded map with bounded Cache
replayCacheMu sync.RWMutex // Use RWMutex for better read performance
replayCache *Cache // Replace unbounded map with bounded Cache
replayCacheOnce sync.Once // CRITICAL FIX: Ensure thread-safe singleton initialization
)
// initReplayCache initializes the global replay cache with size limit
// CRITICAL FIX: Thread-safe initialization with proper size limits and LRU eviction
func initReplayCache() {
if replayCache == nil {
replayCacheOnce.Do(func() {
replayCache = NewCache()
replayCache.SetMaxSize(10000) // Set size limit to 10,000 entries
replayCache.SetMaxSize(10000) // Set size limit to 10,000 entries (prevents unbounded growth)
// Note: NewCache() already starts auto-cleanup goroutine with LRU eviction
})
}
// CRITICAL FIX: Add cleanup function for global replay cache
func cleanupReplayCache() {
replayCacheMu.Lock()
defer replayCacheMu.Unlock()
if replayCache != nil {
replayCache.Close() // Stop auto-cleanup goroutine
replayCache = nil // Allow GC
}
}
// CRITICAL FIX: Add function to get cache stats for monitoring
func getReplayCacheStats() (size int, maxSize int) {
replayCacheMu.RLock()
defer replayCacheMu.RUnlock()
if replayCache == nil {
return 0, 0
}
// We need to add a method to get current size from Cache
// For now, return maxSize as approximation
return 0, 10000 // TODO: Add Size() method to Cache
}
// CRITICAL FIX: Add periodic cleanup mechanism for long-running scenarios
func startReplayCacheCleanup(ctx context.Context, logger *Logger) {
// Start background cleanup goroutine with periodic stats logging
go func() {
ticker := time.NewTicker(5 * time.Minute) // Run cleanup every 5 minutes
defer ticker.Stop()
for {
select {
case <-ticker.C:
// Log cache stats for monitoring
size, maxSize := getReplayCacheStats()
if logger != nil {
logger.Debugf("Replay cache stats: size=%d, maxSize=%d", size, maxSize)
}
// Force cleanup if cache exists (the cache handles its own cleanup,
// but this provides additional periodic maintenance)
replayCacheMu.RLock()
if replayCache != nil {
// Cache already handles cleanup automatically,
// this just ensures cleanup runs periodically for long-running scenarios
}
replayCacheMu.RUnlock()
case <-ctx.Done():
// Context cancelled, cleanup and exit
cleanupReplayCache()
if logger != nil {
logger.Debug("Replay cache cleanup goroutine stopped due to context cancellation")
}
return
}
}
}()
}
// STABILITY FIX: Standardize clock skew tolerance usage
// ClockSkewToleranceFuture defines the tolerance for future-based claims like 'exp'.
// Allows for more leniency with expiration checks.
@@ -195,16 +260,16 @@ func (j *JWT) Verify(issuerURL, clientID string, skipReplayCheck ...bool) error
return nil
}
// SECURITY FIX: Use bounded Cache with thread-safe operations
replayCacheMu.Lock()
defer replayCacheMu.Unlock()
// CRITICAL FIX: Use thread-safe initialization and proper locking
initReplayCache() // Thread-safe initialization
// Initialize cache if not already done
initReplayCache()
// SECURITY FIX: Use read lock for checking, write lock for writing
replayCacheMu.RLock()
_, exists := replayCache.Get(jti)
replayCacheMu.RUnlock()
// SECURITY FIX: Check for replay attack using Cache API
if _, exists := replayCache.Get(jti); exists {
return fmt.Errorf("token replay detected")
if exists {
return fmt.Errorf("token replay detected (jti: %s)", jti)
}
// Calculate expiration time
@@ -216,10 +281,15 @@ func (j *JWT) Verify(issuerURL, clientID string, skipReplayCheck ...bool) error
expTime = time.Now().Add(10 * time.Minute)
}
// SECURITY FIX: Add to replay cache with expiration using Cache API
// SECURITY FIX: Add to replay cache with expiration using write lock
duration := time.Until(expTime)
if duration > 0 {
replayCache.Set(jti, true, duration)
replayCacheMu.Lock()
// Double-check pattern: verify cache is still initialized
if replayCache != nil {
replayCache.Set(jti, true, duration)
}
replayCacheMu.Unlock()
}
}