mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
1e33bb0a4d
revocation endpoints, joining the existing client_secret_post default. Both are opt-in via the new clientAuthMethod config field. Closes #135. private_key_jwt (RFC 7523 §2.2 / OpenID Connect Core §9) ======================================================== Plugin signs a short-lived JWT with a configured private key and presents it as client_assertion. Use when the IdP enforces short secret TTLs or requires secretless client auth (Microsoft Entra ID / Azure AD, Okta, Auth0, Keycloak). New Config fields: clientAuthMethod (default: client_secret_post) clientAssertionPrivateKey (inline PEM) clientAssertionKeyPath (PEM file path; mutually exclusive) clientAssertionKeyID (JWS kid header — required) clientAssertionAlg (default: RS256; RS/PS/ES 256–512 supported) PEM forms accepted: PKCS#8, PKCS#1, SEC1. Assertion claims: iss=sub=clientID, aud=tokenURL, iat=now, exp=now+60s, random 16-byte hex jti per request. ECDSA signatures are raw r||s per RFC 7515 (not ASN.1). client_secret_basic (RFC 6749 §2.3.1) ===================================== Sends credentials in the Authorization: Basic header instead of the body. Both halves are form-urlencoded individually before base64 — that encoding step is required by the spec and is NOT what stdlib's http.Request.SetBasicAuth does, so the plugin uses its own helper. The form body omits client_id and client_secret on this path. Wire-up ======= Both methods are dispatched at the same two call sites: helpers.go:exchangeTokens — auth_code + refresh_token grants token_manager.go:RevokeTokenWithProvider — RFC 7009 revocation Existing clientSecret deployments are unaffected — empty clientAuthMethod maps to the historical client_secret_post behavior, and clientAssertion remains nil unless the new fields are set. Yaegi compatibility =================== All required crypto/rsa, crypto/ecdsa, crypto/x509, encoding/pem and crypto/sha256/384/512 symbols are exposed by the traefik/yaegi stdlib symbol tables (RSA SignPKCS1v15 + SignPSS, ECDSA Sign, ParsePKCS8/1PrivateKey, ParseECPrivateKey). Tests (16 new) ============== Algorithm-family coverage: TestIssue135_SignerRSAFamily — RS256/384/512 + PS256/384/512 TestIssue135_SignerECDSAFamily — ES256/384/512, raw r||s shape TestIssue135_SignerRejectsAlgKeyMismatch TestIssue135_SignerJTIUniqueness — 50 sigs, all jti distinct TestIssue135_SignerPEMVariants — PKCS#8, PKCS#1, SEC1 Config validation: TestIssue135_ConfigValidation — full Validate() matrix TestIssue135_ConfigKeyPathLoadsFile Wire-up: TestIssue135_AuthCodeExchangeUsesAssertion TestIssue135_RefreshTokenUsesAssertion TestIssue135_BackcompatClientSecretPath TestIssue135_RevocationUsesAssertion TestIssue135_BuildSignerFromInlineConfig TestIssue135_BuildSignerDefaultsToRS256 TestIssue135_ClientSecretBasicAuth — Authorization header, no body creds TestIssue135_ClientSecretBasicURLEncodesReservedChars — :, +, /, @, =, & TestIssue135_ClientSecretBasicRevocation — revocation parity Documentation ============= README.md — required-row note + 5 optional rows + dedicated section docs/CONFIGURATION.md — new Client Authentication section with three method subsections, OpenSSL keygen snippet, RFC links docs/index.html — 5 new config-table rows + Private Key JWT explainer card .traefik.yml + examples/complete-traefik-config.yaml — commented opt-in example Out of scope (deferred) ======================= mTLS / tls_client_auth (RFC 8705) — separate change; requires per-call http.Client with tls.Config.Certificates and conflicts with the current pooled HTTP client architecture.
153 lines
6.0 KiB
Go
153 lines
6.0 KiB
Go
// Package traefikoidc provides OIDC authentication middleware for Traefik.
|
|
package traefikoidc
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
// CacheInterface defines the common cache operations
|
|
type CacheInterface interface {
|
|
Set(key string, value any, ttl time.Duration)
|
|
Get(key string) (any, bool)
|
|
Delete(key string)
|
|
SetMaxSize(size int)
|
|
Size() int
|
|
Clear()
|
|
Cleanup()
|
|
Close()
|
|
GetStats() map[string]any // For testing and monitoring
|
|
}
|
|
|
|
// TokenVerifier interface defines token verification capabilities.
|
|
// Implementations should validate token format, signature, and claims.
|
|
type TokenVerifier interface {
|
|
VerifyToken(token string) error
|
|
}
|
|
|
|
// JWTVerifier interface defines JWT-specific verification capabilities.
|
|
// Implementations should validate JWT structure, signature using JWKs, and standard claims.
|
|
type JWTVerifier interface {
|
|
VerifyJWTSignatureAndClaims(jwt *JWT, token string) error
|
|
}
|
|
|
|
// TokenExchanger interface defines OAuth 2.0 and OpenID Connect token exchange capabilities.
|
|
// Implementations should handle authorization code exchange, refresh tokens, and 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
|
|
}
|
|
|
|
// ProviderMetadata represents OIDC provider configuration data.
|
|
// This data is typically retrieved from the provider's .well-known/openid-configuration endpoint
|
|
// and contains essential URLs for authentication, token exchange, and key retrieval.
|
|
type ProviderMetadata struct {
|
|
Issuer string `json:"issuer"`
|
|
AuthURL string `json:"authorization_endpoint"`
|
|
TokenURL string `json:"token_endpoint"`
|
|
JWKSURL string `json:"jwks_uri"`
|
|
RevokeURL string `json:"revocation_endpoint"`
|
|
EndSessionURL string `json:"end_session_endpoint"`
|
|
IntrospectionURL string `json:"introspection_endpoint,omitempty"`
|
|
RegistrationURL string `json:"registration_endpoint,omitempty"`
|
|
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
|
}
|
|
|
|
// TraefikOidc is the main middleware struct that implements OIDC authentication for Traefik.
|
|
// It integrates with various OIDC providers, manages sessions, caches tokens, and handles
|
|
// the complete authentication flow. It's designed to work seamlessly with Traefik's
|
|
// plugin system and provides flexible configuration options.
|
|
type TraefikOidc struct {
|
|
lastMetadataRetryTime time.Time
|
|
jwkCache JWKCacheInterface
|
|
jwtVerifier JWTVerifier
|
|
ctx context.Context
|
|
tokenVerifier TokenVerifier
|
|
next http.Handler
|
|
tokenExchanger TokenExchanger
|
|
tokenBlacklist CacheInterface
|
|
tokenTypeCache CacheInterface
|
|
introspectionCache CacheInterface
|
|
initComplete chan struct{}
|
|
limiter *rate.Limiter
|
|
headerTemplates map[string]*template.Template
|
|
sessionManager *SessionManager
|
|
tokenCleanupStopChan chan struct{}
|
|
excludedURLs map[string]struct{}
|
|
extractClaimsFunc func(tokenString string) (map[string]any, error)
|
|
initiateAuthenticationFunc func(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string)
|
|
metadataCache *MetadataCache
|
|
allowedRolesAndGroups map[string]struct{}
|
|
allowedUsers map[string]struct{}
|
|
allowedUserDomains map[string]struct{}
|
|
tokenCache *TokenCache
|
|
httpClient *http.Client
|
|
tokenHTTPClient *http.Client
|
|
logger *Logger
|
|
metadataRefreshStopChan chan struct{}
|
|
cancelFunc context.CancelFunc
|
|
errorRecoveryManager *ErrorRecoveryManager
|
|
tokenResilienceManager *TokenResilienceManager
|
|
refreshCoordinator *RefreshCoordinator
|
|
goroutineWG *sync.WaitGroup
|
|
dcrConfig *DynamicClientRegistrationConfig
|
|
dynamicClientRegistrar *DynamicClientRegistrar
|
|
scopeFilter *ScopeFilter
|
|
securityHeadersApplier func(http.ResponseWriter, *http.Request)
|
|
userIdentifierClaim string
|
|
revocationURL string
|
|
name string
|
|
redirURLPath string
|
|
logoutURLPath string
|
|
tokenURL string
|
|
authURL string
|
|
endSessionURL string
|
|
postLogoutRedirectURI string
|
|
jwksURL string
|
|
issuerURL string
|
|
groupClaimName string
|
|
introspectionURL string
|
|
providerURL string
|
|
roleClaimName string
|
|
audience string
|
|
clientID string
|
|
clientSecret string
|
|
clientAuthMethod string
|
|
clientAssertion *ClientAssertionSigner
|
|
registrationURL string
|
|
backchannelLogoutPath string
|
|
frontchannelLogoutPath string
|
|
scopesSupported []string
|
|
scopes []string
|
|
refreshGracePeriod time.Duration
|
|
maxRefreshTokenAge time.Duration
|
|
metadataMu sync.RWMutex
|
|
shutdownOnce sync.Once
|
|
metadataRetryMutex sync.Mutex
|
|
firstRequestMutex sync.Mutex
|
|
sessionInvalidationCache CacheInterface
|
|
refreshResultCache CacheInterface
|
|
minimalHeaders bool
|
|
stripAuthCookies bool
|
|
enableBackchannelLogout bool
|
|
enableFrontchannelLogout bool
|
|
firstRequestReceived bool
|
|
requireTokenIntrospection bool
|
|
metadataRefreshStarted bool
|
|
allowPrivateIPAddresses bool
|
|
disableReplayDetection bool
|
|
allowOpaqueTokens bool
|
|
strictAudienceValidation bool
|
|
overrideScopes bool
|
|
enablePKCE bool
|
|
forceHTTPS bool
|
|
suppressDiagnosticLogs bool
|
|
}
|