Files
traefikoidc/internal/cache/manager.go
T
lukaszraczylo 1b49e133da Complete rebuild of the plugin
* Fix bug affecting Azure OIDC authentication ( and most likely others )

* Fixes issue #51

* Ensure that appended roles are unique. Update the documentation.

* Improvements targetting possible memory usage spikes.

* Additional fixes and cleanup

* Refactoring code to fix the issues identified by the users.

* Modernize run

* Fieldalignment

* Multiple changes to improve performance and reduce complexity.
- Optimise the errors and recovery.
- Deduplicate code in metadata cache.
- Remove unused performance monitoring code.
- Simplify session management and settings handling.

* Fix claims issue.

* Add ability to overwrite the default scopes in the settings file

* Well.. that escalated quickly.

Completely forgot that Traefik uses outdated Yaegi and requires compatibility with 1.20 ( pre-generic Go code ).

* Bugfix #51: Ensures that user provided scopes overrides work.

* fixup! Bugfix #51: Ensures that user provided scopes overrides work.

* fixup! fixup! Bugfix #51: Ensures that user provided scopes overrides work.

* Abstract the provider logic into a separate package.

* Additional micro fixes and cleanups.

* Simplify all the things.

* fixup! Simplify all the things.

* fixup! fixup! Simplify all the things.

* fixup! fixup! fixup! Simplify all the things.

* fixup! fixup! fixup! fixup! Simplify all the things.

* ...

* Cleanup tests.

* fixup! Cleanup tests.

* fixup! fixup! fixup! Cleanup tests.

* fixup! fixup! fixup! fixup! Cleanup tests.

* fixup! fixup! fixup! fixup! fixup! Cleanup tests.

* Issue #53: Fix CSRF token handling in reverse proxy

1.  HTTPS Detection Fixed (session.go:723)
- Now uses X-Forwarded-Proto header instead of r.URL.Scheme
- Properly detects HTTPS in reverse proxy environments
2.  SameSite Cookie Attribute Fixed
- Removed automatic SameSiteStrictMode for HTTPS (would break OAuth)
- Keeps SameSiteLaxMode to allow OAuth callbacks from external domains
- Only uses Strict for AJAX requests which don't involve OAuth redirects
3.  Cookie Domain Handling Fixed
- Now respects X-Forwarded-Host header for cookie domain
- Ensures cookies are set for the public domain, not internal proxy domain
4.  EnhanceSessionSecurity Properly Integrated
- Function is now actually called during session save
- Applies security enhancements without breaking OAuth flow

Why Issue #53 Failed Before:

1. Cookies were not marked Secure in HTTPS environments (browser wouldn't send them back)
2. If they had been Secure with SameSite=Strict, Azure callbacks would still fail
3. Cookie domain might have been wrong (internal vs public domain)

Why It Works Now:

1. Cookies are properly marked Secure for HTTPS
2. Uses SameSite=Lax to allow OAuth provider callbacks
3. Cookie domain uses public domain from X-Forwarded-Host
4. CSRF token persists through the entire OAuth flow

* Next set of enhancements together with memory usage improvements.

* Memory leak fixes and optimisations.

* CSRF and Cookie Domain fixes

* fixup! CSRF and Cookie Domain fixes

* Metadata cache leak fix + profiling

* fixup! Metadata cache leak fix + profiling

* Memory leaks hunting, part 1337.

* Further pursue of perfection.

* fixup! Further pursue of perfection.

* fixup! fixup! Further pursue of perfection.

* fixup! fixup! fixup! Further pursue of perfection.

* fixup! fixup! fixup! fixup! Further pursue of perfection.

* fixup! fixup! fixup! fixup! fixup! Further pursue of perfection.

* fixup! fixup! fixup! fixup! fixup! fixup! Further pursue of perfection.

* fixup! fixup! fixup! fixup! fixup! fixup! fixup! Further pursue of perfection.

* fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Further pursue of perfection.

* fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Further pursue of perfection.

* Clear race conditions

* fixup! Clear race conditions

* Weekend fun with memory leaks

* Splitting code into multiple files with reasonable testing coverage.

```
ok      github.com/lukaszraczylo/traefikoidc    117.017s        coverage: 72.6% of statements
ok      github.com/lukaszraczylo/traefikoidc/auth       0.505s  coverage: 87.1% of statements
ok      github.com/lukaszraczylo/traefikoidc/circuit_breaker    0.283s  coverage: 99.0% of statements
        github.com/lukaszraczylo/traefikoidc/config             coverage: 0.0% of statements
ok      github.com/lukaszraczylo/traefikoidc/handlers   0.349s  coverage: 98.2% of statements
ok      github.com/lukaszraczylo/traefikoidc/internal/providers (cached)        coverage: 94.3% of statements
ok      github.com/lukaszraczylo/traefikoidc/middleware 0.808s  coverage: 78.0% of statements
ok      github.com/lukaszraczylo/traefikoidc/recovery   0.653s  coverage: 100.0% of statements
ok      github.com/lukaszraczylo/traefikoidc/session/chunking   (cached)        coverage: 87.8% of statements
ok      github.com/lukaszraczylo/traefikoidc/session/core       (cached)        coverage: 85.6% of statements
ok      github.com/lukaszraczylo/traefikoidc/session/crypto     (cached)        coverage: 81.8% of statements
ok      github.com/lukaszraczylo/traefikoidc/session/storage    (cached)        coverage: 93.5% of statements
ok      github.com/lukaszraczylo/traefikoidc/session/validators (cached)        coverage: 98.8% of statements
````

* fixup! Splitting code into multiple files with reasonable testing coverage.

* fixup! fixup! Splitting code into multiple files with reasonable testing coverage.

* Weekend fun with further optimisations.

* fixup! Weekend fun with further optimisations.

* fixup! fixup! Weekend fun with further optimisations.

* fixup! fixup! fixup! Weekend fun with further optimisations.

* fixup! fixup! fixup! fixup! Weekend fun with further optimisations.

* fixup! fixup! fixup! fixup! fixup! Weekend fun with further optimisations.

* Pre-release cleanup.

* Enhance test coverage.

* fixup! Enhance test coverage.

* fixup! fixup! Enhance test coverage.

* fixup! fixup! fixup! Enhance test coverage.
2025-09-18 11:01:30 +01:00

285 lines
6.8 KiB
Go

package cache
import (
"sync"
"time"
)
// Manager manages multiple cache instances with singleton pattern
type Manager struct {
mu sync.RWMutex
// Core caches
tokenCache *Cache
metadataCache *Cache
jwkCache *Cache
sessionCache *Cache
generalCache *Cache
// Typed wrappers
typedToken *TokenCache
typedMetadata *MetadataCache
typedJWK *JWKCache
typedSession *SessionCache
logger Logger
}
var (
globalManager *Manager
globalManagerOnce sync.Once
)
// GetGlobalManager returns the singleton cache manager instance
func GetGlobalManager(logger Logger) *Manager {
globalManagerOnce.Do(func() {
globalManager = NewManager(logger)
})
return globalManager
}
// NewManager creates a new cache manager
func NewManager(logger Logger) *Manager {
if logger == nil {
logger = &noOpLogger{}
}
m := &Manager{
logger: logger,
}
// Initialize core caches with appropriate configurations
m.initializeCaches()
return m
}
// initializeCaches creates all cache instances with appropriate configurations
func (m *Manager) initializeCaches() {
// Token cache configuration
tokenConfig := Config{
Type: TypeToken,
MaxSize: 5000,
MaxMemoryBytes: 32 * 1024 * 1024, // 32MB
DefaultTTL: 1 * time.Hour,
CleanupInterval: 5 * time.Minute,
EnableAutoCleanup: true,
EnableMemoryLimit: true,
EnableMetrics: true,
Logger: m.logger,
TokenConfig: &TokenConfig{
BlacklistTTL: 24 * time.Hour,
RefreshTokenTTL: 7 * 24 * time.Hour,
EnableTokenRotation: true,
},
}
m.tokenCache = New(tokenConfig)
m.typedToken = NewTokenCache(m.tokenCache)
// Metadata cache configuration
metadataConfig := Config{
Type: TypeMetadata,
MaxSize: 100,
MaxMemoryBytes: 10 * 1024 * 1024, // 10MB
DefaultTTL: 24 * time.Hour,
CleanupInterval: 30 * time.Minute,
EnableAutoCleanup: true,
EnableMemoryLimit: true,
EnableMetrics: true,
Logger: m.logger,
MetadataConfig: &MetadataConfig{
GracePeriod: 5 * time.Minute,
ExtendedGracePeriod: 15 * time.Minute,
MaxGracePeriod: 1 * time.Hour,
SecurityCriticalMaxGracePeriod: 30 * time.Minute,
SecurityCriticalFields: []string{"issuer", "jwks_uri"},
},
}
m.metadataCache = New(metadataConfig)
m.typedMetadata = NewMetadataCache(m.metadataCache, *metadataConfig.MetadataConfig)
// JWK cache configuration
jwkConfig := Config{
Type: TypeJWK,
MaxSize: 50,
MaxMemoryBytes: 5 * 1024 * 1024, // 5MB
DefaultTTL: 1 * time.Hour,
CleanupInterval: 10 * time.Minute,
EnableAutoCleanup: true,
EnableMemoryLimit: true,
EnableMetrics: true,
Logger: m.logger,
JWKConfig: &JWKConfig{
RefreshInterval: 1 * time.Hour,
MinRefreshTime: 5 * time.Minute,
MaxKeyAge: 24 * time.Hour,
},
}
m.jwkCache = New(jwkConfig)
m.typedJWK = NewJWKCache(m.jwkCache)
// Session cache configuration
sessionConfig := Config{
Type: TypeSession,
MaxSize: 10000,
MaxMemoryBytes: 64 * 1024 * 1024, // 64MB
DefaultTTL: 30 * time.Minute,
CleanupInterval: 5 * time.Minute,
EnableAutoCleanup: true,
EnableMemoryLimit: true,
EnableMetrics: true,
Logger: m.logger,
}
m.sessionCache = New(sessionConfig)
m.typedSession = NewSessionCache(m.sessionCache)
// General cache configuration
generalConfig := Config{
Type: TypeGeneral,
MaxSize: 1000,
MaxMemoryBytes: 16 * 1024 * 1024, // 16MB
DefaultTTL: 10 * time.Minute,
CleanupInterval: 5 * time.Minute,
EnableAutoCleanup: true,
EnableMemoryLimit: true,
EnableMetrics: true,
Logger: m.logger,
}
m.generalCache = New(generalConfig)
}
// GetTokenCache returns the token cache instance
func (m *Manager) GetTokenCache() *TokenCache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.typedToken
}
// GetMetadataCache returns the metadata cache instance
func (m *Manager) GetMetadataCache() *MetadataCache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.typedMetadata
}
// GetJWKCache returns the JWK cache instance
func (m *Manager) GetJWKCache() *JWKCache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.typedJWK
}
// GetSessionCache returns the session cache instance
func (m *Manager) GetSessionCache() *SessionCache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.typedSession
}
// GetGeneralCache returns the general cache instance
func (m *Manager) GetGeneralCache() *Cache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.generalCache
}
// GetRawTokenCache returns the raw token cache for compatibility
func (m *Manager) GetRawTokenCache() *Cache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.tokenCache
}
// GetRawMetadataCache returns the raw metadata cache for compatibility
func (m *Manager) GetRawMetadataCache() *Cache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.metadataCache
}
// GetRawJWKCache returns the raw JWK cache for compatibility
func (m *Manager) GetRawJWKCache() *Cache {
m.mu.RLock()
defer m.mu.RUnlock()
return m.jwkCache
}
// GetStats returns statistics for all caches
func (m *Manager) GetStats() map[string]map[string]interface{} {
m.mu.RLock()
defer m.mu.RUnlock()
return map[string]map[string]interface{}{
"token": m.tokenCache.GetStats(),
"metadata": m.metadataCache.GetStats(),
"jwk": m.jwkCache.GetStats(),
"session": m.sessionCache.GetStats(),
"general": m.generalCache.GetStats(),
}
}
// ClearAll clears all cache instances
func (m *Manager) ClearAll() {
m.mu.Lock()
defer m.mu.Unlock()
m.tokenCache.Clear()
m.metadataCache.Clear()
m.jwkCache.Clear()
m.sessionCache.Clear()
m.generalCache.Clear()
}
// Close gracefully shuts down all cache instances
func (m *Manager) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
var firstErr error
if err := m.tokenCache.Close(); err != nil && firstErr == nil {
firstErr = err
}
if err := m.metadataCache.Close(); err != nil && firstErr == nil {
firstErr = err
}
if err := m.jwkCache.Close(); err != nil && firstErr == nil {
firstErr = err
}
if err := m.sessionCache.Close(); err != nil && firstErr == nil {
firstErr = err
}
if err := m.generalCache.Close(); err != nil && firstErr == nil {
firstErr = err
}
return firstErr
}
// CleanupAll runs cleanup on all cache instances
func (m *Manager) CleanupAll() {
m.mu.RLock()
defer m.mu.RUnlock()
m.tokenCache.Cleanup()
m.metadataCache.Cleanup()
m.jwkCache.Cleanup()
m.sessionCache.Cleanup()
m.generalCache.Cleanup()
}
// SetLogger updates the logger for all caches
func (m *Manager) SetLogger(logger Logger) {
m.mu.Lock()
defer m.mu.Unlock()
m.logger = logger
if logger != nil {
m.tokenCache.logger = logger
m.metadataCache.logger = logger
m.jwkCache.logger = logger
m.sessionCache.logger = logger
m.generalCache.logger = logger
}
}