feat(traefik-forward-auth): serve several domains and portals from one instance

A session cookie scoped to one registrable domain is never sent to a host under
another, so an app on a second domain could not be protected by this instance at
all. It failed with a 500 naming the cause: "return URL host does not match any
configured cookie domain".

`domains` is now a list of {domain, authHost}, matching the upstream
`server.domains` schema. The chart previously emitted `server.hostname` and
`cookies.domain`, which are not in 4.14.1's documented options — a deprecated
form that still worked. The single-domain values remain as shorthand and fold
into one entry, so an existing release renders the same protection it did before.

`portals` is a list too. A portal is one OAuth2 client, chosen by the middleware
address, so two portals means two Pocket ID applications — which is how access
can be separated per domain rather than merely shared across them. Each portal
gets its own Middleware and its own mounted secret, under
/var/run/secrets/traefik-forward-auth/<portal>/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
sha
2026-08-18 00:10:40 +03:00
co-authored by Claude Opus 5
parent 308b164894
commit cf2aebf298
6 changed files with 130 additions and 25 deletions
+12 -5
View File
@@ -1,3 +1,4 @@
{{- $portals := include "tfa.portals" . | fromYamlArray -}}
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -43,18 +44,24 @@ spec:
- name: config
mountPath: /etc/traefik-forward-auth
readOnly: true
- name: client-secret
mountPath: /var/run/secrets/traefik-forward-auth
{{- range $portals }}
# One directory per portal: each has its own OAuth2 client, so each
# has its own secret.
- name: client-secret-{{ .name }}
mountPath: "{{ include "tfa.secretDir" $ }}/{{ .name }}"
readOnly: true
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: config
configMap:
name: {{ .Release.Name }}-config
- name: client-secret
{{- range $portals }}
- name: client-secret-{{ .name }}
secret:
secretName: {{ required "existingSecret is required" .Values.existingSecret }}
secretName: {{ .existingSecret }}
items:
- key: client-secret
- key: {{ .secretKey }}
path: client-secret
{{- end }}