diff --git a/charts/plane-ce/Chart.yaml b/charts/plane-ce/Chart.yaml index 5d45ab7f..f173b2df 100644 --- a/charts/plane-ce/Chart.yaml +++ b/charts/plane-ce/Chart.yaml @@ -5,7 +5,7 @@ description: Meet Plane. An open-source software development tool to manage issu type: application -version: 1.6.2 +version: 1.6.3 appVersion: "1.4.1" home: https://plane.so diff --git a/charts/plane-ce/README.md b/charts/plane-ce/README.md index c72d5ebf..a71ba2d8 100644 --- a/charts/plane-ce/README.md +++ b/charts/plane-ce/README.md @@ -156,6 +156,135 @@ The default value is `"traefik"`. If you previously relied on the implicit defau | `ingress.ingressClass` | `traefik` | Selects which template is active (see table above). | | `ingress.traefik.*` | see values | Traefik-only settings (middleware body limit). Ignored when using the standard `Ingress`. | | `ingress.ingress_annotations` | `{}` | Standard `Ingress` annotations. Ignored when `ingressClass` starts with `traefik`. | +| `ingress.traefik.entryPoints` | `[]` | Traefik entrypoints for the `IngressRoute`s. Empty derives them from your SSL settings — see below. | +| `ssl.externalTermination` | `false` | Declare that TLS is terminated in front of Plane — see below. | + +### TLS options: choosing how HTTPS is handled + +TLS is **optional**. Your `ssl.*` settings decide which Traefik entrypoint the +`IngressRoute`s bind to and whether a `tls:` block is emitted. Find the row that +matches your environment: + +| Your setup | Set | Entrypoint | `tls:` block | +| --- | --- | :---: | :---: | +| No certificate yet — trial, internal network | *nothing* (default) | `web` | — | +| You already hold a TLS Secret | `ssl.tls_secret_name` | `websecure` | your Secret | +| Let cert-manager issue one | `ssl.createIssuer` + `ssl.generateCerts` | `websecure` | `-ssl-cert` | +| TLS terminated in front of Plane | `ssl.externalTermination: true` | `websecure` | — | + +Only the `tls:` block needs 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. All three +`IngressRoute`s (app, MinIO, RabbitMQ) follow the same rule. + +#### Option 1 — No TLS, plain HTTP + +The default. Leave the `ssl` block alone and Plane is reachable at +`http://`: + +```yaml +ingress: + appHost: plane.example.com + ingressClass: traefik +``` + +Good for a quick trial, an 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 + +```bash +kubectl create secret tls my-tls-secret \ + --cert=fullchain.pem --key=privkey.pem -n plane-ns +``` + +```yaml +ssl: + tls_secret_name: my-tls-secret +``` + +#### 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 # or cloudflare / digitalocean + email: you@example.com + # token: # required for cloudflare / digitalocean +``` + +#### 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`s bind to `websecure`, 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. + +#### 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"] # a bare string also works +``` + +Leave it empty (the default) to derive the entrypoint from the table above. This +controls the entrypoint *only* — whether a `tls:` block is emitted still follows +your `ssl.*` configuration. + +#### 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 +--entryPoints.web.http.redirections.entryPoint.scheme=https +--entryPoints.websecure.http.tls=true +``` + +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. + +#### Upgrading from 1.6.2 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. + +Earlier releases always bound the Traefik `IngressRoute`s to `websecure` and always +emitted a `tls:` block, even with no certificate 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 +``` ## Configuration Settings Available @@ -390,6 +519,7 @@ The default value is `"traefik"`. If you previously relied on the implicit defau | ingress.ingressClass | traefik | Yes | Set to `traefik` (or a name starting with `traefik`) to use native Traefik `IngressRoute` CRDs. Set to `nginx` (or any other class) to use a standard `networking.k8s.io/v1 Ingress` resource. | | ingress.ingress_annotations | `{ "nginx.ingress.kubernetes.io/proxy-body-size": "5m" }` | | Annotations applied to the standard `Ingress` resource. **Only used when `ingressClass` is not `traefik`.** When Traefik is selected, use `ingress.traefik.maxRequestBodyBytes` to control request body size instead. | | ingress.traefik.maxRequestBodyBytes | `5242880` | | Maximum allowed request body size in bytes for Traefik's buffering middleware (default: 5 MiB). Only used when `ingressClass` starts with `traefik`. | +| ingress.traefik.entryPoints | `[]` | | Traefik entrypoints the `IngressRoute`s bind 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"]`. Only used when `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) | @@ -397,6 +527,7 @@ The default value is `"traefik"`. If you previously relied on the implicit defau | 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`s bind to `websecure` but no `tls:` block is emitted. Leave `false` if you set `ssl.tls_secret_name` or `ssl.generateCerts` | #### Using Traefik as the ingress controller diff --git a/charts/plane-ce/templates/_helpers.tpl b/charts/plane-ce/templates/_helpers.tpl index 9d81287f..6a6dfe01 100644 --- a/charts/plane-ce/templates/_helpers.tpl +++ b/charts/plane-ce/templates/_helpers.tpl @@ -55,4 +55,63 @@ Call with a dict carrying the root context and the component values: {{- with .values.annotations }} annotations: {{ toYaml . | nindent 4 }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} +{{/* +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). + +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.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. +*/}} +{{- define "plane.tlsEnabled" -}} + {{- if or (eq (include "plane.chartManagedCert" .) "true") .Values.ssl.externalTermination -}} + true + {{- end -}} +{{- end -}} + +{{/* +Traefik entrypoint names for the IngressRoutes. + +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 -}} + {{- if kindIs "string" . -}} + {{- toYaml (list .) -}} + {{- else -}} + {{- toYaml . -}} + {{- end -}} + {{- else -}} + {{- if eq (include "plane.tlsEnabled" $) "true" -}} +- websecure + {{- else -}} +- web + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/plane-ce/templates/ingress-traefik.yaml b/charts/plane-ce/templates/ingress-traefik.yaml index 6382f647..30a99a05 100644 --- a/charts/plane-ce/templates/ingress-traefik.yaml +++ b/charts/plane-ce/templates/ingress-traefik.yaml @@ -9,7 +9,7 @@ metadata: namespace: {{ .Release.Namespace }} spec: entryPoints: - - websecure + {{- include "plane.traefikEntryPoints" . | nindent 4 }} routes: @@ -74,8 +74,10 @@ spec: - name: {{ .Release.Name }}-web port: 3000 + {{- if eq (include "plane.chartManagedCert" .) "true" }} tls: secretName: {{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }} + {{- end }} {{- end }} @@ -90,7 +92,7 @@ metadata: namespace: {{ .Release.Namespace }} spec: entryPoints: - - websecure + {{- include "plane.traefikEntryPoints" . | nindent 4 }} routes: - match: Host(`{{ .Values.ingress.minioHost }}`) kind: Rule @@ -99,8 +101,10 @@ spec: services: - name: {{ .Release.Name }}-minio port: 9090 + {{- if eq (include "plane.chartManagedCert" .) "true" }} tls: secretName: {{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }} + {{- end }} {{- end }} {{- if and .Values.ingress.enabled (hasPrefix "traefik" .Values.ingress.ingressClass) .Values.rabbitmq.local_setup .Values.ingress.rabbitmqHost }} @@ -114,7 +118,7 @@ metadata: namespace: {{ .Release.Namespace }} spec: entryPoints: - - websecure + {{- include "plane.traefikEntryPoints" . | nindent 4 }} routes: - match: Host(`{{ .Values.ingress.rabbitmqHost }}`) kind: Rule @@ -123,6 +127,8 @@ spec: services: - name: {{ .Release.Name }}-rabbitmq port: 15672 + {{- if eq (include "plane.chartManagedCert" .) "true" }} tls: secretName: {{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }} + {{- end }} {{- end }} diff --git a/charts/plane-ce/templates/ingress.yaml b/charts/plane-ce/templates/ingress.yaml index 8894efa5..af6505a4 100644 --- a/charts/plane-ce/templates/ingress.yaml +++ b/charts/plane-ce/templates/ingress.yaml @@ -7,9 +7,9 @@ metadata: name: {{ .Release.Name }}-ingress labels: {{- include "plane.commonLabels" $ | nindent 4 }} - {{- if gt (len .Values.ingress.ingress_annotations) 0 }} + {{- with .Values.ingress.ingress_annotations }} annotations: - {{- range $key, $value := .Values.ingress.ingress_annotations }} + {{- range $key, $value := . }} {{ $key }}: {{ $value | quote }} {{- end }} {{- end }} diff --git a/charts/plane-ce/values.yaml b/charts/plane-ce/values.yaml index 87fc8416..0184a108 100644 --- a/charts/plane-ce/values.yaml +++ b/charts/plane-ce/values.yaml @@ -18,6 +18,15 @@ ingress: # Traefik-specific options (only used when ingressClass starts with "traefik") traefik: maxRequestBodyBytes: 20971520 # in bytes (default: 20 MiB) + # Traefik entrypoints the IngressRoutes attach 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, + # or externalTermination) -> ['websecure'] + # - 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. + # Set explicitly only if your Traefik install renamed the default entrypoints, + # e.g. entryPoints: ["websecure", "web"] or ["https"]. + entryPoints: [] # ingress_annotations: { "nginx.ingress.kubernetes.io/proxy-body-size": "5m" } # SSL Configuration - Valid only if ingress.enabled is true @@ -30,6 +39,16 @@ 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 Traefik IngressRoutes then bind to the websecure entrypoint, but 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 redis: local_setup: true