Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3e73552
feat(helm): add generic gateway TOML serializer
gmenher Sep 15, 2026
7eb6ee7
docs(architecture): define Helm gateway configuration boundary
gmenher Sep 15, 2026
793251f
feat(helm): define default gateway configuration map
gmenher Sep 15, 2026
41a6b49
refactor(helm): render gateway config from values map
gmenher Sep 15, 2026
3553da7
test(helm): cover generic gateway TOML rendering
gmenher Sep 15, 2026
385327f
feat(helm): protect gateway config secret boundary
gmenher Sep 15, 2026
864424c
docs(helm): classify legacy gateway configuration values
gmenher Sep 15, 2026
5a90c18
refactor(helm): derive dual-use resources from gateway config
gmenher Sep 15, 2026
276033b
test(helm): migrate gateway config scenarios
gmenher Sep 15, 2026
1a87694
refactor(helm): derive credential resources from gateway config
gmenher Sep 15, 2026
8f1c91d
refactor(helm): migrate gateway config overlays
gmenher Sep 15, 2026
00e64a7
refactor(helm): remove gateway configuration shadow values
gmenher Sep 15, 2026
ba37e7e
refactor(helm): align runtime config with resources
gmenher Sep 15, 2026
2369950
docs(helm): add gateway config migration guide
gmenher Sep 15, 2026
50f5d5f
test(helm): validate rendered gateway config
gmenher Sep 15, 2026
3e27264
test(helm): cover config resource coherence
gmenher Sep 15, 2026
0d71cb0
fix(helm): make Kubernetes E2E deployable
gmenher Sep 15, 2026
345fcf9
fix(e2e): pass host aliases through gateway config
gmenher Sep 16, 2026
cc55573
docs(e2e): reference gateway config host alias
gmenher Sep 16, 2026
b9a5ffd
fix(helm): enforce gateway resource ownership
gmenher Sep 16, 2026
179874c
test(e2e): cover chart host gateway input
gmenher Sep 16, 2026
5a8067b
fix(helm): address gateway config review findings
gmenher Sep 16, 2026
018726a
fix(helm): repair RFC 0012 migration integration
gmenher Sep 16, 2026
125c1f2
fix(e2e): align Kubernetes parity with RFC 0012
gmenher Sep 16, 2026
5b733fe
fix(helm): complete gateway config migration
gmenher Sep 18, 2026
1926606
chore(helm): add SPDX header to TOML template
gmenher Sep 21, 2026
e52c9ba
fix(helm): preserve legacy gateway configuration aliases
gmenher Sep 23, 2026
f9a9346
fix(helm): complete legacy gateway config compatibility
gmenher Sep 30, 2026
8394486
feat(helm): support gateway config arrays of tables
gmenher Oct 2, 2026
4329258
fix(helm): preserve sandbox identity aliases
gmenher Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/openshell-server/src/config_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,17 @@ mod tests {
}
}

#[test]
fn gateway_rate_limits_reject_negative_values_during_toml_parsing() {
for field in ["grpc_rate_limit_requests", "grpc_rate_limit_window_seconds"] {
let tmp = write_tmp(&format!("[openshell.gateway]\n{field} = -1\n"));
assert!(
matches!(load(tmp.path()), Err(ConfigFileError::Parse { .. })),
"{field} must reject negative values because gateway rate limits are unsigned"
);
}
}

#[test]
fn canonical_compute_driver_is_singular() {
let file: ConfigFile = toml::from_str(
Expand Down
52 changes: 43 additions & 9 deletions deploy/helm/openshell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,37 @@ database. The chart creates a retained Kubernetes Secret with the shared
key-encryption key and injects that key into every gateway pod, so the same
default works for single-replica and external database-backed HA deployments.

Use `kubernetes-secrets` or `vault` instead when credentials should live in a
cluster or external secret backend. Enabling one external credential driver
disables the default credential-storage key-encryption key Secret and env injection.
Use `gatewayConfig` to select `kubernetes-secrets` or `vault` when credentials
should live in a cluster or external secret backend. Selecting an external
credential driver disables the default credential-storage key-encryption-key
Secret and environment injection. The map is rendered directly as gateway TOML,
so it uses the gateway's snake_case field names:

```yaml
gatewayConfig:
openshell.gateway:
credential_drivers:
- vault
openshell.credential_drivers.vault:
address: https://vault.vault.svc.cluster.local:8200
mount: secret
kv_version: "2"
auth_method: kubernetes
role: openshell-gateway
```

> `gatewayConfig` must contain only non-secret values. Helm serializes unknown
> fields generically and cannot determine whether an arbitrary string, such as
> `api_token`, is confidential. Do not put passwords, tokens, private keys,
> database URLs, or other secret material in this map. Use Secret-backed
> environment variables, files, volumes, or gateway credential drivers instead.
> The chart rejects known unsafe forms such as `database_url`, inline URL
> credentials, and PEM private keys; it is not a general secret scanner.

For the Kubernetes Secret driver, use
`openshell.credential_drivers.kubernetes-secrets.namespace` in the same map.
The chart derives any required RBAC from the selected driver; use a dedicated
namespace to limit access to OpenShell-managed Secrets.

#### OpenShift

Expand Down Expand Up @@ -291,12 +319,16 @@ JWT signing Secret.

## SPIFFE/SPIRE provider token grants

Set `server.providerTokenGrants.spiffe.enabled=true` to let the gateway and
sandbox supervisors use SPIFFE JWT-SVIDs for dynamic provider token grants. The
chart keeps supervisor-to-gateway authentication on gateway-minted sandbox JWTs,
mounts the SPIFFE CSI socket into the gateway pod, exports
`OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET`, and passes the socket path to
the Kubernetes driver so sandbox pods can mount the same socket.
Set `gatewayConfig.openshell.drivers.kubernetes.provider_spiffe_workload_api_socket_path`
to let sandbox supervisors use SPIFFE JWT-SVIDs for dynamic provider token
grants. The chart keeps supervisor-to-gateway authentication on gateway-minted
sandbox JWTs and passes the configured socket path to the Kubernetes driver.

```yaml
gatewayConfig:
openshell.drivers.kubernetes:
provider_spiffe_workload_api_socket_path: /spiffe-workload-api/spire-agent.sock
```

For local development, uncomment the SPIRE Helm releases in `skaffold.yaml` and
add `ci/values-spire.yaml` to the OpenShell release values files.
Expand All @@ -319,12 +351,14 @@ discovery endpoint or its TLS CA.
| 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. |
| certManager.serverIpAddresses | list | `["127.0.0.1"]` | IP SANs on the cert-manager-issued server certificate. |
| certManager.serverIssuerRef | object | `{"group":"","kind":"","name":""}` | Override the issuerRef for the external server Certificate (e.g. a real LetsEncrypt/ACME ClusterIssuer for a publicly-trusted cert on an external hostname). When set, the chart creates a second server certificate from this issuer with only the hostnames in serverDnsNames; the internal server certificate is always signed by the chart's own CA. Leave name empty to use the chart CA for all server certificates (default). Requires certManager.enabled=true. |
| credentialDrivers.vault.caConfigMapName | string | `""` | ConfigMap containing the private Vault/OpenBao CA certificate under the ca.crt key. Helm mounts it only when the Vault driver is selected. |
| fullnameOverride | string | `""` | Override the full generated resource name. |
| gateway.image.digest | string | `""` | Gateway image digest. When set, this takes precedence over tag. |
| gateway.image.pullPolicy | string | `nil` | Gateway image pull policy. Empty uses global.image.pullPolicy. |
| gateway.image.registry | string | `""` | Gateway image registry. Empty uses global.image.registry. |
| gateway.image.repository | string | `"openshell/gateway"` | Gateway image repository. |
| gateway.image.tag | string | `""` | Gateway image tag. Defaults to the chart appVersion when empty. |
| gatewayConfig | object | `{"openshell":{"version":2}}` | Non-secret gateway application configuration. Top-level keys name TOML tables and are rendered into the mounted gateway.toml file. Kubernetes resource inputs remain outside this map; template expressions derive the corresponding runtime values from their resource owner. |
| global.image.pullPolicy | string | `"IfNotPresent"` | Shared OpenShell image pull policy. Individual image pull policies take precedence. |
| global.image.registry | string | `"ghcr.io/nvidia"` | Shared OpenShell image registry. Individual image registries take precedence. |
| global.image.tag | string | `""` | Shared OpenShell image tag. Defaults to the chart appVersion when empty. |
Expand Down
50 changes: 41 additions & 9 deletions deploy/helm/openshell/README.md.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,37 @@ database. The chart creates a retained Kubernetes Secret with the shared
key-encryption key and injects that key into every gateway pod, so the same
default works for single-replica and external database-backed HA deployments.

Use `kubernetes-secrets` or `vault` instead when credentials should live in a
cluster or external secret backend. Enabling one external credential driver
disables the default credential-storage key-encryption key Secret and env injection.
Use `gatewayConfig` to select `kubernetes-secrets` or `vault` when credentials
should live in a cluster or external secret backend. Selecting an external
credential driver disables the default credential-storage key-encryption-key
Secret and environment injection. The map is rendered directly as gateway TOML,
so it uses the gateway's snake_case field names:

```yaml
gatewayConfig:
openshell.gateway:
credential_drivers:
- vault
openshell.credential_drivers.vault:
address: https://vault.vault.svc.cluster.local:8200
mount: secret
kv_version: "2"
auth_method: kubernetes
role: openshell-gateway
```

> `gatewayConfig` must contain only non-secret values. Helm serializes unknown
> fields generically and cannot determine whether an arbitrary string, such as
> `api_token`, is confidential. Do not put passwords, tokens, private keys,
> database URLs, or other secret material in this map. Use Secret-backed
> environment variables, files, volumes, or gateway credential drivers instead.
> The chart rejects known unsafe forms such as `database_url`, inline URL
> credentials, and PEM private keys; it is not a general secret scanner.

For the Kubernetes Secret driver, use
`openshell.credential_drivers.kubernetes-secrets.namespace` in the same map.
The chart derives any required RBAC from the selected driver; use a dedicated
namespace to limit access to OpenShell-managed Secrets.

#### OpenShift

Expand Down Expand Up @@ -292,12 +320,16 @@ JWT signing Secret.

## SPIFFE/SPIRE provider token grants

Set `server.providerTokenGrants.spiffe.enabled=true` to let the gateway and
sandbox supervisors use SPIFFE JWT-SVIDs for dynamic provider token grants. The
chart keeps supervisor-to-gateway authentication on gateway-minted sandbox JWTs,
mounts the SPIFFE CSI socket into the gateway pod, exports
`OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET`, and passes the socket path to
the Kubernetes driver so sandbox pods can mount the same socket.
Set `gatewayConfig.openshell.drivers.kubernetes.provider_spiffe_workload_api_socket_path`
to let sandbox supervisors use SPIFFE JWT-SVIDs for dynamic provider token
grants. The chart keeps supervisor-to-gateway authentication on gateway-minted
sandbox JWTs and passes the configured socket path to the Kubernetes driver.

```yaml
gatewayConfig:
openshell.drivers.kubernetes:
provider_spiffe_workload_api_socket_path: /spiffe-workload-api/spire-agent.sock
```

For local development, uncomment the SPIRE Helm releases in `skaffold.yaml` and
add `ci/values-spire.yaml` to the OpenShell release values files.
Expand Down
13 changes: 8 additions & 5 deletions deploy/helm/openshell/ci/values-corporate-proxy-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

# The Kubernetes corporate-proxy e2e wrapper supplies the generated proxy URL
# and creates `openshell-e2e-proxy-auth` before Helm installs the gateway.
upstreamProxy:
authSecret:
name: openshell-e2e-proxy-auth
key: proxy-auth
authAllowInsecure: true
gatewayConfig:
openshell.drivers.kubernetes:
# The e2e wrapper replaces this endpoint with its dynamically allocated
# port. Keep the overlay valid when rendered independently as well.
https_proxy: http://host.openshell.internal:8080
proxy_auth_secret_name: openshell-e2e-proxy-auth
proxy_auth_secret_key: proxy-auth
proxy_auth_allow_insecure: true
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
# Use with:
# skaffold run -p credential-driver-kubernetes-secrets
#
server:
credentialDrivers:
kubernetesSecrets:
enabled: true
namespace: openshell
gatewayConfig:
openshell.gateway:
credential_drivers:
- kubernetes-secrets
openshell.credential_drivers.kubernetes-secrets:
namespace: openshell
26 changes: 15 additions & 11 deletions deploy/helm/openshell/ci/values-credential-driver-vault.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@
#
# The profile assumes another process has already deployed a Vault-compatible
# backend. Local e2e validation deploys OpenBao in the `openbao` namespace with
# TLS enabled, publishes its private CA in the `openbao-ca` ConfigMap, and
# creates an `openbao-0` DNS alias matching the OpenBao dev certificate. It also
# configures a Kubernetes auth role named `openshell-gateway` bound to the
# OpenShell gateway ServiceAccount in the `openshell` namespace.
# a Kubernetes auth role named `openshell-gateway` bound to the OpenShell
# gateway ServiceAccount in the `openshell` namespace.

server:
credentialDrivers:
vault:
enabled: true
address: https://openbao-0:8200
caConfigMapName: openbao-ca
role: openshell-gateway
gatewayConfig:
openshell.gateway:
credential_drivers:
- vault
openshell.credential_drivers.vault:
# The local E2E fixture exposes this alias in the gateway namespace. It
# also matches the DNS SAN in OpenBao's development TLS certificate.
address: https://openbao-0:8200
role: openshell-gateway

credentialDrivers:
vault:
caConfigMapName: openbao-ca
4 changes: 3 additions & 1 deletion deploy/helm/openshell/ci/values-gateway-tls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ grpcRoute:
server:
# Envoy terminates TLS at the edge; the gateway listens plaintext behind it.
disableTls: true
oidc:

gatewayConfig:
openshell.gateway.oidc:
issuer: "https://keycloak.example.com/realms/openshell"
audience: "openshell-cli"
19 changes: 12 additions & 7 deletions deploy/helm/openshell/ci/values-keycloak.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,23 @@
# CLI token acquisition: keep a port-forward running while using openshell login:
# kubectl -n keycloak port-forward svc/keycloak 9090:80

server:
oidc:
gatewayConfig:
openshell.gateway.oidc:
# Must match KC_HOSTNAME set by keycloak:k8s:setup (in-cluster service hostname).
issuer: "https://keycloak.keycloak.svc.cluster.local:443/realms/openshell"
caConfigMapName: "openshell-keycloak-ca"
# Must match the client ID in the imported realm (openshell-cli).
audience: "openshell-cli"
# Short TTL for dev so JWKS key rotation is picked up quickly.
# Use 3600 (default) in production.
jwksTtl: 60
jwks_ttl_secs: 60
# Keycloak puts realm roles at realm_access.roles in the JWT.
rolesClaim: "realm_access.roles"
roles_claim: "realm_access.roles"
# Leave both empty for authentication-only mode (any valid token is accepted).
adminRole: "openshell-admin"
userRole: "openshell-user"
admin_role: "openshell-admin"
user_role: "openshell-user"

# This is a Kubernetes ConfigMap mounted by the chart, not a gateway TOML
# field, so it remains a chart-owned resource reference.
server:
oidc:
caConfigMapName: "openshell-keycloak-ca"
15 changes: 8 additions & 7 deletions deploy/helm/openshell/ci/values-openshift-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,22 @@
# is true and mTLS is MANDATORY at the TLS handshake — a caller with only the
# Route URL and no client certificate is rejected before any RPC. The passthrough
# Route terminates TLS at the gateway pod, so this holds end-to-end.
# `allowUnauthenticatedUsers` only promotes the already cert-verified caller to a
# `allow_unauthenticated_users` only promotes the already cert-verified caller to a
# dev principal at the app layer (mtls_auth is unsupported with the Kubernetes
# driver). Both are required together; the client certificate is the access gate.
gateway:
image:
pullPolicy: Always

supervisor:
image:
pullPolicy: Always

server:
disableTls: false
auth:
allowUnauthenticatedUsers: true

gatewayConfig:
openshell.gateway.auth:
allow_unauthenticated_users: true
openshell.drivers.kubernetes:
sandbox_runtime_image_pull_policy: always
supervisor_image_pull_policy: always

openshiftRoute:
enabled: true
Expand Down
8 changes: 8 additions & 0 deletions deploy/helm/openshell/ci/values-skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
# SPDX-License-Identifier: Apache-2.0

# Merge with values.yaml for Skaffold-driven local image builds (see skaffold.yaml).
gatewayConfig:
openshell.drivers.kubernetes:
image_pull_policy: if_not_present
openshell.gateway.otlp:
endpoint: http://openshell-collector.observability.svc.cluster.local:4317
openshell.gateway.auth:
allow_unauthenticated_users: true

server:
otlp:
endpoint: http://openshell-collector.observability.svc.cluster.local:4317
Expand Down
8 changes: 3 additions & 5 deletions deploy/helm/openshell/ci/values-spire.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
# SPDX-License-Identifier: Apache-2.0

# OpenShell overlay for local SPIRE-backed provider token grants.
server:
providerTokenGrants:
spiffe:
enabled: true
workloadApiSocketPath: /spiffe-workload-api/spire-agent.sock
gatewayConfig:
openshell.drivers.kubernetes:
provider_spiffe_workload_api_socket_path: /spiffe-workload-api/spire-agent.sock
11 changes: 5 additions & 6 deletions deploy/helm/openshell/ci/values-workspace-managed.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
#
# E2E overlay: deploy the gateway in managed workspace mode.
# Sandbox namespaces are auto-created as openshell-{gateway_id}-{workspace}.
server:
sandboxImagePullSecrets:
- name: e2e-regcred
drivers:
kubernetes:
workspaceMode: "managed"
gatewayConfig:
openshell.drivers.kubernetes:
image_pull_secrets:
- e2e-regcred
workspace_mode: managed
9 changes: 4 additions & 5 deletions deploy/helm/openshell/ci/values-workspace-operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
#
# E2E overlay: deploy the gateway in operator workspace mode.
# Namespaces must be pre-provisioned and labeled before sandbox creation.
server:
drivers:
kubernetes:
workspaceMode: "operator"
operatorNamespaceLabel: "openshell.ai/e2e-operator-workspace=true"
gatewayConfig:
openshell.drivers.kubernetes:
workspace_mode: operator
operator_namespace_label: "openshell.ai/e2e-operator-workspace=true"
1 change: 1 addition & 0 deletions deploy/helm/openshell/skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ deploy:
supervisor.image.tag: '{{.IMAGE_TAG_openshell_supervisor}}'
sandboxRuntime.image.repository: '{{.IMAGE_REPO_openshell_sandbox}}'
sandboxRuntime.image.tag: '{{.IMAGE_TAG_openshell_sandbox}}'
gatewayConfig.openshell\\.drivers\\.kubernetes.supervisor_image: '{{.IMAGE_REPO_openshell_supervisor}}:{{.IMAGE_TAG_openshell_supervisor}}'
profiles:
# Full HA test path: installs Envoy Gateway and layers both HA replicas and
# Gateway API routing values onto the OpenShell release.
Expand Down
Loading
Loading