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.
497 lines
14 KiB
YAML
497 lines
14 KiB
YAML
# ============================================================================
|
|
# Complete Traefik Configuration Example with TraefikOIDC Plugin + Redis
|
|
# ============================================================================
|
|
#
|
|
# This example shows a complete, production-ready configuration for using
|
|
# the TraefikOIDC plugin with Redis caching in a multi-replica deployment.
|
|
#
|
|
|
|
# ============================================================================
|
|
# Part 1: Traefik Static Configuration (traefik.yml)
|
|
# ============================================================================
|
|
# This file configures Traefik itself and enables the plugin.
|
|
# Place this in /etc/traefik/traefik.yml or mount it in your container.
|
|
|
|
---
|
|
# Static Configuration
|
|
api:
|
|
dashboard: true
|
|
insecure: false # Set to true only for local development
|
|
|
|
entryPoints:
|
|
web:
|
|
address: ":80"
|
|
http:
|
|
redirections:
|
|
entryPoint:
|
|
to: websecure
|
|
scheme: https
|
|
|
|
websecure:
|
|
address: ":443"
|
|
http:
|
|
tls:
|
|
certResolver: letsencrypt
|
|
|
|
certificatesResolvers:
|
|
letsencrypt:
|
|
acme:
|
|
email: admin@example.com
|
|
storage: /letsencrypt/acme.json
|
|
httpChallenge:
|
|
entryPoint: web
|
|
|
|
providers:
|
|
file:
|
|
filename: /etc/traefik/dynamic.yml
|
|
watch: true
|
|
|
|
# Enable the TraefikOIDC plugin
|
|
experimental:
|
|
plugins:
|
|
traefikoidc:
|
|
moduleName: github.com/lukaszraczylo/traefikoidc
|
|
version: v0.8.0
|
|
|
|
log:
|
|
level: INFO
|
|
format: json
|
|
|
|
accessLog:
|
|
format: json
|
|
|
|
|
|
# ============================================================================
|
|
# Part 2: Traefik Dynamic Configuration (dynamic.yml)
|
|
# ============================================================================
|
|
# This file defines your routes, services, and middleware.
|
|
# Place this in /etc/traefik/dynamic.yml
|
|
|
|
---
|
|
http:
|
|
# -------------------------------------------------------------------------
|
|
# Middleware Definitions
|
|
# -------------------------------------------------------------------------
|
|
middlewares:
|
|
# Example 1: Minimal Redis Configuration
|
|
# Perfect for getting started quickly
|
|
oidc-minimal:
|
|
plugin:
|
|
traefikoidc:
|
|
# Required OIDC settings
|
|
clientID: "your-application-client-id"
|
|
clientSecret: "your-client-secret-from-provider"
|
|
providerURL: "https://auth.example.com"
|
|
callbackURL: "/oauth2/callback"
|
|
sessionEncryptionKey: "your-secure-64-character-encryption-key-must-be-kept-secret"
|
|
|
|
# Minimal Redis configuration
|
|
redis:
|
|
enabled: true
|
|
address: "redis:6379"
|
|
|
|
# Example 2: Production Redis Configuration
|
|
# Recommended for production deployments with multiple Traefik replicas
|
|
oidc-production:
|
|
plugin:
|
|
traefikoidc:
|
|
# OIDC Provider Configuration
|
|
clientID: "prod-client-id"
|
|
clientSecret: "prod-client-secret"
|
|
providerURL: "https://auth.example.com"
|
|
callbackURL: "/oauth2/callback"
|
|
|
|
# ----------------------------------------------------------------
|
|
# Optional: switch to RFC 7523 private_key_jwt client auth
|
|
# (Entra ID, Okta, Auth0, Keycloak). Replaces clientSecret with a
|
|
# signed JWT assertion. See README for details and PEM formats.
|
|
# ----------------------------------------------------------------
|
|
# clientAuthMethod: "private_key_jwt"
|
|
# clientAssertionKeyPath: "/etc/traefik/oidc/client-key.pem"
|
|
# clientAssertionKeyID: "prod-key-2026"
|
|
# clientAssertionAlg: "RS256" # or PS256/384/512, ES256/384/512
|
|
|
|
# Session Configuration
|
|
sessionEncryptionKey: "prod-encryption-key-64-chars-long-keep-it-secret-and-safe"
|
|
sessionMaxAge: 28800 # 8 hours
|
|
|
|
# Security Settings
|
|
forceHTTPS: true
|
|
strictAudienceValidation: true
|
|
|
|
# Redis Configuration for Multi-Replica Deployment
|
|
redis:
|
|
enabled: true
|
|
address: "redis-master.redis-namespace.svc.cluster.local:6379"
|
|
password: "REPLACE_WITH_YOUR_REDIS_PASSWORD"
|
|
db: 0
|
|
keyPrefix: "traefikoidc:prod:"
|
|
|
|
# Cache Strategy
|
|
cacheMode: "hybrid" # Fast local cache + shared Redis
|
|
|
|
# Connection Pooling
|
|
poolSize: 20
|
|
connectTimeout: 5
|
|
readTimeout: 3
|
|
writeTimeout: 3
|
|
|
|
# Resilience Features
|
|
enableCircuitBreaker: true
|
|
circuitBreakerThreshold: 5
|
|
circuitBreakerTimeout: 60
|
|
enableHealthCheck: true
|
|
healthCheckInterval: 30
|
|
|
|
# Example 3: Redis with TLS (for production security)
|
|
oidc-secure:
|
|
plugin:
|
|
traefikoidc:
|
|
clientID: "secure-client-id"
|
|
clientSecret: "secure-client-secret"
|
|
providerURL: "https://auth.example.com"
|
|
callbackURL: "/oauth2/callback"
|
|
sessionEncryptionKey: "secure-64-character-encryption-key-for-production-use-only"
|
|
|
|
redis:
|
|
enabled: true
|
|
address: "redis.example.com:6380"
|
|
password: "REPLACE_WITH_YOUR_REDIS_PASSWORD"
|
|
enableTLS: true
|
|
tlsSkipVerify: false # Verify certificates in production
|
|
cacheMode: "redis"
|
|
|
|
# Example 4: Hybrid Mode (Best Performance + Consistency)
|
|
# Local cache for hot data, Redis for consistency across replicas
|
|
oidc-hybrid:
|
|
plugin:
|
|
traefikoidc:
|
|
clientID: "app-client-id"
|
|
clientSecret: "app-client-secret"
|
|
providerURL: "https://auth.example.com"
|
|
callbackURL: "/oauth2/callback"
|
|
sessionEncryptionKey: "hybrid-mode-encryption-key-64-characters-long-and-secure"
|
|
|
|
redis:
|
|
enabled: true
|
|
address: "redis:6379"
|
|
password: "REPLACE_WITH_YOUR_REDIS_PASSWORD"
|
|
cacheMode: "hybrid"
|
|
|
|
# Hybrid mode L1 cache settings
|
|
hybridL1Size: 1000 # Number of items in local cache
|
|
hybridL1MemoryMB: 20 # MB of memory for local cache
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Router Definitions
|
|
# -------------------------------------------------------------------------
|
|
routers:
|
|
# Protected application using OIDC authentication
|
|
my-app:
|
|
rule: "Host(`app.example.com`)"
|
|
entryPoints:
|
|
- websecure
|
|
middlewares:
|
|
- oidc-production # Use the OIDC middleware
|
|
service: my-app-service
|
|
tls:
|
|
certResolver: letsencrypt
|
|
|
|
# Another app with minimal OIDC config
|
|
simple-app:
|
|
rule: "Host(`simple.example.com`)"
|
|
entryPoints:
|
|
- websecure
|
|
middlewares:
|
|
- oidc-minimal
|
|
service: simple-app-service
|
|
tls:
|
|
certResolver: letsencrypt
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Service Definitions
|
|
# -------------------------------------------------------------------------
|
|
services:
|
|
my-app-service:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://my-app:8080"
|
|
healthCheck:
|
|
path: /health
|
|
interval: 30s
|
|
timeout: 5s
|
|
|
|
simple-app-service:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://simple-app:3000"
|
|
|
|
|
|
# ============================================================================
|
|
# Part 3: Docker Compose Example
|
|
# ============================================================================
|
|
|
|
---
|
|
# docker-compose.yml
|
|
version: '3.8'
|
|
|
|
services:
|
|
# Redis service for shared caching
|
|
redis:
|
|
image: redis:7-alpine
|
|
command: redis-server --requirepass yourredispassword --maxmemory 256mb --maxmemory-policy allkeys-lru
|
|
ports:
|
|
- "6379:6379"
|
|
volumes:
|
|
- redis-data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
|
interval: 10s
|
|
timeout: 3s
|
|
retries: 5
|
|
networks:
|
|
- traefik-network
|
|
|
|
# Traefik with TraefikOIDC plugin
|
|
traefik:
|
|
image: traefik:v3.2
|
|
command:
|
|
- "--api.dashboard=true"
|
|
- "--providers.docker=true"
|
|
- "--providers.docker.exposedbydefault=false"
|
|
- "--providers.file.filename=/etc/traefik/dynamic.yml"
|
|
- "--entrypoints.web.address=:80"
|
|
- "--entrypoints.websecure.address=:443"
|
|
- "--experimental.plugins.traefikoidc.modulename=github.com/lukaszraczylo/traefikoidc"
|
|
- "--experimental.plugins.traefikoidc.version=v0.8.0"
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "8080:8080" # Dashboard
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- ./traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro
|
|
- ./letsencrypt:/letsencrypt
|
|
depends_on:
|
|
- redis
|
|
networks:
|
|
- traefik-network
|
|
|
|
# Your application
|
|
my-app:
|
|
image: my-app:latest
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.my-app.rule=Host(`app.example.com`)"
|
|
- "traefik.http.routers.my-app.entrypoints=websecure"
|
|
- "traefik.http.routers.my-app.tls.certresolver=letsencrypt"
|
|
|
|
# OIDC Middleware Configuration with Redis (using labels)
|
|
- "traefik.http.routers.my-app.middlewares=my-oidc@docker"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.clientID=your-client-id"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.clientSecret=your-client-secret"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.providerURL=https://auth.example.com"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.callbackURL=/oauth2/callback"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.sessionEncryptionKey=your-64-character-encryption-key-here"
|
|
|
|
# Redis configuration
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.redis.enabled=true"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.redis.address=redis:6379"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.redis.password=yourredispassword"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.redis.db=0"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.redis.keyPrefix=traefikoidc:"
|
|
- "traefik.http.middlewares.my-oidc.plugin.traefikoidc.redis.cacheMode=hybrid"
|
|
networks:
|
|
- traefik-network
|
|
deploy:
|
|
replicas: 3 # Multiple replicas sharing Redis cache
|
|
|
|
volumes:
|
|
redis-data:
|
|
|
|
networks:
|
|
traefik-network:
|
|
driver: bridge
|
|
|
|
|
|
# ============================================================================
|
|
# Part 4: Kubernetes Example
|
|
# ============================================================================
|
|
|
|
---
|
|
# kubernetes-example.yaml
|
|
|
|
# Redis Deployment
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: redis
|
|
namespace: traefik
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: redis
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: redis
|
|
spec:
|
|
containers:
|
|
- name: redis
|
|
image: redis:7-alpine
|
|
args:
|
|
- redis-server
|
|
- --requirepass
|
|
- $(REDIS_PASSWORD)
|
|
- --maxmemory
|
|
- 512mb
|
|
- --maxmemory-policy
|
|
- allkeys-lru
|
|
env:
|
|
- name: REDIS_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: redis-secret
|
|
key: password
|
|
ports:
|
|
- containerPort: 6379
|
|
resources:
|
|
requests:
|
|
memory: "256Mi"
|
|
cpu: "100m"
|
|
limits:
|
|
memory: "512Mi"
|
|
cpu: "500m"
|
|
---
|
|
# Redis Service
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: redis
|
|
namespace: traefik
|
|
spec:
|
|
selector:
|
|
app: redis
|
|
ports:
|
|
- port: 6379
|
|
targetPort: 6379
|
|
---
|
|
# Redis Secret
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: redis-secret
|
|
namespace: traefik
|
|
type: Opaque
|
|
stringData:
|
|
password: "REPLACE_WITH_YOUR_REDIS_PASSWORD"
|
|
---
|
|
# OIDC Middleware with Redis
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: Middleware
|
|
metadata:
|
|
name: oidc-auth
|
|
namespace: traefik
|
|
spec:
|
|
plugin:
|
|
traefikoidc:
|
|
# OIDC Configuration
|
|
clientID: "kubernetes-client-id"
|
|
clientSecret: "kubernetes-client-secret"
|
|
providerURL: "https://auth.example.com"
|
|
callbackURL: "/oauth2/callback"
|
|
sessionEncryptionKey: "kubernetes-64-character-session-encryption-key-keep-secret"
|
|
|
|
# Redis Configuration
|
|
redis:
|
|
enabled: true
|
|
address: "redis.traefik.svc.cluster.local:6379"
|
|
password: "REPLACE_WITH_YOUR_REDIS_PASSWORD"
|
|
db: 0
|
|
keyPrefix: "traefikoidc:k8s:"
|
|
cacheMode: "hybrid"
|
|
poolSize: 20
|
|
enableCircuitBreaker: true
|
|
enableHealthCheck: true
|
|
---
|
|
# IngressRoute using the middleware
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: my-app
|
|
namespace: default
|
|
spec:
|
|
entryPoints:
|
|
- websecure
|
|
routes:
|
|
- match: Host(`app.example.com`)
|
|
kind: Rule
|
|
middlewares:
|
|
- name: oidc-auth
|
|
namespace: traefik
|
|
services:
|
|
- name: my-app
|
|
port: 80
|
|
tls:
|
|
certResolver: letsencrypt
|
|
|
|
|
|
# ============================================================================
|
|
# Part 5: Environment Variables (Optional Fallback)
|
|
# ============================================================================
|
|
|
|
# If you prefer environment variables as fallback (not recommended for production),
|
|
# you can set these. NOTE: Plugin configuration takes precedence!
|
|
|
|
# Docker Compose env file (.env)
|
|
---
|
|
# OIDC Configuration
|
|
OIDC_CLIENT_ID=your-client-id
|
|
OIDC_CLIENT_SECRET=your-client-secret
|
|
OIDC_PROVIDER_URL=https://auth.example.com
|
|
|
|
# Redis Configuration (fallback)
|
|
REDIS_ENABLED=true
|
|
REDIS_ADDRESS=redis:6379
|
|
REDIS_PASSWORD=yourredispassword
|
|
REDIS_DB=0
|
|
REDIS_KEY_PREFIX=traefikoidc:
|
|
REDIS_CACHE_MODE=hybrid
|
|
REDIS_POOL_SIZE=20
|
|
REDIS_ENABLE_CIRCUIT_BREAKER=true
|
|
REDIS_ENABLE_HEALTH_CHECK=true
|
|
|
|
|
|
# ============================================================================
|
|
# Configuration Cheat Sheet
|
|
# ============================================================================
|
|
|
|
# Minimal Setup (Quick Start):
|
|
# redis:
|
|
# enabled: true
|
|
# address: "redis:6379"
|
|
|
|
# Production Setup (Recommended):
|
|
# redis:
|
|
# enabled: true
|
|
# address: "redis-master:6379"
|
|
# password: "strong-password"
|
|
# cacheMode: "hybrid"
|
|
# enableCircuitBreaker: true
|
|
# enableHealthCheck: true
|
|
|
|
# High Security Setup:
|
|
# redis:
|
|
# enabled: true
|
|
# address: "redis.example.com:6380"
|
|
# password: "strong-password"
|
|
# enableTLS: true
|
|
# tlsSkipVerify: false
|
|
# cacheMode: "redis"
|
|
|
|
# Cache Modes:
|
|
# - "memory": Local cache only (default, no Redis needed)
|
|
# - "redis": Redis only (consistent, shared across replicas)
|
|
# - "hybrid": Local L1 + Redis L2 (best performance + consistency)
|