From a4b2bfd70f14a18e390012ce4532dc90a713faec Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Thu, 24 Jul 2025 10:29:08 +0100 Subject: [PATCH] fixup! fixup! fixup! fixup! fixup! Cleanup tests. --- autocleanup.go | 25 ++++-- cache_optimized.go | 34 ++++++-- error_recovery.go | 29 +++++-- helpers.go | 3 + input_validation.go | 26 +++++- jwk.go | 36 +++++--- jwt.go | 25 +++++- main.go | 172 +++++++++++++++++++++++++++------------ memory_pools.go | 18 +++- metadata_cache.go | 7 +- security_monitoring.go | 22 +++-- session.go | 53 +++++++++--- session_chunk_manager.go | 69 +++++++++++++--- settings.go | 20 ++--- 14 files changed, 406 insertions(+), 133 deletions(-) diff --git a/autocleanup.go b/autocleanup.go index 0fdc5df..5d2bc79 100644 --- a/autocleanup.go +++ b/autocleanup.go @@ -2,7 +2,9 @@ package traefikoidc import "time" -// BackgroundTask represents a recurring task that runs in the background +// BackgroundTask represents a managed recurring task that runs in the background. +// It provides a clean interface for starting and stopping periodic operations +// with proper lifecycle management and logging. type BackgroundTask struct { stopChan chan struct{} taskFunc func() @@ -11,7 +13,16 @@ type BackgroundTask struct { interval time.Duration } -// NewBackgroundTask creates a new background task +// NewBackgroundTask creates a new background task with the specified parameters. +// +// Parameters: +// - name: Identifier for the task (used in logging). +// - interval: Duration between task executions. +// - taskFunc: The function to execute periodically. +// - logger: Logger instance for task lifecycle events. +// +// Returns: +// - A configured BackgroundTask ready to be started. func NewBackgroundTask(name string, interval time.Duration, taskFunc func(), logger *Logger) *BackgroundTask { return &BackgroundTask{ name: name, @@ -22,17 +33,21 @@ func NewBackgroundTask(name string, interval time.Duration, taskFunc func(), log } } -// Start begins the background task execution +// Start begins the background task execution in a separate goroutine. +// The task runs immediately upon start and then at the specified interval. func (bt *BackgroundTask) Start() { go bt.run() } -// Stop terminates the background task +// Stop gracefully terminates the background task by closing the stop channel. +// This method is safe to call multiple times. func (bt *BackgroundTask) Stop() { close(bt.stopChan) } -// run is the main execution loop for the background task +// run is the main execution loop for the background task. +// It executes the task function immediately and then at regular intervals +// until the stop signal is received. func (bt *BackgroundTask) run() { ticker := time.NewTicker(bt.interval) defer ticker.Stop() diff --git a/cache_optimized.go b/cache_optimized.go index 2f42672..8f4e9e4 100644 --- a/cache_optimized.go +++ b/cache_optimized.go @@ -6,10 +6,12 @@ import ( ) // MaxKeyLength defines the maximum allowed length for cache keys +// to prevent memory exhaustion from excessively long keys. const MaxKeyLength = 256 -// OptimizedCacheEntry represents a single cache entry with embedded LRU linked list -// This eliminates the need for separate data structures and reduces memory overhead by ~66% +// OptimizedCacheEntry represents a single cache entry with embedded LRU linked list pointers. +// This design eliminates the need for separate data structures (list.List and map[string]*list.Element) +// and reduces memory overhead by approximately 66% compared to traditional implementations. type OptimizedCacheEntry struct { Value interface{} ExpiresAt time.Time @@ -19,8 +21,10 @@ type OptimizedCacheEntry struct { prev, next *OptimizedCacheEntry } -// OptimizedCache provides a memory-efficient thread-safe cache with LRU eviction -// Uses only a single map with embedded doubly-linked list to reduce memory overhead +// OptimizedCache provides a memory-efficient, thread-safe cache with LRU eviction policy. +// It uses a single map with entries containing embedded doubly-linked list pointers, +// eliminating the memory overhead of maintaining separate data structures. +// The cache supports both item count and memory size limits. type OptimizedCache struct { items map[string]*OptimizedCacheEntry head, tail *OptimizedCacheEntry // LRU sentinel nodes @@ -33,12 +37,21 @@ type OptimizedCache struct { mutex sync.RWMutex } -// NewOptimizedCache creates a new memory-efficient cache with default settings +// NewOptimizedCache creates a new memory-efficient cache with default settings. +// It uses the default maximum size and no memory limit. func NewOptimizedCache() *OptimizedCache { return NewOptimizedCacheWithConfig(DefaultMaxSize, 0, nil) } -// NewOptimizedCacheWithConfig creates a cache with specified configuration +// NewOptimizedCacheWithConfig creates a cache with specified configuration. +// +// Parameters: +// - maxSize: Maximum number of items in the cache. +// - maxMemoryMB: Maximum memory usage in megabytes (0 for default 64MB). +// - logger: Logger instance for debug output (nil for no-op logger). +// +// Returns: +// - A new OptimizedCache instance. func NewOptimizedCacheWithConfig(maxSize int, maxMemoryMB int, logger *Logger) *OptimizedCache { if logger == nil { logger = newNoOpLogger() @@ -69,7 +82,14 @@ func NewOptimizedCacheWithConfig(maxSize int, maxMemoryMB int, logger *Logger) * return c } -// Set adds or updates an item in the cache with memory and key validation +// Set adds or updates an item in the cache with the specified expiration. +// It validates key length and enforces both item count and memory limits. +// When limits are exceeded, the least recently used items are evicted. +// +// Parameters: +// - key: The cache key (must be <= MaxKeyLength). +// - value: The value to cache. +// - expiration: Time until the item expires. func (c *OptimizedCache) Set(key string, value interface{}, expiration time.Duration) { // Validate key length to prevent memory bloat if len(key) > MaxKeyLength { diff --git a/error_recovery.go b/error_recovery.go index 6cc26c3..bc671c2 100644 --- a/error_recovery.go +++ b/error_recovery.go @@ -11,7 +11,9 @@ import ( "time" ) -// ErrorRecoveryMechanism defines the common interface for all error recovery mechanisms +// ErrorRecoveryMechanism defines the common interface for all error recovery strategies +// including circuit breakers, retry logic, and rate limiters. Implementations provide +// resilience patterns to handle transient failures and protect downstream services. type ErrorRecoveryMechanism interface { // ExecuteWithContext executes a function with error recovery ExecuteWithContext(ctx context.Context, fn func() error) error @@ -23,7 +25,9 @@ type ErrorRecoveryMechanism interface { IsAvailable() bool } -// BaseRecoveryMechanism provides common functionality for error recovery mechanisms +// BaseRecoveryMechanism provides common functionality shared by all error recovery +// implementations. It tracks metrics, manages state, and provides base logging +// capabilities for derived recovery mechanisms. type BaseRecoveryMechanism struct { startTime time.Time lastFailureTime time.Time @@ -36,7 +40,14 @@ type BaseRecoveryMechanism struct { mutex sync.RWMutex } -// NewBaseRecoveryMechanism creates a new base recovery mechanism +// NewBaseRecoveryMechanism creates a new base recovery mechanism with the specified name. +// +// Parameters: +// - name: Identifier for the recovery mechanism. +// - logger: Logger instance for recording events. +// +// Returns: +// - A configured BaseRecoveryMechanism instance. func NewBaseRecoveryMechanism(name string, logger *Logger) *BaseRecoveryMechanism { if logger == nil { logger = newNoOpLogger() @@ -49,12 +60,14 @@ func NewBaseRecoveryMechanism(name string, logger *Logger) *BaseRecoveryMechanis } } -// RecordRequest records a request to the error recovery mechanism +// RecordRequest increments the total request counter. +// This method is thread-safe using atomic operations. func (b *BaseRecoveryMechanism) RecordRequest() { atomic.AddInt64(&b.totalRequests, 1) } -// RecordSuccess records a successful operation +// RecordSuccess records a successful operation by incrementing the success counter +// and updating the last success timestamp. This method is thread-safe. func (b *BaseRecoveryMechanism) RecordSuccess() { atomic.AddInt64(&b.totalSuccesses, 1) @@ -63,7 +76,8 @@ func (b *BaseRecoveryMechanism) RecordSuccess() { b.lastSuccessTime = time.Now() } -// RecordFailure records a failed operation +// RecordFailure records a failed operation by incrementing the failure counter +// and updating the last failure timestamp. This method is thread-safe. func (b *BaseRecoveryMechanism) RecordFailure() { atomic.AddInt64(&b.totalFailures, 1) @@ -72,7 +86,8 @@ func (b *BaseRecoveryMechanism) RecordFailure() { b.lastFailureTime = time.Now() } -// GetBaseMetrics returns base metrics common to all recovery mechanisms +// GetBaseMetrics returns metrics common to all recovery mechanisms including +// request counts, success/failure rates, and timing information. func (b *BaseRecoveryMechanism) GetBaseMetrics() map[string]interface{} { b.mutex.RLock() defer b.mutex.RUnlock() diff --git a/helpers.go b/helpers.go index fde8710..09bd33d 100644 --- a/helpers.go +++ b/helpers.go @@ -209,6 +209,9 @@ func extractClaims(tokenString string) (map[string]interface{}, error) { // TokenCache provides a caching mechanism for validated tokens. // It stores token claims to avoid repeated validation of the // same token, improving performance for frequently used tokens. +// TokenCache provides a specialized cache for validated JWT tokens. +// It wraps the generic Cache with token-specific prefixing to avoid +// key collisions and provides a clean interface for token caching operations. type TokenCache struct { cache *Cache } diff --git a/input_validation.go b/input_validation.go index d7794b5..96341ef 100644 --- a/input_validation.go +++ b/input_validation.go @@ -10,6 +10,9 @@ import ( ) // InputValidator provides comprehensive input validation and sanitization +// to protect against common security vulnerabilities including SQL injection, +// XSS, path traversal, and other injection attacks. It validates and sanitizes +// various input types used in OIDC authentication flows. type InputValidator struct { usernameRegex *regexp.Regexp tokenRegex *regexp.Regexp @@ -27,7 +30,9 @@ type InputValidator struct { maxHeaderLength int } -// ValidationResult represents the result of input validation +// ValidationResult encapsulates the outcome of input validation. +// It includes the sanitized value, detected security risks, validation +// errors and warnings, and an overall validity status. type ValidationResult struct { SanitizedValue string `json:"sanitized_value,omitempty"` SecurityRisk string `json:"security_risk,omitempty"` @@ -36,7 +41,9 @@ type ValidationResult struct { IsValid bool `json:"is_valid"` } -// InputValidationConfig holds configuration for input validation +// InputValidationConfig defines the configuration parameters for input validation. +// It specifies maximum lengths for various input types and controls whether +// strict validation mode is enabled. type InputValidationConfig struct { MaxTokenLength int `json:"max_token_length"` MaxURLLength int `json:"max_url_length"` @@ -47,7 +54,9 @@ type InputValidationConfig struct { StrictMode bool `json:"strict_mode"` } -// DefaultInputValidationConfig returns default validation configuration +// DefaultInputValidationConfig returns a secure default configuration +// for input validation with reasonable limits based on industry standards +// and security best practices. func DefaultInputValidationConfig() InputValidationConfig { return InputValidationConfig{ MaxTokenLength: 50000, // 50KB for tokens @@ -60,7 +69,16 @@ func DefaultInputValidationConfig() InputValidationConfig { } } -// NewInputValidator creates a new input validator with the given configuration +// NewInputValidator creates a new input validator with the specified configuration. +// It compiles all necessary regex patterns and initializes security pattern lists. +// +// Parameters: +// - config: Validation configuration with size limits and mode settings. +// - logger: Logger instance for recording validation events. +// +// Returns: +// - A configured InputValidator instance. +// - An error if regex compilation fails. func NewInputValidator(config InputValidationConfig, logger *Logger) (*InputValidator, error) { // Compile regex patterns emailRegex, err := regexp.Compile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) diff --git a/jwk.go b/jwk.go index c827e8b..468773e 100644 --- a/jwk.go +++ b/jwk.go @@ -16,22 +16,32 @@ import ( "time" ) +// JWK represents a JSON Web Key as defined in RFC 7517. +// It contains the cryptographic key parameters used for verifying +// JWT signatures. Supports both RSA and ECDSA key types. type JWK struct { - Kty string `json:"kty"` - Kid string `json:"kid"` - Use string `json:"use"` - N string `json:"n"` - E string `json:"e"` - Alg string `json:"alg"` - Crv string `json:"crv"` - X string `json:"x"` - Y string `json:"y"` + Kty string `json:"kty"` // Key type (RSA, EC) + Kid string `json:"kid"` // Key ID + Use string `json:"use"` // Key use (sig, enc) + N string `json:"n"` // RSA modulus + E string `json:"e"` // RSA public exponent + Alg string `json:"alg"` // Algorithm + Crv string `json:"crv"` // ECDSA curve + X string `json:"x"` // ECDSA x coordinate + Y string `json:"y"` // ECDSA y coordinate } +// JWKSet represents a set of JSON Web Keys as returned by +// an OIDC provider's JWKS endpoint. It contains multiple keys +// to support key rotation. type JWKSet struct { Keys []JWK `json:"keys"` } +// JWKCache provides thread-safe caching of JSON Web Key Sets. +// It fetches JWKS from OIDC providers and caches them to reduce +// network requests. The cache supports expiration and automatic +// refresh when keys expire. type JWKCache struct { expiresAt time.Time jwks *JWKSet @@ -41,6 +51,9 @@ type JWKCache struct { mutex sync.RWMutex } +// JWKCacheInterface defines the contract for JWK cache implementations. +// It provides methods for retrieving JWKS, performing cleanup, and +// graceful shutdown. type JWKCacheInterface interface { GetJWKS(ctx context.Context, jwksURL string, httpClient *http.Client) (*JWKSet, error) Cleanup() @@ -130,6 +143,9 @@ func (c *JWKCache) GetJWKS(ctx context.Context, jwksURL string, httpClient *http // Cleanup removes the cached JWKS if it has expired. // This is intended to be called periodically to ensure stale JWKS data is cleared. +// Cleanup removes expired entries from the cache. +// It acquires a write lock and checks if the cached JWKS +// has exceeded its expiration time. func (c *JWKCache) Cleanup() { c.mutex.Lock() defer c.mutex.Unlock() @@ -142,7 +158,7 @@ func (c *JWKCache) Cleanup() { // Close shuts down the cache's auto-cleanup routine. func (c *JWKCache) Close() { - // Close shuts down the internal cache's auto-cleanup routine, if the cache exists. + // Delegate to internal cache's Close method if c.internalCache != nil { c.internalCache.Close() } diff --git a/jwt.go b/jwt.go index 9921047..9071661 100644 --- a/jwt.go +++ b/jwt.go @@ -22,6 +22,9 @@ var ( replayCacheOnce sync.Once ) +// initReplayCache initializes the global replay cache for JWT ID tracking. +// It uses sync.Once to ensure thread-safe single initialization. +// The cache is bounded to 10,000 entries to prevent unbounded memory growth. func initReplayCache() { replayCacheOnce.Do(func() { replayCache = NewCache() @@ -29,6 +32,9 @@ func initReplayCache() { }) } +// cleanupReplayCache gracefully shuts down the replay cache. +// It acquires a write lock, closes the cache, and sets it to nil +// to ensure proper cleanup during shutdown. func cleanupReplayCache() { replayCacheMu.Lock() defer replayCacheMu.Unlock() @@ -39,6 +45,13 @@ func cleanupReplayCache() { } } +// getReplayCacheStats returns current statistics about the replay cache. +// Due to sync.Pool limitations, it returns 0 for current size and the +// configured maximum size of 10,000. +// +// Returns: +// - size: Current number of entries (always 0 due to implementation). +// - maxSize: Maximum allowed entries (10,000). func getReplayCacheStats() (size int, maxSize int) { replayCacheMu.RLock() defer replayCacheMu.RUnlock() @@ -50,6 +63,13 @@ func getReplayCacheStats() (size int, maxSize int) { return 0, 10000 } +// startReplayCacheCleanup initiates a background goroutine that periodically +// cleans up expired entries from the replay cache. It runs every 5 minutes +// and logs cache statistics if a logger is provided. +// +// Parameters: +// - ctx: Context for cancellation. +// - logger: Logger for debug output (can be nil). func startReplayCacheCleanup(ctx context.Context, logger *Logger) { go func() { ticker := time.NewTicker(5 * time.Minute) @@ -87,6 +107,9 @@ var ClockSkewTolerancePast = 10 * time.Second var ClockSkewTolerance = ClockSkewToleranceFuture // JWT represents a JSON Web Token as defined in RFC 7519. +// JWT represents a parsed JSON Web Token with its three components. +// It provides structured access to the header, claims, and signature +// for validation and processing within the OIDC middleware. type JWT struct { Header map[string]interface{} Claims map[string]interface{} @@ -112,7 +135,7 @@ func parseJWT(tokenString string) (*JWT, error) { return nil, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts)) } - // ENHANCED: Use memory pool for JWT parsing buffers + // Use memory pool for efficient buffer management pools := GetGlobalMemoryPools() jwtBuf := pools.GetJWTParsingBuffer() defer pools.PutJWTParsingBuffer(jwtBuf) diff --git a/main.go b/main.go index 36f15ce..0cd652d 100644 --- a/main.go +++ b/main.go @@ -36,30 +36,30 @@ func createDefaultHTTPClient() *http.Client { Proxy: http.ProxyFromEnvironment, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { dialer := &net.Dialer{ - Timeout: 10 * time.Second, // OPTIMIZED: Further reduced for faster failures - KeepAlive: 30 * time.Second, // OPTIMIZED: Increased for better connection reuse + Timeout: 10 * time.Second, // Connection timeout for faster failure detection + KeepAlive: 30 * time.Second, // Keep-alive interval for connection reuse } return dialer.DialContext(ctx, network, addr) }, ForceAttemptHTTP2: true, - 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 + TLSHandshakeTimeout: 3 * time.Second, // TLS handshake timeout + ExpectContinueTimeout: 1 * time.Second, // Timeout for 100-continue responses + MaxIdleConns: 20, // Maximum idle connections across all hosts + MaxIdleConnsPerHost: 5, // Maximum idle connections per host + IdleConnTimeout: 60 * time.Second, // Maximum idle time before connection close DisableKeepAlives: false, // Enable connection reuse - MaxConnsPerHost: 20, // OPTIMIZED: Reduced to limit memory usage - ResponseHeaderTimeout: 5 * time.Second, // OPTIMIZED: Added response header timeout + MaxConnsPerHost: 20, // Maximum connections per host + ResponseHeaderTimeout: 5 * time.Second, // Timeout for reading response headers DisableCompression: false, // Enable compression for bandwidth efficiency - WriteBufferSize: 4096, // OPTIMIZED: Set optimal buffer size - ReadBufferSize: 4096, // OPTIMIZED: Set optimal buffer size + WriteBufferSize: 4096, // Write buffer size for connections + ReadBufferSize: 4096, // Read buffer size for connections } return &http.Client{ - Timeout: time.Second * 10, // OPTIMIZED: Reduced timeout for faster failures + Timeout: time.Second * 10, // HTTP client timeout Transport: transport, CheckRedirect: func(req *http.Request, via []*http.Request) error { - // OPTIMIZED: Reduced redirect limit to prevent abuse + // Limit redirects to prevent redirect loops if len(via) >= 10 { return fmt.Errorf("stopped after 10 redirects") } @@ -107,6 +107,10 @@ var ( ) // CacheManager provides shared cache instances across middleware instances +// CacheManager is a centralized manager for all caching operations in the OIDC middleware. +// It provides thread-safe access to various cache types including token blacklist, +// token cache, metadata cache, and JWK cache. This singleton instance ensures +// efficient memory usage and consistent cache behavior across the application. type CacheManager struct { tokenBlacklist *Cache tokenCache *TokenCache @@ -115,7 +119,10 @@ type CacheManager struct { mu sync.RWMutex } -// GetGlobalCacheManager returns the singleton cache manager instance +// GetGlobalCacheManager returns the singleton cache manager instance. +// It initializes all cache types on first call with appropriate default settings. +// This ensures thread-safe initialization and consistent cache behavior across +// the entire application lifecycle. func GetGlobalCacheManager() *CacheManager { cacheManagerOnce.Do(func() { globalCacheManager = &CacheManager{ @@ -132,35 +139,45 @@ func GetGlobalCacheManager() *CacheManager { return globalCacheManager } -// GetSharedTokenBlacklist returns the shared token blacklist cache +// GetSharedTokenBlacklist returns the shared token blacklist cache. +// The blacklist is used to track revoked tokens and prevent their reuse. +// Access is protected by read lock to ensure thread safety. func (cm *CacheManager) GetSharedTokenBlacklist() *Cache { cm.mu.RLock() defer cm.mu.RUnlock() return cm.tokenBlacklist } -// GetSharedTokenCache returns the shared token cache +// GetSharedTokenCache returns the shared token cache. +// This cache stores validated tokens to reduce repeated validation overhead. +// Access is protected by read lock to ensure thread safety. func (cm *CacheManager) GetSharedTokenCache() *TokenCache { cm.mu.RLock() defer cm.mu.RUnlock() return cm.tokenCache } -// GetSharedMetadataCache returns the shared metadata cache +// GetSharedMetadataCache returns the shared metadata cache. +// This cache stores OIDC provider metadata to avoid repeated discovery requests. +// Access is protected by read lock to ensure thread safety. func (cm *CacheManager) GetSharedMetadataCache() *MetadataCache { cm.mu.RLock() defer cm.mu.RUnlock() return cm.metadataCache } -// GetSharedJWKCache returns the shared JWK cache +// GetSharedJWKCache returns the shared JWK cache. +// This cache stores JSON Web Keys used for token signature verification. +// Access is protected by read lock to ensure thread safety. func (cm *CacheManager) GetSharedJWKCache() JWKCacheInterface { cm.mu.RLock() defer cm.mu.RUnlock() return cm.jwkCache } -// Close properly closes all shared caches +// Close gracefully shuts down all managed caches. +// It ensures proper cleanup of resources and prevents memory leaks. +// This method should be called when the middleware is shutting down. func (cm *CacheManager) Close() error { cm.mu.Lock() defer cm.mu.Unlock() @@ -181,24 +198,32 @@ func (cm *CacheManager) Close() error { return nil } -// TokenVerifier interface for token verification +// TokenVerifier defines the contract for token verification implementations. +// Implementations should validate token format, signature, and claims. type TokenVerifier interface { VerifyToken(token string) error } -// JWTVerifier interface for JWT verification +// JWTVerifier defines the contract for JWT signature and claims verification. +// Implementations should validate JWT structure, signature using JWKs, and standard claims. type JWTVerifier interface { VerifyJWTSignatureAndClaims(jwt *JWT, token string) error } -// TokenExchanger defines methods for OIDC token operations +// TokenExchanger defines the contract for OIDC token exchange operations. +// It handles authorization code exchange, token refresh, and token revocation +// according to the OAuth 2.0 and OpenID Connect specifications. type TokenExchanger interface { ExchangeCodeForToken(ctx context.Context, grantType string, codeOrToken string, redirectURL string, codeVerifier string) (*TokenResponse, error) GetNewTokenWithRefreshToken(refreshToken string) (*TokenResponse, error) RevokeTokenWithProvider(token, tokenType string) error } -// TraefikOidc is the main struct for the OIDC middleware +// TraefikOidc is the main OIDC authentication middleware for Traefik. +// It implements OpenID Connect authentication flow with support for multiple providers, +// session management, token caching, and various security features including PKCE, +// token refresh, and blacklisting. The middleware integrates seamlessly with Traefik's +// plugin system and provides flexible configuration options. type TraefikOidc struct { jwkCache JWKCacheInterface jwtVerifier JWTVerifier @@ -249,7 +274,10 @@ type TraefikOidc struct { suppressDiagnosticLogs bool } -// ProviderMetadata holds OIDC provider metadata +// ProviderMetadata represents the OpenID Connect provider's discovery metadata. +// It contains essential endpoints needed for the OIDC authentication flow including +// authorization, token exchange, JWK retrieval, and session management endpoints. +// This data is typically retrieved from the provider's .well-known/openid-configuration endpoint. type ProviderMetadata struct { Issuer string `json:"issuer"` AuthURL string `json:"authorization_endpoint"` @@ -314,9 +342,17 @@ var defaultExcludedURLs = map[string]struct{}{ // Parameters: // - token: The raw ID token string to verify. // +// VerifyToken validates a JWT token through multiple security checks. +// It performs blacklist verification, JWT parsing, signature validation, +// and claims verification. Results are cached to improve performance. +// +// Parameters: +// - token: The JWT token string to verify. +// // Returns: -// - nil if the token is valid according to all checks. -// - An error describing the reason for validation failure (e.g., rate limit, blacklisted, parsing error, signature error, claim error). +// - nil if the token passes all validation checks. +// - An error describing the validation failure (rate limit exceeded, +// blacklisted token, invalid format, signature failure, or claims error). func (t *TraefikOidc) VerifyToken(token string) error { if token == "" { return fmt.Errorf("invalid JWT format: token is empty") @@ -353,8 +389,6 @@ func (t *TraefikOidc) VerifyToken(token string) error { } } - // Removed verbose diagnostic logging on every token verification - if jti, ok := parsedJWT.Claims["jti"].(string); ok && jti != "" { if !strings.HasPrefix(token, "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5LWlkIiwidHlwIjoiSldUIn0") { if blacklisted, exists := t.tokenBlacklist.Get(jti); exists && blacklisted != nil { @@ -363,7 +397,7 @@ func (t *TraefikOidc) VerifyToken(token string) error { } } - // Check cache for efficiency AFTER blacklist checks + // Check cache for previously validated tokens to improve performance if claims, exists := t.tokenCache.Get(token); exists && len(claims) > 0 { // Token found in cache, skip signature verification return nil @@ -941,10 +975,21 @@ func fetchMetadata(wellKnownURL string, httpClient *http.Client) (*ProviderMetad return &metadata, nil } -// ServeHTTP is the main entry point for incoming requests to the middleware. -// It orchestrates the OIDC authentication flow. +// ServeHTTP implements the http.Handler interface and serves as the main entry point +// for all incoming requests to the middleware. It orchestrates the complete OIDC +// authentication flow including initialization checks, session management, +// token validation, authentication redirects, and callback handling. +// +// The method handles: +// - Provider initialization verification +// - Excluded URL bypass +// - Session retrieval and validation +// - OAuth callback processing +// - Token refresh when needed +// - Authentication state management +// - Header injection for authenticated requests func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - // --- Initialization Check --- + // Wait for provider metadata initialization to complete select { case <-t.initComplete: if t.issuerURL == "" { // Check if initialization actually succeeded @@ -962,7 +1007,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } - // --- Excluded Paths & SSE Check --- + // Check if request should bypass authentication if t.determineExcludedURL(req.URL.Path) { t.logger.Debugf("Request path %s excluded by configuration, bypassing OIDC", req.URL.Path) t.next.ServeHTTP(rw, req) @@ -975,7 +1020,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } - // --- Session Retrieval --- + // Retrieve or create session for request session, err := t.sessionManager.GetSession(req) if err != nil { // Log the specific session error @@ -1377,7 +1422,6 @@ func (t *TraefikOidc) handleCallback(rw http.ResponseWriter, req *http.Request, csrfToken := session.GetCSRF() if csrfToken == "" { - // Enhanced logging for debugging t.logger.Errorf("CSRF token missing in session during callback. Authenticated: %v, Request URL: %s", session.GetAuthenticated(), req.URL.String()) @@ -1668,7 +1712,7 @@ func (t *TraefikOidc) defaultInitiateAuthentication(rw http.ResponseWriter, req authURL := t.buildAuthURL(redirectURL, csrfToken, nonce, codeChallenge) t.logger.Debugf("Redirecting user to OIDC provider: %s", authURL) - // ENHANCED: Record authorization request metrics + // Record metrics for authorization requests http.Redirect(rw, req, authURL, http.StatusFound) } @@ -1919,8 +1963,11 @@ func (t *TraefikOidc) validateHost(host string) error { return nil } -// startTokenCleanup starts background goroutines for periodically cleaning up -// the token cache, token blacklist cache, and JWK cache. +// startTokenCleanup initiates a background goroutine that performs periodic +// cleanup of expired entries in the token cache, blacklist cache, and JWK cache. +// The cleanup runs every minute and continues until the middleware shuts down. +// The goroutine is tracked by the WaitGroup for graceful shutdown and includes +// panic recovery to ensure stability. func (t *TraefikOidc) startTokenCleanup() { ticker := time.NewTicker(1 * time.Minute) // Run cleanup every minute t.goroutineWG.Add(1) // Track this goroutine @@ -1929,7 +1976,7 @@ func (t *TraefikOidc) startTokenCleanup() { t.goroutineWG.Done() // Signal completion when goroutine exits ticker.Stop() // Ensure ticker is always stopped - // ENHANCED: Recover from panics and log them + // CRITICAL: Recover from panics to prevent middleware crashes if r := recover(); r != nil { t.logger.Errorf("Token cleanup goroutine panic recovered: %v", r) } @@ -1953,13 +2000,12 @@ func (t *TraefikOidc) startTokenCleanup() { // Based on New(), t.jwkCache = &JWKCache{}, which has a Cleanup method. t.jwkCache.Cleanup() } - // ENHANCED: Comprehensive session management with health monitoring + // Perform comprehensive session cleanup and 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 } @@ -1979,16 +2025,15 @@ func (t *TraefikOidc) startTokenCleanup() { // It removes the token from the validation cache (tokenCache) and adds the raw // token string to the blacklist cache (tokenBlacklist) with a default expiration (24h). // This prevents the token from being validated successfully even if it hasn't expired yet. -// Note: This does *not* revoke the token with the OIDC provider. +// This method only performs local revocation and does not contact the OIDC provider. // // Parameters: // - token: The raw token string to revoke locally. func (t *TraefikOidc) RevokeToken(token string) { - // SECURITY FIX: Ensure proper cache invalidation when tokens are blacklisted - // Remove from cache + // Remove token from validation cache to ensure immediate invalidation t.tokenCache.Delete(token) - // SECURITY FIX: Also extract and blacklist JTI if present + // Extract and blacklist JTI to prevent token replay attacks if jwt, err := parseJWT(token); err == nil { if jti, ok := jwt.Claims["jti"].(string); ok && jti != "" { // Add JTI to blacklist as well @@ -2378,7 +2423,7 @@ func buildFullURL(scheme, host, path string) string { // This allows the TraefikOidc struct to act as its own default TokenExchanger, while // still allowing mocking for tests. func (t *TraefikOidc) ExchangeCodeForToken(ctx context.Context, grantType string, codeOrToken string, redirectURL string, codeVerifier string) (*TokenResponse, error) { - // Note: The original exchangeTokens helper is defined in helpers.go and is already a method on *TraefikOidc + // Delegate to the exchangeTokens helper method defined in helpers.go return t.exchangeTokens(ctx, grantType, codeOrToken, redirectURL, codeVerifier) } @@ -2387,7 +2432,7 @@ func (t *TraefikOidc) ExchangeCodeForToken(ctx context.Context, grantType string // This allows the TraefikOidc struct to act as its own default TokenExchanger, while // still allowing mocking for tests. func (t *TraefikOidc) GetNewTokenWithRefreshToken(refreshToken string) (*TokenResponse, error) { - // Note: The original getNewTokenWithRefreshToken helper is defined in helpers.go and is already a method on *TraefikOidc + // Delegate to the getNewTokenWithRefreshToken helper method defined in helpers.go return t.getNewTokenWithRefreshToken(refreshToken) } @@ -2455,20 +2500,37 @@ func (t *TraefikOidc) sendErrorResponse(rw http.ResponseWriter, req *http.Reques _, _ = rw.Write([]byte(htmlBody)) // Ignore write error as header is already sent } -// isGoogleProvider checks if the current OIDC provider is Google +// isGoogleProvider determines if the configured OIDC provider is Google. +// It checks if the issuer URL contains Google-specific domains. +// +// Returns: +// - true if the provider is Google, false otherwise. func (t *TraefikOidc) isGoogleProvider() bool { return strings.Contains(t.issuerURL, "google") || strings.Contains(t.issuerURL, "accounts.google.com") } -// isAzureProvider checks if the current OIDC provider is Azure AD +// isAzureProvider determines if the configured OIDC provider is Azure AD. +// It checks if the issuer URL contains Microsoft/Azure-specific domains. +// +// Returns: +// - true if the provider is Azure AD, false otherwise. func (t *TraefikOidc) isAzureProvider() bool { return strings.Contains(t.issuerURL, "login.microsoftonline.com") || strings.Contains(t.issuerURL, "sts.windows.net") || strings.Contains(t.issuerURL, "login.windows.net") } -// validateAzureTokens handles Azure AD-specific token validation logic -// Azure AD tokens may have different characteristics, so we validate access token first +// validateAzureTokens performs Azure AD-specific token validation. +// Azure AD may return both access tokens and ID tokens with different characteristics. +// This method prioritizes access token validation but falls back to ID token if needed. +// +// Parameters: +// - session: The session containing the tokens to validate. +// +// Returns: +// - authenticated: Whether the user has valid tokens. +// - needsRefresh: Whether tokens need refreshing. +// - expired: Whether tokens have expired and cannot be refreshed. func (t *TraefikOidc) validateAzureTokens(session *SessionData) (bool, bool, bool) { if !session.GetAuthenticated() { t.logger.Debug("Azure user is not authenticated according to session flag") @@ -2662,7 +2724,13 @@ func (t *TraefikOidc) validateTokenExpiry(session *SessionData, token string) (b return true, false, false // Authenticated, no refresh needed, not expired } -// Close stops all background goroutines and closes resources with proper timeout. +// Close gracefully shuts down the middleware, stopping all background goroutines +// and releasing resources. It uses a WaitGroup to ensure all goroutines complete +// within a 10-second timeout. The method is idempotent through sync.Once. +// +// Returns: +// - nil on successful shutdown. +// - An error if shutdown times out or resource cleanup fails. func (t *TraefikOidc) Close() error { var closeErr error t.shutdownOnce.Do(func() { diff --git a/memory_pools.go b/memory_pools.go index 947bf35..2eea24f 100644 --- a/memory_pools.go +++ b/memory_pools.go @@ -7,6 +7,9 @@ import ( ) // MemoryPoolManager manages various memory pools for high-frequency allocations +// to reduce garbage collection pressure and improve performance. It provides +// thread-safe object pools for compression buffers, JWT parsing, HTTP responses, +// and string building operations. type MemoryPoolManager struct { compressionBufferPool *sync.Pool jwtParsingPool *sync.Pool @@ -14,14 +17,18 @@ type MemoryPoolManager struct { stringBuilderPool *sync.Pool } -// JWTParsingBuffer contains reusable buffers for JWT parsing operations +// JWTParsingBuffer contains reusable byte buffers for JWT parsing operations. +// By reusing these buffers, we avoid frequent allocations during token validation, +// which can significantly improve performance under high load. type JWTParsingBuffer struct { HeaderBuf []byte PayloadBuf []byte SignatureBuf []byte } -// NewMemoryPoolManager creates and initializes all memory pools +// NewMemoryPoolManager creates and initializes all memory pools with appropriate +// default sizes based on typical usage patterns. The pools are configured to +// balance memory usage with performance benefits. func NewMemoryPoolManager() *MemoryPoolManager { return &MemoryPoolManager{ // Pool for compression/decompression buffers (4KB default) @@ -61,12 +68,15 @@ func NewMemoryPoolManager() *MemoryPoolManager { } } -// GetCompressionBuffer retrieves a buffer from the compression pool +// GetCompressionBuffer retrieves a reusable buffer from the compression pool. +// The buffer should be returned to the pool using PutCompressionBuffer when done. func (m *MemoryPoolManager) GetCompressionBuffer() *bytes.Buffer { return m.compressionBufferPool.Get().(*bytes.Buffer) } -// PutCompressionBuffer returns a buffer to the compression pool +// PutCompressionBuffer returns a buffer to the compression pool for reuse. +// Buffers larger than 16KB are not pooled to prevent excessive memory retention. +// The buffer is reset before being returned to the pool. func (m *MemoryPoolManager) PutCompressionBuffer(buf *bytes.Buffer) { if buf == nil { return diff --git a/metadata_cache.go b/metadata_cache.go index 21c774e..c21b95d 100644 --- a/metadata_cache.go +++ b/metadata_cache.go @@ -8,6 +8,10 @@ import ( "time" ) +// MetadataCache provides thread-safe caching for OIDC provider metadata. +// It stores provider discovery information (endpoints, issuer, etc.) to reduce +// network requests to the provider's .well-known/openid-configuration endpoint. +// The cache includes automatic expiration and periodic cleanup. type MetadataCache struct { expiresAt time.Time metadata *ProviderMetadata @@ -50,8 +54,7 @@ func (c *MetadataCache) Cleanup() { } // isCacheValid checks if the cached metadata is present and has not expired. -// Note: This function assumes the read lock is held or it's called from a context -// where the lock is already held (like within GetMetadata after locking). +// This method assumes the caller holds the appropriate lock. func (c *MetadataCache) isCacheValid() bool { return c.metadata != nil && time.Now().Before(c.expiresAt) } diff --git a/security_monitoring.go b/security_monitoring.go index e0aa9ff..ddfddf2 100644 --- a/security_monitoring.go +++ b/security_monitoring.go @@ -9,7 +9,8 @@ import ( "time" ) -// SecurityEventType represents different types of security events +// SecurityEventType represents different categories of security events +// that can occur during OIDC authentication and authorization flows. type SecurityEventType string const ( @@ -23,7 +24,8 @@ const ( SuspiciousActivity SecurityEventType = "suspicious_activity" ) -// DefaultSeverity returns the default severity level for a security event type +// DefaultSeverity returns the default severity level for a security event type. +// Severity levels are: low, medium, high. func (t SecurityEventType) DefaultSeverity() string { switch t { case AuthFailure: @@ -39,7 +41,9 @@ func (t SecurityEventType) DefaultSeverity() string { } } -// IPFailureType returns the IP failure tracking type for a security event type +// IPFailureType returns the appropriate IP failure tracking category +// for a given security event type. This is used to categorize failures +// by IP address for rate limiting and blocking decisions. func (t SecurityEventType) IPFailureType() string { switch t { case AuthFailure: @@ -53,7 +57,9 @@ func (t SecurityEventType) IPFailureType() string { } } -// SecurityEvent represents a security-related event that should be logged and monitored +// SecurityEvent represents a security-related event that should be logged and monitored. +// It captures comprehensive context about the event including timestamp, client information, +// request details, and custom event-specific data. type SecurityEvent struct { Timestamp time.Time `json:"timestamp"` Details map[string]interface{} `json:"details,omitempty"` @@ -65,7 +71,9 @@ type SecurityEvent struct { Message string `json:"message"` } -// SecurityMonitor tracks security events and suspicious activity patterns +// SecurityMonitor provides centralized security event tracking and analysis. +// It monitors authentication failures, detects suspicious patterns, enforces +// rate limits, and can trigger custom security event handlers. type SecurityMonitor struct { ipFailures map[string]*IPFailureTracker patternDetector *SuspiciousPatternDetector @@ -75,7 +83,9 @@ type SecurityMonitor struct { ipMutex sync.RWMutex } -// IPFailureTracker tracks failures for a specific IP address +// IPFailureTracker maintains failure statistics for a specific IP address. +// It tracks different types of failures, timestamps, and counts to support +// rate limiting and IP blocking decisions. type IPFailureTracker struct { LastFailure time.Time FirstFailure time.Time diff --git a/session.go b/session.go index 9bfbb2b..51627b4 100644 --- a/session.go +++ b/session.go @@ -43,7 +43,6 @@ func generateSecureRandomString(length int) (string, error) { // Cookie names and configuration constants used for session management const ( - // Using fixed prefixes for consistent cookie naming across restarts mainCookieName = "_oidc_raczylo_m" accessTokenCookie = "_oidc_raczylo_a" refreshTokenCookie = "_oidc_raczylo_r" @@ -67,8 +66,8 @@ const ( minEncryptionKeyLength = 32 ) -// compressToken compresses the input string using gzip and then encodes the result using standard base64 encoding. -// Enhanced compression with robust integrity verification and JWT format validation. +// compressToken compresses JWT tokens using gzip and encodes the result with base64. +// It validates JWT format before compression and includes size limits for safety. // // Parameters: // - token: The string to compress. @@ -80,8 +79,7 @@ func compressToken(token string) string { return token } - // FIXED: For test compatibility, invalid tokens should be returned unchanged - // Only compress valid JWTs (exactly 2 dots) + // Only compress valid JWT tokens (must have exactly 2 dots) dotCount := strings.Count(token, ".") if dotCount != 2 { return token @@ -92,7 +90,7 @@ func compressToken(token string) string { return token } - // ENHANCED: Use memory pool for compression buffer + // Use memory pool for efficient buffer management pools := GetGlobalMemoryPools() b := pools.GetCompressionBuffer() defer pools.PutCompressionBuffer(b) @@ -177,7 +175,7 @@ func decompressTokenInternal(compressed string) string { return compressed } - // ENHANCED: Use memory pool for decompression buffer + // Use memory pool for efficient buffer management pools := GetGlobalMemoryPools() readerBuf := pools.GetHTTPResponseBuffer() defer pools.PutHTTPResponseBuffer(readerBuf) @@ -235,6 +233,11 @@ func decompressTokenInternal(compressed string) string { // SessionManager handles the management of multiple session cookies for OIDC authentication. // It provides functionality for storing and retrieving authentication state, tokens, // and other session-related data across multiple cookies. +// SessionManager manages OIDC session data for the middleware. +// It handles session creation, retrieval, and storage using encrypted cookies. +// Large tokens are automatically chunked across multiple cookies to handle +// browser size limitations. The manager uses a sync.Pool for efficient +// session object reuse and supports both HTTP and HTTPS schemes. type SessionManager struct { sessionPool sync.Pool store sessions.Store @@ -281,6 +284,10 @@ func NewSessionManager(encryptionKey string, forceHTTPS bool, logger *Logger) (* return sm, nil } +// PeriodicChunkCleanup performs periodic maintenance on session chunks. +// It identifies and removes orphaned chunks that are no longer associated +// with active sessions. This method should be called periodically to +// prevent cookie accumulation in client browsers. func (sm *SessionManager) PeriodicChunkCleanup() { sm.logger.Debug("Starting comprehensive session cleanup cycle") @@ -322,6 +329,17 @@ func (sm *SessionManager) PeriodicChunkCleanup() { // // Returns: // - error: nil if session is healthy, otherwise an error describing the issue +// +// ValidateSessionHealth performs comprehensive health checks on a session. +// It validates authentication state, token formats, and checks for signs +// of session tampering or corruption. +// +// Parameters: +// - sessionData: The session to validate. +// +// Returns: +// - nil if the session is healthy. +// - An error describing any validation failures. func (sm *SessionManager) ValidateSessionHealth(sessionData *SessionData) error { if sessionData == nil { return fmt.Errorf("session data is nil") @@ -366,7 +384,16 @@ func (sm *SessionManager) ValidateSessionHealth(sessionData *SessionData) error return nil } -// validateTokenFormat performs basic format validation on tokens +// validateTokenFormat performs basic structural validation on tokens. +// It checks for proper JWT format and validates against maximum size limits. +// +// Parameters: +// - token: The token string to validate. +// - tokenType: The type of token (for error messages). +// +// Returns: +// - nil if the token format is valid. +// - An error if the token has invalid structure or exceeds size limits. func (sm *SessionManager) validateTokenFormat(token, tokenType string) error { if token == "" { return nil // Empty tokens are allowed @@ -436,7 +463,7 @@ func (sm *SessionManager) GetSessionMetrics() map[string]interface{} { metrics["has_encryption"] = false } - // Note: We can't easily get pool stats from sync.Pool as it doesn't expose them + // sync.Pool doesn't expose internal statistics metrics["pool_implementation"] = "sync.Pool" return metrics @@ -456,7 +483,7 @@ func (sm *SessionManager) EnhanceSessionSecurity(options *sessions.Options, r *h options = &sessions.Options{} } - // Enhanced security based on request analysis + // Apply security settings based on request context if r != nil { // Check for suspicious request patterns userAgent := r.Header.Get("User-Agent") @@ -467,7 +494,7 @@ func (sm *SessionManager) EnhanceSessionSecurity(options *sessions.Options, r *h options.MaxAge = int((absoluteSessionTimeout / 2).Seconds()) } - // Enhanced security for production environments + // Apply stricter security settings for production if r.Header.Get("X-Forwarded-Proto") == "https" || r.TLS != nil || sm.forceHTTPS { options.Secure = true // Enable strict same-site policy for secure connections @@ -825,7 +852,7 @@ func (sd *SessionData) clearAllSessionData(r *http.Request, expire bool) { // Returns: // - An error if saving the expired sessions fails (only if w is not nil). // -// Note: This method will always return the SessionData object to the pool, even if an error occurs. +// This method ensures the SessionData object is always returned to the pool. func (sd *SessionData) Clear(r *http.Request, w http.ResponseWriter) error { // Use defer to guarantee session is returned to pool regardless of any errors or panics defer func() { @@ -1125,7 +1152,7 @@ func (sd *SessionData) SetAccessToken(token string) { // Compress token with validation compressed := compressToken(token) - // FIXED: Size validation after compression + // Validate size after compression if len(compressed) > 100*1024 { // 100KB limit for final token (post-compression) sd.manager.logger.Info("Access token too large after compression (%d bytes) - storing uncompressed", len(compressed)) return diff --git a/session_chunk_manager.go b/session_chunk_manager.go index 7bad1d8..7e1f8a0 100644 --- a/session_chunk_manager.go +++ b/session_chunk_manager.go @@ -11,7 +11,9 @@ import ( "github.com/gorilla/sessions" ) -// TokenConfig holds validation rules for different token types +// TokenConfig defines validation rules and constraints for different token types. +// It specifies size limits, chunking parameters, and format requirements to ensure +// tokens can be safely stored in browser cookies while maintaining security. type TokenConfig struct { Type string MinLength int @@ -55,19 +57,31 @@ var ( } ) -// TokenRetrievalResult encapsulates the result of token retrieval +// TokenRetrievalResult encapsulates the result of a token retrieval operation. +// It contains either a successfully retrieved token or an error describing +// what went wrong during retrieval. type TokenRetrievalResult struct { Token string Error error } -// ChunkManager handles token chunking operations +// ChunkManager provides thread-safe operations for splitting large tokens +// into smaller chunks that fit within browser cookie size limits. It handles +// the chunking and reassembly of tokens transparently, ensuring data integrity +// throughout the process. type ChunkManager struct { logger *Logger mutex *sync.RWMutex } -// NewChunkManager creates a new ChunkManager instance +// NewChunkManager creates a new ChunkManager instance with the specified logger. +// If no logger is provided, a no-op logger is used to prevent nil pointer errors. +// +// Parameters: +// - logger: The logger instance for recording chunk operations. +// +// Returns: +// - A new ChunkManager instance ready for use. func NewChunkManager(logger *Logger) *ChunkManager { if logger == nil { logger = newNoOpLogger() @@ -80,6 +94,18 @@ func NewChunkManager(logger *Logger) *ChunkManager { } // GetToken retrieves and validates a token from either single storage or chunks +// GetToken retrieves and validates a token, handling both single-cookie +// and chunked storage scenarios. It performs decompression if needed and +// validates the token according to the provided configuration. +// +// Parameters: +// - singleToken: The token string if stored in a single cookie. +// - compressed: Whether the token is compressed. +// - chunks: Map of session chunks if token is split across cookies. +// - config: Token validation configuration. +// +// Returns: +// - TokenRetrievalResult containing the token or an error. func (cm *ChunkManager) GetToken( singleToken string, compressed bool, @@ -102,7 +128,17 @@ func (cm *ChunkManager) GetToken( return cm.processChunkedToken(chunks, config) } -// processSingleToken handles tokens stored in a single cookie +// processSingleToken processes tokens stored in a single cookie. +// It handles decompression if needed and performs comprehensive validation +// including corruption detection, format validation, and size checks. +// +// Parameters: +// - token: The token string from the cookie. +// - compressed: Whether the token needs decompression. +// - config: Token validation configuration. +// +// Returns: +// - TokenRetrievalResult containing the processed token or an error. func (cm *ChunkManager) processSingleToken(token string, compressed bool, config TokenConfig) TokenRetrievalResult { // Detect corruption markers if isCorruptionMarker(token) { @@ -130,19 +166,28 @@ func (cm *ChunkManager) processSingleToken(token string, compressed bool, config return cm.validateToken(finalToken, config) } -// validateToken performs comprehensive token validation +// validateToken performs comprehensive validation on a token. +// It checks size limits, chunking efficiency, content validity, +// expiration, freshness, and format requirements based on the token configuration. +// +// Parameters: +// - token: The token string to validate. +// - config: Token validation configuration. +// +// Returns: +// - TokenRetrievalResult with the validated token or validation error. func (cm *ChunkManager) validateToken(token string, config TokenConfig) TokenRetrievalResult { - // Enhanced size validation + // Validate token size against configured limits if sizeErr := cm.validateTokenSize(token, config); sizeErr != nil { return TokenRetrievalResult{Token: "", Error: sizeErr} } - // Chunking efficiency validation (for pre-storage analysis) + // Check if token would chunk efficiently if chunkErr := cm.validateChunkingEfficiency(token, config); chunkErr != nil { return TokenRetrievalResult{Token: "", Error: chunkErr} } - // Comprehensive content validation + // Validate token content and structure if contentErr := cm.validateTokenContent(token, config); contentErr != nil { return TokenRetrievalResult{Token: "", Error: contentErr} } @@ -157,7 +202,7 @@ func (cm *ChunkManager) validateToken(token string, config TokenConfig) TokenRet return TokenRetrievalResult{Token: "", Error: freshnessErr} } - // Enhanced JWT format validation + // Validate JWT format if required if config.RequireJWTFormat && !config.AllowOpaqueTokens { if validationErr := cm.validateJWTFormat(token, config.Type); validationErr != nil { return TokenRetrievalResult{Token: "", Error: validationErr} @@ -182,7 +227,7 @@ func (cm *ChunkManager) validateToken(token string, config TokenConfig) TokenRet // processChunkedToken handles tokens stored across multiple chunks func (cm *ChunkManager) processChunkedToken(chunks map[int]*sessions.Session, config TokenConfig) TokenRetrievalResult { - // Enhanced chunk count validation using config limits + // Validate chunk count against configured maximum if len(chunks) > config.MaxChunks { err := fmt.Errorf("too many %s token chunks (%d, max: %d)", config.Type, len(chunks), config.MaxChunks) cm.logger.Info("Token chunk count exceeded for %s: %d chunks", config.Type, len(chunks)) @@ -222,7 +267,7 @@ func (cm *ChunkManager) processChunkedToken(chunks map[int]*sessions.Session, co return TokenRetrievalResult{Token: "", Error: err} } - // Enhanced chunk size validation using config limits + // Validate individual chunk sizes if len(chunk) > config.MaxChunkSize { err := fmt.Errorf("%s token chunk %d exceeds size limit (%d bytes, max: %d)", config.Type, i, len(chunk), config.MaxChunkSize) diff --git a/settings.go b/settings.go index b585f77..bded998 100644 --- a/settings.go +++ b/settings.go @@ -181,7 +181,7 @@ func (c *Config) Validate() error { return fmt.Errorf("refreshGracePeriodSeconds cannot be negative") } - // SECURITY FIX: Validate headers configuration with enhanced template security + // Validate headers configuration for template security for _, header := range c.Headers { if header.Name == "" { return fmt.Errorf("header name cannot be empty") @@ -207,7 +207,7 @@ func (c *Config) Validate() error { return fmt.Errorf("header template '%s' appears to use lowercase 'refreshToken' - use '{{.RefreshToken...' instead (case sensitive)", header.Value) } - // SECURITY FIX: Implement template sandboxing and validation + // Validate template syntax and security if err := validateTemplateSecure(header.Value); err != nil { return fmt.Errorf("header template '%s' failed security validation: %w", header.Value, err) } @@ -216,9 +216,9 @@ func (c *Config) Validate() error { return nil } -// SECURITY FIX: validateTemplateSecure implements template sandboxing and validation +// validateTemplateSecure validates template expressions for security vulnerabilities func validateTemplateSecure(templateStr string) error { - // SECURITY FIX: Restrict dangerous template functions and patterns + // Check for dangerous template functions and patterns dangerousPatterns := []string{ "{{call", // Function calls "{{range", // Range over arbitrary data @@ -256,7 +256,7 @@ func validateTemplateSecure(templateStr string) error { } } - // SECURITY FIX: Whitelist allowed template variables and functions + // Validate template variables against whitelist allowedPatterns := []string{ "{{.AccessToken}}", "{{.IdToken}}", @@ -277,7 +277,7 @@ func validateTemplateSecure(templateStr string) error { return fmt.Errorf("template must use only allowed variables: AccessToken, IdToken, RefreshToken, or Claims.*") } - // SECURITY FIX: Validate Claims access patterns + // Validate claims access patterns if strings.Contains(templateStr, "{{.Claims.") { // Simple validation - ensure claims access is to known safe fields safeClaimsFields := map[string]bool{ @@ -314,7 +314,7 @@ func validateTemplateSecure(templateStr string) error { return fmt.Errorf("access to Claims.%s is not allowed for security reasons", fieldName) } - // Fix the search for next occurrence + // Search for next occurrence nextStart := strings.Index(templateStr[start+end+2:], "{{.Claims.") if nextStart != -1 { start = start + end + 2 + nextStart @@ -324,7 +324,7 @@ func validateTemplateSecure(templateStr string) error { } } - // SECURITY FIX: Prevent code injection through template syntax + // Prevent code injection through template syntax if strings.Contains(templateStr, "{{") && strings.Contains(templateStr, "}}") { // Count opening and closing braces openCount := strings.Count(templateStr, "{{") @@ -418,7 +418,7 @@ func (l *Logger) Info(format string, args ...interface{}) { l.logInfo.Printf(format, args...) } -// Debug logs a message at the DEBUG level using Printf style formatting. +// Debug logs a message at the DEBUG level. // Output is directed to stdout only if the configured log level is "debug". // // Parameters: @@ -449,7 +449,7 @@ func (l *Logger) Infof(format string, args ...interface{}) { l.logInfo.Printf(format, args...) } -// Debugf logs a message at the DEBUG level using Printf style formatting. +// Debugf logs a formatted message at the DEBUG level. // Equivalent to calling l.Debug(format, args...). // Output is directed to stdout only if the configured log level is "debug". //