diff --git a/.github/workflows/helm-lint.yml b/.github/workflows/helm-lint.yml index 78739793c..9f3e2fbcb 100644 --- a/.github/workflows/helm-lint.yml +++ b/.github/workflows/helm-lint.yml @@ -9,6 +9,10 @@ on: - "pull-request/[0-9]+" paths: - "deploy/helm/**" + - "mise.toml" + - "mise.lock" + - "tasks/helm.toml" + - ".github/workflows/helm-lint.yml" workflow_dispatch: env: @@ -56,5 +60,8 @@ jobs: - name: Lint Helm chart run: mise run helm:lint + - name: Check Helm chart README + run: mise run helm:docs:check + - name: Run Helm chart unit tests run: mise run helm:test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c091c6c4..9dddac69a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -174,15 +174,16 @@ openshell sandbox create -- codex These are the primary `mise` tasks for day-to-day development: -| Task | Purpose | -| ------------------ | ------------------------------------------------------- | -| `mise run gateway` | Run a standalone gateway for local development | -| `mise run sandbox` | Create or reconnect to the dev sandbox | -| `mise run test` | Default test suite | -| `mise run e2e` | Default end-to-end test lane | -| `mise run ci` | Full local CI checks (lint, compile/type checks, tests) | -| `mise run docs` | Validate Fern docs locally | -| `mise run clean` | Clean build artifacts | +| Task | Purpose | +| -------------------- | ------------------------------------------------------- | +| `mise run gateway` | Run a standalone gateway for local development | +| `mise run sandbox` | Create or reconnect to the dev sandbox | +| `mise run test` | Default test suite | +| `mise run e2e` | Default end-to-end test lane | +| `mise run ci` | Full local CI checks (lint, compile/type checks, tests) | +| `mise run docs` | Validate Fern docs locally | +| `mise run helm:docs` | Regenerate the Helm chart README | +| `mise run clean` | Clean build artifacts | ## Project Structure diff --git a/deploy/docker/Dockerfile.ci b/deploy/docker/Dockerfile.ci index ff947a886..77a8c94e2 100644 --- a/deploy/docker/Dockerfile.ci +++ b/deploy/docker/Dockerfile.ci @@ -4,7 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # CI runner image with all development tools pre-installed -# Rebuild triggered automatically when mise.toml or this file changes +# Rebuild triggered automatically when mise.toml, mise.lock, tasks, or this file changes FROM nvcr.io/nvidia/base/ubuntu:noble-20251013 diff --git a/deploy/helm/openshell/.helmignore b/deploy/helm/openshell/.helmignore index a12325802..0aecc346a 100644 --- a/deploy/helm/openshell/.helmignore +++ b/deploy/helm/openshell/.helmignore @@ -18,5 +18,6 @@ .vscode/ # Ignore development files +README.md.gotmpl skaffold.yaml ci/ diff --git a/deploy/helm/openshell/README.md b/deploy/helm/openshell/README.md index cc856731d..c5484684b 100644 --- a/deploy/helm/openshell/README.md +++ b/deploy/helm/openshell/README.md @@ -1,6 +1,11 @@ # OpenShell Helm Chart -> **Experimental** — the Kubernetes deployment path is under active development. Expect rough edges and breaking changes. + + +> **Experimental** - the Kubernetes deployment path is under active development. Expect rough edges and breaking changes. This chart deploys the OpenShell gateway into a Kubernetes cluster. It is published as an OCI artifact to GHCR at `oci://ghcr.io/nvidia/openshell/helm-chart`. @@ -8,19 +13,19 @@ This chart deploys the OpenShell gateway into a Kubernetes cluster. It is publis The Kubernetes Agent Sandbox CRDs and controller must be installed on the cluster before deploying OpenShell. Install them with: -```bash +```shell kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/latest/download/manifest.yaml ``` ## Install on Kubernetes -```bash +```shell helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version ``` ## Install on OpenShift -```bash +```shell # Precreate the openshell namespace so we can create the SCC cluster role oc create ns openshell @@ -47,11 +52,11 @@ The `dev` tags are intended for testing changes ahead of a release. Production d ## Configuration -See [`values.yaml`](values.yaml) for configurable values. Selected overlays: +See [`values.yaml`](values.yaml) for source defaults. Selected overlays: -- [`ci/values-gateway.yaml`](ci/values-gateway.yaml) — gateway-only configuration -- [`ci/values-cert-manager.yaml`](ci/values-cert-manager.yaml) — cert-manager integration -- [`ci/values-keycloak.yaml`](ci/values-keycloak.yaml) — Keycloak OIDC integration +- [`ci/values-gateway.yaml`](ci/values-gateway.yaml) - gateway-only configuration +- [`ci/values-cert-manager.yaml`](ci/values-cert-manager.yaml) - cert-manager integration +- [`ci/values-keycloak.yaml`](ci/values-keycloak.yaml) - Keycloak OIDC integration ## PKI bootstrap @@ -62,11 +67,100 @@ openssl/alpine sidecar). The Job is idempotent: -- Both target Secrets exist → log and exit 0. -- Exactly one exists → fail with `kubectl delete secret -n ` recovery hint. -- Neither exists → generate a CA, server cert, and client cert; POST both `kubernetes.io/tls` - Secrets (`tls.crt`, `tls.key`, `ca.crt`). +- Both target Secrets exist: log and exit 0. +- Exactly one exists: fail with `kubectl delete secret -n ` recovery hint. +- Neither exists: generate a CA, server cert, and client cert; POST both `kubernetes.io/tls` Secrets (`tls.crt`, `tls.key`, `ca.crt`). Disable with `--set pkiInitJob.enabled=false` when bringing your own PKI (cert-manager, external CA, or pre-created Secrets). See `certManager.*` in `values.yaml` for the cert-manager alternative. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Affinity rules for the gateway pod. | +| certManager.caSecretName | string | `"openshell-ca-tls"` | Secret created for the intermediate CA (Certificate with isCA: true). | +| certManager.certificateDuration | string | `"8760h"` | Duration for cert-manager-issued certificates. | +| certManager.certificateRenewBefore | string | `"720h"` | Renewal window for cert-manager-issued certificates. | +| certManager.clientCaFromServerTlsSecret | bool | `true` | Mount gateway client CA from the server TLS secret's ca.crt (populated by cert-manager for certs issued by a CA Issuer). Avoids a separate openshell-server-client-ca Secret. | +| certManager.enabled | bool | `false` | Create cert-manager Issuer and Certificate resources instead of using the PKI bootstrap Job. | +| 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. | +| fullnameOverride | string | `""` | Override the full generated resource name. | +| grpcRoute.enabled | bool | `false` | Create a Gateway API GRPCRoute for the gateway service. | +| grpcRoute.gateway.className | string | `"eg"` | GatewayClass to reference. Envoy Gateway installs one named "eg". | +| 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. | +| grpcRoute.gateway.listener.allowedRoutes | string | `"Same"` | "Same" restricts attached routes to the release namespace; "All" allows any namespace. | +| grpcRoute.gateway.listener.port | int | `80` | Listener port for the generated Gateway resource. | +| grpcRoute.gateway.listener.protocol | string | `"HTTP"` | Listener protocol for the generated Gateway resource. | +| grpcRoute.gateway.name | string | `""` | Name of the Gateway resource. Defaults to the chart fullname. | +| grpcRoute.gateway.namespace | string | `""` | Namespace of the Gateway referenced by the GRPCRoute parentRef. Defaults to the release namespace. | +| grpcRoute.hostnames | list | `[]` | Hostnames the GRPCRoute matches on. Leave empty to match all hosts. | +| image.pullPolicy | string | `"IfNotPresent"` | Gateway image pull policy. | +| image.repository | string | `"ghcr.io/nvidia/openshell/gateway"` | Gateway image repository. | +| image.tag | string | `""` | Gateway image tag. Defaults to the chart appVersion when empty. | +| imagePullSecrets | list | `[]` | Image pull secrets attached to gateway and helper pods. | +| nameOverride | string | `"openshell"` | Override the chart name used in generated resource names. | +| networkPolicy.enabled | bool | `true` | Create a NetworkPolicy restricting SSH ingress on sandbox pods to the gateway. | +| nodeSelector | object | `{}` | Node selector for the gateway pod. | +| pkiInitJob.enabled | bool | `true` | Run a pre-install/pre-upgrade Job that creates gateway and client mTLS Secrets. | +| pkiInitJob.serverDnsNames | list | `[]` | Extra DNS SANs to append to the server certificate. | +| pkiInitJob.serverIpAddresses | list | `[]` | Extra IP SANs to append to the server certificate. | +| podAnnotations | object | `{}` | Extra annotations to add to the gateway pod. | +| podLabels | object | `{}` | Extra labels to add to the gateway pod. | +| podLifecycle.terminationGracePeriodSeconds | int | `5` | Grace period, in seconds, before Kubernetes terminates the gateway pod. | +| podSecurityContext.fsGroup | int | `1000` | fsGroup assigned to the gateway pod. | +| probes.liveness.failureThreshold | int | `3` | Liveness probe failure threshold before the container is restarted. | +| probes.liveness.initialDelaySeconds | int | `2` | Liveness probe initial delay, in seconds. | +| probes.liveness.periodSeconds | int | `5` | Liveness probe period, in seconds. | +| probes.liveness.timeoutSeconds | int | `1` | Liveness probe timeout, in seconds. | +| probes.readiness.failureThreshold | int | `3` | Readiness probe failure threshold before the pod is marked not ready. | +| probes.readiness.initialDelaySeconds | int | `1` | Readiness probe initial delay, in seconds. | +| probes.readiness.periodSeconds | int | `2` | Readiness probe period, in seconds. | +| probes.readiness.timeoutSeconds | int | `1` | Readiness probe timeout, in seconds. | +| probes.startup.failureThreshold | int | `30` | Startup probe failure threshold before the container is killed. | +| probes.startup.periodSeconds | int | `2` | Startup probe period, in seconds. | +| probes.startup.timeoutSeconds | int | `1` | Startup probe timeout, in seconds. | +| replicaCount | int | `1` | Number of OpenShell gateway replicas. | +| resources | object | `{}` | Gateway pod resource requests and limits. | +| securityContext.allowPrivilegeEscalation | bool | `false` | Whether the gateway container can gain additional privileges. | +| securityContext.capabilities.drop | list | `["ALL"]` | Linux capabilities dropped from the gateway container. | +| securityContext.runAsNonRoot | bool | `true` | Require the gateway container to run as a non-root user. | +| securityContext.runAsUser | int | `1000` | UID assigned to the gateway container. | +| server.dbUrl | string | `"sqlite:/var/openshell/openshell.db"` | Gateway database URL. | +| server.disableTls | bool | `false` | Disable TLS entirely - the server listens on plaintext HTTP. Set to true when a reverse proxy / tunnel terminates TLS at the edge. | +| server.enableLoopbackServiceHttp | bool | `true` | Enable plaintext HTTP routing for loopback sandbox service URLs on TLS-enabled gateways. | +| server.enableUserNamespaces | bool | `false` | Enable Kubernetes user namespace isolation (hostUsers: false) for sandbox pods. Requires Kubernetes 1.33+ with user namespace support available (beta through 1.35, GA in 1.36+), plus a supporting container runtime and Linux 5.12+. When enabled, container UID 0 maps to an unprivileged host UID and capabilities become namespaced. | +| server.grpcEndpoint | string | `""` | gRPC endpoint sandboxes call back into the gateway. Leave empty to derive it from the chart fullname, release namespace, service port, and disableTls flag, for example https://openshell.openshell.svc.cluster.local:8080. Override only when sandboxes must reach the gateway via a different hostname (e.g. an external ingress or a host alias). | +| server.hostGatewayIP | string | `""` | Host gateway IP for sandbox pod hostAliases. When set, sandbox pods get hostAliases entries mapping host.docker.internal and host.openshell.internal to this IP, allowing them to reach services running on the Docker host. Auto-detected by the cluster entrypoint script. | +| server.logLevel | string | `"info"` | Gateway log level. | +| server.oidc.adminRole | string | `""` | Role name for admin access. Leave empty (with userRole also empty) for authentication-only mode. Both must be set or both empty. | +| server.oidc.audience | string | `"openshell-cli"` | Expected audience claim for the API resource server. This should match the server's --oidc-audience, NOT the CLI client ID. | +| server.oidc.caConfigMapName | string | `""` | Name of a ConfigMap containing a CA certificate bundle (key: ca.crt) for verifying the OIDC issuer's TLS certificate. Required when the issuer uses a non-public CA (e.g. OpenShift ingress, private PKI). | +| server.oidc.issuer | string | `""` | OIDC issuer URL (e.g. https://keycloak.example.com/realms/openshell). | +| server.oidc.jwksTtl | int | `3600` | JWKS key cache TTL in seconds. | +| server.oidc.rolesClaim | string | `""` | Dot-separated path to the roles array in the JWT claims. Keycloak: "realm_access.roles", Entra ID: "roles", Okta: "groups". | +| server.oidc.scopesClaim | string | `""` | Dot-separated path to the scopes array in the JWT claims. | +| server.oidc.userRole | string | `""` | Role name for standard user access. | +| server.sandboxImage | string | `"ghcr.io/nvidia/openshell-community/sandboxes/base:latest"` | Default sandbox image used when requests do not specify one. | +| server.sandboxImagePullPolicy | string | `""` | Kubernetes imagePullPolicy for sandbox pods. Empty = Kubernetes default (Always for :latest, IfNotPresent otherwise). Set to "Always" for dev clusters so new images are picked up without manual eviction. | +| server.sandboxNamespace | string | `""` | Namespace where sandbox pods are created. Defaults to the Helm release namespace (.Release.Namespace) when left empty. | +| server.tls.certSecretName | string | `"openshell-server-tls"` | K8s secret (type kubernetes.io/tls) with tls.crt and tls.key for the server. | +| server.tls.clientCaSecretName | string | `"openshell-server-client-ca"` | K8s secret with ca.crt for client certificate verification (mTLS). Set to "" to disable mTLS and run HTTPS-only (use OIDC for auth instead). | +| server.tls.clientTlsSecretName | string | `"openshell-client-tls"` | K8s secret mounted into sandbox pods for mTLS to the server. | +| service.healthPort | int | `8081` | Gateway health service port. | +| service.metricsPort | int | `9090` | Gateway metrics service port. | +| service.port | int | `8080` | Gateway gRPC/HTTP service port. | +| service.type | string | `"ClusterIP"` | Kubernetes Service type for the gateway. | +| serviceAccount.annotations | object | `{}` | Annotations to add to the generated service account. | +| serviceAccount.create | bool | `true` | Create a service account for the gateway. | +| serviceAccount.name | string | `""` | Existing service account name to use when serviceAccount.create is false. | +| supervisor.image.pullPolicy | string | `""` | Supervisor image pull policy. Defaults to the gateway image pull policy when empty. | +| supervisor.image.repository | string | `"ghcr.io/nvidia/openshell/supervisor"` | Supervisor image repository. | +| supervisor.image.tag | string | `""` | Supervisor image tag. Defaults to the chart appVersion when empty. | +| supervisor.sideloadMethod | string | `""` | How the supervisor binary is delivered into sandbox pods. Empty (default) = auto-detect from cluster version: K8s >= v1.35 -> "image-volume" (ImageVolume enabled by default; GA in v1.36) K8s < v1.35 -> "init-container" (copies via init container + emptyDir) On K8s v1.33-v1.34 with the ImageVolume feature gate manually enabled, set this to "image-volume" explicitly. | +| tolerations | list | `[]` | Tolerations for the gateway pod. | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/deploy/helm/openshell/README.md.gotmpl b/deploy/helm/openshell/README.md.gotmpl new file mode 100644 index 000000000..5068d6848 --- /dev/null +++ b/deploy/helm/openshell/README.md.gotmpl @@ -0,0 +1,79 @@ +# OpenShell Helm Chart + + + +> **Experimental** - the Kubernetes deployment path is under active development. Expect rough edges and breaking changes. + +This chart deploys the OpenShell gateway into a Kubernetes cluster. It is published as an OCI artifact to GHCR at `oci://ghcr.io/nvidia/openshell/helm-chart`. + +## Prerequisites + +The Kubernetes Agent Sandbox CRDs and controller must be installed on the cluster before deploying OpenShell. Install them with: + +```shell +kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/latest/download/manifest.yaml +``` + +## Install on Kubernetes + +```shell +helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version +``` + +## Install on OpenShift + +```shell +# Precreate the openshell namespace so we can create the SCC cluster role +oc create ns openshell + +# Sandboxes are deployed into the openshell namespace and use the default service account for now +oc adm policy add-scc-to-user privileged -z default -n openshell + +# Deploy openshell with overrides to allow SCC assignment of fsGroup and runAsUser for the gateway +helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version -n openshell \ + --set pkiInitJob.enabled=false \ + --set server.disableTls=true \ + --set podSecurityContext.fsGroup=null \ + --set securityContext.runAsUser=null +``` + +## Available versions + +| Tag | Source | Notes | +| --- | --- | --- | +| `` (e.g. `0.6.0`) | Tagged GitHub release | Tracks the matching gateway and supervisor image versions. Recommended for production. | +| `0.0.0-dev` | Latest commit on `main` | Floating tag, overwritten on every push. `appVersion` is `dev`, so images resolve to the `:dev` tag. | +| `0.0.0-dev.` | A specific commit on `main` | Per-commit pin. Chart version and `appVersion` both use the full 40-character commit SHA, which matches the image tag pushed by CI. | + +The `dev` tags are intended for testing changes ahead of a release. Production deployments should pin to a tagged release. + +## Configuration + +See [`values.yaml`](values.yaml) for source defaults. Selected overlays: + +- [`ci/values-gateway.yaml`](ci/values-gateway.yaml) - gateway-only configuration +- [`ci/values-cert-manager.yaml`](ci/values-cert-manager.yaml) - cert-manager integration +- [`ci/values-keycloak.yaml`](ci/values-keycloak.yaml) - Keycloak OIDC integration + +## PKI bootstrap + +By default, a pre-install/pre-upgrade hook Job runs `openshell-gateway generate-certs` +to create the gateway's server and client mTLS Secrets. The Job uses the gateway image +itself, so air-gapped environments only need to mirror that one image (no separate +openssl/alpine sidecar). + +The Job is idempotent: + +- Both target Secrets exist: log and exit 0. +- Exactly one exists: fail with `kubectl delete secret -n ` recovery hint. +- Neither exists: generate a CA, server cert, and client cert; POST both `kubernetes.io/tls` Secrets (`tls.crt`, `tls.key`, `ca.crt`). + +Disable with `--set pkiInitJob.enabled=false` when bringing your own PKI (cert-manager, +external CA, or pre-created Secrets). See `certManager.*` in `values.yaml` for the +cert-manager alternative. + +{{ template "chart.valuesSection" . }} +{{ template "helm-docs.versionFooter" . }} diff --git a/deploy/helm/openshell/values.yaml b/deploy/helm/openshell/values.yaml index c7fa50296..fd689c8bf 100644 --- a/deploy/helm/openshell/values.yaml +++ b/deploy/helm/openshell/values.yaml @@ -3,165 +3,209 @@ # Default values for OpenShell +# -- Number of OpenShell gateway replicas. replicaCount: 1 image: + # -- Gateway image repository. repository: ghcr.io/nvidia/openshell/gateway + # -- Gateway image pull policy. pullPolicy: IfNotPresent + # -- Gateway image tag. Defaults to the chart appVersion when empty. tag: "" -# Supervisor image — provides the openshell-sandbox binary injected into sandbox +# Supervisor image - provides the openshell-sandbox binary injected into sandbox # pods. tag defaults to appVersion (same as the gateway image) so both stay in # sync when the chart is released. supervisor: image: + # -- Supervisor image repository. repository: ghcr.io/nvidia/openshell/supervisor + # -- Supervisor image pull policy. Defaults to the gateway image pull policy when empty. pullPolicy: "" + # -- Supervisor image tag. Defaults to the chart appVersion when empty. tag: "" - # How the supervisor binary is delivered into sandbox pods. + # -- How the supervisor binary is delivered into sandbox pods. # Empty (default) = auto-detect from cluster version: - # K8s >= v1.35 → "image-volume" (ImageVolume enabled by default; GA in v1.36) - # K8s < v1.35 → "init-container" (copies via init container + emptyDir) + # K8s >= v1.35 -> "image-volume" (ImageVolume enabled by default; GA in v1.36) + # K8s < v1.35 -> "init-container" (copies via init container + emptyDir) # On K8s v1.33-v1.34 with the ImageVolume feature gate manually enabled, # set this to "image-volume" explicitly. sideloadMethod: "" +# -- Image pull secrets attached to gateway and helper pods. imagePullSecrets: [] +# -- Override the chart name used in generated resource names. nameOverride: "openshell" +# -- Override the full generated resource name. fullnameOverride: "" serviceAccount: + # -- Create a service account for the gateway. create: true + # -- Annotations to add to the generated service account. annotations: {} + # -- Existing service account name to use when serviceAccount.create is false. name: "" +# -- Extra annotations to add to the gateway pod. podAnnotations: {} +# -- Extra labels to add to the gateway pod. podLabels: {} podSecurityContext: + # -- fsGroup assigned to the gateway pod. fsGroup: 1000 securityContext: + # -- Require the gateway container to run as a non-root user. runAsNonRoot: true + # -- UID assigned to the gateway container. runAsUser: 1000 + # -- Whether the gateway container can gain additional privileges. allowPrivilegeEscalation: false capabilities: + # -- Linux capabilities dropped from the gateway container. drop: - ALL service: + # -- Kubernetes Service type for the gateway. type: ClusterIP + # -- Gateway gRPC/HTTP service port. port: 8080 + # -- Gateway health service port. healthPort: 8081 + # -- Gateway metrics service port. metricsPort: 9090 # Pod restart behavior and health probe tuning. podLifecycle: + # -- Grace period, in seconds, before Kubernetes terminates the gateway pod. terminationGracePeriodSeconds: 5 probes: startup: + # -- Startup probe period, in seconds. periodSeconds: 2 + # -- Startup probe timeout, in seconds. timeoutSeconds: 1 + # -- Startup probe failure threshold before the container is killed. failureThreshold: 30 liveness: + # -- Liveness probe initial delay, in seconds. initialDelaySeconds: 2 + # -- Liveness probe period, in seconds. periodSeconds: 5 + # -- Liveness probe timeout, in seconds. timeoutSeconds: 1 + # -- Liveness probe failure threshold before the container is restarted. failureThreshold: 3 readiness: + # -- Readiness probe initial delay, in seconds. initialDelaySeconds: 1 + # -- Readiness probe period, in seconds. periodSeconds: 2 + # -- Readiness probe timeout, in seconds. timeoutSeconds: 1 + # -- Readiness probe failure threshold before the pod is marked not ready. failureThreshold: 3 +# -- Gateway pod resource requests and limits. resources: {} +# -- Node selector for the gateway pod. nodeSelector: {} +# -- Tolerations for the gateway pod. tolerations: [] +# -- Affinity rules for the gateway pod. affinity: {} # Server configuration server: + # -- Gateway log level. logLevel: info - # Namespace where sandbox pods are created. Defaults to the Helm release + # -- Namespace where sandbox pods are created. Defaults to the Helm release # namespace (.Release.Namespace) when left empty. sandboxNamespace: "" + # -- Gateway database URL. dbUrl: "sqlite:/var/openshell/openshell.db" + # -- Default sandbox image used when requests do not specify one. sandboxImage: "ghcr.io/nvidia/openshell-community/sandboxes/base:latest" - # Kubernetes imagePullPolicy for sandbox pods. Empty = Kubernetes default - # (Always for :latest, IfNotPresent otherwise). Set to "Always" for dev + # -- Kubernetes imagePullPolicy for sandbox pods. Empty = Kubernetes default + # (Always for :latest, IfNotPresent otherwise). Set to "Always" for dev # clusters so new images are picked up without manual eviction. sandboxImagePullPolicy: "" - # gRPC endpoint sandboxes call back into the gateway. Leave empty to derive + # -- gRPC endpoint sandboxes call back into the gateway. Leave empty to derive # it from the chart fullname, release namespace, service port, and - # disableTls flag (i.e. ://..svc.cluster.local:). + # disableTls flag, for example https://openshell.openshell.svc.cluster.local:8080. # Override only when sandboxes must reach the gateway via a different # hostname (e.g. an external ingress or a host alias). grpcEndpoint: "" # TLS configuration for the server. The server always terminates mTLS # directly and requires client certificates. - # Host gateway IP for sandbox pod hostAliases. When set, sandbox pods get + # -- Host gateway IP for sandbox pod hostAliases. When set, sandbox pods get # hostAliases entries mapping host.docker.internal and host.openshell.internal # to this IP, allowing them to reach services running on the Docker host. # Auto-detected by the cluster entrypoint script. hostGatewayIP: "" - # Enable Kubernetes user namespace isolation (hostUsers: false) for sandbox + # -- Enable Kubernetes user namespace isolation (hostUsers: false) for sandbox # pods. Requires Kubernetes 1.33+ with user namespace support available # (beta through 1.35, GA in 1.36+), plus a supporting container runtime and # Linux 5.12+. When enabled, container UID 0 maps to an unprivileged host # UID and capabilities become namespaced. enableUserNamespaces: false - # Disable TLS entirely — the server listens on plaintext HTTP. + # -- Disable TLS entirely - the server listens on plaintext HTTP. # Set to true when a reverse proxy / tunnel terminates TLS at the edge. disableTls: false - # Enable plaintext HTTP routing for loopback sandbox service URLs on + # -- Enable plaintext HTTP routing for loopback sandbox service URLs on # TLS-enabled gateways. enableLoopbackServiceHttp: true tls: - # K8s secret (type kubernetes.io/tls) with tls.crt and tls.key for the server + # -- K8s secret (type kubernetes.io/tls) with tls.crt and tls.key for the server. certSecretName: openshell-server-tls - # K8s secret with ca.crt for client certificate verification (mTLS). + # -- K8s secret with ca.crt for client certificate verification (mTLS). # Set to "" to disable mTLS and run HTTPS-only (use OIDC for auth instead). clientCaSecretName: openshell-server-client-ca - # K8s secret mounted into sandbox pods for mTLS to the server + # -- K8s secret mounted into sandbox pods for mTLS to the server. clientTlsSecretName: openshell-client-tls # OIDC (OpenID Connect) configuration for JWT-based authentication. # When issuer is set, the server validates Bearer tokens on gRPC requests. oidc: - # OIDC issuer URL (e.g. https://keycloak.example.com/realms/openshell). + # -- OIDC issuer URL (e.g. https://keycloak.example.com/realms/openshell). issuer: "" - # Expected audience claim for the API resource server. + # -- Expected audience claim for the API resource server. # This should match the server's --oidc-audience, NOT the CLI client ID. audience: "openshell-cli" - # JWKS key cache TTL in seconds. + # -- JWKS key cache TTL in seconds. jwksTtl: 3600 - # Dot-separated path to the roles array in the JWT claims. + # -- Dot-separated path to the roles array in the JWT claims. # Keycloak: "realm_access.roles", Entra ID: "roles", Okta: "groups". rolesClaim: "" - # Role name for admin access. Leave empty (with userRole also empty) for + # -- Role name for admin access. Leave empty (with userRole also empty) for # authentication-only mode. Both must be set or both empty. adminRole: "" - # Role name for standard user access. + # -- Role name for standard user access. userRole: "" - # Dot-separated path to the scopes array in the JWT claims. + # -- Dot-separated path to the scopes array in the JWT claims. scopesClaim: "" - # Name of a ConfigMap containing a CA certificate bundle (key: ca.crt) + # -- Name of a ConfigMap containing a CA certificate bundle (key: ca.crt) # for verifying the OIDC issuer's TLS certificate. Required when the # issuer uses a non-public CA (e.g. OpenShift ingress, private PKI). caConfigMapName: "" # NetworkPolicy restricting SSH ingress on sandbox pods to the gateway only. networkPolicy: + # -- Create a NetworkPolicy restricting SSH ingress on sandbox pods to the gateway. enabled: true # PKI bootstrap via a pre-install/pre-upgrade hook Job. # Runs `openshell-gateway generate-certs` to create the server and client TLS # Secrets in-cluster. Key material is written directly to K8s Secrets and # never appears in Helm release history. Idempotent: existing secrets are -# left untouched on upgrade. Reuses the gateway image — no extra image to +# left untouched on upgrade. Reuses the gateway image - no extra image to # mirror in air-gapped environments. # # The server certificate already includes the built-in cluster SANs @@ -172,24 +216,29 @@ networkPolicy: # that domain, for example `*.apps.example.com` enables # `--.apps.example.com`. pkiInitJob: + # -- Run a pre-install/pre-upgrade Job that creates gateway and client mTLS Secrets. enabled: true - # Extra DNS SANs to append to the server certificate. + # -- Extra DNS SANs to append to the server certificate. serverDnsNames: [] - # Extra IP SANs to append to the server certificate. + # -- Extra IP SANs to append to the server certificate. serverIpAddresses: [] # cert-manager Certificate/Issuer resources (requires cert-manager CRDs in-cluster). # Uses namespaced Issuers only (no ClusterIssuer). Does not install cert-manager itself. certManager: + # -- Create cert-manager Issuer and Certificate resources instead of using the PKI bootstrap Job. enabled: false - # Secret created for the intermediate CA (Certificate with isCA: true). + # -- Secret created for the intermediate CA (Certificate with isCA: true). caSecretName: openshell-ca-tls - # Mount gateway client CA from the server TLS secret's ca.crt (populated by + # -- Mount gateway client CA from the server TLS secret's ca.crt (populated by # cert-manager for certs issued by a CA Issuer). Avoids a separate # openshell-server-client-ca Secret. clientCaFromServerTlsSecret: true + # -- Duration for cert-manager-issued certificates. certificateDuration: 8760h + # -- Renewal window for cert-manager-issued certificates. certificateRenewBefore: 720h + # -- DNS SANs on the cert-manager-issued server certificate. serverDnsNames: - openshell - openshell.openshell.svc @@ -198,32 +247,36 @@ certManager: - openshell.localhost - "*.openshell.localhost" - host.docker.internal + # -- IP SANs on the cert-manager-issued server certificate. serverIpAddresses: - 127.0.0.1 -# Kubernetes Gateway API — HTTPRoute and Gateway resources. +# Kubernetes Gateway API - HTTPRoute and Gateway resources. # Requires a Gateway API controller in the cluster. Install Envoy Gateway via # the skaffold.yaml releases or independently: # helm install eg oci://docker.io/envoyproxy/gateway-helm \ # --version v1.4.1 -n envoy-gateway-system --create-namespace grpcRoute: + # -- Create a Gateway API GRPCRoute for the gateway service. enabled: false - # Hostnames the GRPCRoute matches on. Leave empty to match all hosts. + # -- Hostnames the GRPCRoute matches on. Leave empty to match all hosts. hostnames: [] gateway: - # When true, a Gateway resource is created in the release namespace. + # -- 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. create: false - # GatewayClass to reference. Envoy Gateway installs one named "eg". + # -- GatewayClass to reference. Envoy Gateway installs one named "eg". className: "eg" - # Name of the Gateway resource. Defaults to the chart fullname. + # -- Name of the Gateway resource. Defaults to the chart fullname. name: "" - # Namespace of the Gateway referenced by the GRPCRoute parentRef. + # -- Namespace of the Gateway referenced by the GRPCRoute parentRef. # Defaults to the release namespace. namespace: "" # Listener settings (only used when gateway.create is true). listener: + # -- Listener port for the generated Gateway resource. port: 80 + # -- Listener protocol for the generated Gateway resource. protocol: HTTP - # "Same" restricts attached routes to the release namespace; "All" allows any namespace. + # -- "Same" restricts attached routes to the release namespace; "All" allows any namespace. allowedRoutes: Same diff --git a/mise.lock b/mise.lock index 73da25cdb..40050e701 100644 --- a/mise.lock +++ b/mise.lock @@ -101,6 +101,22 @@ url = "https://get.helm.sh/helm-v4.2.0-linux-amd64.tar.gz" checksum = "sha256:f13f959015447b6bc309f9fd506509926543988a39035c088b52522ec95e2acb" url = "https://get.helm.sh/helm-v4.2.0-darwin-arm64.tar.gz" +[[tools.helm-docs]] +version = "1.14.2" +backend = "aqua:norwoodj/helm-docs" + +[tools.helm-docs."platforms.linux-arm64"] +checksum = "sha256:c3787212332386dcd122debef7848feb165aa701467ae3e3442df7638f3ac4e4" +url = "https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_arm64.tar.gz" + +[tools.helm-docs."platforms.linux-x64"] +checksum = "sha256:a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc" +url = "https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_x86_64.tar.gz" + +[tools.helm-docs."platforms.macos-arm64"] +checksum = "sha256:2d8399db5b33d240d5f8985241bcf5483563150b968e3229823822979f3e4b8b" +url = "https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Darwin_arm64.tar.gz" + [[tools.k3d]] version = "5.8.3" backend = "aqua:k3d-io/k3d" diff --git a/mise.toml b/mise.toml index 313b654a5..f3aa2cb9d 100644 --- a/mise.toml +++ b/mise.toml @@ -26,6 +26,7 @@ kubectl = "1.36.1" uv = "0.10.12" protoc = "29.6" helm = "4.2.0" +helm-docs = "1.14.2" skaffold = "2.20.0" # Keep k3d out of Linux CI images until upstream ships a release rebuilt with # patched Go/container dependencies. Linux Kubernetes E2E uses kind or an diff --git a/tasks/ci.toml b/tasks/ci.toml index 791c4b3b0..b9bfe0d27 100644 --- a/tasks/ci.toml +++ b/tasks/ci.toml @@ -51,7 +51,7 @@ hide = true [lint] description = "Run repository lint checks" -depends = ["license:check", "rust:format:check", "rust:lint", "python:format:check", "python:lint", "helm:lint", "markdown:lint"] +depends = ["license:check", "rust:format:check", "rust:lint", "python:format:check", "python:lint", "helm:lint", "helm:docs:check", "markdown:lint"] hide = true [ci] diff --git a/tasks/helm.toml b/tasks/helm.toml index 8f53cffbb..31788088a 100644 --- a/tasks/helm.toml +++ b/tasks/helm.toml @@ -3,6 +3,25 @@ # Helm chart tasks +["helm:docs"] +description = "Generate the openshell Helm chart README from Chart.yaml, values.yaml, and README.md.gotmpl" +run = "helm-docs --chart-search-root deploy/helm/openshell" + +["helm:docs:check"] +description = "Verify the openshell Helm chart README is generated and up to date" +run = """ + set -e + tmp="$(mktemp)" + trap 'rm -f "$tmp"' EXIT + + helm-docs --chart-search-root deploy/helm/openshell --dry-run > "$tmp" + if ! diff -u deploy/helm/openshell/README.md "$tmp"; then + echo "Helm chart README is out of sync. Run: mise run helm:docs" >&2 + exit 1 + fi +""" +hide = true + ["helm:lint"] description = "Lint the openshell Helm chart (defaults + all CI configuration variants)" run = """