December 2025 Improvements - Azure AD, Internal Networks, Startup Race Condition (#100)

* Allow internal IPs for OIDC configuration via extra flag.

Addresses issue #97

* Allow for internal IPs in OIDC configuration.

Addresses issue #97.

* feat: Add allowPrivateIPAddresses config option for internal networks

Adds a new configuration option `allowPrivateIPAddresses` that allows
OIDC provider URLs to use private IP addresses (10.x.x.x, 172.16-31.x.x,
192.168.x.x). This is useful for internal deployments where Keycloak or
other OIDC providers run on private networks without DNS resolution.

Security considerations:
- Loopback addresses (127.0.0.1, localhost, ::1) remain blocked
- Link-local addresses (169.254.x.x) remain blocked
- Default is false (secure by default)

Fixes #97

* feat: Support non-email user identifiers for Azure AD

Add userIdentifierClaim configuration option to support Azure AD users
without email addresses. This allows using alternative JWT claims like
"sub", "oid", "upn", or "preferred_username" for user identification.

- Default behavior uses "email" claim (backward compatible)
- Falls back to "sub" claim if configured claim is missing
- allowedUsers matches against the configured claim value
- allowedUserDomains only applies when using email-based identification

Fixes #95

* Race condition on traefik pod startup

When the plugin initializes and calls GetMetadataWithRecovery():

1. Checks cache first (if metadata is cached, returns immediately)
2. Creates a retry executor with startup-optimized settings (10 attempts, 1s delays)
3. Attempts to fetch metadata from the OIDC provider
4. If the fetch fails with a retryable error (connection refused, EOF, TLS/certificate errors, Traefik default cert), it waits and retries
5. After 10 attempts or on a non-retryable error, returns the error

This allows the plugin to handle the race condition where:
- Traefik initializes the plugin before routes are established
- Traefik serves its default certificate before loading real ones
- The OIDC provider pod isn't fully ready yet

Fixes issue #90

* Race condition on traefik pod startup

When the plugin initializes and calls GetMetadataWithRecovery():

1. Checks cache first (if metadata is cached, returns immediately)
2. Creates a retry executor with startup-optimized settings (10 attempts, 1s delays)
3. Attempts to fetch metadata from the OIDC provider
4. If the fetch fails with a retryable error (connection refused, EOF, TLS/certificate errors, Traefik default cert), it waits and retries
5. After 10 attempts or on a non-retryable error, returns the error

This allows the plugin to handle the race condition where:
- Traefik initializes the plugin before routes are established
- Traefik serves its default certificate before loading real ones
- The OIDC provider pod isn't fully ready yet

Fixes issue #90

* Headers too big and 431 responses

Added new option `minimalHeaders` to reduce the size of forwarded headers from the auth middleware to backend services.

  - When minimalHeaders: false (default): All headers are forwarded as before
    - X-Forwarded-User (always set)
    - X-Auth-Request-Redirect
    - X-Auth-Request-User
    - X-Auth-Request-Token (the large ID token)
    - X-User-Groups, X-User-Roles (if configured)
  - When minimalHeaders: true: Reduces header overhead
    - X-Forwarded-User (always set)
    - X-User-Groups, X-User-Roles (still forwarded if configured)
    - Custom templated headers (still processed)
    - Skipped: X-Auth-Request-Token, X-Auth-Request-User, X-Auth-Request-Redirect

Fixes issues #64 and #86
This commit is contained in:
2025-12-08 14:21:17 +00:00
committed by GitHub
parent a750c4f5b9
commit 9126c74723
25 changed files with 1642 additions and 212 deletions
+49 -4
View File
@@ -189,11 +189,56 @@ func (mc *MetadataCache) GetMetadata(providerURL string, httpClient *http.Client
return mc.GetProviderMetadata(ctx, providerURL, httpClient)
}
// GetMetadataWithRecovery fetches metadata with recovery support
// GetMetadataWithRecovery fetches metadata with retry support for startup scenarios.
// This handles the race condition where Traefik initializes the plugin before the
// OIDC provider routes are fully established, or before TLS certificates are loaded.
// Uses aggressive retry settings (10 attempts, 1s intervals) to give the infrastructure
// time to stabilize during cold starts.
// See: https://github.com/lukaszraczylo/traefikoidc/issues/90
func (mc *MetadataCache) GetMetadataWithRecovery(providerURL string, httpClient *http.Client, logger *Logger, errorRecoveryManager *ErrorRecoveryManager) (*ProviderMetadata, error) {
// For now, just use regular GetMetadata
// Recovery would be handled by ErrorRecoveryManager if needed
return mc.GetMetadata(providerURL, httpClient, logger)
// Check cache first - if we have valid cached metadata, use it
if metadata, exists := mc.Get(providerURL); exists {
return metadata, nil
}
// Create a retry executor with metadata-fetch-specific configuration
retryConfig := MetadataFetchRetryConfig()
retryExecutor := NewRetryExecutor(retryConfig, logger)
var metadata *ProviderMetadata
var lastErr error
// Use context with overall timeout for the entire retry sequence
// 10 attempts * ~10s max delay = ~100s worst case, so use 2 minute timeout
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
err := retryExecutor.ExecuteWithContext(ctx, func() error {
// Create per-attempt context with shorter timeout
attemptCtx, attemptCancel := context.WithTimeout(ctx, 15*time.Second)
defer attemptCancel()
var fetchErr error
metadata, fetchErr = mc.GetProviderMetadata(attemptCtx, providerURL, httpClient)
if fetchErr != nil {
lastErr = fetchErr
if logger != nil {
logger.Debugf("Metadata fetch attempt failed: %v", fetchErr)
}
return fetchErr
}
return nil
})
if err != nil {
// Return the last actual error, not the retry wrapper error
if lastErr != nil {
return nil, lastErr
}
return nil, err
}
return metadata, nil
}
// GetStats returns cache statistics for testing