# Nginx proxy that rewrites Host header for Authentik ext_authz # # Problem: Envoy's ext_authz HTTP filter forwards the ORIGINAL request's Host # header (e.g., prometheus.kube.huskypup.net) to Authentik. But Authentik's # embedded outpost only serves /outpost.goauthentik.io/auth/nginx on the # brand domain (auth.kube.huskypup.net), returning 404 for other hosts. # # Solution: This lightweight nginx proxy sits between Istio's ext_authz filter # and Authentik. It rewrites the Host header to auth.kube.huskypup.net while # preserving all other headers (X-Forwarded-Host, cookies, etc.) so Authentik # knows the original service being accessed. # # Flow: # 1. Client → Istio ingress gateway (Host: prometheus.kube.huskypup.net) # 2. ext_authz filter → this proxy (Host: prometheus.kube.huskypup.net) # 3. This proxy → Authentik (Host: auth.kube.huskypup.net, X-Forwarded-Host preserved) # 4. Authentik checks auth, returns 200 (allow) or 302 (redirect to login) --- apiVersion: v1 kind: ConfigMap metadata: name: ext-authz-proxy-config namespace: authentik data: nginx.conf: | load_module modules/ngx_http_js_module.so; worker_processes 1; error_log /dev/stderr warn; pid /tmp/nginx.pid; events { worker_connections 256; } http { access_log /dev/stdout; proxy_temp_path /tmp/proxy_temp; client_body_temp_path /tmp/client_temp; js_path /etc/nginx/njs/; js_import cookie from cookie_domain.js; server { listen 4180; location / { # Rewrite all paths to the exact Authentik traefik handler path. # Envoy ext_authz appends the original request path to pathPrefix, # creating paths like /auth/traefik/ which Authentik's Go router # doesn't match (exact path only, no trailing slash). rewrite ^ /outpost.goauthentik.io/auth/traefik break; proxy_pass http://authentik-server.authentik.svc.cluster.local; proxy_http_version 1.1; proxy_set_header Host auth.kube.huskypup.net; proxy_set_header Connection ""; proxy_pass_request_headers on; # Add Domain=kube.huskypup.net to Set-Cookie headers from Authentik. # The outpost creates session cookies without Domain attribute, causing # browser to scope them to the original request domain (e.g., frigate.kube.huskypup.net). # The callback goes to auth.kube.huskypup.net, so the cookie must be domain-scoped. js_header_filter cookie.addDomain; } } } cookie_domain.js: | function addDomain(r) { var cookies = r.headersOut['Set-Cookie']; if (cookies) { if (!Array.isArray(cookies)) { cookies = [cookies]; } var modified = cookies.map(function(c) { if (c.indexOf('Domain=') === -1) { return c + '; Domain=kube.huskypup.net'; } return c; }); r.headersOut['Set-Cookie'] = modified; } } export default { addDomain }; --- apiVersion: apps/v1 kind: Deployment metadata: name: ext-authz-proxy namespace: authentik labels: app: ext-authz-proxy spec: replicas: 1 selector: matchLabels: app: ext-authz-proxy template: metadata: labels: app: ext-authz-proxy spec: automountServiceAccountToken: false containers: - name: nginx image: nginx:1.27-alpine ports: - containerPort: 4180 protocol: TCP volumeMounts: - name: config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf readOnly: true - name: njs mountPath: /etc/nginx/njs/cookie_domain.js subPath: cookie_domain.js readOnly: true - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache/nginx resources: requests: cpu: 10m memory: 32Mi limits: cpu: 50m memory: 64Mi securityContext: runAsNonRoot: true runAsUser: 10101 runAsGroup: 10101 allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL seccompProfile: type: RuntimeDefault readinessProbe: tcpSocket: port: 4180 initialDelaySeconds: 2 periodSeconds: 5 livenessProbe: tcpSocket: port: 4180 initialDelaySeconds: 5 periodSeconds: 10 volumes: - name: config configMap: name: ext-authz-proxy-config - name: njs configMap: name: ext-authz-proxy-config - name: tmp emptyDir: {} - name: cache emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: ext-authz-proxy namespace: authentik labels: app: ext-authz-proxy spec: selector: app: ext-authz-proxy ports: - port: 4180 targetPort: 4180 protocol: TCP