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

This commit is contained in:
2025-07-24 10:29:08 +01:00
parent 00ed2ea61b
commit a4b2bfd70f
14 changed files with 406 additions and 133 deletions
+20 -5
View File
@@ -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()
+27 -7
View File
@@ -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 {
+22 -7
View File
@@ -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()
+3
View File
@@ -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
}
+22 -4
View File
@@ -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,}$`)
+26 -10
View File
@@ -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()
}
+24 -1
View File
@@ -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)
+120 -52
View File
@@ -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() {
+14 -4
View File
@@ -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
+5 -2
View File
@@ -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)
}
+16 -6
View File
@@ -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
+40 -13
View File
@@ -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
+57 -12
View File
@@ -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)
+10 -10
View File
@@ -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".
//