From d6e95fea7795e7831a3d6828a92b7de0a537f96f Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Tue, 18 Aug 2026 19:51:06 +0530 Subject: [PATCH 1/7] fix(plane-enterprise): stop forcing TLS on the Traefik ingress templates/ingress-traefik.yaml hardcoded the `websecure` entrypoint and emitted its `tls:` block unconditionally, so an install with SSL left off was unreachable over HTTP and pointed Traefik at a `-ssl-cert` Secret that nothing ever creates -- templates/certs/certs.yaml only mints it when ssl.createIssuer AND ssl.generateCerts are set. Traefik then fell back to its built-in self-signed certificate, which is what users hit when deploying on an sslip.io domain without a certificate. The rest of the chart already treated SSL as optional: templates/ingress.yaml gates its `tls:` block, and app-env/live-env/silo/pi-api render APP_BASE_URL, PLANE_FRONTEND_URL, EXPORT_DOWNLOAD_BASE_URL and friends with an `http://` scheme under the same condition. The Traefik route was the sole outlier, and the mismatch meant the ingress served HTTPS while the app advertised HTTP. Factor that shared condition into a `plane.tlsEnabled` helper and drive both the entrypoint and the `tls:` block from it, so the Traefik path stays in step with the nginx path and the app config: - no certificate configured -> `web` entrypoint, no `tls:` block - ssl.tls_secret_name, or generateCerts + createIssuer -> `websecure` + `tls:` Add ingress.traefik.entryPoints for clusters that renamed Traefik's default entrypoints or want to serve both schemes at once. Installs that already configure SSL render exactly as before. --- charts/plane-enterprise/Chart.yaml | 2 +- charts/plane-enterprise/README.md | 40 +++++++++++++++++++ .../plane-enterprise/templates/_helpers.tpl | 37 +++++++++++++++++ .../templates/ingress-traefik.yaml | 4 +- charts/plane-enterprise/values.yaml | 10 +++++ 5 files changed, 91 insertions(+), 2 deletions(-) diff --git a/charts/plane-enterprise/Chart.yaml b/charts/plane-enterprise/Chart.yaml index 6a85f88d..15374ca9 100644 --- a/charts/plane-enterprise/Chart.yaml +++ b/charts/plane-enterprise/Chart.yaml @@ -5,7 +5,7 @@ description: Meet Plane. An Enterprise software development tool to manage issue type: application -version: 3.3.0 +version: 3.4.0 appVersion: "3.1.1" home: https://plane.so/ diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 39bc2d2d..3bb0e382 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -89,8 +89,48 @@ The default value is `"traefik"`. If you are switching to a standard ingress con | `ingress.enabled` | `true` | Master switch — set to `false` to render neither template. | | `ingress.ingressClass` | `traefik` | Selects which template is active (see table above). | | `ingress.traefik.maxRequestBodyBytes` | `20971520` | Max request body size for Traefik's buffering middleware. Ignored when not using Traefik. | +| `ingress.traefik.entryPoints` | `[]` | Traefik entrypoints for the `IngressRoute`. Empty means derive from your SSL settings — see below. Ignored when not using Traefik. | | `ingress.ingress_annotations` | `{}` | Standard `Ingress` annotations. Ignored when `ingressClass` starts with `traefik`. | +### Running Traefik without TLS + +SSL is optional. When no certificate is configured — that is, `ssl.tls_secret_name` +is empty **and** `ssl.generateCerts`/`ssl.createIssuer` are `false` — the +`IngressRoute` is published on Traefik's plain-HTTP `web` entrypoint and no `tls:` +block is emitted, so Plane is reachable over `http://`. The rest of +the chart already follows the same rule: `APP_BASE_URL`, `PLANE_FRONTEND_URL`, +`EXPORT_DOWNLOAD_BASE_URL` and friends are rendered with an `http://` scheme in +this configuration. + +Configure a certificate by either route and the `IngressRoute` moves back to the +`websecure` entrypoint with its `tls:` block, unchanged from previous releases: + +```yaml +# Bring your own certificate... +ssl: + tls_secret_name: my-tls-secret + +# ...or have cert-manager mint one +ssl: + createIssuer: true + generateCerts: true + issuer: http + email: you@example.com +``` + +Override the entrypoint names only if your Traefik installation renamed the +defaults, or if you want to serve both schemes at once: + +```yaml +ingress: + traefik: + entryPoints: ['websecure', 'web'] +``` + +> Plain HTTP is fine for a quick trial, an internal network, or when TLS is +> terminated in front of Plane by a cloud load balancer, Cloudflare, or a service +> mesh. Terminate TLS somewhere before exposing Plane on the public internet. + ## Installing Plane 1. Open Terminal or any other command-line app that has access to Kubernetes tools on your local system. diff --git a/charts/plane-enterprise/templates/_helpers.tpl b/charts/plane-enterprise/templates/_helpers.tpl index de139b01..751ec5ab 100644 --- a/charts/plane-enterprise/templates/_helpers.tpl +++ b/charts/plane-enterprise/templates/_helpers.tpl @@ -292,3 +292,40 @@ reports its own service.name). Call with a dict and nindent, e.g. value: {{ .service | quote }} {{- end -}} {{- end -}} + +{{/* +Returns "true" when the chart has an actual TLS certificate to serve: either the +user pointed at their own Secret via ssl.tls_secret_name, or cert-manager is set +up to mint one (ssl.generateCerts + ssl.createIssuer, which is what gates +templates/certs/certs.yaml). + +This is the same condition templates/ingress.yaml uses to decide whether to emit +a `tls:` block, factored out so the Traefik path stays in step with it. Without +it, ingress-traefik.yaml would advertise a Secret that nothing ever creates. +*/}} +{{- define "plane.tlsEnabled" -}} + {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.generateCerts .Values.ssl.createIssuer) -}} + true + {{- end -}} +{{- end -}} + +{{/* +Traefik entrypoint names for the IngressRoute. + +Honours an explicit ingress.traefik.entryPoints override (some clusters rename +the defaults); otherwise derives them from whether TLS is configured, so an +install with SSL left off is reachable over plain HTTP instead of serving +Traefik's fallback self-signed certificate. +Caller must nindent to the correct depth. +*/}} +{{- define "plane.traefikEntryPoints" -}} + {{- with .Values.ingress.traefik.entryPoints -}} + {{- toYaml . -}} + {{- else -}} + {{- if eq (include "plane.tlsEnabled" .) "true" -}} +- websecure + {{- else -}} +- web + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/plane-enterprise/templates/ingress-traefik.yaml b/charts/plane-enterprise/templates/ingress-traefik.yaml index 3aac24cf..79e9e674 100644 --- a/charts/plane-enterprise/templates/ingress-traefik.yaml +++ b/charts/plane-enterprise/templates/ingress-traefik.yaml @@ -9,7 +9,7 @@ metadata: namespace: {{ .Release.Namespace }} spec: entryPoints: - - websecure + {{- include "plane.traefikEntryPoints" . | nindent 4 }} routes: @@ -117,7 +117,9 @@ spec: - name: {{ .Release.Name }}-web port: 3000 + {{- if eq (include "plane.tlsEnabled" .) "true" }} tls: secretName: {{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }} + {{- end }} {{- end }} diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index 56f03792..04646cfb 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -48,6 +48,16 @@ ingress: # ingress_annotations: { "nginx.ingress.kubernetes.io/proxy-body-size": "5m", "nginx.ingress.kubernetes.io/proxy-buffer-size": "16k" } traefik: maxRequestBodyBytes: 20971520 # in bytes (default: 20 MiB) + # Traefik entrypoints the IngressRoute attaches to. Leave empty to derive them + # from your SSL settings, which is what you want in almost every case: + # - TLS configured (ssl.tls_secret_name, or generateCerts + createIssuer) + # -> ['websecure'], and a `tls:` block is emitted. + # - No TLS configured -> ['web'], plain HTTP, and NO `tls:` block. Use this + # to reach Plane over http:// while you are still sorting out DNS/certs, + # or when TLS is terminated upstream (cloud LB, Cloudflare, a service mesh). + # Set explicitly only if your Traefik install renamed the default entrypoints, + # e.g. entryPoints: ['websecure', 'web'] or ['https']. + entryPoints: [] ssl: tls_secret_name: '' # If you have a custom TLS secret name From d1b55c3d4bcd32de5577238205e18be7b2391130 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Tue, 18 Aug 2026 19:58:44 +0530 Subject: [PATCH 2/7] fix(plane-enterprise): accept a scalar ingress.traefik.entryPoints toYaml on a bare string rendered a list-less mapping that the IngressRoute CRD rejects, so `--set ingress.traefik.entryPoints=websecure` (which yields a scalar, not a list) produced an invalid manifest. Wrap a string into a single-item list; list values are unchanged. Also pin the derive branch to $ rather than . for clarity inside the with/else. --- charts/plane-enterprise/templates/_helpers.tpl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/charts/plane-enterprise/templates/_helpers.tpl b/charts/plane-enterprise/templates/_helpers.tpl index 751ec5ab..cc75aa4a 100644 --- a/charts/plane-enterprise/templates/_helpers.tpl +++ b/charts/plane-enterprise/templates/_helpers.tpl @@ -316,13 +316,22 @@ Honours an explicit ingress.traefik.entryPoints override (some clusters rename the defaults); otherwise derives them from whether TLS is configured, so an install with SSL left off is reachable over plain HTTP instead of serving Traefik's fallback self-signed certificate. + +An empty value is the "derive it" sentinel, never a literal empty list -- the +CRD requires at least one entrypoint. A bare string is accepted and wrapped into +a single-item list, since `--set ingress.traefik.entryPoints=websecure` yields a +scalar and would otherwise render a list-less mapping the CRD rejects. Caller must nindent to the correct depth. */}} {{- define "plane.traefikEntryPoints" -}} {{- with .Values.ingress.traefik.entryPoints -}} - {{- toYaml . -}} + {{- if kindIs "string" . -}} + {{- toYaml (list .) -}} + {{- else -}} + {{- toYaml . -}} + {{- end -}} {{- else -}} - {{- if eq (include "plane.tlsEnabled" .) "true" -}} + {{- if eq (include "plane.tlsEnabled" $) "true" -}} - websecure {{- else -}} - web From 2506bcdbad0071943100e6632a173d31f698ba0c Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Tue, 18 Aug 2026 20:07:08 +0530 Subject: [PATCH 3/7] feat(plane-enterprise): add ssl.externalTermination for TLS terminated upstream The chart inferred "is this install HTTPS?" purely from its own certificate settings, so TLS terminated in front of Plane was invisible to it: a cloud load balancer, Cloudflare, a service mesh, or a Traefik entrypoint carrying its own cert (websecure.http.tls=true). Such installs had no way to say so -- they got http:// APP_BASE_URL / PLANE_FRONTEND_URL / PLANE_OAUTH_REDIRECT_URI / EXPORT_DOWNLOAD_BASE_URL while actually being served over https, breaking OAuth callbacks and export links, and after the previous commit their Traefik route would also drop to the plain-HTTP entrypoint. Split the single condition in two, because "traffic is HTTPS" and "this chart owns a Secret to reference" are different questions and only the second may gate a `tls:` block: plane.chartManagedCert = tls_secret_name OR (generateCerts AND createIssuer) plane.tlsEnabled = chartManagedCert OR ssl.externalTermination chartManagedCert gates the `tls:` blocks; tlsEnabled drives the Traefik entrypoint and the URL scheme, so the ingress and the app config can no longer disagree. Under externalTermination the route binds to websecure with NO `tls:` block -- Traefik serves whatever its entrypoint is configured with, and the chart never names a Secret it does not create. Also collapses the 9 copies of that condition in config-secrets/ onto plane.tlsEnabled; that duplication is what let the Traefik path drift in the first place. Verified against the plane-eks-dev fleet, where all 39 IngressRoutes use the cert-manager path and are unaffected. That cluster's Traefik also redirects web->websecure at the entrypoint level, which is exactly the configuration externalTermination exists to serve; the README now documents that trap. Rendering is byte-identical for every pre-existing configuration. --- charts/plane-enterprise/README.md | 45 +++++++++++++++++-- .../plane-enterprise/templates/_helpers.tpl | 32 ++++++++++--- .../templates/config-secrets/app-env.yaml | 4 +- .../templates/config-secrets/live-env.yaml | 4 +- .../templates/config-secrets/pi-api-env.yaml | 6 +-- .../templates/config-secrets/silo.yaml | 4 +- .../templates/ingress-traefik.yaml | 2 +- charts/plane-enterprise/values.yaml | 13 ++++++ 8 files changed, 90 insertions(+), 20 deletions(-) diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 3bb0e382..8daaf154 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -127,9 +127,48 @@ ingress: entryPoints: ['websecure', 'web'] ``` -> Plain HTTP is fine for a quick trial, an internal network, or when TLS is -> terminated in front of Plane by a cloud load balancer, Cloudflare, or a service -> mesh. Terminate TLS somewhere before exposing Plane on the public internet. +> Plain HTTP is fine for a quick trial or an internal network. Terminate TLS +> somewhere before exposing Plane on the public internet. + +**Check your Traefik entrypoints before relying on the HTTP default.** Many +installations redirect `web` to HTTPS in Traefik's own static config: + +``` +--entryPoints.web.http.redirections.entryPoint.to=:443 +--entryPoints.web.http.redirections.entryPoint.scheme=https +--entryPoints.websecure.http.tls=true +``` + +On such a cluster every plain-HTTP request is answered with a permanent redirect +before it ever reaches a route, so the `web` entrypoint cannot serve Plane. Either +drop the redirection, or use `ssl.externalTermination` below. + +### TLS terminated outside the chart + +Set `ssl.externalTermination: true` when something in front of Plane terminates +TLS and this chart manages no certificate — a cloud load balancer, Cloudflare, a +service mesh, or a Traefik entrypoint carrying its own certificate: + +```yaml +ssl: + externalTermination: true +``` + +The `IngressRoute` then binds to `websecure` and every self-referential URL given +to the app (`APP_BASE_URL`, `PLANE_FRONTEND_URL`, `PLANE_OAUTH_REDIRECT_URI`, +`EXPORT_DOWNLOAD_BASE_URL`, …) is rendered `https://`. No `tls:` block is emitted, +because there is no Secret for the chart to reference — Traefik serves whatever +certificate its entrypoint is configured with. + +Leave it `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts`; those +already imply HTTPS. Use it only for TLS this chart cannot see. + +| `ssl` configuration | Traefik entrypoint | `tls:` block | App URL scheme | +| --- | --- | --- | --- | +| nothing set | `web` | — | `http://` | +| `tls_secret_name` | `websecure` | your Secret | `https://` | +| `generateCerts` + `createIssuer` | `websecure` | `-ssl-cert` | `https://` | +| `externalTermination: true` | `websecure` | — | `https://` | ## Installing Plane diff --git a/charts/plane-enterprise/templates/_helpers.tpl b/charts/plane-enterprise/templates/_helpers.tpl index cc75aa4a..007be93c 100644 --- a/charts/plane-enterprise/templates/_helpers.tpl +++ b/charts/plane-enterprise/templates/_helpers.tpl @@ -294,21 +294,39 @@ reports its own service.name). Call with a dict and nindent, e.g. {{- end -}} {{/* -Returns "true" when the chart has an actual TLS certificate to serve: either the -user pointed at their own Secret via ssl.tls_secret_name, or cert-manager is set -up to mint one (ssl.generateCerts + ssl.createIssuer, which is what gates +Returns "true" when THIS CHART has a TLS Secret to point an ingress at: either +the user supplied one via ssl.tls_secret_name, or cert-manager is set up to mint +one (ssl.generateCerts + ssl.createIssuer, which is what gates templates/certs/certs.yaml). -This is the same condition templates/ingress.yaml uses to decide whether to emit -a `tls:` block, factored out so the Traefik path stays in step with it. Without -it, ingress-traefik.yaml would advertise a Secret that nothing ever creates. +Gates the `tls:` blocks. Never widen this to cover externally-terminated TLS -- +referencing a Secret that nothing creates is the bug this helper exists to stop. */}} -{{- define "plane.tlsEnabled" -}} +{{- define "plane.chartManagedCert" -}} {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.generateCerts .Values.ssl.createIssuer) -}} true {{- end -}} {{- end -}} +{{/* +Returns "true" when users reach Plane over https://, whoever terminates it. + +That is either a chart-managed certificate, or ssl.externalTermination for TLS +handled in front of Plane -- a cloud load balancer, Cloudflare, a service mesh, +or a Traefik entrypoint with its own certificate (`websecure.http.tls=true`). +The chart owns no Secret in that second case, so this must NOT be used to emit a +`tls:` block; use plane.chartManagedCert for that. + +Drives the ingress entrypoint and the scheme of every self-referential URL +handed to the app (APP_BASE_URL, PLANE_FRONTEND_URL, PLANE_OAUTH_REDIRECT_URI, +EXPORT_DOWNLOAD_BASE_URL, ...), so the two can never disagree. +*/}} +{{- define "plane.tlsEnabled" -}} + {{- if or (eq (include "plane.chartManagedCert" .) "true") .Values.ssl.externalTermination -}} + true + {{- end -}} +{{- end -}} + {{/* Traefik entrypoint names for the IngressRoute. diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index 7675020d..970de2bd 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -82,7 +82,7 @@ data: CELERY_BROKER_POOL_LIMIT: {{ .Values.env.celery_broker_pool_limit | quote }} {{- if .Values.env.web_url}} WEB_URL: {{ .Values.env.web_url | default "" | quote }} - {{- else if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- else if eq (include "plane.tlsEnabled" .) "true" }} WEB_URL: "https://{{ .Values.license.licenseDomain }}" {{- else }} WEB_URL: "http://{{ .Values.license.licenseDomain }}" @@ -90,7 +90,7 @@ data: LIVE_BASE_URL: "http://{{ .Release.Name }}-live.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:3000/" LIVE_BASE_PATH: "/live" - {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- if eq (include "plane.tlsEnabled" .) "true" }} PI_BASE_URL: "https://{{ .Values.license.licenseDomain }}" {{- else }} PI_BASE_URL: "http://{{ .Values.license.licenseDomain }}" diff --git a/charts/plane-enterprise/templates/config-secrets/live-env.yaml b/charts/plane-enterprise/templates/config-secrets/live-env.yaml index 5f249ce6..645a5796 100644 --- a/charts/plane-enterprise/templates/config-secrets/live-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/live-env.yaml @@ -36,7 +36,7 @@ data: LIVE_SENTRY_ENVIRONMENT: {{ .Values.env.live_sentry_environment | default "" | quote }} LIVE_SENTRY_TRACES_SAMPLE_RATE: {{ .Values.env.live_sentry_traces_sample_rate | default "" | quote }} LIVE_BASE_PATH: "/live" - {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- if eq (include "plane.tlsEnabled" .) "true" }} PI_BASE_URL: "https://{{ .Values.license.licenseDomain }}" {{- else }} PI_BASE_URL: "http://{{ .Values.license.licenseDomain }}" @@ -49,7 +49,7 @@ data: IFRAMELY_URL: http://{{ .Release.Name }}-iframely.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8061/ {{- end }} - {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- if eq (include "plane.tlsEnabled" .) "true" }} EXPORT_DOWNLOAD_BASE_URL: {{ .Values.env.export_download_base_url | default (printf "https://%s" .Values.license.licenseDomain) | quote }} {{- else }} EXPORT_DOWNLOAD_BASE_URL: {{ .Values.env.export_download_base_url | default (printf "http://%s" .Values.license.licenseDomain) | quote }} diff --git a/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml b/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml index 6a9fff9a..66670e98 100644 --- a/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml @@ -93,7 +93,7 @@ data: LIVE_BASE_URL: "http://{{ .Release.Name }}-live.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:3000/" LIVE_BASE_PATH: "/live" - {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- if eq (include "plane.tlsEnabled" .) "true" }} PLANE_FRONTEND_URL: "https://{{ .Values.license.licenseDomain }}" {{- else }} PLANE_FRONTEND_URL: "http://{{ .Values.license.licenseDomain }}" @@ -101,7 +101,7 @@ data: {{- if .Values.env.pi_envs.plane_api_host }} PLANE_API_HOST: {{ .Values.env.pi_envs.plane_api_host | quote }} - {{- else if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- else if eq (include "plane.tlsEnabled" .) "true" }} PLANE_API_HOST: "https://{{ .Values.license.licenseDomain }}" {{- else }} PLANE_API_HOST: "http://{{ .Values.license.licenseDomain }}" @@ -120,7 +120,7 @@ data: CORS_ALLOWED_ORIGINS: "http://{{ .Values.license.licenseDomain }},https://{{ .Values.license.licenseDomain }}" {{- end }} - {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- if eq (include "plane.tlsEnabled" .) "true" }} PLANE_OAUTH_REDIRECT_URI: "https://{{ .Values.license.licenseDomain }}/pi/api/v1/oauth/callback/" {{- else }} PLANE_OAUTH_REDIRECT_URI: "http://{{ .Values.license.licenseDomain }}/pi/api/v1/oauth/callback/" diff --git a/charts/plane-enterprise/templates/config-secrets/silo.yaml b/charts/plane-enterprise/templates/config-secrets/silo.yaml index 7fdfc50a..c4fa1b5d 100644 --- a/charts/plane-enterprise/templates/config-secrets/silo.yaml +++ b/charts/plane-enterprise/templates/config-secrets/silo.yaml @@ -95,7 +95,7 @@ data: CORS_ALLOWED_ORIGINS: "http://{{ .Values.license.licenseDomain }},https://{{ .Values.license.licenseDomain }}" {{- end }} - {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- if eq (include "plane.tlsEnabled" .) "true" }} APP_BASE_URL: "https://{{ .Values.license.licenseDomain }}" {{- else }} APP_BASE_URL: "http://{{ .Values.license.licenseDomain }}" @@ -104,7 +104,7 @@ data: API_BASE_URL: "http://{{ .Release.Name }}-api.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8000/" API_INTERNAL_BASE_URL: "http://{{ .Release.Name }}-api.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8000/" - {{- if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} + {{- if eq (include "plane.tlsEnabled" .) "true" }} SILO_API_BASE_URL: "https://{{ .Values.license.licenseDomain }}" {{- else }} SILO_API_BASE_URL: "http://{{ .Values.license.licenseDomain }}" diff --git a/charts/plane-enterprise/templates/ingress-traefik.yaml b/charts/plane-enterprise/templates/ingress-traefik.yaml index 79e9e674..2c75f218 100644 --- a/charts/plane-enterprise/templates/ingress-traefik.yaml +++ b/charts/plane-enterprise/templates/ingress-traefik.yaml @@ -117,7 +117,7 @@ spec: - name: {{ .Release.Name }}-web port: 3000 - {{- if eq (include "plane.tlsEnabled" .) "true" }} + {{- if eq (include "plane.chartManagedCert" .) "true" }} tls: secretName: {{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }} {{- end }} diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index 04646cfb..9dd0fb82 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -68,6 +68,19 @@ ssl: server: https://acme-v02.api.letsencrypt.org/directory email: plane@example.com generateCerts: false + # Set true when TLS is terminated IN FRONT of Plane and this chart manages no + # certificate of its own -- a cloud load balancer, Cloudflare, a service mesh, + # or a Traefik entrypoint that carries its own cert (websecure.http.tls=true). + # + # The chart then treats traffic as https://: the Traefik IngressRoute binds to + # the websecure entrypoint and every self-referential URL handed to the app + # (APP_BASE_URL, PLANE_FRONTEND_URL, PLANE_OAUTH_REDIRECT_URI, + # EXPORT_DOWNLOAD_BASE_URL, ...) is rendered with an https:// scheme. No `tls:` + # block is emitted, because there is no Secret for this chart to reference. + # + # Leave false if you set tls_secret_name or generateCerts -- those already + # imply https. This is only for TLS this chart cannot see. + externalTermination: false # ============================================================ # Pod Security (Kubernetes Pod Security Admission "restricted") From f234cb1b615ba9430ef9c708844d9812bb78841e Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Tue, 18 Aug 2026 21:08:02 +0530 Subject: [PATCH 4/7] docs(plane-enterprise): document the four TLS options and how to use them Restructures the Traefik TLS section around the decision a user actually makes. A single table maps each environment (no cert / own Secret / cert-manager / terminated upstream) to the entrypoint, the tls: block and the resulting app URL scheme, followed by a copy-pasteable recipe per option. Adds ingress.traefik.entryPoints, ingress.traefik.maxRequestBodyBytes and ssl.externalTermination to the Ingress and SSL Setup reference table, so they are discoverable where settings are looked up rather than only in prose. Also documents the entrypoint-redirection caveat (a Traefik that redirects web to HTTPS in its static config cannot serve the plain-HTTP option, with the kubectl command to check), spells out that createIssuer needs generateCerts to actually mint a certificate, and adds an upgrade note for 3.2.1 installs that relied on the old unconditional websecure binding. Corrects one overstatement: CORS_ALLOWED_ORIGINS always lists both schemes and is not scheme-switched, so it is called out as unaffected rather than listed among the derived URLs. --- charts/plane-enterprise/README.md | 157 ++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 43 deletions(-) diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 8daaf154..9efcf91d 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -92,46 +92,118 @@ The default value is `"traefik"`. If you are switching to a standard ingress con | `ingress.traefik.entryPoints` | `[]` | Traefik entrypoints for the `IngressRoute`. Empty means derive from your SSL settings — see below. Ignored when not using Traefik. | | `ingress.ingress_annotations` | `{}` | Standard `Ingress` annotations. Ignored when `ingressClass` starts with `traefik`. | -### Running Traefik without TLS +### TLS options: choosing how HTTPS is handled -SSL is optional. When no certificate is configured — that is, `ssl.tls_secret_name` -is empty **and** `ssl.generateCerts`/`ssl.createIssuer` are `false` — the -`IngressRoute` is published on Traefik's plain-HTTP `web` entrypoint and no `tls:` -block is emitted, so Plane is reachable over `http://`. The rest of -the chart already follows the same rule: `APP_BASE_URL`, `PLANE_FRONTEND_URL`, -`EXPORT_DOWNLOAD_BASE_URL` and friends are rendered with an `http://` scheme in -this configuration. +TLS is **optional**. From your `ssl.*` settings the chart derives two things at +once, so they can never disagree: -Configure a certificate by either route and the `IngressRoute` moves back to the -`websecure` entrypoint with its `tls:` block, unchanged from previous releases: +1. which Traefik entrypoint the `IngressRoute` binds to, and whether a `tls:` + block is emitted; +2. the scheme of every URL Plane is told about itself — `WEB_URL`, + `APP_BASE_URL`, `PI_BASE_URL`, `PLANE_FRONTEND_URL`, `PLANE_API_HOST`, + `PLANE_OAUTH_REDIRECT_URI`, `SILO_API_BASE_URL`, `EXPORT_DOWNLOAD_BASE_URL`. + (`CORS_ALLOWED_ORIGINS` always lists both schemes and is unaffected.) + +Find the row that matches your environment: + +| Your setup | Set | Entrypoint | `tls:` block | App URLs | +| --- | --- | :---: | :---: | :---: | +| No certificate yet — trial, internal network | *nothing* (default) | `web` | — | `http://` | +| You already hold a TLS Secret | `ssl.tls_secret_name` | `websecure` | your Secret | `https://` | +| Let cert-manager issue one | `ssl.createIssuer` + `ssl.generateCerts` | `websecure` | `-ssl-cert` | `https://` | +| TLS terminated in front of Plane | `ssl.externalTermination: true` | `websecure` | — | `https://` | + +Only the `tls:` block requires a Secret this chart can actually see, which is why +the last row emits none — the chart never names a Secret it does not create. + +#### Option 1 — No TLS, plain HTTP + +The default. Nothing to set; leave the `ssl` block alone and Plane is reachable at +`http://`: + +```yaml +license: + licenseDomain: plane.example.com +ingress: + ingressClass: traefik +``` + +Good for a quick trial, an air-gapped or internal network, or while you are still +sorting out DNS and certificates. **Read the entrypoint caveat below before +relying on it** — and terminate TLS somewhere before exposing Plane on the public +internet. + +#### Option 2 — Bring your own certificate + +Create a `kubernetes.io/tls` Secret in the release namespace and name it: + +```bash +kubectl create secret tls my-tls-secret \ + --cert=fullchain.pem --key=privkey.pem -n plane-ns +``` ```yaml -# Bring your own certificate... ssl: tls_secret_name: my-tls-secret +``` -# ...or have cert-manager mint one +#### Option 3 — Let cert-manager issue the certificate + +Requires cert-manager in the cluster. **Both** flags are needed — `createIssuer` +alone creates an Issuer but no Certificate, and the chart then treats the install +as having no certificate at all: + +```yaml ssl: createIssuer: true generateCerts: true - issuer: http + issuer: http # or cloudflare / digitalocean email: you@example.com + # token: # required for cloudflare / digitalocean ``` -Override the entrypoint names only if your Traefik installation renamed the -defaults, or if you want to serve both schemes at once: +The Certificate is written to `-ssl-cert` and the `IngressRoute` +references it. + +#### Option 4 — TLS terminated in front of Plane + +Use this when something ahead of Plane already terminates TLS and this chart +manages no certificate: a cloud load balancer, Cloudflare, a service mesh, or a +Traefik entrypoint carrying its own certificate (`websecure.http.tls=true`). + +```yaml +ssl: + externalTermination: true +``` + +The `IngressRoute` binds to `websecure` and all app URLs are rendered `https://`, +but no `tls:` block is emitted — Traefik serves whatever certificate its +entrypoint is configured with. + +Leave it `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts`; those +already imply HTTPS. Use it *only* for TLS this chart cannot see. Without it, such +an install would advertise `http://` URLs to itself while being served over HTTPS, +breaking OAuth callbacks and export download links. + +#### Overriding the entrypoint names + +Only needed if your Traefik installation renamed the default `web` / `websecure` +entrypoints, or you want to serve both schemes at once: ```yaml ingress: traefik: - entryPoints: ['websecure', 'web'] + entryPoints: ['websecure', 'web'] # a bare string also works ``` -> Plain HTTP is fine for a quick trial or an internal network. Terminate TLS -> somewhere before exposing Plane on the public internet. +Leave it empty (the default) to derive the entrypoint from the table above. This +setting controls the entrypoint *only* — whether a `tls:` block is emitted still +follows your `ssl.*` configuration. -**Check your Traefik entrypoints before relying on the HTTP default.** Many -installations redirect `web` to HTTPS in Traefik's own static config: +#### Caveat: check your Traefik entrypoints before relying on plain HTTP + +Many Traefik installations redirect `web` to HTTPS in Traefik's own static +configuration: ``` --entryPoints.web.http.redirections.entryPoint.to=:443 @@ -139,37 +211,33 @@ installations redirect `web` to HTTPS in Traefik's own static config: --entryPoints.websecure.http.tls=true ``` -On such a cluster every plain-HTTP request is answered with a permanent redirect -before it ever reaches a route, so the `web` entrypoint cannot serve Plane. Either -drop the redirection, or use `ssl.externalTermination` below. +Check yours with: + +```bash +kubectl get deploy -n traefik \ + -o jsonpath='{.spec.template.spec.containers[0].args}' | tr ',' '\n' | grep -i redirect +``` + +If the redirection is present, every plain-HTTP request is answered with a +permanent redirect *before* it reaches a route, so Option 1 cannot serve Plane on +that cluster. Either drop the redirection, or use Option 2/3/4. -### TLS terminated outside the chart +#### Upgrading from 3.2.1 or earlier -Set `ssl.externalTermination: true` when something in front of Plane terminates -TLS and this chart manages no certificate — a cloud load balancer, Cloudflare, a -service mesh, or a Traefik entrypoint carrying its own certificate: +If you configure TLS through `ssl.tls_secret_name` or `ssl.generateCerts` + +`ssl.createIssuer`, the rendered ingress is unchanged and no action is needed. + +One case needs a value added. Earlier releases always bound the Traefik +`IngressRoute` to `websecure` and always emitted a `tls:` block, even when no +certificate was configured — pointing at a `-ssl-cert` Secret that was +never created, so Traefik fell back to its built-in self-signed certificate. If +you relied on that, or on TLS terminated at Traefik itself, adopt Option 4: ```yaml ssl: externalTermination: true ``` -The `IngressRoute` then binds to `websecure` and every self-referential URL given -to the app (`APP_BASE_URL`, `PLANE_FRONTEND_URL`, `PLANE_OAUTH_REDIRECT_URI`, -`EXPORT_DOWNLOAD_BASE_URL`, …) is rendered `https://`. No `tls:` block is emitted, -because there is no Secret for the chart to reference — Traefik serves whatever -certificate its entrypoint is configured with. - -Leave it `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts`; those -already imply HTTPS. Use it only for TLS this chart cannot see. - -| `ssl` configuration | Traefik entrypoint | `tls:` block | App URL scheme | -| --- | --- | --- | --- | -| nothing set | `web` | — | `http://` | -| `tls_secret_name` | `websecure` | your Secret | `https://` | -| `generateCerts` + `createIssuer` | `websecure` | `-ssl-cert` | `https://` | -| `externalTermination: true` | `websecure` | — | `https://` | - ## Installing Plane 1. Open Terminal or any other command-line app that has access to Kubernetes tools on your local system. @@ -949,6 +1017,8 @@ Note: When the email service is enabled, the cert-issuer will be automatically c | ingress.rabbitmqHost | | | Based on above configuration, if you want to expose the `rabbitmq` web console to set of users, use this key to set the `host` mapping or leave it as `EMPTY` to not expose interface. | | ingress.ingressClass | nginx | Yes | Kubernetes cluster setup comes with various options of `ingressClass`. Based on your setup, set this value to the right one (eg. nginx, traefik, etc). Leave it to default in case you are using external ingress provider. | | ingress.ingress_annotations | `{ "nginx.ingress.kubernetes.io/proxy-body-size": "5m" }` | | Ingress controllers comes with various configuration options which can be passed as annotations. Setting this value lets you change the default value to user required. | +| ingress.traefik.entryPoints | `[]` | | Traefik entrypoints the `IngressRoute` binds to. Leave empty to derive them from your `ssl.*` settings (`websecure` when TLS is configured, otherwise `web`). Set explicitly only if your Traefik renamed the default entrypoints, e.g. `['websecure','web']`. Ignored unless `ingressClass` starts with `traefik` | +| ingress.traefik.maxRequestBodyBytes | 20971520 | | Max request body size in bytes for Traefik's buffering middleware (upload size limit). Ignored unless `ingressClass` starts with `traefik` | | ssl.createIssuer | false | | Kubernets cluster setup supports creating `issuer` type resource. After deployment, this is step towards creating secure access to the ingress url. Issuer is required for you generate SSL certifiate. Kubernetes can be configured to use any of the certificate authority to generate SSL (depending on CertManager configuration). Set it to `true` to create the issuer. Applicable only when `ingress.enabled=true` | | ssl.issuer | http | | CertManager configuration allows user to create issuers using `http` or any of the other DNS Providers like `cloudflare`, `digitalocean`, etc. As of now Plane supports `http`, `cloudflare`, `digitalocean` | | ssl.token | | | To create issuers using DNS challenge, set the issuer api token of dns provider like cloudflare`or`digitalocean`(not required for http) | @@ -956,6 +1026,7 @@ Note: When the email service is enabled, the cert-issuer will be automatically c | ssl.email | | | Certificate generation authority needs a valid email id before generating certificate. Required when `ssl.createIssuer=true` | | ssl.generateCerts | false | | After creating the issuers, user can still not create the certificate untill sure of configuration. Setting this to `true` will try to generate SSL certificate and associate with ingress. Applicable only when `ingress.enabled=true` and `ssl.createIssuer=true` | | ssl.tls_secret_name | | | If you have a custom TLS secret name, set this to the name of the secret. Applicable only when `ingress.enabled=true` and `ssl.createIssuer=false` | +| ssl.externalTermination | false | | Set to `true` when TLS is terminated in front of Plane and this chart manages no certificate (cloud load balancer, Cloudflare, service mesh, or a Traefik entrypoint carrying its own cert). The Traefik `IngressRoute` binds to `websecure` and all app URLs are rendered `https://`, but no `tls:` block is emitted. Leave `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts`. See [TLS options](#tls-options-choosing-how-https-is-handled) | ### Common Environment Settings From a3495825ed2ee08a5d3be50e612abe199149e098 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Thu, 20 Aug 2026 11:58:47 +0530 Subject: [PATCH 5/7] docs(plane-enterprise): correct the upgrade note to 3.3.0 or earlier --- charts/plane-enterprise/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 9efcf91d..299b6598 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -222,7 +222,7 @@ If the redirection is present, every plain-HTTP request is answered with a permanent redirect *before* it reaches a route, so Option 1 cannot serve Plane on that cluster. Either drop the redirection, or use Option 2/3/4. -#### Upgrading from 3.2.1 or earlier +#### Upgrading from 3.3.0 or earlier If you configure TLS through `ssl.tls_secret_name` or `ssl.generateCerts` + `ssl.createIssuer`, the rendered ingress is unchanged and no action is needed. From d9fd18bcd8f410ca3f653cc0328f871d638ccf90 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Thu, 20 Aug 2026 13:09:42 +0530 Subject: [PATCH 6/7] fix(plane-enterprise): stop ssl.externalTermination from moving the entrypoint ssl.externalTermination drove both the URL scheme and the Traefik entrypoint, which conflates two facts that disagree in the most common topology it exists to serve. An upstream terminator -- an ALB with an ACM cert, an NLB with a TLS listener, Cloudflare -- terminates TLS and forwards *cleartext* to the cluster. That arrives on Traefik's `web` entrypoint, but the route was attached only to `websecure`, so nothing matched it and requests 404'd. Only the narrower case of Traefik's own entrypoint carrying a certificate wants `websecure`. Key the entrypoint on plane.chartManagedCert instead, so it follows whether THIS CHART terminates TLS, and leave plane.tlsEnabled driving the URL scheme alone. ingress.traefik.entryPoints selects `websecure` for the Traefik-terminated case, which is what that override is for. Each setting now controls exactly one thing: chartManagedCert -> `tls:` block + entrypoint tlsEnabled -> https:// scheme in self-referential app URLs entryPoints -> entrypoint override Documents both sub-cases as Option 4a/4b, and notes that getting them wrong is a routing failure rather than a certificate one. Also adds the missing language to a fenced block (markdownlint MD040). Co-Authored-By: Claude Opus 5 (1M context) --- charts/plane-enterprise/README.md | 67 ++++++++++++++----- .../plane-enterprise/templates/_helpers.tpl | 26 +++++-- charts/plane-enterprise/values.yaml | 14 ++-- 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 299b6598..bc0786c1 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -94,11 +94,12 @@ The default value is `"traefik"`. If you are switching to a standard ingress con ### TLS options: choosing how HTTPS is handled -TLS is **optional**. From your `ssl.*` settings the chart derives two things at -once, so they can never disagree: +TLS is **optional**. Your `ssl.*` settings drive two *separate* derivations — +separate because "users are on HTTPS" and "this chart holds the certificate" are +different facts: -1. which Traefik entrypoint the `IngressRoute` binds to, and whether a `tls:` - block is emitted; +1. whether a `tls:` block is emitted, and which Traefik entrypoint the + `IngressRoute` binds to — both from whether **this chart** terminates TLS; 2. the scheme of every URL Plane is told about itself — `WEB_URL`, `APP_BASE_URL`, `PI_BASE_URL`, `PLANE_FRONTEND_URL`, `PLANE_API_HOST`, `PLANE_OAUTH_REDIRECT_URI`, `SILO_API_BASE_URL`, `EXPORT_DOWNLOAD_BASE_URL`. @@ -111,10 +112,17 @@ Find the row that matches your environment: | No certificate yet — trial, internal network | *nothing* (default) | `web` | — | `http://` | | You already hold a TLS Secret | `ssl.tls_secret_name` | `websecure` | your Secret | `https://` | | Let cert-manager issue one | `ssl.createIssuer` + `ssl.generateCerts` | `websecure` | `-ssl-cert` | `https://` | -| TLS terminated in front of Plane | `ssl.externalTermination: true` | `websecure` | — | `https://` | +| TLS terminated upstream (ALB, NLB TLS listener, Cloudflare) | `ssl.externalTermination: true` | `web` | — | `https://` | +| TLS terminated by Traefik's own entrypoint | `ssl.externalTermination: true` + `ingress.traefik.entryPoints: ['websecure']` | `websecure` | — | `https://` | Only the `tls:` block requires a Secret this chart can actually see, which is why -the last row emits none — the chart never names a Secret it does not create. +the last two rows emit none — the chart never names a Secret it does not create. + +Note the last two rows share a scheme but need **opposite entrypoints**: an +upstream terminator forwards cleartext, which arrives on `web`, whereas a Traefik +entrypoint carrying its own certificate serves TLS on `websecure`. That is why +`ssl.externalTermination` sets the URL scheme only and never moves the +entrypoint. #### Option 1 — No TLS, plain HTTP @@ -168,22 +176,40 @@ references it. #### Option 4 — TLS terminated in front of Plane Use this when something ahead of Plane already terminates TLS and this chart -manages no certificate: a cloud load balancer, Cloudflare, a service mesh, or a -Traefik entrypoint carrying its own certificate (`websecure.http.tls=true`). +manages no certificate. `ssl.externalTermination` renders every app URL +`https://` and emits no `tls:` block. It does **not** move the entrypoint, so +pick the sub-case that matches where TLS actually ends. + +**4a — an upstream terminator forwards cleartext** (ALB with an ACM cert, NLB +with a TLS listener, Cloudflare, most service meshes). Traffic reaches Traefik as +plain HTTP, so the route stays on `web` — the default: ```yaml ssl: externalTermination: true ``` -The `IngressRoute` binds to `websecure` and all app URLs are rendered `https://`, -but no `tls:` block is emitted — Traefik serves whatever certificate its -entrypoint is configured with. +**4b — Traefik's own entrypoint terminates TLS** (`websecure.http.tls=true`, an +ACME `certResolver`, or a default `TLSStore`). Traffic reaches Traefik as TLS, so +the route must bind `websecure` as well: -Leave it `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts`; those -already imply HTTPS. Use it *only* for TLS this chart cannot see. Without it, such -an install would advertise `http://` URLs to itself while being served over HTTPS, -breaking OAuth callbacks and export download links. +```yaml +ssl: + externalTermination: true +ingress: + traefik: + entryPoints: ['websecure'] +``` + +Getting the sub-case wrong is a routing failure, not a certificate failure: a +route bound only to `websecure` never matches cleartext arriving on `web`, so +requests 404 instead of reaching Plane. + +Leave `externalTermination` `false` if you set `ssl.tls_secret_name` or +`ssl.generateCerts`; those already imply HTTPS. Use it *only* for TLS this chart +cannot see. Without it, such an install would advertise `http://` URLs to itself +while being served over HTTPS, breaking OAuth callbacks and export download +links. #### Overriding the entrypoint names @@ -198,14 +224,15 @@ ingress: Leave it empty (the default) to derive the entrypoint from the table above. This setting controls the entrypoint *only* — whether a `tls:` block is emitted still -follows your `ssl.*` configuration. +follows your `ssl.*` configuration. It is also how you select `websecure` for +option 4b, where TLS ends at Traefik itself. #### Caveat: check your Traefik entrypoints before relying on plain HTTP Many Traefik installations redirect `web` to HTTPS in Traefik's own static configuration: -``` +```text --entryPoints.web.http.redirections.entryPoint.to=:443 --entryPoints.web.http.redirections.entryPoint.scheme=https --entryPoints.websecure.http.tls=true @@ -231,11 +258,15 @@ One case needs a value added. Earlier releases always bound the Traefik `IngressRoute` to `websecure` and always emitted a `tls:` block, even when no certificate was configured — pointing at a `-ssl-cert` Secret that was never created, so Traefik fell back to its built-in self-signed certificate. If -you relied on that, or on TLS terminated at Traefik itself, adopt Option 4: +you relied on that, or on TLS terminated at Traefik itself, adopt Option 4b — +both settings, since `externalTermination` alone leaves the route on `web`: ```yaml ssl: externalTermination: true +ingress: + traefik: + entryPoints: ['websecure'] ``` ## Installing Plane diff --git a/charts/plane-enterprise/templates/_helpers.tpl b/charts/plane-enterprise/templates/_helpers.tpl index 007be93c..29eca847 100644 --- a/charts/plane-enterprise/templates/_helpers.tpl +++ b/charts/plane-enterprise/templates/_helpers.tpl @@ -317,9 +317,16 @@ or a Traefik entrypoint with its own certificate (`websecure.http.tls=true`). The chart owns no Secret in that second case, so this must NOT be used to emit a `tls:` block; use plane.chartManagedCert for that. -Drives the ingress entrypoint and the scheme of every self-referential URL -handed to the app (APP_BASE_URL, PLANE_FRONTEND_URL, PLANE_OAUTH_REDIRECT_URI, -EXPORT_DOWNLOAD_BASE_URL, ...), so the two can never disagree. +Drives ONLY the scheme of every self-referential URL handed to the app +(APP_BASE_URL, PLANE_FRONTEND_URL, PLANE_OAUTH_REDIRECT_URI, +EXPORT_DOWNLOAD_BASE_URL, ...). + +Deliberately NOT the Traefik entrypoint. "Users are on https" says nothing about +which entrypoint traffic arrives on: an upstream terminator (ALB, NLB TLS +listener, Cloudflare) forwards cleartext, which lands on `web`, while a Traefik +entrypoint carrying its own certificate lands on `websecure`. Those need +opposite entrypoints from the same value, so the entrypoint derives from +plane.chartManagedCert instead and ingress.traefik.entryPoints overrides it. */}} {{- define "plane.tlsEnabled" -}} {{- if or (eq (include "plane.chartManagedCert" .) "true") .Values.ssl.externalTermination -}} @@ -331,9 +338,14 @@ EXPORT_DOWNLOAD_BASE_URL, ...), so the two can never disagree. Traefik entrypoint names for the IngressRoute. Honours an explicit ingress.traefik.entryPoints override (some clusters rename -the defaults); otherwise derives them from whether TLS is configured, so an -install with SSL left off is reachable over plain HTTP instead of serving -Traefik's fallback self-signed certificate. +the defaults, and it is the way to select `websecure` when Traefik's own +entrypoint terminates TLS); otherwise derives them from whether THIS CHART +terminates TLS, so an install with SSL left off is reachable over plain HTTP +instead of serving Traefik's fallback self-signed certificate. + +Keyed on plane.chartManagedCert, NOT plane.tlsEnabled: with TLS terminated +upstream the chart must still bind `web`, because the terminator forwards +cleartext and a route attached only to `websecure` would never match it. An empty value is the "derive it" sentinel, never a literal empty list -- the CRD requires at least one entrypoint. A bare string is accepted and wrapped into @@ -349,7 +361,7 @@ Caller must nindent to the correct depth. {{- toYaml . -}} {{- end -}} {{- else -}} - {{- if eq (include "plane.tlsEnabled" $) "true" -}} + {{- if eq (include "plane.chartManagedCert" $) "true" -}} - websecure {{- else -}} - web diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index 9dd0fb82..779d6d67 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -72,11 +72,17 @@ ssl: # certificate of its own -- a cloud load balancer, Cloudflare, a service mesh, # or a Traefik entrypoint that carries its own cert (websecure.http.tls=true). # - # The chart then treats traffic as https://: the Traefik IngressRoute binds to - # the websecure entrypoint and every self-referential URL handed to the app + # The chart then renders every self-referential URL handed to the app # (APP_BASE_URL, PLANE_FRONTEND_URL, PLANE_OAUTH_REDIRECT_URI, - # EXPORT_DOWNLOAD_BASE_URL, ...) is rendered with an https:// scheme. No `tls:` - # block is emitted, because there is no Secret for this chart to reference. + # EXPORT_DOWNLOAD_BASE_URL, ...) with an https:// scheme. No `tls:` block is + # emitted, because there is no Secret for this chart to reference. + # + # This does NOT change the Traefik entrypoint. An upstream terminator (ALB, + # NLB TLS listener, Cloudflare) forwards cleartext, which arrives on `web` -- + # the default. If TLS is instead terminated by Traefik's own entrypoint + # (websecure.http.tls=true), also set: + # ingress.traefik.entryPoints: ['websecure'] + # otherwise the route binds `web` and never matches the TLS traffic. # # Leave false if you set tls_secret_name or generateCerts -- those already # imply https. This is only for TLS this chart cannot see. From aaf938b2bf808626ed346075a651308416352063 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Thu, 20 Aug 2026 13:40:26 +0530 Subject: [PATCH 7/7] docs(plane-enterprise): correct externalTermination reference row; expose new TLS values in questions.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ssl.externalTermination row in the README settings table still described the pre-d9fd18b behavior ("binds to websecure"); the entrypoint actually stays web unless ingress.traefik.entryPoints is set, as the TLS-options table above it already says. Also surface ssl.externalTermination and ingress.traefik.entryPoints in questions.yml so Rancher UI installs can discover them; the entrypoints helper already accepts a bare string, which is what the UI field yields. Docs/UI only — rendered templates verified byte-identical across all five TLS options (timestamps normalized). Co-Authored-By: Claude Fable 5 --- charts/plane-enterprise/README.md | 2 +- charts/plane-enterprise/questions.yml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index bc0786c1..8b850d49 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -1057,7 +1057,7 @@ Note: When the email service is enabled, the cert-issuer will be automatically c | ssl.email | | | Certificate generation authority needs a valid email id before generating certificate. Required when `ssl.createIssuer=true` | | ssl.generateCerts | false | | After creating the issuers, user can still not create the certificate untill sure of configuration. Setting this to `true` will try to generate SSL certificate and associate with ingress. Applicable only when `ingress.enabled=true` and `ssl.createIssuer=true` | | ssl.tls_secret_name | | | If you have a custom TLS secret name, set this to the name of the secret. Applicable only when `ingress.enabled=true` and `ssl.createIssuer=false` | -| ssl.externalTermination | false | | Set to `true` when TLS is terminated in front of Plane and this chart manages no certificate (cloud load balancer, Cloudflare, service mesh, or a Traefik entrypoint carrying its own cert). The Traefik `IngressRoute` binds to `websecure` and all app URLs are rendered `https://`, but no `tls:` block is emitted. Leave `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts`. See [TLS options](#tls-options-choosing-how-https-is-handled) | +| ssl.externalTermination | false | | Set to `true` when TLS is terminated in front of Plane and this chart manages no certificate (cloud load balancer, Cloudflare, service mesh, or a Traefik entrypoint carrying its own cert). All app URLs are rendered `https://`; no `tls:` block is emitted and the Traefik entrypoint is unchanged (stays `web` unless you also set `ingress.traefik.entryPoints: ['websecure']` — see Option 4b). Leave `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts`. See [TLS options](#tls-options-choosing-how-https-is-handled) | ### Common Environment Settings diff --git a/charts/plane-enterprise/questions.yml b/charts/plane-enterprise/questions.yml index 30e436a2..df80c714 100644 --- a/charts/plane-enterprise/questions.yml +++ b/charts/plane-enterprise/questions.yml @@ -1753,6 +1753,22 @@ questions: group: "Ingress" show_if: "ssl.createIssuer=false" +- variable: ssl.externalTermination + label: "TLS Terminated in Front of Plane" + description: "Enable when TLS is terminated before traffic reaches Plane (cloud load balancer, Cloudflare, service mesh, or a Traefik entrypoint carrying its own cert) and this chart manages no certificate. App URLs are rendered https:// but no tls block is emitted. Leave off if you set a TLS secret name or generate certificates. See the chart README's 'TLS options' section." + type: boolean + default: false + group: "Ingress" + show_if: "ingress.enabled=true" + +- variable: ingress.traefik.entryPoints + label: "Traefik Entrypoints Override" + description: "Traefik entrypoints the IngressRoute binds to, e.g. 'websecure'. Leave empty to derive from the SSL settings (websecure when this chart manages a certificate, otherwise web). Required as 'websecure' when TLS is terminated by Traefik's own entrypoint. Ignored unless the ingress class is traefik." + type: string + default: "" + group: "Ingress" + show_if: "ingress.enabled=true" + - variable: external_secrets.rabbitmq_existingSecret label: "RabbitMQ Secrets File Name" type: string