Skip to content

Commit 898069c

Browse files
committed
feat(helm): use TLS passthrough for Gateway API
This revises the ingress approach introduced by commit abcd15d to use TLS passthrough instead of termination at the Envoy proxy. Envoy reads the ClientHello SNI for routing and forwards the encrypted stream to the OpenShell gateway, which remains the TLS termnation endpoint. That preserves end-to-end TLS and client certificate authentication, so existing mTLS continues to work through Envoy. Passthrough also avoids certificate management at the Envoy listener. It does not require reusing the OpenShell server certificate as a Gateway certificate or provisioning a separate edge certificate and Secret. The server certificate only needs to cover the external SNI hostname. This also restores the HTTP listener on the Gateway, adding TLS as an additional listener when enabled. The values were restructured to be under a gatewayApi umbrella rather than grpcRoute as there are now both GRPCRoute and TLSRoute resources. Signed-off-by: Kris Hicks <khicks@nvidia.com>
1 parent 45060f4 commit 898069c

13 files changed

Lines changed: 353 additions & 154 deletions

File tree

.agents/skills/debug-openshell-cluster/SKILL.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,9 @@ openshell logs <sandbox-name>
296296
| Kubernetes gateway pod crash loops | Missing secret, bad DB URL, bad TLS config | `kubectl -n openshell logs deployment/openshell -c openshell-gateway` or `kubectl -n openshell logs statefulset/openshell -c openshell-gateway` |
297297
| CLI TLS error | Local mTLS bundle does not match server cert/CA | Check `~/.config/openshell/gateways/<name>/mtls/` |
298298
| Image pull failure | Gateway or sandbox image cannot be pulled | Runtime events and image pull credentials |
299-
| `K8s namespace not ready` with `envoy-gateway-openshell.yaml: the server could not find the requested resource` | Optional Gateway API manifest was applied without Envoy Gateway CRDs, or k3s Helm controller startup exceeded the namespace wait | Apply `deploy/kube/manifests/envoy-gateway-openshell.yaml` manually only after Envoy Gateway is installed and `grpcRoute` is enabled |
300-
| HTTPS ingress (`grpcRoute.gateway.listener.protocol=HTTPS`) connection resets or TLS handshake hangs | Envoy terminates TLS but the gateway pod still expects TLS, so the plaintext backend hop fails | Set `server.disableTls=true` so Envoy forwards plaintext to the pod; verify the listener `certificateRefs` Secret exists in the release namespace and `openshell status` over `https://<host>` |
301-
| HTTPS ingress returns `Unauthenticated` after connecting | TLS terminates at Envoy, so the gateway never sees a client cert; no OIDC issuer is configured for identity | Configure `server.oidc.issuer` and register with `openshell gateway add https://<host> --oidc-issuer <url>`, or set `server.auth.allowUnauthenticatedUsers=true` for a trusted-proxy/dev cluster |
302-
303-
## Reporting
299+
| `K8s namespace not ready` with `envoy-gateway-openshell.yaml: the server could not find the requested resource` | Optional Gateway API manifest was applied without Envoy Gateway CRDs, or k3s Helm controller startup exceeded the namespace wait | Apply `deploy/kube/manifests/envoy-gateway-openshell.yaml` manually only after Envoy Gateway is installed and a `gatewayApi` resource is enabled |
300+
| TLSRoute remains unaccepted or port 443 is not exposed | The referenced Gateway has no compatible TLS passthrough listener, or the Gateway API controller does not support TLSRoute | Check `kubectl describe tlsroute -n <namespace>` and `kubectl describe gateway -n <namespace>`; with a chart-created Gateway, verify both `gatewayApi.gateway.create=true` and `gatewayApi.routes.tls.enabled=true` |
301+
| TLS passthrough connection fails during the handshake | The gateway server is running plaintext or its certificate does not cover the external SNI hostname | Keep `server.disableTls=false`, verify the server certificate SAN, and configure `pkiInitJob.serverDnsNames` or `certManager.serverDnsNames` before issuing the certificate |
304302

305303
When handing results back to the user, include:
306304

deploy/helm/openshell/README.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ The `dev` tags are intended for testing changes ahead of a release. Production d
5353

5454
See [`values.yaml`](values.yaml) for source defaults. Selected overlays:
5555

56-
- [`ci/values-gateway.yaml`](ci/values-gateway.yaml) - gateway-only configuration
56+
- [`ci/values-gateway.yaml`](ci/values-gateway.yaml) - Gateway API configuration
57+
- [`ci/values-gateway-tls.yaml`](ci/values-gateway-tls.yaml) - Gateway API TLS passthrough configuration
5758
- [`ci/values-cert-manager.yaml`](ci/values-cert-manager.yaml) - cert-manager integration
5859
- [`ci/values-keycloak.yaml`](ci/values-keycloak.yaml) - Keycloak OIDC integration
5960
- [`ci/values-high-availability.yaml`](ci/values-high-availability.yaml) - CI overlay for multi-replica external PostgreSQL testing
@@ -147,16 +148,14 @@ add `ci/values-spire.yaml` to the OpenShell release values files.
147148
| certManager.serverDnsNames | list | `["openshell","openshell.openshell.svc","openshell.openshell.svc.cluster.local","localhost","openshell.localhost","*.openshell.localhost","host.docker.internal"]` | DNS SANs on the cert-manager-issued server certificate. |
148149
| certManager.serverIpAddresses | list | `["127.0.0.1"]` | IP SANs on the cert-manager-issued server certificate. |
149150
| fullnameOverride | string | `""` | Override the full generated resource name. |
150-
| grpcRoute.enabled | bool | `false` | Create a Gateway API GRPCRoute for the gateway service. |
151-
| grpcRoute.gateway.className | string | `"eg"` | GatewayClass to reference. Envoy Gateway installs one named "eg". |
152-
| grpcRoute.gateway.create | bool | `false` | When true, a Gateway resource is created in the release namespace. Set to false and provide name/namespace to attach to a pre-existing Gateway. |
153-
| grpcRoute.gateway.listener.allowedRoutes | string | `"Same"` | "Same" restricts attached routes to the release namespace; "All" allows any namespace. |
154-
| grpcRoute.gateway.listener.port | int | `80` | Listener port for the generated Gateway resource. Use 443 with protocol HTTPS. |
155-
| grpcRoute.gateway.listener.protocol | string | `"HTTP"` | Listener protocol for the generated Gateway resource: HTTP or HTTPS. HTTPS terminates TLS at the Envoy Gateway listener; pair it with server.disableTls=true so Envoy forwards plaintext to the gateway pod, and use OIDC for client identity (the gateway never sees the client cert). |
156-
| grpcRoute.gateway.listener.tls.certificateRefs | list | `[]` | certificateRefs for the HTTPS listener. Required when protocol is HTTPS. Each entry needs a `name` pointing at a kubernetes.io/tls Secret in the Gateway's namespace. May reference a cert-manager-issued Secret or the existing openshell-server-tls Secret (its SANs must include the external hostname). |
157-
| grpcRoute.gateway.name | string | `""` | Name of the Gateway resource. Defaults to the chart fullname. |
158-
| grpcRoute.gateway.namespace | string | `""` | Namespace of the Gateway referenced by the GRPCRoute parentRef. Defaults to the release namespace. |
159-
| grpcRoute.hostnames | list | `[]` | Hostnames the GRPCRoute matches on. Leave empty to match all hosts. |
151+
| gatewayApi.gateway.className | string | `"eg"` | GatewayClass to reference. Envoy Gateway installs one named "eg". |
152+
| gatewayApi.gateway.create | bool | `false` | When true, a Gateway resource is created. Set to false and provide name/namespace to attach to a pre-existing Gateway. |
153+
| gatewayApi.gateway.name | string | `""` | Name of the Gateway resource. Defaults to the chart fullname. |
154+
| gatewayApi.gateway.namespace | string | `""` | Namespace of a pre-existing Gateway referenced by routes. Defaults to the release namespace. Chart-created Gateways always use the release namespace. |
155+
| gatewayApi.routes.grpc.enabled | bool | `false` | Create a Gateway API GRPCRoute for the gateway service. |
156+
| gatewayApi.routes.grpc.hostnames | list | `[]` | Hostnames the GRPCRoute matches on. Leave empty to match all hosts. |
157+
| gatewayApi.routes.tls.enabled | bool | `false` | Create a Gateway API TLSRoute that passes encrypted traffic through to the gateway service. When gatewayApi.gateway.create is true, the generated Gateway also gets a TLS listener on port 443 in Passthrough mode. |
158+
| gatewayApi.routes.tls.hostnames | list | `[]` | SNI hostnames the TLSRoute matches on. Leave empty to match all hosts. |
160159
| image.pullPolicy | string | `"IfNotPresent"` | Gateway image pull policy. |
161160
| image.repository | string | `"ghcr.io/nvidia/openshell/gateway"` | Gateway image repository. |
162161
| image.tag | string | `""` | Gateway image tag. Defaults to the chart appVersion when empty. |

deploy/helm/openshell/README.md.gotmpl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ The `dev` tags are intended for testing changes ahead of a release. Production d
5353

5454
See [`values.yaml`](values.yaml) for source defaults. Selected overlays:
5555

56-
- [`ci/values-gateway.yaml`](ci/values-gateway.yaml) - gateway-only configuration
56+
- [`ci/values-gateway.yaml`](ci/values-gateway.yaml) - Gateway API configuration
57+
- [`ci/values-gateway-tls.yaml`](ci/values-gateway-tls.yaml) - Gateway API TLS passthrough configuration
5758
- [`ci/values-cert-manager.yaml`](ci/values-cert-manager.yaml) - cert-manager integration
5859
- [`ci/values-keycloak.yaml`](ci/values-keycloak.yaml) - Keycloak OIDC integration
5960
- [`ci/values-high-availability.yaml`](ci/values-high-availability.yaml) - CI overlay for multi-replica external PostgreSQL testing
Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
# Gateway API overlay with TLS termination at the Envoy Gateway listener.
5-
#
6-
# Exercises the HTTPS listener branch of templates/gateway.yaml for
7-
# `helm template`/lint coverage. Envoy Gateway terminates TLS using the
8-
# referenced kubernetes.io/tls Secret and forwards plaintext to the gateway pod,
9-
# so the gateway runs with TLS disabled and uses OIDC for client identity.
10-
#
11-
# The certificate Secret (openshell-ingress-tls) and the OIDC issuer below are
12-
# placeholders for render coverage; a real deployment must provide a valid TLS
13-
# Secret in the release namespace and a reachable OIDC issuer.
4+
# Gateway API overlay with TLS passthrough to the OpenShell gateway server.
5+
# Envoy routes by SNI without terminating TLS, preserving server TLS and mTLS.
146

15-
grpcRoute:
16-
enabled: true
7+
gatewayApi:
178
gateway:
189
create: true
1910
className: "eg"
20-
listener:
21-
port: 443
22-
protocol: HTTPS
23-
tls:
24-
certificateRefs:
25-
- name: openshell-ingress-tls
26-
hostnames: []
11+
routes:
12+
tls:
13+
enabled: true
14+
hostnames:
15+
- openshell.example.com
2716

28-
server:
29-
# Envoy terminates TLS at the edge; the gateway listens plaintext behind it.
30-
disableTls: true
31-
oidc:
32-
issuer: "https://keycloak.example.com/realms/openshell"
33-
audience: "openshell-cli"
17+
# Include the external hostname in certificates generated for fresh installs.
18+
pkiInitJob:
19+
serverDnsNames:
20+
- openshell.example.com

deploy/helm/openshell/ci/values-gateway.yaml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
# kubectl -n openshell port-forward svc/<envoy-service> 8080:80
1616
# # then: grpcurl -plaintext localhost:8080 ...
1717

18-
grpcRoute:
19-
enabled: true
18+
gatewayApi:
2019
gateway:
2120
create: true
2221
className: "eg"
23-
# Set one or more hostnames to scope the route, e.g.:
24-
# hostnames:
25-
# - openshell.example.com
26-
hostnames: []
22+
routes:
23+
grpc:
24+
enabled: true
25+
# Set one or more hostnames to scope the route, e.g.:
26+
# hostnames:
27+
# - openshell.example.com
28+
hostnames: []

deploy/helm/openshell/skaffold.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@ deploy:
7171
# crds.enabled: true
7272
# Envoy Gateway — Kubernetes Gateway API implementation.
7373
# Installs the Gateway API CRDs and the "eg" GatewayClass.
74-
# Required when grpcRoute.enabled is true in the openshell release.
74+
# Required when Gateway API routes are enabled in the openshell release.
7575
#- name: envoy-gateway
7676
# remoteChart: oci://docker.io/envoyproxy/gateway-helm
77-
# version: v1.7.2
77+
# version: v1.8.1
7878
# namespace: envoy-gateway-system
7979
# createNamespace: true
8080
# # wait ensures Gateway API CRDs are registered before the openshell
81-
# # release attempts to create Gateway and HTTPRoute resources.
81+
# # release attempts to create Gateway and route resources.
8282
# wait: true
8383
# SPIRE — installs SPIRE Server, Agent, Controller Manager, CSI Driver,
8484
# and OIDC Discovery Provider using the SPIFFE hardened charts.
@@ -114,8 +114,10 @@ deploy:
114114
# setup task first, then uncomment the line below:
115115
# mise run keycloak:k8s:setup
116116
#- ci/values-keycloak.yaml
117-
# To enable the Gateway API HTTPRoute (requires Envoy Gateway above):
117+
# To enable the Gateway API Gateway and GRPCRoute (requires Envoy Gateway above):
118118
#- ci/values-gateway.yaml
119+
# To enable TLS passthrough instead (requires Envoy Gateway above):
120+
#- ci/values-gateway-tls.yaml
119121
# To enable SPIFFE/SPIRE provider token grants (requires the
120122
# spire-crds and spire releases above):
121123
#- ci/values-spire.yaml

deploy/helm/openshell/templates/_helpers.tpl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ Name of the Secret holding gateway-minted sandbox JWT signing material.
109109
{{- .Values.server.sandboxJwt.signingSecretName | default (printf "%s-jwt-keys" (include "openshell.fullname" .)) -}}
110110
{{- end }}
111111

112+
{{/*
113+
Name of the Kubernetes Gateway API Gateway resource created or referenced by
114+
the chart.
115+
*/}}
116+
{{- define "openshell.gatewayApiGatewayName" -}}
117+
{{- .Values.gatewayApi.gateway.name | default (include "openshell.fullname" .) -}}
118+
{{- end }}
119+
120+
{{/*
121+
Namespace of the Kubernetes Gateway API Gateway resource created or referenced
122+
by the chart.
123+
*/}}
124+
{{- define "openshell.gatewayApiGatewayNamespace" -}}
125+
{{- if .Values.gatewayApi.gateway.create -}}
126+
{{- .Release.Namespace -}}
127+
{{- else -}}
128+
{{- .Values.gatewayApi.gateway.namespace | default .Release.Namespace -}}
129+
{{- end -}}
130+
{{- end }}
131+
112132
{{/*
113133
gRPC endpoint sandbox pods use to call back into the gateway. An explicit
114134
.Values.server.grpcEndpoint is used verbatim. Otherwise it is derived from
@@ -178,6 +198,12 @@ database requires persistent per-pod storage.
178198
Validate chart values that Helm would otherwise accept silently.
179199
*/}}
180200
{{- define "openshell.validateValues" -}}
201+
{{- if hasKey .Values "grpcRoute" -}}
202+
{{- fail "grpcRoute values were replaced by gatewayApi; configure Gateway creation under gatewayApi.gateway and GRPCRoute creation under gatewayApi.routes.grpc." -}}
203+
{{- end -}}
204+
{{- if and .Values.gatewayApi.routes.tls.enabled .Values.server.disableTls -}}
205+
{{- fail "gatewayApi.routes.tls.enabled requires server.disableTls=false because TLS passthrough forwards the encrypted connection to the gateway server." -}}
206+
{{- end -}}
181207
{{- $workloadKind := include "openshell.workloadKind" . -}}
182208
{{- $workload := .Values.workload | default dict -}}
183209
{{- $replicaCount := int (default 1 .Values.replicaCount) -}}
Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
{{- if and .Values.grpcRoute.enabled .Values.grpcRoute.gateway.create }}
4+
{{- if .Values.gatewayApi.gateway.create }}
55
apiVersion: gateway.networking.k8s.io/v1
66
kind: Gateway
77
metadata:
8-
name: {{ default (include "openshell.fullname" .) .Values.grpcRoute.gateway.name }}
9-
namespace: {{ .Release.Namespace }}
8+
name: {{ include "openshell.gatewayApiGatewayName" . }}
9+
namespace: {{ include "openshell.gatewayApiGatewayNamespace" . }}
1010
labels:
1111
{{- include "openshell.labels" . | nindent 4 }}
1212
spec:
13-
gatewayClassName: {{ .Values.grpcRoute.gateway.className }}
13+
gatewayClassName: {{ .Values.gatewayApi.gateway.className }}
1414
listeners:
15-
- name: {{ ternary "https" "http" (eq .Values.grpcRoute.gateway.listener.protocol "HTTPS") }}
16-
port: {{ .Values.grpcRoute.gateway.listener.port }}
17-
protocol: {{ .Values.grpcRoute.gateway.listener.protocol }}
18-
{{- if eq .Values.grpcRoute.gateway.listener.protocol "HTTPS" }}
19-
{{- if not .Values.grpcRoute.gateway.listener.tls.certificateRefs }}
20-
{{- fail "grpcRoute.gateway.listener.tls.certificateRefs is required when grpcRoute.gateway.listener.protocol is HTTPS" }}
21-
{{- end }}
22-
{{- if not .Values.server.disableTls }}
23-
{{- fail "grpcRoute.gateway.listener.protocol=HTTPS terminates TLS at Envoy Gateway, which forwards plaintext gRPC to the gateway pod; set server.disableTls=true so the pod listens plaintext (this chart does not render a BackendTLSPolicy for re-encryption to a TLS backend)" }}
24-
{{- end }}
15+
- name: http
16+
port: 80
17+
protocol: HTTP
18+
{{- if .Values.gatewayApi.routes.tls.enabled }}
19+
- name: tls
20+
port: 443
21+
protocol: TLS
2522
tls:
26-
mode: Terminate
27-
certificateRefs:
28-
{{- toYaml .Values.grpcRoute.gateway.listener.tls.certificateRefs | nindent 10 }}
29-
{{- end }}
30-
allowedRoutes:
31-
namespaces:
32-
from: {{ .Values.grpcRoute.gateway.listener.allowedRoutes }}
23+
mode: Passthrough
24+
{{- end }}
3325
{{- end }}

deploy/helm/openshell/templates/grpcroute.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
{{- if .Values.grpcRoute.enabled }}
4+
{{- if .Values.gatewayApi.routes.grpc.enabled }}
55
apiVersion: gateway.networking.k8s.io/v1
66
kind: GRPCRoute
77
metadata:
@@ -11,11 +11,11 @@ metadata:
1111
{{- include "openshell.labels" . | nindent 4 }}
1212
spec:
1313
parentRefs:
14-
- name: {{ default (include "openshell.fullname" .) .Values.grpcRoute.gateway.name }}
15-
namespace: {{ default .Release.Namespace .Values.grpcRoute.gateway.namespace }}
16-
{{- if .Values.grpcRoute.hostnames }}
14+
- name: {{ include "openshell.gatewayApiGatewayName" . }}
15+
namespace: {{ include "openshell.gatewayApiGatewayNamespace" . }}
16+
{{- if .Values.gatewayApi.routes.grpc.hostnames }}
1717
hostnames:
18-
{{- toYaml .Values.grpcRoute.hostnames | nindent 4 }}
18+
{{- toYaml .Values.gatewayApi.routes.grpc.hostnames | nindent 4 }}
1919
{{- end }}
2020
rules:
2121
- backendRefs:
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
{{- if .Values.gatewayApi.routes.tls.enabled }}
5+
apiVersion: gateway.networking.k8s.io/v1
6+
kind: TLSRoute
7+
metadata:
8+
name: {{ include "openshell.fullname" . }}
9+
namespace: {{ .Release.Namespace }}
10+
labels:
11+
{{- include "openshell.labels" . | nindent 4 }}
12+
spec:
13+
parentRefs:
14+
- name: {{ include "openshell.gatewayApiGatewayName" . }}
15+
namespace: {{ include "openshell.gatewayApiGatewayNamespace" . }}
16+
{{- if .Values.gatewayApi.routes.tls.hostnames }}
17+
hostnames:
18+
{{- toYaml .Values.gatewayApi.routes.tls.hostnames | nindent 4 }}
19+
{{- end }}
20+
rules:
21+
- backendRefs:
22+
- name: {{ include "openshell.fullname" . }}
23+
port: {{ .Values.service.port }}
24+
{{- end }}

0 commit comments

Comments
 (0)