Files
Aleksei Sviridkin 64f43f4f77 fix(computeplane): do not grant tenants the cluster admin kubeconfig
The computeplane module deploys the kubernetes chart into the tenant
namespace, and that chart's dashboard Role lets the tenant's "use"
subjects read <release>-admin-kubeconfig. Withholding the Secret from
the tenant secrets view does not help when RBAC grants it directly, so
any tenant user at "use" level or above could fetch the cluster-admin
credential of a cluster the module promises to keep operator-owned.

The kubernetes chart now drops that rule when the internal value
withholdAdminKubeconfig is set, and the module sets it in the cluster
HelmRelease's inline values. The tenant cannot reach those values: the
module reads only nodeGroups from them and splices an allowlisted
subset into the pool releases, and Flux applies spec.values over
valuesFrom. A regular kubernetes app keeps the rule, so its dashboard
can still offer the kubeconfig.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-10-02 14:36:57 +03:00
..

ComputePlane

A managed, isolated environment for running code-executing (untrusted-code) catalog applications — notebooks, workflow "code" nodes, plugin systems.

This tenant module provisions a single-tenant, Cozystack-managed cluster by deploying the ordinary apps/kubernetes chart (Kamaji control plane + KubeVirt-VM worker nodes) with operator-fixed values, sourced from this package's own source-only ExternalArtifact (cozystack-computeplane-application-kubevirt-kubernetes). The tenant gets the same substrate as any managed kind: Kubernetes cluster, but holds no admin kubeconfig for it. The security posture (Cozystack-enablement addons, withheld credentials) is baked into the chart and cannot be overridden; the one tenant-facing knob is nodeGroups — the module forwards only the documented per-pool fields to the pool chart, so any other NodeGroup field (e.g. talos, version, images) is silently dropped: accepted but inert.

The cluster HelmRelease is named computeplane-cluster and its release name is derived from it, so Kamaji writes the admin kubeconfig to the Secret computeplane-cluster-admin-kubeconfig (key super-admin.svc) — the derived contract consumed by the HelmReleases of placement: ComputePlane applications. The tenant never sees that Secret: tenant-visible secrets are an allowlist, and this module's ApplicationDefinition includes nothing, which withholds every Secret in the module's lineage by construction.

Prerequisite: an etcd module must be available to the tenant — its own, or one inherited from an ancestor tenant (the _namespace.etcd context propagates down the tenant tree, so enabling a redundant local etcd is not required). The tenant Kubernetes machinery — including the cluster-autoscaler that brings up the default scale-from-zero md0 node group — is backed by that etcd; without it the wrapped chart collapses to its awaiting-etcd status beacon and the ComputePlane provisions no control plane or workers (the HelmReleases still report Ready over the empty shell, so check for the beacon ConfigMap when nothing comes up).

See the full design in cozystack/community design-proposals/compute-plane.

Parameters

Cluster shape

Name Description Type Value
nodeGroups Worker node groups of the ComputePlane cluster (cluster shape only — the security posture is fixed by the module and cannot be overridden through this knob: the module forwards only the documented per-pool fields to the kubernetes-nodes pool chart, so any other NodeGroup field, e.g. talos/version/images, is silently dropped — accepted but inert). Same structure as the kubernetes application's nodeGroups. When left empty, the module itself materialises a default scale-from-zero md0 pool with roles: [ingress-nginx] (the wrapped kubernetes chart no longer injects one after the Phase 2 split), so the cluster sits idle until a pending Pod (e.g. the ingress-nginx controller) triggers the cluster-autoscaler. Supplied groups are authoritative (not merged with the default), so at least one of them must carry roles: [ingress-nginx] — enforced at render time. Each supplied pool key must be at most 15 characters so its kubernetes-nodes-computeplane-cluster-<pool> HelmRelease name fits Helm's 53-character limit — also enforced at render time. map[string]object {}
nodeGroups[name].minReplicas Minimum number of replicas. int 0
nodeGroups[name].maxReplicas Maximum number of replicas. int 10
nodeGroups[name].instanceType Virtual machine instance type. string u1.medium
nodeGroups[name].diskSize System disk size for the worker VM. Carries the Talos OS image, kubelet state, containerd image cache, and any local-path PVCs. quantity 20Gi
nodeGroups[name].storageClass StorageClass for worker node persistent disks. When empty, the kubernetes-nodes pool chart applies its own default (replicated). string ""
nodeGroups[name].roles List of node roles. At least one group must carry ingress-nginx — the module pins the ingress-nginx addon on, and its controller only schedules onto nodes with that role (enforced at render time). []string []
nodeGroups[name].resources Explicit CPU and memory for each worker node, as an alternative to instanceType sizing. Set both cpu and memory together or neither. object {}
nodeGroups[name].resources.cpu CPU available. quantity ""
nodeGroups[name].resources.memory Memory (RAM) available. quantity ""
nodeGroups[name].gpus List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). []object []
nodeGroups[name].gpus[i].name Name of GPU, such as "nvidia.com/AD102GL_L40S". string ""
nodeGroups[name].kubelet Kubelet resource reservations for this node group. object {}
nodeGroups[name].kubelet.systemReservedMemory Memory reserved for host OS. Auto-computed from instanceType if empty. string ""
nodeGroups[name].kubelet.kubeReservedMemory Memory reserved for kubelet and container runtime. Auto-computed from instanceType if empty. string ""
nodeGroups[name].kubelet.systemReservedCpu CPU reserved for host OS. Auto-computed from instanceType if empty. string ""
nodeGroups[name].kubelet.kubeReservedCpu CPU reserved for kubelet and container runtime. Auto-computed from instanceType if empty. string ""
nodeGroups[name].kubelet.evictionHardMemory Hard eviction threshold for memory (absolute like 200Mi or percentage like 7%). string 7%
nodeGroups[name].kubelet.evictionSoftMemory Soft eviction threshold for memory (absolute like 1Gi or percentage like 10%). string 10%