Skip to content

Follow-up to #17 (ComputePlane): deliver it as composable presets on kind: Kubernetes, not a new kind #26

Description

@myasnikovdaniil

Follow-up to #17 (ComputePlane for untrusted-code workloads, merged). The mechanism #17 settled on is right — remote Flux apply onto a Kamaji+KubeVirt cluster, untrusted code behind a per-VM kernel boundary. This is about the user-facing surface: I'd argue ComputePlane should not be a new top-level kind. It should be the existing kind: Kubernetes carrying composable presets along two orthogonal axes, plus a tenant-side access-control extension that lets some apps be hidden from regular tenant users.

This is a small generalization of something #17 already does: it defines a ComputePlane "<profile>" (node groups, GPU types, k8s version, autoscaling bounds) "defined once at the tenant/platform level and referenced by name." I'm proposing to lift that profile idea onto kind: Kubernetes itself, split it into two axes, and drop the separate kind.

Why not a new kind

  • design-proposal: compute plane for untrusted-code workloads #17 concedes they're the same object. From the approval summary: "from the management plane, a ComputePlane and a regular managed kind: Kubernetes are the same object." A distinct kind is UX sugar over identical machinery (Kamaji control plane, KubeVirt nodes, autoscaler, addon Flux apply). Make the variation a field, not a type.
  • Kinds don't compose; presets do. "Isolation posture" and "what's preinstalled" are independent. As kinds you'd get a combinatorial explosion (a kind per cell: sandbox-gpu, sandbox-minimal, meshed-gpu…). As two preset fields you get the whole matrix for free.
  • One substrate to maintain. Every future isolation/connectivity need becomes a new preset value, not a new CRD + reconcile path + RBAC surface.

Axis 1 — isolation / trust posture (isolationProfile)

Where the cluster sits on the trust ↔ containment spectrum; drives credentials, hardening, and network policy.

Preset Tenant holds admin? Network posture For
standard (default) yes — tenant administers normal egress; tenant-trusted today's behavior — clusters a tenant runs themselves
sandbox no — kubeconfig withheld (managedDataplane) hardened: restricted PSA + admission; deny egress → management kube-apiserver; scoped per-service egress only #17's ComputePlane — untrusted-code catalog apps; hardening is tamper-proof because the tenant can't get admin
cluster-meshed yes — tenant-trusted wired into the cross-cluster data plane (PR #7 mesh / TenantMeshLink) to reach shared/tenant services (Ceph, Postgres), still denied host kube-API trusted tenant clusters that need tight coupling to tenant data

sandbox vs cluster-meshed is exactly the distinction review #4 drew: a broad node-to-node mesh is fine for a tenant-trusted cluster, but untrusted sandbox code needs narrow per-service egress — never a wide mesh. Encoding that as two named presets makes the connectivity contract explicit instead of a hand-tuned policy. (#17 §5's scoped egress thus becomes the definition of the sandbox preset, not a separate section.)

Axis 2 — component bundle (componentProfile)

What's preinstalled inside, independent of trust posture. #17 already notes the kubernetes app deploys its own addons (cert-manager, ingress-nginx, CNI, monitoring) via remote Flux apply — a bundle preset just parameterizes that set.

  • minimal — CNI only, bare cluster
  • standard-addons — current default set (ingress-nginx, cert-manager, monitoring agents, autoscaler)
  • gpu — adds GPU node groups + operator/drivers
  • named app bundles — e.g. a notebook/code-exec runtime stack

Orthogonal to axis 1: isolationProfile: sandbox + componentProfile: gpu is a hardened GPU cluster for notebooks; isolationProfile: standard + componentProfile: gpu is a tenant-run GPU cluster — same substrate, two field values.

Hiding / blocking app access inside a tenant (RBAC / cozystack-api extension) — initial sketch

A capability #17 only touches obliquely (its review-#1 resolution: "withhold admin/write, allow scoped read") but that deserves first-class treatment, because it does double duty — a general tenant feature and the enforcement substrate for the sandbox preset above.

Idea. Let managed apps be marked so a tenant's regular users cannot see or manage them, while privileged subjects (tenant-admin / parent-tenant operator / superadmin) still can. Two separable things to control:

  • Visibility — does the app object appear in kubectl get / the dashboard for a given subject.
  • Mutation — can a subject create / update / delete it.

Where it's enforced. In the aggregated cozystack-api apiserver (pkg/registry/apps/application/rest.go), which already serves apps.cozystack.io/* and converts them to HelmReleases — not UI-only. A dashboard-only filter is bypassable by anyone holding a kubeconfig, so the gate must live at the API layer.

Granularity vs. RBAC limits.

  • Per-kind ("regular users can't touch Jupyter at all") → plain Kubernetes RBAC (a tenant Role that omits the verbs on that kind).
  • Per-instance ("this Jupyter, not that one") or label-driven ("hide apps labelled X") → cannot ride vanilla RBAC, because list/watch can't be filtered by name or label. Needs cozystack-api to filter responses by caller identity. Feasible since Cozystack owns the apiserver, but it's apiserver work, not a Role manifest.

Shape sketch (early, names TBD). An annotation/field on ApplicationDefinition — likely extending spec.dashboard, which already drives UI presentation / module: true — marking an app (or instance) as restricted, plus a tenant-role tier (regular vs privileged) that cozystack-api checks before listing/mutating.

How it composes.

  • It is the enforcement layer for isolationProfile: sandbox: "withhold the admin kubeconfig" and "regular tenant users can't manage the sandbox cluster object or its hardening" are the same RBAC-scoping mechanism, applied to the <cluster>-admin-kubeconfig secret and the cluster CR. Note "hide the app CR from the dashboard" ≠ "withhold the kubeconfig secret" — different objects; sandbox needs both.
  • Standalone, it's intra-tenant privilege separation: a parent/operator deploys infra or sensitive apps into a child namespace that the child's regular users shouldn't see or break.

What carries over from #17 unchanged

Open questions

  • Preset naming + defaults (standard as default keeps every existing manifest valid).
  • Are axis-1 presets mutually exclusive, or can sandbox + a scoped mesh-link coexist (hardened cluster that still reaches one tenant service)? The matrix suggests posture might be better as composable flags than a single enum.
  • How bundles are defined — as platform-level profile objects referenced by name (echoing design-proposal: compute plane for untrusted-code workloads #17's single-source-of-truth profile argument), so a tenant references a preset, never inlines an override blob.
  • Access-control model: is the visibility/mutation split worth exposing, or is a single restricted flag enough for v1? And do we start per-kind only (plain RBAC) and defer per-instance/label filtering to a later iteration?

cc the #17 discussion.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions