Skip to content

feat(helm): add engine authentication and TLS support (FB-1943) - #23

Merged
kop merged 3 commits into
mainfrom
kop/fb-1943-auth-and-access-sso-support
Jul 7, 2026
Merged

feat(helm): add engine authentication and TLS support (FB-1943)#23
kop merged 3 commits into
mainfrom
kop/fb-1943-auth-and-access-sso-support

Conversation

@kop

@kop kop commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds first-class chart support for:

  • Engine authentication — bootstrap admin account + JWT signing keys, with rotation support (first entry in auth.signingKeys is the active signer; prepend to rotate).
  • TLS on the gateway's client listener (tls.gateway) and the engine's query listener (tls.engine), including the engine's own self-health-check and the gateway's upstream TLS to engines.

Every secret artifact (admin password, signing keys, TLS certs) is provisioned by exactly one of existingSecret (bring-your-own) or a chart-rendered cert-manager Certificate — no Helm-generated self-signed certs, no plaintext secret values anywhere.

The old auth.{mode,local,oidc}auth.json scaffold is removed: it was never enforced (values.yaml said so explicitly) and the engine never read that file.

Notable design points

  • auth.admin.password, tls.gateway, tls.engine, and each auth.signingKeys[] entry all share the same existingSecret / certManager shape, resolved by a shared fbinstance.secretSourceName helper that fails the render if a source sets both or neither.
  • The engine's own internal health check dials its query listener over HTTPS once tls.engine.enabled, and validates using the same file it serves as certificate_file — this requires a full certificate chain, not a leaf-only cert, so an init container concatenates tls.crt + ca.crt from the resolved Secret.
  • The gateway's Envoy dynamic_forward_proxy cluster gets auto_sni / auto_san_validation on its upstream TLS context, since the Lua filter rewrites :authority per request.
  • certificates.yaml gates every rendered cert-manager Certificate on its owning feature's enabled flag, so a stale certManager block on a disabled feature never creates an unused cert-manager dependency.
  • The core-ui dev sidecar's FIREBOLT_CORE_URL now follows tls.engine.enabled (its nginx reverse proxy defaults proxy_ssl_verify to off, so this needs no extra trust configuration for the loopback connection).

Caveat

The chart's pinned appVersion predates the instance.auth config schema landing in the engine — an engine on that build crash-loops with Configuration entry does not exist if auth.enabled or tls.engine.enabled is set. This is documented as a prerequisite in docs/usage/authentication.mdx and docs/prerequisites.mdx; the chart code itself is verified correct against engine source and against a :dev engine build. Merging as-is (docs-only) per discussion — the feature activates automatically once a release image with the schema ships.

One open judgment call left for reviewers: this PR does not add a BREAKING CHANGE: footer even though it removes the old (non-functional) auth.* values shape, since nothing could have relied on it for real auth. Flag in review if a major version bump is wanted instead of the default minor bump a feat: commit triggers.

Test plan

  • make lint / helm lint --strict pass
  • ./helm/scripts/validate-chart.sh passes (one pre-existing, unrelated yamllint blank-line issue confirmed present on main too)
  • Live kind-cluster verification: base install + full existing test suite, zero regressions
  • Live kind-cluster verification: auth.enabledtest-auth helm test passes all 4 assertions (unauthenticated rejection, health-check exemption, token issuance, authenticated query) against an engine build with instance.auth support
  • Live kind-cluster verification: tls.engine.enabled — engine self-probe validates its own full-chain cert (pod reaches Ready); gateway's upstream TLS with auto_sni/auto_san_validation correctly proxies to the engine (details=via_upstream in Envoy access logs, not a local TLS failure)
  • Static verification (helm template + schema): cert-manager paths for signing keys, gateway TLS, and engine TLS; stale-certManager-on-disabled-feature no longer renders a Certificate; missing dnsNames on gateway certManager fails the render; missing signingKeys[].id fails both schema validation and (if skipped) the template render
  • Live kind-cluster verification of the cert-manager provisioning path (only existingSecret was live-tested; cert-manager paths are static-only so far)

Note

High Risk
Touches authentication, JWT signing, and TLS on the query path and Envoy upstream routing; misconfiguration can block engine readiness or break gateway-to-engine traffic. Values shape for auth.* changes from the old non-functional scaffold.

Overview
Adds first-class Helm support for engine authentication and TLS, replacing the unused auth.json / auth.mode scaffold with values that render into engine config.yaml and wire through gateway and engine pods.

Authentication (auth.enabled) renders instance.auth (bootstrap admin from a required Secret, ordered JWT signingKeys with prepend-to-rotate semantics). TLS adds independent tls.gateway (client termination) and tls.engine (query listener on 3473 plus gateway upstream and active health checks). Secrets are BYO existingSecret or cert-manager Certificate only—no inline passwords or chart-generated certs—with shared fbinstance.secretSourceName validation.

Implementation highlights: optional certificates.yaml; engine tls-chain-setup init builds fullchain.pem from tls.crt + ca.crt; Envoy gets downstream TLS and upstream auto_sni / auto_san_validation; pod rollout checksums use lookup on live Secrets; helm tests cover auth, gateway HTTPS, and token-backed SQL via the gateway.

Docs: new usage/authentication.mdx, nav/prerequisites/security updates, and note that pinned appVersion needs a newer engine image when auth or engine TLS is enabled.

Reviewed by Cursor Bugbot for commit 429b8ec. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for all 3 issues found in the latest run.

  • ✅ Fixed: Cert-manager signing rotation breaks
    • Cert-manager signing Certificate and Secret names now derive stably from each signing key id instead of the list index, preserving outgoing key material when keys are prepended.
  • ✅ Fixed: Auth test ignores nodeHostSuffix
    • The auth test engine host now appends engineSpec.nodeHostSuffix instead of hardcoding .svc.cluster.local.
  • ✅ Fixed: customEngineConfig overrides chart auth
    • Chart-owned auth fields are stripped from customEngineConfig before merging when auth is enabled, while non-owned auth settings still merge normally.

Create PR

Or push these changes by commenting:

@cursor push 584ed9b656
Preview (584ed9b656)
diff --git a/helm/AGENTS.md b/helm/AGENTS.md
--- a/helm/AGENTS.md
+++ b/helm/AGENTS.md
@@ -29,10 +29,10 @@
 - **Pod label set is intentionally narrower than resource label set.** `fbinstance.podLabels` excludes `app.kubernetes.io/version` so that an `appVersion` bump on an unrelated component does not mutate the pod template and trigger an unwanted rollout.
 - **`engineSpec` defaults, per-engine override.** Shared engine pod settings live under `engineSpec`. Each entry under `engines:` may override any of those keys for its own StatefulSets. Fall back to `engineSpec.defaultStorage` when `engines[i].storage` is omitted.
 - **Engine FQDN format.** `engine-{name}-node-{i}-0.{baseName}-hl.{namespace}.svc{nodeHostSuffix}` (default `nodeHostSuffix` is `.cluster.local`). Generated by the `fbinstance.engineConfig` helper. Changing this format breaks engine peer discovery in `config.yaml`.
-- **`customEngineConfig` is merged into the rendered engine `config.yaml`.** `fbinstance.engineConfig` builds a canonical document on the new Firebolt Core schema (`schema_version: "1.0"`, `engine.{id,nodes,termination_grace_period}`, `instance.{type,multi_engine.metadata_endpoint}`, plus `instance.auth`/`endpoints` when `auth.enabled`/`tls.engine.enabled`) and deep-merges `.Values.customEngineConfig` on top at the root. Use it to set `instance.id` (which the engine fans out to `account_id`/`account_name`/`organization_id`/`organization_name`), add a `logging:` block, or add auth settings the chart doesn't expose as first-class values (`instance.auth.oidc`, `password_login`, `jwt`, `preferred_authorization_server`, `signing_algorithm`) — these deep-merge alongside the admin account and signing keys the chart renders. Chart-authoritative paths are silently stripped before the merge: `schema_version`, `engine.id`, `engine.nodes`, `engine.termination_grace_period`, `instance.type`, `instance.multi_engine`, and — only while `tls.engine.enabled` is actually rendering it — `endpoints.http.listeners`. `engine.termination_grace_period` is derived from `engineSpec.terminationGracePeriodSeconds` (minus a 5s margin, floored at 1s). Add new fields here rather than carving out new top-level value keys. The rendered file is YAML; the engine reads it from `/var/lib/firebolt/config.yaml` (the data dir, `--data-dir /var/lib/firebolt`).
+- **`customEngineConfig` is merged into the rendered engine `config.yaml`.** `fbinstance.engineConfig` builds a canonical document on the new Firebolt Core schema (`schema_version: "1.0"`, `engine.{id,nodes,termination_grace_period}`, `instance.{type,multi_engine.metadata_endpoint}`, plus `instance.auth`/`endpoints` when `auth.enabled`/`tls.engine.enabled`) and deep-merges `.Values.customEngineConfig` on top at the root. Use it to set `instance.id` (which the engine fans out to `account_id`/`account_name`/`organization_id`/`organization_name`), add a `logging:` block, or add auth settings the chart doesn't expose as first-class values (`instance.auth.oidc`, `password_login`, `jwt`, `preferred_authorization_server`, `signing_algorithm`) — these deep-merge alongside the admin account and signing keys the chart renders. Chart-authoritative paths are silently stripped before the merge: `schema_version`, `engine.id`, `engine.nodes`, `engine.termination_grace_period`, `instance.type`, `instance.multi_engine`, and — only while `auth.enabled` / `tls.engine.enabled` are actually rendering them — `instance.auth.enabled`, `instance.auth.admin`, `instance.auth.local.signing_keys`, and `endpoints.http.listeners`. `engine.termination_grace_period` is derived from `engineSpec.terminationGracePeriodSeconds` (minus a 5s margin, floored at 1s). Add new fields here rather than carving out new top-level value keys. The rendered file is YAML; the engine reads it from `/var/lib/firebolt/config.yaml` (the data dir, `--data-dir /var/lib/firebolt`).
 - **Pensieve `default_account_id` reads from `customEngineConfig.instance.id`.** `templates/metadata-service-configmap.yaml` populates `<pensieve_lite><default_account_id>` from the same value the engine sees as `instance.id`, so Pensieve and the engine agree on the account ULID without two separate value keys.
 - **Auth and TLS secrets are provisioned by `existingSecret` or `certManager`, never both.** `fbinstance.secretSourceName` (`_helpers.tpl`) resolves each of `auth.signingKeys[]`, `tls.gateway`, and `tls.engine` to the Secret name to mount, and `fail`s the render if a source sets both or neither (detected via a non-empty `existingSecret.secretRef` vs. a non-empty `certManager.issuerRef.name` — the `certManager` map is always present with structural defaults, so `issuerRef.name` is the actual signal). `fbinstance.usesCertManager` mirrors the same check as a non-failing template guard, used by `certificates.yaml` (which also gates each artifact's `Certificate` on its owning feature — `tls.gateway.enabled` / `tls.engine.enabled` / `auth.enabled` — so a stale `certManager` block on a disabled feature never renders one) to decide whether to render a `Certificate`. `auth.admin.password` mirrors the same `existingSecret.secretRef` shape for consistency but only ever supports `existingSecret` — a password isn't a certificate, so there's no `certManager` alternative — and is validated by its own `fail` check rather than the shared helper. Nothing under `instance.auth` is rendered at all unless `auth.enabled` — the engine refuses to start if `admin`/`oidc`/`preferred_authorization_server` are present while auth is disabled.
-- **`auth.signingKeys` is ordered; the first entry signs.** The engine always uses `signing_keys[0]` to sign new tokens; every other entry only verifies. Rotation is prepend-a-new-entry, not replace-the-list — removing an entry immediately invalidates tokens signed under it. `auth.enabled` with an empty `signingKeys` fails the render (a per-pod dev key would differ across nodes and break cross-node token validation), and so does any entry with an empty `id` (schema-required, plus a template `fail` for defense in depth) — it's published as the JWT `kid` header and used as the cert-manager Certificate's `commonName`.
+- **`auth.signingKeys` is ordered; the first entry signs.** The engine always uses `signing_keys[0]` to sign new tokens; every other entry only verifies. Rotation is prepend-a-new-entry, not replace-the-list — removing an entry immediately invalidates tokens signed under it. `auth.enabled` with an empty `signingKeys` fails the render (a per-pod dev key would differ across nodes and break cross-node token validation), and so does any entry with an empty `id` (schema-required, plus a template `fail` for defense in depth) — it's published as the JWT `kid` header, used as the cert-manager Certificate's `commonName`, and hashed into the chart-managed cert-manager Secret/Certificate name so list reordering preserves old private key material.
 - **The engine's own TLS cert must be a full chain, not leaf-only.** When `tls.engine.enabled`, the engine's internal self-health-check dials its own query listener over HTTPS and validates using `certificate_file` as its curl trust bundle — a leaf-only cert (cert-manager's default `tls.crt`) fails self-verification. The `tls-chain-setup` init container in `engine-statefulset.yaml` concatenates `tls.crt` + `ca.crt` from the resolved Secret into `certificate_file`'s path; `ca.crt` is therefore a required key in `tls.engine`'s Secret (BYO or cert-manager), not optional. The Envoy gateway's `trusted_ca` reads `ca.crt` directly (standard chain validation needs only the CA, no concatenation).
 - **`endpoints.http.listeners` only ever governs the query port (3473).** Confirmed against the engine source: health (8122), metrics (9090), and metadata (6500) listeners are configured by independent keys untouched by `endpoints`, so the chart's minimal TLS overlay never needs to re-declare them. kubelet probes stay on plain HTTP against 8122 regardless of `tls.engine.enabled`.
 - **No secrets in `values.yaml`.** `postgresql.password` is required when `local_enabled: true` and is left empty in `values.yaml`. External-DB users should set `postgresql.credentials.existingSecret` instead.

diff --git a/helm/README.md b/helm/README.md
--- a/helm/README.md
+++ b/helm/README.md
@@ -14,16 +14,16 @@
 
 | Key | Type | Default | Description |
 |-----|------|---------|-------------|
-| auth | object | {} | Authentication configuration for the Firebolt engine, rendered into the engine's `config.yaml` under `instance.auth`. When `enabled` is false, nothing is rendered under `instance.auth` — the engine refuses to start if `admin` or `oidc` are present while auth is disabled. Everything beyond the admin account and signing keys (OIDC providers, `password_login`, `jwt.*`, JIT provisioning, `preferred_authorization_server`) is not a first-class value here — set it under `customEngineConfig.instance.auth`, which is deep-merged on top of the block this chart renders. |
+| auth | object | {} | Authentication configuration for the Firebolt engine, rendered into the engine's `config.yaml` under `instance.auth`. When `enabled` is false, nothing is rendered under `instance.auth` — the engine refuses to start if `admin` or `oidc` are present while auth is disabled. Everything beyond the admin account and signing keys (OIDC providers, `password_login`, `jwt.*`, JIT provisioning, `preferred_authorization_server`) is not a first-class value here — set it under `customEngineConfig.instance.auth`, which is deep-merged on top of the block this chart renders. The chart-owned `enabled`, `admin`, and `local.signing_keys` fields always come from this `auth` block when auth is enabled. |
 | auth.admin | object | {} | Bootstrap administrator account (`instance.auth.admin`). Required when `auth.enabled` is true. |
 | auth.admin.name | string | `"firebolt"` | Admin username (`instance.auth.admin.name`). |
 | auth.admin.password | object | {} | Admin password source. The chart only accepts an existing Secret — there is no literal-password value or certManager option (a password isn't a certificate), so enabling auth always requires a Secret to already exist. |
 | auth.admin.password.existingSecret | object | {} | Existing Secret containing the admin password under key `password`. Mounted read-only and passed to the engine as `password_file`. |
 | auth.admin.password.existingSecret.secretRef | string | `""` | Secret name. |
 | auth.enabled | bool | `false` | Enable authentication on the engine (`instance.auth.enabled`). |
-| auth.signingKeys | list | [] | JWT signing keys for the engine's embedded Authorization Server (`instance.auth.local.signing_keys`). An ordered list: the **first** entry is the active signer used for new tokens; every entry remains valid for verifying tokens already issued under it. **To rotate the signing key, prepend a new entry** rather than replacing the list, so tokens signed by the outgoing key keep validating until it's removed. At least one entry is required when `auth.enabled` is true — an engine with no explicit signing key falls back to a per-pod dev key that differs across nodes and breaks token validation in a multi-node engine.  Each entry sets exactly one of `existingSecret` / `certManager`:   - `id` — key identifier, published as the JWT `kid` header     (`instance.auth.local.signing_keys[].id`).   - `existingSecret.secretRef` — Secret containing the PEM private key     under key `tls.key`.   - `certManager` — a chart-rendered cert-manager `Certificate` request     (`algorithm`, `size`, `issuerRef.{name,kind}`). Requires cert-manager     and its CRDs installed in-cluster. The chart pins     `privateKey.rotationPolicy: Never` so cert-manager never silently     rotates a key out from under already-issued tokens — rotate by     prepending a new list entry instead. |
+| auth.signingKeys | list | [] | JWT signing keys for the engine's embedded Authorization Server (`instance.auth.local.signing_keys`). An ordered list: the **first** entry is the active signer used for new tokens; every entry remains valid for verifying tokens already issued under it. **To rotate the signing key, prepend a new entry** rather than replacing the list, so tokens signed by the outgoing key keep validating until it's removed. At least one entry is required when `auth.enabled` is true — an engine with no explicit signing key falls back to a per-pod dev key that differs across nodes and breaks token validation in a multi-node engine.  Each entry sets exactly one of `existingSecret` / `certManager`:   - `id` — key identifier, published as the JWT `kid` header     (`instance.auth.local.signing_keys[].id`). For cert-manager keys,     chart-managed Certificate and Secret names are derived from this id,     so prepending a new key preserves outgoing key material.   - `existingSecret.secretRef` — Secret containing the PEM private key     under key `tls.key`.   - `certManager` — a chart-rendered cert-manager `Certificate` request     (`algorithm`, `size`, `issuerRef.{name,kind}`). Requires cert-manager     and its CRDs installed in-cluster. The chart pins     `privateKey.rotationPolicy: Never` so cert-manager never silently     rotates a key out from under already-issued tokens — rotate by     prepending a new list entry instead. |
 | createNamespace | bool | `false` | When true, a Namespace resource is included in the chart output. Pair with `helm install --create-namespace --set createNamespace=false`. |
-| customEngineConfig | object | {} | Custom engine configuration deep-merged into the rendered engine config.yaml at the root. The rendered document follows the Firebolt Core configuration schema (`schema_version: "1.0"`); user-supplied keys at the top of `customEngineConfig` become siblings of the chart-managed `engine:` and `instance:` blocks (e.g. `logging:`), and keys nested under `instance:` merge into the instance block (e.g. `instance.id`, or `instance.auth.*` alongside the admin account and signing keys the `auth` value block below renders when `auth.enabled` is true — see `auth` for what belongs there instead of here).  Chart-authoritative paths are silently stripped from this input and cannot be overridden: `schema_version`, `engine.id`, `engine.nodes`, `engine.termination_grace_period`, `instance.type`, `instance.multi_engine`, and — only while `tls.engine.enabled` is actually rendering it — `endpoints.http.listeners`. |
+| customEngineConfig | object | {} | Custom engine configuration deep-merged into the rendered engine config.yaml at the root. The rendered document follows the Firebolt Core configuration schema (`schema_version: "1.0"`); user-supplied keys at the top of `customEngineConfig` become siblings of the chart-managed `engine:` and `instance:` blocks (e.g. `logging:`), and keys nested under `instance:` merge into the instance block (e.g. `instance.id`, or `instance.auth.*` alongside the admin account and signing keys the `auth` value block below renders when `auth.enabled` is true — see `auth` for what belongs there instead of here).  Chart-authoritative paths are silently stripped from this input and cannot be overridden: `schema_version`, `engine.id`, `engine.nodes`, `engine.termination_grace_period`, `instance.type`, `instance.multi_engine`, and — only while `auth.enabled` / `tls.engine.enabled` are actually rendering them — `instance.auth.enabled`, `instance.auth.admin`, `instance.auth.local.signing_keys`, and `endpoints.http.listeners`. |
 | customEngineConfig.instance | object | {} | Instance identity. `id` propagates internally to `account_id`, `account_name`, `organization_id`, and `organization_name`, so the chart only needs to set the ULID once. |
 | customEngineConfig.instance.id | string | `"01KP98J0000000000000000000"` | ULID for the Firebolt instance. Must match the account reconciled by the metadata service at startup. |
 | engineSpec | object | {} | Shared engine pod defaults applied to all engines unless overridden per-engine. |

diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl
--- a/helm/templates/_helpers.tpl
+++ b/helm/templates/_helpers.tpl
@@ -131,10 +131,9 @@
 Chart-authoritative paths are silently stripped from the user input
 before the merge: `schema_version`, `engine.id`, `engine.nodes`,
 `engine.termination_grace_period`, `instance.type`,
-`instance.multi_engine`, and — only when `tls.engine.enabled` — the
-`endpoints.http.listeners` this helper renders for the query
-listener's TLS. The same customEngineConfig therefore stays portable
-across chart versions.
+`instance.multi_engine`, and — only when `auth.enabled` /
+`tls.engine.enabled` — the auth/TLS subtrees this helper renders. The
+same customEngineConfig therefore stays portable across chart versions.
 
 When `auth.enabled` is true, `instance.auth.{enabled,admin,
 local.signing_keys}` are built from `auth.admin` / `auth.signingKeys`
@@ -198,6 +197,21 @@
 {{-   if kindIs "map" $user.instance -}}
 {{-     $_ := unset $user.instance "type" -}}
 {{-     $_ := unset $user.instance "multi_engine" -}}
+{{-     if and $root.Values.auth.enabled (hasKey $user.instance "auth") -}}
+{{-       if kindIs "map" $user.instance.auth -}}
+{{-         $_ := unset $user.instance.auth "enabled" -}}
+{{-         $_ := unset $user.instance.auth "admin" -}}
+{{-         if hasKey $user.instance.auth "local" -}}
+{{-           if kindIs "map" $user.instance.auth.local -}}
+{{-             $_ := unset $user.instance.auth.local "signing_keys" -}}
+{{-           else -}}
+{{-             $_ := unset $user.instance.auth "local" -}}
+{{-           end -}}
+{{-         end -}}
+{{-       else -}}
+{{-         $_ := unset $user.instance "auth" -}}
+{{-       end -}}
+{{-     end -}}
 {{-   else -}}
 {{-     $_ := unset $user "instance" -}}
 {{-   end -}}
@@ -306,6 +320,17 @@
 {{- end -}}
 
 {{/*
+Stable chart-managed Secret/Certificate name for a cert-manager auth signing key.
+Use the key id instead of the list index so prepending a new signer does not move
+the outgoing key to a freshly-generated Secret.
+
+Usage: {{ include "fbinstance.authSigningKeySecretName" (dict "root" $ "key" $key) }}
+*/}}
+{{- define "fbinstance.authSigningKeySecretName" -}}
+{{- printf "%s-auth-signing-%s" (include "fbinstance.fullname" .root) (.key.id | sha256sum | trunc 16) -}}
+{{- end -}}
+
+{{/*
 DNS names for the shared engine TLS certificate (one Secret/Certificate mounted by every
 engine pod across every engine in .Values.engines). Includes every node's per-node FQDN,
 each engine's headless-service FQDN, and "localhost".

diff --git a/helm/templates/certificates.yaml b/helm/templates/certificates.yaml
--- a/helm/templates/certificates.yaml
+++ b/helm/templates/certificates.yaml
@@ -69,13 +69,13 @@
 apiVersion: cert-manager.io/v1
 kind: Certificate
 metadata:
-  name: {{ include "fbinstance.fullname" $ }}-auth-signing-{{ $ki }}
+  name: {{ include "fbinstance.authSigningKeySecretName" (dict "root" $ "key" $key) }}
   namespace: {{ $.Release.Namespace }}
   labels:
     {{- include "fbinstance.labels" $ | nindent 4 }}
     firebolt/component: engine
 spec:
-  secretName: {{ include "fbinstance.fullname" $ }}-auth-signing-{{ $ki }}
+  secretName: {{ include "fbinstance.authSigningKeySecretName" (dict "root" $ "key" $key) }}
   # No DNS/service identity — this is a JWT signing key, not a TLS endpoint cert. commonName
   # is the only identity cert-manager requires when dnsNames/ipAddresses/uris are all empty.
   commonName: {{ $key.id | quote }}

diff --git a/helm/templates/engine-statefulset.yaml b/helm/templates/engine-statefulset.yaml
--- a/helm/templates/engine-statefulset.yaml
+++ b/helm/templates/engine-statefulset.yaml
@@ -21,7 +21,7 @@
 {{- if $.Values.auth.enabled -}}
 {{-   $authAdminSecret = $.Values.auth.admin.password.existingSecret.secretRef -}}
 {{-   range $ki, $key := $.Values.auth.signingKeys -}}
-{{-     $authSigningSecrets = append $authSigningSecrets (include "fbinstance.secretSourceName" (dict "source" $key "certManagerSecretName" (printf "%s-auth-signing-%d" (include "fbinstance.fullname" $) $ki) "context" (printf "auth.signingKeys[%d]" $ki))) -}}
+{{-     $authSigningSecrets = append $authSigningSecrets (include "fbinstance.secretSourceName" (dict "source" $key "certManagerSecretName" (include "fbinstance.authSigningKeySecretName" (dict "root" $ "key" $key)) "context" (printf "auth.signingKeys[%d]" $ki))) -}}
 {{-   end -}}
 {{- end -}}
 {{- $engineTlsSecret := "" -}}

diff --git a/helm/templates/tests/test-auth.yaml b/helm/templates/tests/test-auth.yaml
--- a/helm/templates/tests/test-auth.yaml
+++ b/helm/templates/tests/test-auth.yaml
@@ -36,7 +36,7 @@
         - |
           set -e
           {{- include "fbinstance.testShellHelpers" . | nindent 10 }}
-          HOST="{{ include "fbinstance.fullname" . }}-engine-{{ $engine.name }}-node-0-0.{{ include "fbinstance.fullname" . }}-engine-{{ $engine.name }}-hl.{{ .Release.Namespace }}.svc.cluster.local"
+          HOST="{{ include "fbinstance.fullname" . }}-engine-{{ $engine.name }}-node-0-0.{{ include "fbinstance.fullname" . }}-engine-{{ $engine.name }}-hl.{{ .Release.Namespace }}.svc{{ .Values.engineSpec.nodeHostSuffix }}"
           {{- if .Values.tls.engine.enabled }}
           # tls.engine.enabled: the query listener speaks HTTPS. This test only exercises
           # auth enforcement, not certificate trust (the live TLS path covers that

diff --git a/helm/values.yaml b/helm/values.yaml
--- a/helm/values.yaml
+++ b/helm/values.yaml
@@ -424,7 +424,9 @@
 # the admin account and signing keys (OIDC providers, `password_login`,
 # `jwt.*`, JIT provisioning, `preferred_authorization_server`) is not a
 # first-class value here — set it under `customEngineConfig.instance.auth`,
-# which is deep-merged on top of the block this chart renders.
+# which is deep-merged on top of the block this chart renders. The
+# chart-owned `enabled`, `admin`, and `local.signing_keys` fields always
+# come from this `auth` block when auth is enabled.
 # @default -- {}
 auth:
   # -- Enable authentication on the engine (`instance.auth.enabled`).
@@ -463,7 +465,9 @@
   #
   # Each entry sets exactly one of `existingSecret` / `certManager`:
   #   - `id` — key identifier, published as the JWT `kid` header
-  #     (`instance.auth.local.signing_keys[].id`).
+  #     (`instance.auth.local.signing_keys[].id`). For cert-manager keys,
+  #     chart-managed Certificate and Secret names are derived from this id,
+  #     so prepending a new key preserves outgoing key material.
   #   - `existingSecret.secretRef` — Secret containing the PEM private key
   #     under key `tls.key`.
   #   - `certManager` — a chart-rendered cert-manager `Certificate` request
@@ -629,8 +633,10 @@
 # Chart-authoritative paths are silently stripped from this input and
 # cannot be overridden: `schema_version`, `engine.id`, `engine.nodes`,
 # `engine.termination_grace_period`, `instance.type`,
-# `instance.multi_engine`, and — only while `tls.engine.enabled` is
-# actually rendering it — `endpoints.http.listeners`.
+# `instance.multi_engine`, and — only while `auth.enabled` /
+# `tls.engine.enabled` are actually rendering them —
+# `instance.auth.enabled`, `instance.auth.admin`,
+# `instance.auth.local.signing_keys`, and `endpoints.http.listeners`.
 # @default -- {}
 customEngineConfig:
   # -- Instance identity. `id` propagates internally to `account_id`,

You can send follow-ups to the cloud agent here.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 429b8ec. Configure here.

Comment thread helm/templates/certificates.yaml Outdated
Comment thread helm/templates/tests/test-auth.yaml Outdated
Comment thread helm/templates/_helpers.tpl
@fireboltdb-docs-integration

fireboltdb-docs-integration Bot commented Jul 3, 2026

Copy link
Copy Markdown

Mintlify docs preview

Docs were aggregated into the Firebolt documentation site.

Status Preview Updated (UTC)
🟢 Ready View Preview Jul 07, 2026, 12:46 PM

Mintlify may take a few minutes to finish building. This comment updates on each sync while the source PR is open.

kop added 3 commits July 7, 2026 15:10
Adds first-class chart support for engine authentication (bootstrap
admin account + JWT signing keys with rotation), and TLS on the
gateway's client listener and the engine's query listener. Every
secret artifact is provisioned by existingSecret or a chart-rendered
cert-manager Certificate — no Helm self-signed generation, no
plaintext secret values.

- auth.enabled renders instance.auth from auth.admin / auth.signingKeys;
  signingKeys is ordered (first entry signs) to support rotation.
- tls.gateway / tls.engine enable TLS independently, including the
  engine's own self-health-check (full-chain cert via an init
  container) and the gateway's upstream TLS to engines (auto_sni /
  auto_san_validation on the dynamic_forward_proxy cluster).
- Removes the old auth.{mode,local,oidc} auth.json scaffold: it was
  never enforced and the engine never read it.
- New certificates.yaml (cert-manager Certificate rendering, gated on
  each feature's enabled flag) and test-auth.yaml helm test;
  test-gateway-ready / test-sql-query are now auth/TLS-aware.
- core-ui sidecar's FIREBOLT_CORE_URL follows tls.engine.enabled.
- Docs: new usage/authentication page, prerequisites and security
  updates, including a callout that the chart's pinned appVersion
  predates the instance.auth config schema.

Live-verified in a kind cluster: the auth-enabled helm test and the
TLS engine/gateway request path both pass end-to-end.
- Narrow the gateway's engine-ca mount to project only ca.crt instead of
  the whole engine TLS Secret, keeping the engine's private key off the
  gateway pod's filesystem.
- Document the SAN requirements for BYO tls.engine certs and the ca.crt
  requirement for tls.engine's cert-manager issuer.
- Derive cert-manager signing-key Secret/Certificate names from a hash of
  each key's id instead of its list position, so prepending a new key for
  rotation no longer reissues fresh material for shifted entries.
- Fix templates/tests/test-auth.yaml to respect engineSpec.nodeHostSuffix
  instead of a hardcoded .svc.cluster.local.
- Strip chart-owned instance.auth.{enabled,admin,local.signing_keys} from
  customEngineConfig before merging, so user config can no longer flip
  auth off or repoint admin/signing-key paths the chart doesn't mount.
Split the "Authentication and TLS" documentation into two separate
pages to improve clarity and navigation. Removed stale notes about
unsupported engine versions as the current `appVersion` now supports
these features.

- Move TLS configuration details to `docs/usage/tls.mdx`
- Keep authentication and signing key rotation in
  `docs/usage/authentication.mdx`
- Update `docs/docs.json` and `docs/known_pages.json` navigation
- Add `.legitignore` to suppress false positive secret name leaks
@kop
kop force-pushed the kop/fb-1943-auth-and-access-sso-support branch from 0c09617 to 1fbc301 Compare July 7, 2026 12:41
@kop
kop requested a review from simonzelenskifbio July 7, 2026 12:44
@kop
kop marked this pull request as ready for review July 7, 2026 12:44
@dig-whois

Copy link
Copy Markdown
Contributor

LGTM

@kop
kop merged commit a9a8aa2 into main Jul 7, 2026
9 checks passed
@kop
kop deleted the kop/fb-1943-auth-and-access-sso-support branch July 7, 2026 13:52
@fireboltdb-docs-integration

Copy link
Copy Markdown

Packdb docs aggregation ready for review

Docs were merged to main and aggregated into packdb.

Squash-merge the packdb PR into master to publish the docs. Urgent fixes can be cherry-picked to the active release/packdb-* branch like any other docs change.

gm42 added a commit that referenced this pull request Jul 28, 2026
Automated release PR.
---


##
[0.3.0](0.2.0...0.3.0)
(2026-07-27)


### ⚠ BREAKING CHANGES

* **storage:** migrate engine storage config to schema (FB-1684)
([#25](#25))

### Features

* **helm:** add engine authentication and TLS support (FB-1943)
([#23](#23))
([a9a8aa2](a9a8aa2))
* **storage:** migrate engine storage config to schema (FB-1684)
([#25](#25))
([91f1f5d](91f1f5d))


### Bug Fixes

* **agent:** remove floci AWS env credentials (FB-2197)
([#36](#36))
([58b56d3](58b56d3))
* keep the Core UI sidecar image fresh and probe its readiness (FB-2179,
FB-2180)
([#32](#32))
([fb88c96](fb88c96))
* **security:** disable service account token automount on postgres and
gateway
([#47](#47))
([181ac91](181ac91))


### Dependencies

* **deps:** bump packdb appVersion to
release-5.0.1-0.20260709071413.53735f172429
([#5](#5))
([76ecd18](76ecd18))
* **deps:** bump packdb appVersion to
release-5.0.1-0.20260713060957.513515666721
([#29](#29))
([1dd11b3](1dd11b3))
* **deps:** bump packdb appVersion to
release-5.0.1-0.20260727005216.d09b51086f14
([#38](#38))
([69909db](69909db))

---
This PR was generated with Release Please.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Version and changelog metadata plus lint/format guardrails; no chart
template or runtime behavior changes in this diff.
> 
> **Overview**
> **Automated release-please PR** that publishes **`firebolt-instance`
chart `0.3.0`** (from `0.2.0`) and syncs
`.release-please-manifest.json`, `helm/Chart.yaml` `version`, and
`helm/CHANGELOG.md` with the accumulated conventional-commit history
since `0.2.0`.
> 
> The changelog entry highlights **breaking storage config migration**,
**engine auth/TLS**, security and sidecar fixes, and **`appVersion`**
bumps to `release-5.0.1-0.20260727005216.d09b51086f14`. This diff does
not re-land those features—it only records the release.
> 
> **Release CI hygiene:** `helm/Chart.yaml` `sources` moves from flow to
**block** style (with comments), `.yamllint.yaml` relaxes **`brackets`**
spacing so release-please’s `[ "x" ]` formatting does not fail
`validate-chart`, and `helm/AGENTS.md` documents keeping block-style
collections.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
e1e85e3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: fireboltdb-ci-writer[bot] <207707662+fireboltdb-ci-writer[bot]@users.noreply.github.com>
Co-authored-by: Giuseppe Mazzotta <giuseppe@firebolt.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants