Complete rebuild of the plugin

* Fix bug affecting Azure OIDC authentication ( and most likely others )

* Fixes issue #51

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

* Improvements targetting possible memory usage spikes.

* Additional fixes and cleanup

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

* Modernize run

* Fieldalignment

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

* Fix claims issue.

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

* Well.. that escalated quickly.

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

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

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

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

* Abstract the provider logic into a separate package.

* Additional micro fixes and cleanups.

* Simplify all the things.

* fixup! Simplify all the things.

* fixup! fixup! Simplify all the things.

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

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

* ...

* Cleanup tests.

* fixup! Cleanup tests.

* fixup! fixup! fixup! Cleanup tests.

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

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

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

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

Why Issue #53 Failed Before:

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

Why It Works Now:

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

* Next set of enhancements together with memory usage improvements.

* Memory leak fixes and optimisations.

* CSRF and Cookie Domain fixes

* fixup! CSRF and Cookie Domain fixes

* Metadata cache leak fix + profiling

* fixup! Metadata cache leak fix + profiling

* Memory leaks hunting, part 1337.

* Further pursue of perfection.

* fixup! Further pursue of perfection.

* fixup! fixup! Further pursue of perfection.

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

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

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

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

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

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

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

* Clear race conditions

* fixup! Clear race conditions

* Weekend fun with memory leaks

* Splitting code into multiple files with reasonable testing coverage.

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

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

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

* Weekend fun with further optimisations.

* fixup! Weekend fun with further optimisations.

* fixup! fixup! Weekend fun with further optimisations.

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

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

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

* Pre-release cleanup.

* Enhance test coverage.

* fixup! Enhance test coverage.

* fixup! fixup! Enhance test coverage.

* fixup! fixup! fixup! Enhance test coverage.
This commit is contained in:
2025-09-18 11:01:30 +01:00
committed by GitHub
parent 784b161732
commit 1b49e133da
181 changed files with 78067 additions and 8289 deletions
+287
View File
@@ -0,0 +1,287 @@
package traefikoidc
import (
"os"
"strconv"
"testing"
"time"
)
// TestConfig manages test execution configuration and performance settings
type TestConfig struct {
// Test execution modes
ExtendedTests bool // Run extended/stress tests
LongTests bool // Run long-running performance tests
QuickMode bool // Quick smoke tests only
// Performance settings
MaxConcurrency int // Maximum concurrent operations
MaxIterations int // Maximum test iterations
DefaultTimeout time.Duration // Default test timeout
MemoryThreshold float64 // Memory growth threshold in MB
GoroutineGrowth int // Acceptable goroutine growth
// Cache settings for tests
CacheSize int // Default cache size for tests
CleanupInterval time.Duration // Cleanup interval for tests
// Environment-specific overrides
MemoryStressTest bool // Enable memory stress tests
ConcurrencyTest bool // Enable high concurrency tests
LeakDetection bool // Enable memory leak detection
}
// NewTestConfig creates a test configuration based on flags and environment
func NewTestConfig() *TestConfig {
config := &TestConfig{
// Default quick mode settings - very conservative for 30s target
ExtendedTests: false,
LongTests: false,
QuickMode: true,
MaxConcurrency: 2, // Reduced for quick mode
MaxIterations: 1, // Minimal iterations for quick smoke tests
DefaultTimeout: 5 * time.Second, // Shorter timeout
MemoryThreshold: 1.0, // Strict memory limit
GoroutineGrowth: 1, // Very strict goroutine limit
CacheSize: 10, // Small cache size
CleanupInterval: 50 * time.Millisecond, // Faster cleanup
MemoryStressTest: false,
ConcurrencyTest: false,
LeakDetection: false, // Disable by default in quick mode for speed
}
// Check for extended test flag
if os.Getenv("RUN_EXTENDED_TESTS") == "1" || os.Getenv("RUN_EXTENDED_TESTS") == "true" {
config.EnableExtendedTests()
}
// Check for long test flag
if os.Getenv("RUN_LONG_TESTS") == "1" || os.Getenv("RUN_LONG_TESTS") == "true" {
config.EnableLongTests()
}
// Check for stress tests
if os.Getenv("RUN_STRESS_TESTS") == "1" || os.Getenv("RUN_STRESS_TESTS") == "true" {
config.EnableStressTests()
}
// Check for memory leak detection override
if os.Getenv("DISABLE_LEAK_DETECTION") == "1" || os.Getenv("DISABLE_LEAK_DETECTION") == "true" {
config.LeakDetection = false
}
// Parse custom concurrency limit
if concStr := os.Getenv("TEST_MAX_CONCURRENCY"); concStr != "" {
if conc, err := strconv.Atoi(concStr); err == nil && conc > 0 {
config.MaxConcurrency = conc
}
}
// Parse custom iteration limit
if iterStr := os.Getenv("TEST_MAX_ITERATIONS"); iterStr != "" {
if iter, err := strconv.Atoi(iterStr); err == nil && iter > 0 {
config.MaxIterations = iter
}
}
// Parse memory threshold
if memStr := os.Getenv("TEST_MEMORY_THRESHOLD_MB"); memStr != "" {
if mem, err := strconv.ParseFloat(memStr, 64); err == nil && mem > 0 {
config.MemoryThreshold = mem
}
}
return config
}
// EnableExtendedTests switches to extended test mode
func (c *TestConfig) EnableExtendedTests() {
c.ExtendedTests = true
c.QuickMode = false
c.MaxConcurrency = 20
c.MaxIterations = 10
c.DefaultTimeout = 30 * time.Second
c.MemoryThreshold = 10.0
c.GoroutineGrowth = 5
c.CacheSize = 200
c.CleanupInterval = 50 * time.Millisecond
c.ConcurrencyTest = true
}
// EnableLongTests switches to long-running test mode
func (c *TestConfig) EnableLongTests() {
c.LongTests = true
c.QuickMode = false
c.MaxConcurrency = 50
c.MaxIterations = 100
c.DefaultTimeout = 60 * time.Second
c.MemoryThreshold = 50.0
c.GoroutineGrowth = 10
c.CacheSize = 1000
c.CleanupInterval = 10 * time.Millisecond
c.ConcurrencyTest = true
c.MemoryStressTest = true
}
// EnableStressTests switches to stress test mode
func (c *TestConfig) EnableStressTests() {
c.ExtendedTests = true
c.LongTests = true
c.QuickMode = false
c.MaxConcurrency = 100
c.MaxIterations = 500
c.DefaultTimeout = 120 * time.Second
c.MemoryThreshold = 100.0
c.GoroutineGrowth = 20
c.CacheSize = 2000
c.CleanupInterval = 5 * time.Millisecond
c.ConcurrencyTest = true
c.MemoryStressTest = true
}
// ShouldSkipTest determines if a test should be skipped based on config
func (c *TestConfig) ShouldSkipTest(t *testing.T, testType TestType) bool {
// Always respect testing.Short() - skip everything except basic quick tests
if testing.Short() {
switch testType {
case TestTypeQuick:
return false // Allow quick tests
case TestTypeExtended, TestTypeLong, TestTypeMemoryStress, TestTypeConcurrencyStress:
t.Skip("Skipping extended test in short mode")
return true
case TestTypeLeakDetection:
// Skip leak detection in short mode unless explicitly enabled
if !c.LeakDetection {
t.Skip("Skipping leak detection test in short mode (use RUN_EXTENDED_TESTS=1 to enable)")
return true
}
}
}
// Check specific test type flags
switch testType {
case TestTypeExtended:
if !c.ExtendedTests {
t.Skip("Skipping extended test (use RUN_EXTENDED_TESTS=1 to enable)")
return true
}
case TestTypeLong:
if !c.LongTests {
t.Skip("Skipping long test (use RUN_LONG_TESTS=1 to enable)")
return true
}
case TestTypeMemoryStress:
if !c.MemoryStressTest {
t.Skip("Skipping memory stress test (use RUN_STRESS_TESTS=1 to enable)")
return true
}
case TestTypeConcurrencyStress:
if !c.ConcurrencyTest {
t.Skip("Skipping concurrency stress test (use RUN_EXTENDED_TESTS=1 to enable)")
return true
}
case TestTypeLeakDetection:
if !c.LeakDetection {
t.Skip("Skipping leak detection test (DISABLE_LEAK_DETECTION=1 set)")
return true
}
}
return false
}
// AdjustMemoryLeakTestCase adjusts a memory leak test case based on configuration
func (c *TestConfig) AdjustMemoryLeakTestCase(testCase *MemoryLeakTestCase) {
// Adjust iterations
if testCase.Iterations > c.MaxIterations {
testCase.Iterations = c.MaxIterations
}
// Ensure minimum of 1 iteration
if testCase.Iterations < 1 {
testCase.Iterations = 1
}
// Adjust memory threshold
if testCase.MaxMemoryGrowthMB > c.MemoryThreshold && c.QuickMode {
testCase.MaxMemoryGrowthMB = c.MemoryThreshold
}
// Adjust goroutine growth
if testCase.MaxGoroutineGrowth > c.GoroutineGrowth && c.QuickMode {
testCase.MaxGoroutineGrowth = c.GoroutineGrowth
}
// Adjust timeout
if testCase.Timeout > c.DefaultTimeout && c.QuickMode {
testCase.Timeout = c.DefaultTimeout
} else if testCase.Timeout == 0 {
testCase.Timeout = c.DefaultTimeout
}
}
// AdjustConcurrencyParams adjusts concurrency parameters for tests
func (c *TestConfig) AdjustConcurrencyParams(requested int) int {
if requested > c.MaxConcurrency {
return c.MaxConcurrency
}
return requested
}
// GetCacheSize returns appropriate cache size for tests
func (c *TestConfig) GetCacheSize() int {
return c.CacheSize
}
// GetCleanupInterval returns appropriate cleanup interval for tests
func (c *TestConfig) GetCleanupInterval() time.Duration {
return c.CleanupInterval
}
// TestType represents different categories of tests
type TestType int
const (
TestTypeQuick TestType = iota
TestTypeExtended
TestTypeLong
TestTypeMemoryStress
TestTypeConcurrencyStress
TestTypeLeakDetection
)
// String returns string representation of test type
func (tt TestType) String() string {
switch tt {
case TestTypeQuick:
return "quick"
case TestTypeExtended:
return "extended"
case TestTypeLong:
return "long"
case TestTypeMemoryStress:
return "memory-stress"
case TestTypeConcurrencyStress:
return "concurrency-stress"
case TestTypeLeakDetection:
return "leak-detection"
default:
return "unknown"
}
}
// Global test configuration instance
var globalTestConfig *TestConfig
// GetTestConfig returns the global test configuration
func GetTestConfig() *TestConfig {
if globalTestConfig == nil {
globalTestConfig = NewTestConfig()
}
return globalTestConfig
}
// SetTestConfig sets the global test configuration (useful for testing)
func SetTestConfig(config *TestConfig) {
globalTestConfig = config
}