From a7f0dfc3411068cf50fb5643a519dcacd14ba29d Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 17 Apr 2026 19:23:04 +0200 Subject: [PATCH] updated wger to 2.5 --- workout-system/workout.yaml | 198 ++++++++++++++++++++++++++++-------- 1 file changed, 153 insertions(+), 45 deletions(-) diff --git a/workout-system/workout.yaml b/workout-system/workout.yaml index 5ba99fb..5f271b2 100644 --- a/workout-system/workout.yaml +++ b/workout-system/workout.yaml @@ -1,20 +1,28 @@ # wger - Workout Manager # https://github.com/wger-project/wger -# Version: 2.3 +# Version: 2.5 (official image, no custom fork) # Domain: workout.dooplex.hu -# Auth: Authentik Forward Auth (Proxy) with Remote-User header +# Auth: Authentik Forward Auth (domain mode) + native wger AUTH_PROXY middleware # -# wger supports authentication via headers (X-Remote-User) -# when WGER_USE_PROXY_AUTH is enabled +# ============================================================================ +# MIGRATION NOTES (from 2.3 + custom OIDC fork): +# - Image switched from ghcr.io/kisfenyo/wger-oidc:latest -> wger/server:2.5 +# - All OIDC_* / ENABLE_OIDC env vars removed +# - Native AUTH_PROXY_* env vars added (wger 2.4+ feature, PR #1859) +# - Ingress split into two resources: +# * wger -> path / -> protected by Authentik forward-auth +# * wger-api -> path /api/ -> unprotected (JWT auth for mobile app) +# - nginx sidecar: strips client-supplied X-Authentik-* on /api/ (defense in depth) +# - Authentik: create a new Proxy Provider (Forward auth, single application) +# External Host: https://workout.dooplex.hu +# Attach to existing outpost. The old OIDC provider can be deleted. # -# Authentik Setup: -# 1. Create Proxy Provider: -# - Name: wger -# - External Host: https://workout.dooplex.hu -# - Mode: Forward auth (single application) -# 2. Configure to send X-Remote-User header -# 3. Create Application linked to this provider -# 4. Create Outpost (or add to existing) with this provider +# POST-UPGRADE COMMANDS (run once after rollout stabilises): +# kubectl exec -n workout-system deploy/wger -c wger -- \ +# python manage.py recalculate_statistics --all --active-only +# kubectl exec -n workout-system deploy/wger -c wger -- \ +# python manage.py evaluate_trophies --all +# ============================================================================ --- apiVersion: v1 kind: Namespace @@ -65,6 +73,10 @@ metadata: labels: app.kubernetes.io/instance: wger app.kubernetes.io/name: wger + annotations: + # Track upstream wger releases + extensions.v1alpha1.version-checker.io/wger: "true" + extensions.v1alpha1.version-checker.io/wger.match-regex: "^\\d+\\.\\d+$" spec: replicas: 1 selector: @@ -98,8 +110,8 @@ spec: mountPath: /etc/nginx/conf.d/default.conf subPath: nginx.conf - name: wger - image: ghcr.io/kisfenyo/wger-oidc:latest - imagePullPolicy: Always + image: wger/server:2.5 + imagePullPolicy: IfNotPresent env: # Django settings - name: SECRET_KEY @@ -122,7 +134,9 @@ spec: value: "config.settings.production" - name: DJANGO_CACHE_TIMEOUT value: "120" - # Database + - name: CSRF_TRUSTED_ORIGINS + value: "https://workout.dooplex.hu" + # Database (shared CNPG) - name: DJANGO_DB_ENGINE value: "django.db.backends.postgresql" - name: DJANGO_DB_HOST @@ -153,31 +167,26 @@ spec: value: "redis://wger-redis:6379/2" - name: CELERY_BACKEND value: "redis://wger-redis:6379/2" - - name: ENABLE_OIDC + # ---------------------------------------------------------------- + # Native Authentication Proxy (wger 2.4+) - replaces OIDC fork + # ---------------------------------------------------------------- + - name: AUTH_PROXY_ENABLED value: "True" - - name: OIDC_RP_CLIENT_ID - value: "AXr6k4P1JcgKKMcvGeXOLwd69MJ1UVjz3fW80mEg" - - name: OIDC_RP_CLIENT_SECRET - value: "oaj4yWum0skWoAJVf4VvXSSnc4pdaWQbKtyPaMaG6prBN0av1b1w7bna6nUALoIXwSQWu9seFZl66XsYxaFWXVXcWyI6B63rl5saIFCifVg9hqkl6RlhxHL4X4u42pqd" - - name: OIDC_RP_SIGN_ALGO - value: "RS256" - - name: CSRF_TRUSTED_ORIGINS - value: "https://workout.dooplex.hu" - # Authentik Endpoints (Replace 'authentik.dooplex.hu' with your actual Authentik domain) - - name: OIDC_OP_LOGOUT_ENDPOINT - value: "https://authentik.dooplex.hu/application/o/workout/end-session/" - - name: OIDC_LOGIN_BUTTON_TEXT - value: "Login with Authentik" - - name: OIDC_ALLOW_CREATE_USER - value: "true" - - name: OIDC_OP_AUTHORIZATION_ENDPOINT - value: "https://authentik.dooplex.hu/application/o/authorize/" - - name: OIDC_OP_TOKEN_ENDPOINT - value: "https://authentik.dooplex.hu/application/o/token/" - - name: OIDC_OP_USER_ENDPOINT - value: "https://authentik.dooplex.hu/application/o/userinfo/" - - name: OIDC_OP_JWKS_ENDPOINT - value: "https://authentik.dooplex.hu/application/o/workout/jwks/" + # Django META key format: HTTP_ + uppercase header with - replaced by _ + # So X-Authentik-Username => HTTP_X_AUTHENTIK_USERNAME + - name: AUTH_PROXY_HEADER + value: "HTTP_X_AUTHENTIK_USERNAME" + - name: AUTH_PROXY_CREATE_UNKNOWN_USER + value: "True" + - name: AUTH_PROXY_EMAIL_HEADER + value: "HTTP_X_AUTHENTIK_EMAIL" + - name: AUTH_PROXY_NAME_HEADER + value: "HTTP_X_AUTHENTIK_NAME" + # Only trust the auth header when coming from the nginx sidecar + # (same pod, proxies from 127.0.0.1 to Django on :8000). + # This prevents header-spoofing attacks from anywhere else. + - name: AUTH_PROXY_TRUSTED_IPS + value: "127.0.0.1/32" # Email (disabled - no email sending) - name: ENABLE_EMAIL value: "False" @@ -260,8 +269,8 @@ spec: fsGroup: 1000 containers: - name: celery-worker - image: ghcr.io/kisfenyo/wger-oidc:latest - imagePullPolicy: Always + image: wger/server:2.5 + imagePullPolicy: IfNotPresent command: ["/start-worker"] env: - name: SECRET_KEY @@ -274,6 +283,10 @@ spec: secretKeyRef: name: wger-app key: signing-key + - name: DJANGO_SETTINGS_MODULE + value: "config.settings.production" + - name: TIME_ZONE + value: "Europe/Budapest" - name: DJANGO_DB_ENGINE value: "django.db.backends.postgresql" - name: DJANGO_DB_HOST @@ -337,8 +350,8 @@ spec: fsGroup: 1000 containers: - name: celery-beat - image: ghcr.io/kisfenyo/wger-oidc:latest - imagePullPolicy: Always + image: wger/server:2.5 + imagePullPolicy: IfNotPresent command: ["/start-beat"] env: - name: SECRET_KEY @@ -351,6 +364,10 @@ spec: secretKeyRef: name: wger-app key: signing-key + - name: DJANGO_SETTINGS_MODULE + value: "config.settings.production" + - name: TIME_ZONE + value: "Europe/Budapest" - name: DJANGO_CACHE_TIMEOUT value: "120" - name: DJANGO_CACHE_CLIENT_CLASS @@ -421,7 +438,9 @@ spec: app.kubernetes.io/instance: wger app.kubernetes.io/name: wger --- -# Ingress with Authentik forward auth +# ============================================================================ +# Ingress #1: web UI paths (/) - Authentik forward-auth protected +# ============================================================================ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -435,6 +454,13 @@ metadata: external-dns.alpha.kubernetes.io/hostname: workout.dooplex.hu,workout.home nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: "100m" + # Authentik Forward Auth (domain mode) - same pattern as your other SSO apps + # If you use an internal outpost service URL elsewhere, swap auth-url for it. + nginx.ingress.kubernetes.io/auth-url: "http://ak-outpost-workout-outpost.auth-system.svc.cluster.local:9000/outpost.goauthentik.io/auth/nginx" + nginx.ingress.kubernetes.io/auth-signin: "https://workout.dooplex.hu/outpost.goauthentik.io/start?rd=$escaped_request_uri" + nginx.ingress.kubernetes.io/auth-response-headers: "Set-Cookie,X-Authentik-Username,X-Authentik-Email,X-Authentik-Name,X-Authentik-Groups,X-Authentik-Uid" + nginx.ingress.kubernetes.io/auth-snippet: | + proxy_set_header X-Forwarded-Host $http_host; nginx.ingress.kubernetes.io/configuration-snippet: | set $geo_allowed 0; if ($remote_addr ~ "^192\.168\.") { set $geo_allowed 1; } @@ -471,6 +497,60 @@ spec: - workout.dooplex.hu secretName: wger-tls --- +# ============================================================================ +# Ingress #2: API paths (/api/) - NO forward-auth, JWT token auth only +# Required so the wger Flutter mobile app can hit /api/v2/token for login. +# More-specific path match means /api/* hits this Ingress, not the / one. +# ============================================================================ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wger-api + namespace: workout-system + labels: + app.kubernetes.io/instance: wger + app.kubernetes.io/name: wger-api + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "100m" + nginx.ingress.kubernetes.io/configuration-snippet: | + # Same geo-block as the web UI ingress + set $geo_allowed 0; + if ($remote_addr ~ "^192\.168\.") { set $geo_allowed 1; } + if ($remote_addr ~ "^10\.") { set $geo_allowed 1; } + if ($geoip2_country_code = "HU") { set $geo_allowed 1; } + if ($geo_allowed = 0) { + return 403 "Access restricted to Hungary"; + } +spec: + ingressClassName: nginx-internal + rules: + - host: workout.dooplex.hu + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: wger + port: + number: 80 + - host: workout.home + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: wger + port: + number: 80 + tls: + - hosts: + - workout.dooplex.hu + secretName: wger-tls +--- apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -532,12 +612,40 @@ data: access_log off; } + # API path: strip any client-supplied auth headers before proxying. + # Mobile app + API clients authenticate via JWT (/api/v2/token), not + # proxy auth. This is a defense-in-depth measure so that even if traffic + # somehow reaches this sidecar without going through the forward-auth + # ingress, it cannot forge an AUTH_PROXY login via a spoofed header. + # Nginx treats "" as "do not forward this header." + location /api/ { + proxy_pass http://localhost:8000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_set_header X-Authentik-Username ""; + proxy_set_header X-Authentik-Email ""; + proxy_set_header X-Authentik-Name ""; + proxy_set_header X-Authentik-Groups ""; + proxy_set_header X-Authentik-Uid ""; + } + + # Everything else: pass through the auth headers set by the + # forward-auth ingress so wger's AUTH_PROXY middleware can log the + # user in. $http_x_authentik_username expands to empty if the header + # isn't present (e.g. Tailscale admin access bypassing the ingress). location / { proxy_pass http://localhost:8000; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_set_header X-Authentik-Username $http_x_authentik_username; + proxy_set_header X-Authentik-Email $http_x_authentik_email; + proxy_set_header X-Authentik-Name $http_x_authentik_name; + proxy_set_header X-Authentik-Groups $http_x_authentik_groups; + proxy_set_header X-Authentik-Uid $http_x_authentik_uid; } } ---- +--- \ No newline at end of file