diff --git a/CHANGELOG.md b/CHANGELOG.md index a308dbd3..1ffefca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,16 @@ written while it was being built. See [RELEASING.md](RELEASING.md). ### Changed +- A deployment can now be reached under any hostnames, not only `app.` + and `auth.`. The app's hostname is the first entry of + `frontend.ingress.hosts`, Keycloak's is `keycloak.hostname.hostname`, and the + chart derives the ingresses, the TLS certificates, the OIDC issuer urls and + the realm's login redirect urls from those two. Previously the subdomains were + fixed in the chart's templates, so changing either value pointed the traffic + at one name while the login flow still expected the other. Both values are now + required: the chart refuses to render rather than guessing a hostname nothing + is served under, and the two OIDC issuer settings are gone from `values.yaml` + because they only ever had one working value. - Your hackathon lists are now grouped by when they happen — Happening now, Coming up, Finished — and each one says how far away it is: "starts in 4 days", "day 1 of 4", "ended 3 days ago". The dates are still there behind the diff --git a/helm-chart/Chart.yaml b/helm-chart/Chart.yaml index db3ecf04..a262dfc5 100644 --- a/helm-chart/Chart.yaml +++ b/helm-chart/Chart.yaml @@ -6,7 +6,7 @@ type: application # The chart's own version. Bumped by hand when the chart changes, and # independent of the app: the CI publishes whatever it finds here. -version: 0.3.1 +version: 0.4.0 # The app release this chart deploys. A new app release does # not become deployable until someone points the chart at it. appVersion: "0.9.1" diff --git a/helm-chart/templates/NOTES.txt b/helm-chart/templates/NOTES.txt index fef99450..7a35c30b 100644 --- a/helm-chart/templates/NOTES.txt +++ b/helm-chart/templates/NOTES.txt @@ -1,7 +1,7 @@ Hackagon has been deployed! -Frontend: https://app.{{ .Values.baseDomain }} -Keycloak: https://auth.{{ .Values.baseDomain }} +Frontend: https://{{ include "hackagon.frontendHost" . }} +Keycloak: {{ include "hackagon.keycloakUrl" . }} To get the generated frontend OIDC secrets, run: diff --git a/helm-chart/templates/_helpers.tpl b/helm-chart/templates/_helpers.tpl index 9ae963e1..9976d882 100644 --- a/helm-chart/templates/_helpers.tpl +++ b/helm-chart/templates/_helpers.tpl @@ -62,24 +62,38 @@ Create the name of the service account to use {{- end }} {{/* -Base domain with substitution +First ingress host is the canonical frontend hostname. +(in case we have multiple urls) +Rendered with `tpl`, like every other host value; keep this one free of +`hackagon.frontendHost` or it recurses. */}} -{{- define "hackagon.baseDomain" -}} -{{- .Values.baseDomain | replace "{baseDomain}" .Values.baseDomain }} +{{- define "hackagon.frontendHost" -}} +{{- $first := first (.Values.frontend.ingress.hosts | default list) | required "frontend.ingress.hosts must name at least one host: it is the app's public name" }} +{{- $host := $first.host | required "frontend.ingress.hosts[0].host is required: it is the app's public name" }} +{{- tpl $host . | required "frontend.ingress.hosts[0].host must render to a non-empty hostname" }} {{- end }} {{/* -Frontend host with substitution +Public url Keycloak runs under. Required `enabled` or not: browsers are sent +here, and it is the `iss` claim in every token. */}} -{{- define "hackagon.frontendHost" -}} -{{- printf "app.%s" (include "hackagon.baseDomain" .) | replace "{baseDomain}" .Values.baseDomain }} +{{- define "hackagon.keycloakUrl" -}} +{{- .Values.keycloak.hostname.hostname | required "keycloak.hostname.hostname is required (e.g. \"https://auth.example.com\")" | trimSuffix "/" }} {{- end }} {{/* -Keycloak host with substitution +The `iss` claim; the backend rejects a token that does not match it. The realm +is `hackagon` chart-wide, so this is derived rather than configurable. +*/}} +{{- define "hackagon.oidcIssuer" -}} +{{- printf "%s/realms/hackagon" (include "hackagon.keycloakUrl" .) }} +{{- end }} + +{{/* +The Keycloak url as a bare hostname, for an ingress `host:` field. */}} {{- define "hackagon.keycloakHost" -}} -{{- printf "auth.%s" (include "hackagon.baseDomain" .) | replace "{baseDomain}" .Values.baseDomain }} +{{- include "hackagon.keycloakUrl" . | trimPrefix "https://" | trimPrefix "http://" }} {{- end }} {{/* @@ -127,4 +141,4 @@ Get password: use provided value or generate one {{- else }} {{- include "hackagon.randAlphaNum" .length | b64enc }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/helm-chart/templates/backend-configmap.yaml b/helm-chart/templates/backend-configmap.yaml index 36d85ed2..5c4c559b 100644 --- a/helm-chart/templates/backend-configmap.yaml +++ b/helm-chart/templates/backend-configmap.yaml @@ -1,5 +1,5 @@ -{{- if and (contains "{keycloakService}" .Values.backend.config.oidc.jwksurl) (not .Values.keycloak.enabled) -}} -{{- fail "backend.config.oidc.jwksurl uses {keycloakService}, but keycloak.enabled is false: set jwksurl to the external Keycloak's certs endpoint" -}} +{{- if and (contains "hackagon.keycloakServiceName" .Values.backend.config.oidc.jwksurl) (not .Values.keycloak.enabled) -}} +{{- fail "backend.config.oidc.jwksurl points at the in-cluster Keycloak, but keycloak.enabled is false: set it to the external Keycloak's certs endpoint" -}} {{- end -}} apiVersion: v1 kind: ConfigMap @@ -12,7 +12,7 @@ data: config.yaml: | server: port: {{ .Values.backend.config.server.port | quote }} - adminemail: {{ .Values.backend.config.server.adminemail | replace "{baseDomain}" .Values.baseDomain | quote }} + adminemail: {{ tpl .Values.backend.config.server.adminemail . | quote }} adminkeycloakid: {{ .Values.backend.config.server.adminkeycloakid | quote }} database: driver: {{ .Values.backend.config.database.driver | quote }} @@ -22,8 +22,8 @@ data: user: {{ .Values.backend.config.database.user | quote }} password: {{ .Values.backend.config.database.postgresPassword | required "postgresql.auth.postgresPassword is required" | quote }} oidc: - jwksurl: {{ .Values.backend.config.oidc.jwksurl | replace "{baseDomain}" .Values.baseDomain | replace "{keycloakService}" (include "hackagon.keycloakServiceName" .) | quote }} - issuerurl: {{ .Values.backend.config.oidc.issuerurl | replace "{baseDomain}" .Values.baseDomain | quote }} + jwksurl: {{ tpl .Values.backend.config.oidc.jwksurl . | quote }} + issuerurl: {{ include "hackagon.oidcIssuer" . | quote }} algorithm: {{ .Values.backend.config.oidc.algorithm | quote }} logging: level: {{ .Values.backend.config.logging.level | quote }} diff --git a/helm-chart/templates/frontend-configmap.yaml b/helm-chart/templates/frontend-configmap.yaml index 67a7d073..8ee07355 100644 --- a/helm-chart/templates/frontend-configmap.yaml +++ b/helm-chart/templates/frontend-configmap.yaml @@ -16,5 +16,5 @@ data: useSecure: {{ .Values.frontend.config.cookies.useSecure }} oidc: clientId: {{ .Values.frontend.config.oidc.clientId | quote }} - issuer: {{ .Values.frontend.config.oidc.issuer | replace "{baseDomain}" .Values.baseDomain | quote }} + issuer: {{ include "hackagon.oidcIssuer" . | quote }} audience: {{ .Values.frontend.config.oidc.audience | quote }} \ No newline at end of file diff --git a/helm-chart/templates/frontend-ingress.yaml b/helm-chart/templates/frontend-ingress.yaml index b72f6e40..2af3129e 100644 --- a/helm-chart/templates/frontend-ingress.yaml +++ b/helm-chart/templates/frontend-ingress.yaml @@ -15,7 +15,7 @@ spec: ingressClassName: {{ .Values.frontend.ingress.ingressClass }} rules: {{- range .Values.frontend.ingress.hosts }} - - host: {{ .host | replace "{baseDomain}" $.Values.baseDomain | replace "{releaseName}" $.Release.Name }} + - host: {{ tpl .host $ }} http: paths: {{- range .paths }} @@ -30,12 +30,6 @@ spec: {{- end }} {{- with .Values.frontend.ingress.tls }} tls: - {{- range . }} - - hosts: - {{- range .hosts }} - - {{ . | replace "{baseDomain}" $.Values.baseDomain | replace "{releaseName}" $.Release.Name }} - {{- end }} - secretName: {{ .secretName | replace "{releaseName}" $.Release.Name }} - {{- end }} + {{- tpl (toYaml .) $ | nindent 4 }} {{- end }} {{- end }} \ No newline at end of file diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml index f5b1b440..ec8728de 100644 --- a/helm-chart/values.yaml +++ b/helm-chart/values.yaml @@ -51,7 +51,6 @@ frontend: useSecure: true oidc: clientId: hackagon-frontend - issuer: "https://auth.{baseDomain}/realms/hackagon" audience: hackagon-backend service: @@ -69,15 +68,20 @@ frontend: nginx.ingress.kubernetes.io/proxy-buffers: 8 16k nginx.ingress.kubernetes.io/proxy_busy_buffers_size: 32k nginx.ingress.kubernetes.io/ssl-redirect: "true" + # -- Public hostname of the app. The TLS host below and the realm's login + # Redirects follow the first entry. hosts: - - host: "app.{baseDomain}" + # You can overwrite this default to any hard-coded custom domain, + # e.g. host: "hackagon.datascience.ch". + - host: "app.{{ .Values.baseDomain }}" paths: - path: / pathType: Prefix tls: - - secretName: "{releaseName}-frontend-tls" + - secretName: '{{ .Release.Name }}-frontend-tls' hosts: - - "app.{baseDomain}" + # Evaluates to the first host in frontend.ingress.hosts + - '{{ include "hackagon.frontendHost" . }}' # ============================================================ # Backend @@ -104,7 +108,7 @@ backend: config: server: port: "3000" - adminemail: "admin@{baseDomain}" + adminemail: "admin@{{ .Values.baseDomain }}" # -- Keycloak user id of the platform admin. Must equal the `id` of the # `hackagon-admin` user in realmJson: casbin grants the `admin` role to # exactly this id, and the backend seeds the admin row by it. Importing @@ -123,10 +127,8 @@ backend: # -- Where the backend fetches Keycloak's signing keys. # With an external Keycloak (keycloak.enabled: false), # override this with a url the backend pod can actually reach. - jwksurl: "http://{keycloakService}:8080/realms/hackagon/protocol/openid-connect/certs" - # -- Must stay the PUBLIC url: this is the `iss` claim Keycloak stamps into - # tokens, and the backend rejects any token whose issuer does not match. - issuerurl: "https://auth.{baseDomain}/realms/hackagon" + # the issuer is derived from the keycloak hostname and injected in configmaps. + jwksurl: 'http://{{ include "hackagon.keycloakServiceName" . }}:8080/realms/hackagon/protocol/openid-connect/certs' algorithm: RS256 logging: level: info @@ -145,9 +147,11 @@ keycloak: # -- Production mode requires hostname and database mode: production - # -- Hostname for Keycloak (public admin UI) + # -- Public url Keycloak runs under; the auth hostname and the OIDC issuer + # derive from it. Required. Passed verbatim to the subchart, so `{...}` + # placeholders are not substituted here. e.g. "https://auth.example.com" hostname: - hostname: "" # e.g. "https://auth.{baseDomain}" + hostname: "" # -- Keycloak's own console admin, which lives in the `master` realm. Its # password is NOT in realmJson: a realm export carries only that realm's diff --git a/tools/helm/lint-values.yaml b/tools/helm/lint-values.yaml index a856756d..fc8decd3 100644 --- a/tools/helm/lint-values.yaml +++ b/tools/helm/lint-values.yaml @@ -5,6 +5,14 @@ baseDomain: lint.invalid +frontend: + ingress: + hosts: + - host: "hackagon-app.lint.invalid" + paths: + - path: / + pathType: Prefix + backend: config: server: @@ -14,7 +22,7 @@ backend: keycloak: hostname: - hostname: "https://auth.lint.invalid" + hostname: "https://hackagon-auth.lint.invalid" database: external: host: "hackagon-postgresql" diff --git a/tools/just/helm.just b/tools/just/helm.just index c2704397..2a604f33 100644 --- a/tools/just/helm.just +++ b/tools/just/helm.just @@ -193,5 +193,7 @@ publish: echo "" echo " Install with:" echo " helm install hackagon {{ oci_repo }}/hackagon --version $chart_v \\" - echo " --set baseDomain=example.com --set-file realmJson=@path/to/realm.json" + echo " --set baseDomain=example.com \\" + echo " --set keycloak.hostname.hostname=https://auth.example.com \\" + echo " --set-file realmJson=@path/to/realm.json" echo "" diff --git a/tools/keycloak-handover/README.md b/tools/keycloak-handover/README.md index 59f2fb09..6ac6338d 100644 --- a/tools/keycloak-handover/README.md +++ b/tools/keycloak-handover/README.md @@ -49,6 +49,7 @@ decryptable by you, which is how you know the recipient key took effect. helm upgrade --install hackagon ../../helm-chart \ --set-file realmJson=./realm.json \ --set baseDomain= \ + --set keycloak.hostname.hostname=https://auth. \ --set backend.config.server.adminkeycloakid=1183370a-46a2-4dad-b8fd-dd927d083e14 \ --set frontendSecrets.clientSecret= ```