diff --git a/deploy/helm/openshell/README.md b/deploy/helm/openshell/README.md index 1786a46f63..df6506e92f 100644 --- a/deploy/helm/openshell/README.md +++ b/deploy/helm/openshell/README.md @@ -267,6 +267,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 | `""` | Explicit GID paired with sandboxUid. Empty (default) = omit the field; the driver then reuses the resolved UID as the GID. Setting this without sandboxUid has no effect on UID resolution. Any non-root GID is valid. | | 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.sandboxImagePullSecrets | list | `[]` | Image pull secrets attached to sandbox pods. Referenced Secrets must exist in the sandbox namespace. | @@ -276,6 +277,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 | `""` | Explicit UID for sandbox processes, the supervisor container securityContext, and the workspace PVC init container. Empty (default) = omit the field, letting the driver auto-detect from the OpenShift SCC annotation openshift.io/sa.scc.uid-range on the sandbox namespace and fall back to 1000 when that is absent. Set this on non-OpenShift clusters to pin a sandbox UID without the OpenShift-specific annotation. Any non-root UID is valid. | | 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). Set to "" to disable mTLS and run HTTPS-only (use OIDC for auth instead). | diff --git a/deploy/helm/openshell/templates/gateway-config.yaml b/deploy/helm/openshell/templates/gateway-config.yaml index d7f3cb9a83..97d5f2d700 100644 --- a/deploy/helm/openshell/templates/gateway-config.yaml +++ b/deploy/helm/openshell/templates/gateway-config.yaml @@ -204,6 +204,12 @@ data: {{- if .Values.server.appArmorProfile }} app_armor_profile = {{ .Values.server.appArmorProfile | quote }} {{- end }} + {{- if .Values.server.sandboxUid }} + sandbox_uid = {{ .Values.server.sandboxUid }} + {{- end }} + {{- if .Values.server.sandboxGid }} + sandbox_gid = {{ .Values.server.sandboxGid }} + {{- end }} {{- if .Values.supervisor.image.pullPolicy }} supervisor_image_pull_policy = {{ .Values.supervisor.image.pullPolicy | quote }} {{- end }} diff --git a/deploy/helm/openshell/tests/gateway_config_test.yaml b/deploy/helm/openshell/tests/gateway_config_test.yaml index b54b036c0f..3891ab738f 100644 --- a/deploy/helm/openshell/tests/gateway_config_test.yaml +++ b/deploy/helm/openshell/tests/gateway_config_test.yaml @@ -284,6 +284,46 @@ tests: path: data["gateway.toml"] pattern: 'app_armor_profile\s*=' + - it: omits the sandbox UID and GID by default + template: templates/gateway-config.yaml + asserts: + - notMatchRegex: + path: data["gateway.toml"] + pattern: 'sandbox_uid\s*=' + - notMatchRegex: + path: data["gateway.toml"] + pattern: 'sandbox_gid\s*=' + + # The driver parses both as TOML integers, so the trailing `$` anchors + # matter: a quoted scalar would be rejected at gateway startup rather + # than at render time. + - it: renders an explicit sandbox UID and GID under [openshell.drivers.kubernetes] + template: templates/gateway-config.yaml + set: + server.sandboxUid: 1500 + server.sandboxGid: 1600 + asserts: + - matchRegex: + path: data["gateway.toml"] + pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?sandbox_uid\s*=\s*1500$' + - matchRegex: + path: data["gateway.toml"] + pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?sandbox_gid\s*=\s*1600$' + + # resolve_sandbox_gid() falls back to the configured UID, so a bare + # sandboxUid must not drag an empty sandbox_gid into the config. + - it: renders the sandbox UID without a GID when only sandboxUid is set + template: templates/gateway-config.yaml + 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\s*=' + - it: does not reuse gateway image pull secrets for sandbox pods template: templates/gateway-config.yaml set: diff --git a/deploy/helm/openshell/values.yaml b/deploy/helm/openshell/values.yaml index 6b0b6242b0..996c592c1f 100644 --- a/deploy/helm/openshell/values.yaml +++ b/deploy/helm/openshell/values.yaml @@ -263,6 +263,19 @@ server: # the field, "RuntimeDefault" to force the runtime default profile, or # "Localhost/profile-name" for an operator-managed localhost profile. appArmorProfile: "Unconfined" + # -- Explicit UID for sandbox processes, the supervisor container + # securityContext, and the workspace PVC init container. Empty (default) + # = omit the field, letting the driver auto-detect from the OpenShift SCC + # annotation openshift.io/sa.scc.uid-range on the sandbox namespace and + # fall back to 1000 when that is absent. Set this on non-OpenShift + # clusters to pin a sandbox UID without the OpenShift-specific + # annotation. Any non-root UID is valid. + sandboxUid: "" + # -- Explicit GID paired with sandboxUid. Empty (default) = omit the + # field; the driver then reuses the resolved UID as the GID. Setting this + # without sandboxUid has no effect on UID resolution. Any non-root GID is + # valid. + sandboxGid: "" # Kubernetes compute driver settings. drivers: kubernetes: diff --git a/docs/reference/sandbox-compute-drivers.mdx b/docs/reference/sandbox-compute-drivers.mdx index e453f7f219..9bc3102f8b 100644 --- a/docs/reference/sandbox-compute-drivers.mdx +++ b/docs/reference/sandbox-compute-drivers.mdx @@ -565,6 +565,8 @@ The Kubernetes driver auto-detects the sandbox UID from OpenShift SCC namespace You can override autodetection with explicit `sandbox_uid` / `sandbox_gid` config in `[openshell.drivers.kubernetes]`. When set, the driver skips namespace annotation lookup entirely. +On a Helm-installed gateway, set the `server.sandboxUid` / `server.sandboxGid` chart values; they render into those same config keys. This is the platform-agnostic way to pin a sandbox identity on non-OpenShift clusters, where the SCC annotation is not otherwise used. + The resolved UID/GID appear in: - Supervisor container environment variables (`OPENSHELL_SANDBOX_UID`, `OPENSHELL_SANDBOX_GID`) for direct kernel-level privilege dropping without `/etc/passwd` lookups.