mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-19 05:50:41 +08:00
Install a ValidatingAdmissionPolicy, on by default in managed and operator workspace modes, that matches only the gateway ServiceAccount. It admits Secret, Pod, Sandbox, Service, ServiceAccount, and NetworkPolicy writes only in namespaces labeled as owned by this gateway and namespaces matching the operator selector. Secret writes are also admitted in the credential namespace when the Kubernetes Secrets credential driver is enabled. Namespace writes are admitted only for namespaces owned by this gateway, judged by their existing labels. - Skip rendering the policy when rbac.create or rbac.clusterScoped.create is false; a cluster-admin applies it with the other cluster-scoped objects. - Require Kubernetes 1.30. - Fail rendering with operatorNamespaceFile or set-based operator selectors, which the policy cannot evaluate. admissionPolicy.enabled set to false opts out. - Add e2e checks in managed and operator modes that send server-side dry-run requests as the gateway ServiceAccount outside its namespaces, including Pod creation in the sandbox and credential namespace, and assert the policy rejects them. - Document the policy and opt-out, raise the minimum Kubernetes version in the docs and support matrix, and add policy denials to the cluster debugging skill. Signed-off-by: Kris Hicks <khicks@nvidia.com>
433 lines
17 KiB
Smarty
433 lines
17 KiB
Smarty
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
{{/*
|
|
Expand the name of the chart.
|
|
*/}}
|
|
{{- define "openshell.name" -}}
|
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Create a default fully qualified app name.
|
|
*/}}
|
|
{{- define "openshell.fullname" -}}
|
|
{{- if .Values.fullnameOverride }}
|
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
|
{{- else }}
|
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
|
{{- if contains $name .Release.Name }}
|
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
|
{{- else }}
|
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Create chart name and version as used by the chart label.
|
|
*/}}
|
|
{{- define "openshell.chart" -}}
|
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Common labels
|
|
*/}}
|
|
{{- define "openshell.labels" -}}
|
|
helm.sh/chart: {{ include "openshell.chart" . }}
|
|
{{ include "openshell.selectorLabels" . }}
|
|
{{- if .Chart.AppVersion }}
|
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
{{- end }}
|
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Selector labels
|
|
*/}}
|
|
{{- define "openshell.selectorLabels" -}}
|
|
app.kubernetes.io/name: {{ include "openshell.name" . }}
|
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Create the name of the service account to use
|
|
*/}}
|
|
{{- define "openshell.serviceAccountName" -}}
|
|
{{- if .Values.serviceAccount.create }}
|
|
{{- default (include "openshell.fullname" .) .Values.serviceAccount.name }}
|
|
{{- else }}
|
|
{{- default "default" .Values.serviceAccount.name }}
|
|
{{- end }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Create the name of the service account assigned to sandbox pods
|
|
*/}}
|
|
{{- define "openshell.sandboxServiceAccountName" -}}
|
|
{{- if .Values.sandboxServiceAccount.create }}
|
|
{{- default (printf "%s-sandbox" (include "openshell.fullname" .) | trunc 63 | trimSuffix "-") .Values.sandboxServiceAccount.name }}
|
|
{{- else }}
|
|
{{- default "default" .Values.sandboxServiceAccount.name }}
|
|
{{- end }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Whether this chart owns workspace-scoped resources. Missing legacy values
|
|
default to enabled so upgrades with --reuse-values preserve the old topology.
|
|
*/}}
|
|
{{- define "openshell.workspaceResourcesEnabled" -}}
|
|
{{- $workspaceResources := .Values.workspaceResources | default dict -}}
|
|
{{- $enabled := true -}}
|
|
{{- if hasKey $workspaceResources "enabled" -}}
|
|
{{- $enabled = get $workspaceResources "enabled" -}}
|
|
{{- end -}}
|
|
{{- if $enabled -}}true{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Whether this chart owns gateway RBAC objects. Missing legacy values default to
|
|
enabled so upgrades with --reuse-values preserve the old topology.
|
|
*/}}
|
|
{{- define "openshell.rbacCreate" -}}
|
|
{{- $rbac := .Values.rbac | default dict -}}
|
|
{{- $create := true -}}
|
|
{{- if hasKey $rbac "create" -}}
|
|
{{- $create = get $rbac "create" -}}
|
|
{{- end -}}
|
|
{{- if $create -}}true{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
The rbac.clusterScoped values map, tolerating missing legacy values.
|
|
*/}}
|
|
{{- define "openshell.clusterScopedRbacValues" -}}
|
|
{{- $rbac := .Values.rbac | default dict -}}
|
|
{{- $clusterScoped := dict -}}
|
|
{{- if hasKey $rbac "clusterScoped" -}}
|
|
{{- $clusterScoped = get $rbac "clusterScoped" | default dict -}}
|
|
{{- end -}}
|
|
{{- toYaml $clusterScoped -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Whether this chart owns the cluster-scoped ClusterRole and ClusterRoleBinding.
|
|
Disable for a namespace-admin install where a cluster-admin applies them
|
|
separately. Missing legacy values default to enabled.
|
|
*/}}
|
|
{{- define "openshell.clusterRbacCreate" -}}
|
|
{{- if include "openshell.rbacCreate" . -}}
|
|
{{- $clusterScoped := include "openshell.clusterScopedRbacValues" . | fromYaml -}}
|
|
{{- $create := true -}}
|
|
{{- if hasKey $clusterScoped "create" -}}
|
|
{{- $create = get $clusterScoped "create" -}}
|
|
{{- end -}}
|
|
{{- if $create -}}true{{- end -}}
|
|
{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Name of the gateway ClusterRole. The release namespace is part of the default
|
|
name so multiple releases on one cluster do not collide.
|
|
*/}}
|
|
{{- define "openshell.clusterRoleName" -}}
|
|
{{- $clusterScoped := include "openshell.clusterScopedRbacValues" . | fromYaml -}}
|
|
{{- $default := printf "%s-node-reader-%s" (include "openshell.fullname" .) .Release.Namespace -}}
|
|
{{- default $default (get $clusterScoped "clusterRoleName") -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Name of the gateway ClusterRoleBinding.
|
|
*/}}
|
|
{{- define "openshell.clusterRoleBindingName" -}}
|
|
{{- $clusterScoped := include "openshell.clusterScopedRbacValues" . | fromYaml -}}
|
|
{{- $default := printf "%s-node-reader-%s" (include "openshell.fullname" .) .Release.Namespace -}}
|
|
{{- default $default (get $clusterScoped "clusterRoleBindingName") -}}
|
|
{{- end }}
|
|
|
|
{{/* Gateway image reference. A digest takes precedence over a tag. */}}
|
|
{{- define "openshell.image" -}}
|
|
{{- $image := .Values.gateway.image -}}
|
|
{{- $global := .Values.global.image -}}
|
|
{{- $registry := $image.registry | default $global.registry -}}
|
|
{{- $repository := ternary (printf "%s/%s" $registry $image.repository) $image.repository (ne $registry "") -}}
|
|
{{- if $image.digest -}}
|
|
{{- printf "%s@%s" $repository $image.digest -}}
|
|
{{- else -}}
|
|
{{- printf "%s:%s" $repository ($image.tag | default $global.tag | default .Chart.AppVersion) -}}
|
|
{{- end }}
|
|
{{- end }}
|
|
|
|
{{/* Sandbox image reference. A digest takes precedence over a tag. */}}
|
|
{{- define "openshell.sandboxImage" -}}
|
|
{{- $image := .Values.sandbox.image -}}
|
|
{{- if $image.digest -}}
|
|
{{- printf "%s@%s" $image.repository $image.digest -}}
|
|
{{- else -}}
|
|
{{- printf "%s:%s" $image.repository ($image.tag | default "latest") -}}
|
|
{{- end }}
|
|
{{- end }}
|
|
|
|
{{/* Official sandbox runtime repository used by the gateway's built-in default. */}}
|
|
{{- define "openshell.defaultSandboxRuntimeRepository" -}}
|
|
ghcr.io/nvidia/openshell/sandbox
|
|
{{- end }}
|
|
|
|
{{/* Whether Helm must propagate a sandbox runtime image override. */}}
|
|
{{- define "openshell.sandboxRuntimeImageOverrideEnabled" -}}
|
|
{{- $defaultRepository := include "openshell.defaultSandboxRuntimeRepository" . -}}
|
|
{{- $global := .Values.global.image -}}
|
|
{{- $registry := .Values.sandboxRuntime.image.registry | default $global.registry -}}
|
|
{{- $repository := ternary (printf "%s/%s" $registry .Values.sandboxRuntime.image.repository) .Values.sandboxRuntime.image.repository (ne $registry "") -}}
|
|
{{- if or (ne $repository $defaultRepository) .Values.sandboxRuntime.image.tag .Values.sandboxRuntime.image.digest .Values.global.image.tag -}}true{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/* Sandbox runtime image override. */}}
|
|
{{- define "openshell.sandboxRuntimeImage" -}}
|
|
{{- $global := .Values.global.image -}}
|
|
{{- $registry := .Values.sandboxRuntime.image.registry | default $global.registry -}}
|
|
{{- $repository := ternary (printf "%s/%s" $registry .Values.sandboxRuntime.image.repository) .Values.sandboxRuntime.image.repository (ne $registry "") -}}
|
|
{{- if .Values.sandboxRuntime.image.digest -}}
|
|
{{- printf "%s@%s" $repository .Values.sandboxRuntime.image.digest -}}
|
|
{{- else -}}
|
|
{{- $tag := .Values.sandboxRuntime.image.tag | default $global.tag | default .Chart.AppVersion -}}
|
|
{{- printf "%s:%s" $repository $tag -}}
|
|
{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Whether the gateway listener should verify client certificates (mTLS).
|
|
An explicit empty server.tls.clientCaSecretName disables client-CA wiring in
|
|
both gateway.toml and the workload, overriding built-in PKI and cert-manager
|
|
defaults.
|
|
*/}}
|
|
{{- define "openshell.gatewayClientCaEnabled" -}}
|
|
{{- if .Values.server.disableTls -}}
|
|
{{- else if not .Values.server.tls.enableMtls -}}
|
|
{{- else if eq .Values.server.tls.clientCaSecretName "" -}}
|
|
{{- else if or .Values.server.tls.clientCaSecretName (and .Values.pkiInitJob.enabled (not .Values.certManager.enabled)) (and .Values.certManager.enabled .Values.certManager.clientCaFromServerTlsSecret) -}}
|
|
true
|
|
{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/* Supervisor image override. */}}
|
|
{{- define "openshell.supervisorImage" -}}
|
|
{{- $global := .Values.global.image -}}
|
|
{{- $registry := .Values.supervisor.image.registry | default $global.registry -}}
|
|
{{- $repository := ternary (printf "%s/%s" $registry .Values.supervisor.image.repository) .Values.supervisor.image.repository (ne $registry "") -}}
|
|
{{- if .Values.supervisor.image.digest -}}
|
|
{{- printf "%s@%s" $repository .Values.supervisor.image.digest -}}
|
|
{{- else -}}
|
|
{{- $tag := .Values.supervisor.image.tag | default $global.tag | default .Chart.AppVersion -}}
|
|
{{- printf "%s:%s" $repository $tag -}}
|
|
{{- end }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Namespaced Issuer (selfSigned) for cert-manager CA bootstrap.
|
|
*/}}
|
|
{{- define "openshell.issuerSelfSigned" -}}
|
|
{{- printf "%s-selfsigned" (include "openshell.fullname" .) | trunc 63 | trimSuffix "-" }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Namespace where sandbox pods are created. An explicit
|
|
.Values.server.sandboxNamespace is used verbatim. Otherwise it defaults to
|
|
.Release.Namespace so `helm install -n my-ns` works without extra overrides.
|
|
*/}}
|
|
{{- define "openshell.sandboxNamespace" -}}
|
|
{{- .Values.server.sandboxNamespace | default .Release.Namespace -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Secrets in the sandbox namespace whose contents the Kubernetes driver stages
|
|
into per-generation Secrets in workspace namespaces, as a JSON array. Empty in
|
|
shared workspace mode.
|
|
*/}}
|
|
{{- define "openshell.workspaceSecretSourceNames" -}}
|
|
{{- $workspaceMode := .Values.server.drivers.kubernetes.workspaceMode | default "shared" -}}
|
|
{{- $names := list -}}
|
|
{{- if and (ne $workspaceMode "shared") (not .Values.server.disableTls) -}}
|
|
{{- $names = append $names .Values.server.tls.clientTlsSecretName -}}
|
|
{{- end -}}
|
|
{{- if eq $workspaceMode "managed" -}}
|
|
{{- range .Values.server.sandboxImagePullSecrets -}}
|
|
{{- if .name -}}
|
|
{{- $names = append $names .name -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- uniq $names | toJson -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Whether to render the gateway workspace admission policy. Shared mode grants
|
|
the gateway nothing cluster-wide that the policy covers.
|
|
*/}}
|
|
{{- define "openshell.admissionPolicyEnabled" -}}
|
|
{{- $workspaceMode := .Values.server.drivers.kubernetes.workspaceMode | default "shared" -}}
|
|
{{- if and .Values.admissionPolicy.enabled (ne $workspaceMode "shared") -}}
|
|
true
|
|
{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Namespace where Kubernetes Secret-backed provider credentials live.
|
|
*/}}
|
|
{{- define "openshell.credentialKubernetesSecretsNamespace" -}}
|
|
{{- .Values.server.credentialDrivers.kubernetesSecrets.namespace | default .Release.Namespace -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Name of the Secret holding the default credential storage key-encryption key.
|
|
When server.credentialStorage.existingSecret is set, returns that name instead
|
|
of the chart-generated name (for GitOps / helm-template workflows).
|
|
*/}}
|
|
{{- define "openshell.credentialStorageKeyEncryptionKeySecretName" -}}
|
|
{{- if .Values.server.credentialStorage.existingSecret -}}
|
|
{{- .Values.server.credentialStorage.existingSecret -}}
|
|
{{- else -}}
|
|
{{- printf "%s-credential-storage-key-encryption-key" (include "openshell.fullname" .) | trunc 63 | trimSuffix "-" -}}
|
|
{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Key inside the default credential storage key-encryption key Secret.
|
|
*/}}
|
|
{{- define "openshell.credentialStorageKeyEncryptionKeySecretKey" -}}
|
|
key-encryption-key
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Gateway environment variable used to pass the default credential storage key-encryption key.
|
|
*/}}
|
|
{{- define "openshell.credentialStorageKeyEncryptionKeyEnvName" -}}
|
|
OPENSHELL_GATEWAY_CREDENTIAL_KEY_ENCRYPTION_KEY
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Name of the Secret holding gateway-minted sandbox JWT signing material.
|
|
*/}}
|
|
{{- define "openshell.sandboxJwtSecretName" -}}
|
|
{{- .Values.server.sandboxJwt.signingSecretName | default (printf "%s-jwt-keys" (include "openshell.fullname" .)) -}}
|
|
{{- end }}
|
|
|
|
{{- define "openshell.peerServiceName" -}}
|
|
{{- printf "%s-peer" (include "openshell.fullname" .) -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
gRPC endpoint sandbox pods use to call back into the gateway. An explicit
|
|
.Values.server.grpcEndpoint is used verbatim. Otherwise it is derived from
|
|
the in-cluster Service DNS, release namespace, service port, and disableTls
|
|
flag — so the default value works for any release name or namespace without
|
|
override.
|
|
*/}}
|
|
{{- define "openshell.grpcEndpoint" -}}
|
|
{{- if .Values.server.grpcEndpoint -}}
|
|
{{- .Values.server.grpcEndpoint -}}
|
|
{{- else -}}
|
|
{{- $scheme := ternary "http" "https" (default false .Values.server.disableTls) -}}
|
|
{{- printf "%s://%s.%s.svc.cluster.local:%d" $scheme (include "openshell.fullname" .) .Release.Namespace (int .Values.service.port) -}}
|
|
{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Default server certificate DNS SANs derived from the release name and namespace.
|
|
Returns a YAML list. Append extra SANs from values with range loops.
|
|
*/}}
|
|
{{- define "openshell.defaultServerDnsNames" -}}
|
|
{{- $name := include "openshell.fullname" . -}}
|
|
{{- $ns := .Release.Namespace -}}
|
|
{{- list $name
|
|
(printf "%s.%s.svc" $name $ns)
|
|
(printf "%s.%s.svc.cluster.local" $name $ns)
|
|
"localhost"
|
|
(printf "%s.localhost" $name)
|
|
(printf "*.%s.localhost" $name)
|
|
"host.docker.internal"
|
|
"host.containers.internal"
|
|
| toYaml }}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Name of the ConfigMap holding the backend CA for BackendTLSPolicy validation.
|
|
*/}}
|
|
{{- define "openshell.backendCaConfigMapName" -}}
|
|
{{- .Values.grpcRoute.backendTLSPolicy.caCertificateConfigMapName | default (printf "%s-backend-ca" (include "openshell.fullname" .)) -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Gateway workload kind. StatefulSet is the default because the default SQLite
|
|
database requires persistent per-pod storage.
|
|
*/}}
|
|
{{- define "openshell.workloadKind" -}}
|
|
{{- $workload := .Values.workload | default dict -}}
|
|
{{- if not (kindIs "map" $workload) -}}
|
|
{{- fail "workload must be a map with kind and allowMultiReplicaStatefulSet fields." -}}
|
|
{{- end -}}
|
|
{{- default "statefulset" (get $workload "kind") | lower -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Translate chart image pull policy values to the canonical gateway vocabulary.
|
|
The Kubernetes spellings remain accepted so existing values files continue to
|
|
work across the schema-v2 chart upgrade.
|
|
*/}}
|
|
{{- define "openshell.canonicalImagePullPolicy" -}}
|
|
{{- $policy := printf "%v" . -}}
|
|
{{- if eq $policy "Always" -}}
|
|
always
|
|
{{- else if eq $policy "IfNotPresent" -}}
|
|
if_not_present
|
|
{{- else if eq $policy "Never" -}}
|
|
never
|
|
{{- else if has $policy (list "always" "if_not_present" "never") -}}
|
|
{{- $policy -}}
|
|
{{- else -}}
|
|
{{- fail (printf "image pull policy %q must be one of: always, if_not_present, never, Always, IfNotPresent, Never" $policy) -}}
|
|
{{- end -}}
|
|
{{- end }}
|
|
|
|
{{/*
|
|
Validate chart values that Helm would otherwise accept silently.
|
|
*/}}
|
|
{{- define "openshell.validateValues" -}}
|
|
{{- $workloadKind := include "openshell.workloadKind" . -}}
|
|
{{- $workload := .Values.workload | default dict -}}
|
|
{{- $replicaCount := int (default 1 .Values.replicaCount) -}}
|
|
{{- if and (hasKey .Values "postgres") (kindIs "map" .Values.postgres) (hasKey .Values.postgres "enabled") -}}
|
|
{{- fail "postgres.enabled was removed; the OpenShell chart no longer deploys PostgreSQL. Provision PostgreSQL separately and set server.externalDbSecret to a Secret containing a PostgreSQL URI." -}}
|
|
{{- end -}}
|
|
{{- if not (or (eq $workloadKind "statefulset") (eq $workloadKind "deployment")) -}}
|
|
{{- fail "workload.kind must be one of: statefulset, deployment." -}}
|
|
{{- end -}}
|
|
{{- if and (eq $workloadKind "deployment") (not .Values.server.externalDbSecret) -}}
|
|
{{- fail "workload.kind=deployment requires server.externalDbSecret; use workload.kind=statefulset for the default SQLite database." -}}
|
|
{{- end -}}
|
|
{{- if and (gt $replicaCount 1) (not .Values.server.externalDbSecret) -}}
|
|
{{- fail "replicaCount > 1 requires server.externalDbSecret; multiple gateway replicas cannot share the default per-pod SQLite database." -}}
|
|
{{- end -}}
|
|
{{- if and (eq $workloadKind "statefulset") (gt $replicaCount 1) (not (get $workload "allowMultiReplicaStatefulSet" | default false)) -}}
|
|
{{- fail "replicaCount > 1 with workload.kind=statefulset requires workload.allowMultiReplicaStatefulSet=true; use workload.kind=deployment for external database-backed multi-replica gateways." -}}
|
|
{{- end -}}
|
|
{{- $workspaceMode := .Values.server.drivers.kubernetes.workspaceMode | default "shared" -}}
|
|
{{- if not (has $workspaceMode (list "shared" "managed" "operator")) -}}
|
|
{{- fail "server.drivers.kubernetes.workspaceMode must be one of: shared, managed, operator." -}}
|
|
{{- end -}}
|
|
{{- $credentialDrivers := list -}}
|
|
{{- if .Values.server.credentialDrivers.kubernetesSecrets.enabled -}}
|
|
{{- $credentialDrivers = append $credentialDrivers "kubernetes-secrets" -}}
|
|
{{- end -}}
|
|
{{- if .Values.server.credentialDrivers.vault.enabled -}}
|
|
{{- $credentialDrivers = append $credentialDrivers "vault" -}}
|
|
{{- end -}}
|
|
{{- if gt (len $credentialDrivers) 1 -}}
|
|
{{- fail "only one external server.credentialDrivers backend can be enabled at a time." -}}
|
|
{{- end -}}
|
|
{{- if kindIs "invalid" .Values.server.tls.clientCaSecretName -}}
|
|
{{- fail "server.tls.clientCaSecretName cannot be null; omit the key to use the chart default (openshell-server-client-ca), or set to \"\" to disable client certificate verification for HTTPS-only mode" -}}
|
|
{{- end -}}
|
|
{{- end }}
|