Skip to content

Commit a9a8aa2

Browse files
authored
feat(helm): add engine authentication and TLS support (FB-1943) (#23)
## 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.
1 parent 91f1f5d commit a9a8aa2

21 files changed

Lines changed: 1192 additions & 137 deletions

.legitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
value_67A867BC754C44390E7B54969C75EEE8 # Example Kubernetes Secret name "firebolt-signing-key-2026-02" in helm/values.yaml and docs/usage/authentication.mdx — a resource reference, not a credential value.
2+
value_8FAFF5AF6AA62BB96EAF8C4A1C3ED99F # Example Kubernetes Secret name "firebolt-signing-key-2026-01" in docs/usage/authentication.mdx — a resource reference, not a credential value.

docs/docs.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
]
2424
},
2525
"usage/external-postgres",
26+
"usage/authentication",
27+
"usage/tls",
2628
"usage/image-overrides"
2729
]
2830
},

docs/known_pages.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"/troubleshooting/",
1616
"/usage",
1717
"/usage/",
18+
"/usage/authentication",
19+
"/usage/authentication/",
1820
"/usage/external-postgres",
1921
"/usage/external-postgres/",
2022
"/usage/image-overrides",
@@ -32,5 +34,7 @@
3234
"/usage/object-storage/s3-compatible",
3335
"/usage/object-storage/s3-compatible/",
3436
"/usage/single-engine",
35-
"/usage/single-engine/"
37+
"/usage/single-engine/",
38+
"/usage/tls",
39+
"/usage/tls/"
3640
]

docs/prerequisites.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,4 @@ AKS does not expose `LimitMEMLOCK` through its supported node configuration, so
114114

115115
- **Prometheus Operator CRDs**. Required only if you set `podMonitor.engines.enabled` or `podMonitor.gateway.enabled`. The chart renders `monitoring.coreos.com/v1` `PodMonitor` resources gated on those values.
116116
- **A private image registry**. The chart pulls engine and metadata images from `ghcr.io/firebolt-db` by default. To mirror through your own registry, see [Image overrides](./usage/image-overrides).
117+
- **cert-manager**. Required only if you set a `certManager` block under `auth.signingKeys`, `tls.gateway`, or `tls.engine`. The chart renders `cert-manager.io/v1` `Certificate` resources gated on those blocks.

docs/security.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ Platform teams should apply `NetworkPolicy` objects covering at least the allowe
5959

6060
Cross-engine traffic (replicas of *different* engines), gateway-to-metadata, and gateway-to-PostgreSQL are not required by any chart-emitted control flow and should be denied.
6161

62+
`tls.gateway.enabled` and `tls.engine.enabled` change what's carried on the "External clients → Gateway" and "Gateway → Engine pods" flows above from plaintext HTTP to TLS; they don't change which flows are required. See [TLS](./usage/tls).
63+
6264
### Namespace-level resource ceilings
6365

6466
A `ResourceQuota` capping the aggregate of `requests.cpu`, `requests.memory`, pod count, and PVC size across all engines in a namespace is a platform concern. The chart applies per-engine `resources.requests` and `resources.limits` from your values, but does not emit a `ResourceQuota` and does not validate the per-engine `resources` block against a Helm-side maximum. The per-namespace budget is a deployment-target decision (test cluster vs. multi-tenant production).
@@ -107,8 +109,16 @@ The chart never sees those cloud provider credentials. For an S3-compatible endp
107109

108110
The chart does not currently expose `sslmode` or a CA-bundle mount for the Metadata Service's PostgreSQL connection. Encrypt the PostgreSQL connection at the network layer (VPC peering, sidecar TLS proxy, or service-mesh mTLS) until the chart grows native support.
109111

112+
### Authentication and TLS
113+
114+
`auth.enabled`, `auth.signingKeys`, and `tls.{gateway,engine}` are each provisioned by exactly one of an existing Secret you create yourself, or a chart-rendered [cert-manager](https://cert-manager.io) `Certificate`. The chart never generates or accepts a literal admin password. See [Authentication](./usage/authentication) and [TLS](./usage/tls) for the full setup, including signing-key rotation and the cert-manager option.
115+
116+
When engine query-listener TLS is enabled (`tls.engine.enabled`), the gateway's upstream connection and its active engine health check both switch to TLS along with it; kubelet's own probes against the engine's dedicated health port continue over plain HTTP.
117+
110118
## See also
111119

120+
- [Authentication](./usage/authentication): enabling engine authentication and JWT signing key rotation.
121+
- [TLS](./usage/tls): enabling TLS on the gateway and engine listeners.
112122
- [Monitoring](./monitoring): metrics surface and Prometheus Operator integration.
113123
- [Operator upgrade path](./operator-upgrade-path): when continuous admission, drift correction, and CR-status surfaces are required.
114124
- [`SECURITY.md`](https://github.com/firebolt-db/firebolt-instance-helm/blob/main/SECURITY.md): vulnerability reporting policy for this repository.

docs/usage/authentication.mdx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
description: Enabling engine authentication, provisioning JWT signing keys, and rotating them.
3+
sidebarTitle: Authentication
4+
title: Authentication
5+
---
6+
7+
This page shows how to enable authentication on the Firebolt Engine.
8+
9+
`auth.enabled` turns on the engine's built-in authentication: a bootstrap administrator account and JWT signing keys for the engine's embedded authorization server. Everything beyond that — OIDC providers, `password_login`, JWT lifetime, just-in-time provisioning — is set through `customEngineConfig.instance.auth`, which the chart deep-merges alongside the admin account and signing keys it renders.
10+
11+
The chart never accepts a literal password value for the admin account. Enabling auth always requires an existing Secret.
12+
13+
### Values
14+
15+
```yaml
16+
# my-values.yaml
17+
auth:
18+
enabled: true
19+
admin:
20+
name: firebolt
21+
password:
22+
existingSecret:
23+
secretRef: firebolt-admin-password # Secret with a `password` key
24+
25+
signingKeys:
26+
- id: key-2026-01
27+
existingSecret:
28+
secretRef: firebolt-signing-key-2026-01 # Secret with a `tls.key` PEM private key
29+
```
30+
31+
### Install
32+
33+
Create the admin password Secret and a signing key Secret, then install the chart:
34+
35+
```bash
36+
kubectl create secret generic firebolt-admin-password -n firebolt \
37+
--from-literal=password='…'
38+
39+
# tls.key holds a PEM RSA private key; the chart uses only the private key,
40+
# not a certificate, to sign JSON Web Tokens.
41+
openssl genrsa -out signing-key.pem 2048
42+
kubectl create secret generic firebolt-signing-key-2026-01 -n firebolt \
43+
--from-file=tls.key=signing-key.pem
44+
45+
helm install firebolt ./helm \
46+
--namespace firebolt --create-namespace \
47+
-f my-values.yaml
48+
```
49+
50+
<Note>
51+
A multi-node engine needs an explicit signing key. Without one, each engine node generates its own development key on first startup, and tokens signed by one node fail validation on another. The chart's render fails immediately if `auth.enabled` is true and `signingKeys` is empty.
52+
</Note>
53+
54+
Kubernetes health probes and the gateway's active engine health check keep working without a token — the engine exempts `/health/live` and `/health/ready` from authentication.
55+
56+
### Signing key rotation
57+
58+
`signingKeys` is an ordered list. The **first** entry is the active signer used for new tokens; every entry in the list remains valid for verifying tokens already issued under it. To rotate, prepend a new entry rather than replacing the list, and remove the old entry only once every token it signed has expired:
59+
60+
```yaml
61+
auth:
62+
signingKeys:
63+
- id: key-2026-02 # now active
64+
existingSecret:
65+
secretRef: firebolt-signing-key-2026-02
66+
- id: key-2026-01 # still verifies old tokens until they expire
67+
existingSecret:
68+
secretRef: firebolt-signing-key-2026-01
69+
```
70+
71+
### cert-manager as a signing-key source
72+
73+
As an alternative to an existing Secret, a signing key entry can request a [cert-manager](https://cert-manager.io) `Certificate` instead:
74+
75+
```yaml
76+
auth:
77+
signingKeys:
78+
- id: key-2026-01
79+
certManager:
80+
algorithm: RSA
81+
size: 2048
82+
issuerRef:
83+
name: internal-ca
84+
kind: ClusterIssuer
85+
```
86+
87+
The chart pins `privateKey.rotationPolicy: Never` on signing-key Certificates, so cert-manager's renewal timer never silently replaces an active signing key out from under already-issued tokens. Rotate by prepending a new `signingKeys` entry, the same as with an existing Secret.
88+
89+
### OIDC and other auth settings
90+
91+
OIDC providers, `password_login`, JWT lifetime, and just-in-time provisioning are not first-class values. Set them under `customEngineConfig.instance.auth`, which is deep-merged alongside the admin account and signing keys the chart renders:
92+
93+
```yaml
94+
customEngineConfig:
95+
instance:
96+
auth:
97+
password_login: admin_only
98+
oidc:
99+
providers:
100+
- name: corporate_idp
101+
discovery_url: https://idp.example.com/.well-known/openid-configuration
102+
username_mapping: "{{ email }}"
103+
```
104+
105+
## See also
106+
107+
- [TLS](./tls): enabling TLS on the gateway's client listener and the engine's query listener.
108+
- [Security model](../security): pod-hardening baseline and secrets handling across the whole chart.

docs/usage/tls.mdx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
description: Enabling TLS on the gateway's client listener and the engine's query listener.
3+
sidebarTitle: TLS
4+
title: TLS
5+
---
6+
7+
This page shows how to enable TLS on the gateway and engine listeners.
8+
9+
`tls.gateway` and `tls.engine` each enable TLS independently, provisioned the same way as `auth.signingKeys` (see [Authentication](./authentication)): an existing `kubernetes.io/tls` Secret, or a chart-rendered cert-manager `Certificate`.
10+
11+
- `tls.gateway` terminates TLS on the gateway's client-facing listener (client → gateway).
12+
- `tls.engine` enables TLS on the engine's query listener and, correspondingly, the gateway's connection to engines — including the active engine health check, which runs over the same connection.
13+
14+
They can be enabled independently or together.
15+
16+
### Values
17+
18+
```yaml
19+
# my-values.yaml
20+
tls:
21+
gateway:
22+
enabled: true
23+
existingSecret:
24+
secretRef: firebolt-gateway-tls # kubernetes.io/tls Secret: tls.crt, tls.key
25+
26+
engine:
27+
enabled: true
28+
existingSecret:
29+
secretRef: firebolt-engine-tls # kubernetes.io/tls Secret: tls.crt, tls.key, ca.crt
30+
```
31+
32+
<Note>
33+
`tls.engine`'s Secret requires all three keys — `tls.crt`, `tls.key`, and `ca.crt` — even for a self-signed certificate with no separate CA (set `ca.crt` to a copy of `tls.crt` in that case). The engine's own internal health check and the gateway's upstream connection both need the issuing CA to validate the certificate independently of `tls.crt`.
34+
35+
The certificate's SANs must also cover every engine node's per-node hostname (`<release>-engine-<engine>-node-<i>-0.<release>-engine-<engine>-hl.<namespace>.svc.cluster.local`) and each engine's headless-service hostname (`<release>-engine-<engine>-hl.<namespace>.svc.cluster.local`) — both the engine's self-health-check and the gateway's `auto_san_validation` verify against these names. A missing SAN leaves the affected engine node permanently `NotReady` with no other symptom. `tls.engine.certManager` derives these automatically; a BYO certificate has to include them explicitly.
36+
</Note>
37+
38+
### Install
39+
40+
```bash
41+
kubectl create secret tls firebolt-gateway-tls -n firebolt \
42+
--cert=gateway.crt --key=gateway.key
43+
44+
# ca.crt is required in addition to the standard tls.crt / tls.key keys.
45+
kubectl create secret generic firebolt-engine-tls -n firebolt \
46+
--from-file=tls.crt=engine.crt --from-file=tls.key=engine.key --from-file=ca.crt=ca.crt
47+
48+
helm install firebolt ./helm \
49+
--namespace firebolt --create-namespace \
50+
-f my-values.yaml
51+
```
52+
53+
### cert-manager as a certificate source
54+
55+
```yaml
56+
tls:
57+
engine:
58+
enabled: true
59+
certManager:
60+
algorithm: RSA
61+
size: 2048
62+
issuerRef:
63+
name: internal-ca
64+
kind: ClusterIssuer
65+
66+
gateway:
67+
enabled: true
68+
certManager:
69+
algorithm: RSA
70+
size: 2048
71+
dnsNames:
72+
- firebolt.example.com # the chart can't infer the externally-visible hostname
73+
issuerRef:
74+
name: internal-ca
75+
kind: ClusterIssuer
76+
```
77+
78+
The engine's certificate's DNS names are derived automatically from every engine node's per-node hostname and each engine's internal service hostname — don't set `dnsNames` under `tls.engine.certManager`. The gateway's certificate has no such derivation; set `dnsNames` to whatever hostname clients use to reach the gateway.
79+
80+
<Note>
81+
The issuer behind `tls.engine.certManager` must populate `ca.crt` in the Certificate's Secret, not just `tls.crt`/`tls.key`. CA and Vault issuers do this; a bare self-signed or ACME issuer may not. The chart's `tls-chain-setup` init container needs `ca.crt` to build the engine's own health-check trust bundle and fails the pod at startup if it's missing.
82+
</Note>
83+
84+
### Caveats
85+
86+
- The `core-ui` sidecar (`engineSpec.uiSidecar`) follows `tls.engine.enabled` automatically, proxying to the engine over HTTPS without verifying its certificate — the connection is over loopback within the pod, so this doesn't need a trusted CA.
87+
- cert-manager and its CRDs must already be installed in the cluster before `certManager` blocks are set on any value; the chart's `Certificate` resources otherwise fail to apply.
88+
89+
## See also
90+
91+
- [Authentication](./authentication): enabling engine authentication and JWT signing key rotation.
92+
- [Security model](../security): pod-hardening baseline and secrets handling across the whole chart.

0 commit comments

Comments
 (0)