diff --git a/deploy/helm/openshell/README.md b/deploy/helm/openshell/README.md index df3ea173ad..aec9837368 100644 --- a/deploy/helm/openshell/README.md +++ b/deploy/helm/openshell/README.md @@ -450,6 +450,7 @@ discovery endpoint or its TLS CA. | server.policyValidationFailureMode | string | `"fail_closed"` | Posture when a candidate sandbox policy fails validation. `fail_closed` deactivates the previous policy; `retain_last_valid` keeps it active. | | server.providerTokenGrants.spiffe.enabled | bool | `false` | Mount the SPIFFE Workload API socket into gateway and sandbox pods for dynamic provider token grants. | | server.providerTokenGrants.spiffe.workloadApiSocketPath | string | `"/spiffe-workload-api/spire-agent.sock"` | Path to the SPIFFE Workload API socket mounted into gateway and sandbox pods. | +| server.sandboxGid | string | `""` | GID for sandbox pods (`sandbox_gid`). Empty (default) = same as sandboxUid. Must be an integer between 1 and 4294967294. | | server.sandboxImagePullSecrets | list | `[]` | Image pull secrets attached to sandbox pods. Referenced Secrets must exist in the sandbox namespace. | | server.sandboxJwt.gatewayId | string | `""` | Stable gateway identity embedded in iss/aud of every minted token. Defaults to the release name so HA replicas share identity. | | server.sandboxJwt.k8sSaTokenTtlSecs | int | `3600` | Lifetime (seconds) of the projected ServiceAccount token kubelet writes into each sandbox pod for the IssueSandboxToken bootstrap exchange. Kubelet enforces a minimum of 600s; the driver clamps values outside [600, 86400]. Default 3600 — generous, since the supervisor consumes the token within seconds of pod start. | @@ -457,6 +458,7 @@ discovery endpoint or its TLS CA. | server.sandboxJwt.signingSecretName | string | `""` | Name of the Opaque Secret holding the signing key material. Empty falls back to the chart fullname with "-jwt-keys" appended. | | server.sandboxJwt.ttlSecs | int | `3600` | Token TTL in seconds. Defaults to 3600 (1h). | | server.sandboxNamespace | string | `""` | Namespace where sandbox pods are created. Defaults to the Helm release namespace (.Release.Namespace) when left empty. | +| server.sandboxUid | string | `""` | UID for sandbox pods (`sandbox_uid`). Empty (default) = use the OpenShift SCC namespace annotation if present, otherwise the driver default. Must be an integer between 1 and 4294967294. | | server.telemetryEnabled | bool | `true` | Enable anonymous OpenShell telemetry from the gateway and the sandbox supervisors it launches. | | 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). Only used when enableMtls is true. Set to "" to disable client certificate verification for HTTPS-only mode. | diff --git a/deploy/helm/openshell/templates/_helpers.tpl b/deploy/helm/openshell/templates/_helpers.tpl index ab42458759..85cccaeba4 100644 --- a/deploy/helm/openshell/templates/_helpers.tpl +++ b/deploy/helm/openshell/templates/_helpers.tpl @@ -379,6 +379,23 @@ never {{- end -}} {{- end }} +{{/* +Render a sandbox UID/GID chart value as an integer, or nothing when unset. +Takes a dict with `name` (the values key, for errors) and `value`. The bounds +match openshell_policy::MIN_SANDBOX_UID..=MAX_SANDBOX_UID. Helm parses YAML +numbers as float64, so the integer conversion also avoids `2e+09` rendering. +Booleans are rejected because they would otherwise convert to 1 or 0. +*/}} +{{- define "openshell.sandboxId" -}} +{{- if not (or (kindIs "invalid" .value) (eq (toString .value) "")) -}} +{{- $id := int64 .value -}} +{{- if or (kindIs "bool" .value) (ne (float64 .value) (float64 $id)) (lt $id 1) (gt $id 4294967294) -}} +{{- fail (printf "%s must be an integer between 1 and 4294967294" .name) -}} +{{- end -}} +{{- $id -}} +{{- end -}} +{{- end }} + {{/* Validate chart values that Helm would otherwise accept silently. */}} diff --git a/deploy/helm/openshell/templates/gateway-config.yaml b/deploy/helm/openshell/templates/gateway-config.yaml index a3e0210a35..c6eea2b6a1 100644 --- a/deploy/helm/openshell/templates/gateway-config.yaml +++ b/deploy/helm/openshell/templates/gateway-config.yaml @@ -247,6 +247,12 @@ data: {{- if .Values.server.defaultRuntimeClassName }} default_runtime_class_name = {{ .Values.server.defaultRuntimeClassName | quote }} {{- end }} + {{- with include "openshell.sandboxId" (dict "name" "server.sandboxUid" "value" .Values.server.sandboxUid) }} + sandbox_uid = {{ . }} + {{- end }} + {{- with include "openshell.sandboxId" (dict "name" "server.sandboxGid" "value" .Values.server.sandboxGid) }} + sandbox_gid = {{ . }} + {{- end }} {{- if (.Values.supervisor.image.pullPolicy | default .Values.global.image.pullPolicy) }} supervisor_image_pull_policy = {{ include "openshell.canonicalImagePullPolicy" (.Values.supervisor.image.pullPolicy | default .Values.global.image.pullPolicy) | quote }} {{- end }} diff --git a/deploy/helm/openshell/tests/gateway_sandbox_identity_test.yaml b/deploy/helm/openshell/tests/gateway_sandbox_identity_test.yaml new file mode 100644 index 0000000000..0b8d4adfbd --- /dev/null +++ b/deploy/helm/openshell/tests/gateway_sandbox_identity_test.yaml @@ -0,0 +1,93 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +suite: sandbox UID and GID +templates: + - templates/gateway-config.yaml +release: + name: openshell + namespace: my-namespace + +tests: + - it: omits sandbox_uid and sandbox_gid by default + asserts: + - notMatchRegex: + path: data["gateway.toml"] + pattern: 'sandbox_uid|sandbox_gid' + + - it: renders sandbox_uid and sandbox_gid as integers + set: + server.sandboxUid: 1500 + server.sandboxGid: 2000 + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?m)^\s*sandbox_uid\s*=\s*1500$' + - matchRegex: + path: data["gateway.toml"] + pattern: '(?m)^\s*sandbox_gid\s*=\s*2000$' + + - it: renders sandbox_uid alone + set: + server.sandboxUid: 1500 + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?m)^\s*sandbox_uid\s*=\s*1500$' + - notMatchRegex: + path: data["gateway.toml"] + pattern: 'sandbox_gid' + + - it: renders large IDs without scientific notation + set: + server.sandboxUid: 1000680000 + server.sandboxGid: 4294967294 + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?m)^\s*sandbox_uid\s*=\s*1000680000$' + - matchRegex: + path: data["gateway.toml"] + pattern: '(?m)^\s*sandbox_gid\s*=\s*4294967294$' + + - it: rejects a zero sandbox UID + set: + server.sandboxUid: 0 + asserts: + - failedTemplate: + errorPattern: "server.sandboxUid must be an integer between 1 and 4294967294" + + - it: rejects a sandbox GID above the allowed range + set: + server.sandboxGid: 4294967295 + asserts: + - failedTemplate: + errorPattern: "server.sandboxGid must be an integer between 1 and 4294967294" + + - it: rejects a non-numeric sandbox UID + set: + server.sandboxUid: abc + asserts: + - failedTemplate: + errorPattern: "server.sandboxUid must be an integer between 1 and 4294967294" + + - it: rejects a fractional sandbox UID + set: + server.sandboxUid: 1500.5 + asserts: + - failedTemplate: + errorPattern: "server.sandboxUid must be an integer between 1 and 4294967294" + + - it: rejects a boolean sandbox UID + set: + server.sandboxUid: true + asserts: + - failedTemplate: + errorPattern: "server.sandboxUid must be an integer between 1 and 4294967294" + + - it: rejects a boolean sandbox GID + set: + server.sandboxGid: true + asserts: + - failedTemplate: + errorPattern: "server.sandboxGid must be an integer between 1 and 4294967294" diff --git a/deploy/helm/openshell/values.yaml b/deploy/helm/openshell/values.yaml index 4bf21b88a8..63e90a1e35 100644 --- a/deploy/helm/openshell/values.yaml +++ b/deploy/helm/openshell/values.yaml @@ -328,6 +328,13 @@ server: # Set to a RuntimeClass name (e.g. "kata-containers", "nvidia") to apply it # to all sandboxes that don't explicitly override it. defaultRuntimeClassName: "" + # -- UID for sandbox pods (`sandbox_uid`). Empty (default) = use the OpenShift + # SCC namespace annotation if present, otherwise the driver default. Must be + # an integer between 1 and 4294967294. + sandboxUid: "" + # -- GID for sandbox pods (`sandbox_gid`). Empty (default) = same as + # sandboxUid. Must be an integer between 1 and 4294967294. + sandboxGid: "" # -- 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. diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 1308871cea..4312e62879 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -814,6 +814,7 @@ provider_spiffe_workload_api_socket_path = "/spiffe-workload-api/spire-agent.soc # PVC init container. When unset, the driver auto-detects from OpenShift SCC # namespace annotations (openshift.io/sa.scc.uid-range) if present, falling # back to 1000 on non-OpenShift clusters. Any non-root Linux UID/GID is valid. +# Helm sets these with server.sandboxUid and server.sandboxGid. # sandbox_uid = 1500 # sandbox_gid = 1500 # Operator-mode namespace discovery. At least one must be set when diff --git a/docs/how-it-works/sandboxes/runtimes.mdx b/docs/how-it-works/sandboxes/runtimes.mdx index f52a571bd2..cd64ddc193 100644 --- a/docs/how-it-works/sandboxes/runtimes.mdx +++ b/docs/how-it-works/sandboxes/runtimes.mdx @@ -226,6 +226,7 @@ Only the OpenShell gateway and the Agent Sandbox controller should be able to ma | `supervisor_image` | `supervisor.image.*` | Override the supervisor image. | | `workspace_default_storage_size` | `server.workspaceDefaultStorageSize` | Default workspace PVC size. | | `workspace_storage_class` | `server.workspaceStorageClass` | `StorageClass` for workspace PVCs. Set this if the cluster has no default `StorageClass`. | +| `sandbox_uid` / `sandbox_gid` | `server.sandboxUid` / `server.sandboxGid` | Sandbox pod UID and GID. Takes priority over OpenShift SCC namespace annotations. `sandbox_gid` defaults to the UID. | | `https_proxy` | `upstreamProxy.url` | Corporate proxy for sandbox egress. | | `no_proxy` | `upstreamProxy.noProxy` | Destinations that bypass the corporate proxy. | | `proxy_auth_secret_name` / `proxy_auth_secret_key` | `upstreamProxy.authSecret.name` / `.key` | Secret holding the proxy `user:pass` credential. |