Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ jobs:
run: scripts/test-helm-insights.sh
- name: Helm custom image contracts
run: scripts/test-helm-images.sh
- name: Helm multi-tenancy contracts
run: scripts/test-helm-tenancy.sh
- name: Render LoadBalancer installation
run: helm template devboxes charts/devboxes --namespace devboxes > /tmp/devboxes.yaml
- name: Validate Kubernetes resources
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ controller/**/*.pyc

cli/target/

workspace/**/__pycache__/
workspace/**/*.pyc

dist/
*.tgz
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ All notable changes to Devboxes are documented here. The project follows [Keep a

## [Unreleased]

### Added

- Added opt-in namespace-per-tenant isolation across Helm, the controller API, CLI,
dashboard, authenticated documentation, and public documentation, with principal
memberships, `viewer`/`member`/`admin` roles, tenant switching, creator attribution, and
backward-compatible single-operator defaults.
- Added per-tenant workspace Secrets, tokenless ServiceAccounts, namespaced controller RBAC,
retained namespace creation, ResourceQuota, SSH-only workspace ingress NetworkPolicy,
StorageClass and TTL policy, and preset, GPU, and custom-image allowlists.
- Added tenant-scoped Insights ingest credentials, storage, queries, exports, migrations, and
purge, including sanitized online SQLite backups and legacy-history assignment to the
configured default tenant.
- Added Helm tenancy contract tests plus controller, API, authorization, manager, Insights,
and CLI coverage for isolation, role enforcement, quota failures, migration, and
configuration precedence.

### Security

- Keep principal token values out of Helm values by loading unique strong credentials from a
read-only existing Secret, bind sessions and CLI tokens to credential epochs, and resolve
live tenant membership before every request.

## [0.5.1] - 2026-07-27

### Changed
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ By participating, you agree to follow [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) a
## Engineering expectations

- Preserve explicit persistence semantics: TTL and stop never delete a PVC.
- Keep Kubernetes permissions namespace-scoped and workspace service accounts tokenless.
- Keep Kubernetes permissions namespace-scoped and workspace service accounts tokenless;
tenancy changes must prove cross-namespace and cross-principal denial.
- Do not add plaintext credentials, example secrets, telemetry, or external calls without explicit user control.
- Maintain both `LoadBalancer` and `NodePort` SSH paths.
- Keep the CLI usable in scripts: errors go to stderr, JSON remains stable within a release line, and tokens should not be required on command lines.
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ helm:
scripts/test-helm-insights.sh
scripts/test-helm-gpu.sh
scripts/test-helm-images.sh
scripts/test-helm-tenancy.sh
helm template devboxes charts/devboxes --namespace devboxes --set workspace.sshService.type=NodePort --set workspace.sshService.host=192.0.2.10 >/dev/null

images:
Expand Down
54 changes: 54 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Product

## Register

product

## Users

Devboxes serves developers who need durable, prepared development environments and Kubernetes
operators who make those environments safe and supportable. Developers work from the CLI or
browser workbench and expect fast creation, reliable SSH reconnection, and explicit data
lifecycle behavior. Operators configure cluster policy, credentials, storage, networking,
images, accelerators, tenancy, and capacity without exposing raw Kubernetes controls to users.

## Product Purpose

Devboxes turns approved Kubernetes capacity into ready-to-use development machines. Compute is
disposable, `/home/dev` is durable until an explicit purge, and the same lifecycle contract is
available through the API, CLI, and dashboard. Success means a developer can understand and
control a workspace without learning Kubernetes internals while an operator can prove which
policy, identity, namespace, credentials, and resources govern it.

## Brand Personality

Clear, operational, trustworthy. The product speaks directly, makes destructive behavior
unmistakable, and presents cluster state without hype or false certainty.

## Anti-references

Devboxes should not resemble a decorative SaaS control panel, a consumer cloud drive, or a
generic generated admin template. Avoid hidden automation, ornamental metrics, vague resource
labels, invented interaction patterns, and UI-only security controls. Do not expose arbitrary
Kubernetes fields merely to make a workflow look flexible.

## Design Principles

1. Keep operator policy separate from user intent. Users select stable, named capabilities;
operators own namespaces, credentials, scheduling, quotas, and trusted catalogs.
2. Make persistence and destructive actions explicit. Stop, delete, and purge must remain
visibly distinct in every interface.
3. Preserve terminal, API, and dashboard parity. The same identity, tenancy, capability, and
lifecycle contract should be discoverable everywhere.
4. Show real state and useful recovery paths. Pending capacity, policy rejection, degraded
workloads, and retained storage should be described honestly and specifically.
5. Prefer familiar, accessible controls over novelty. Keyboard operation, visible focus,
semantic markup, reduced motion, readable contrast, and responsive task flows are baseline
product behavior.

## Accessibility & Inclusion

Preserve the existing keyboard-first and screen-reader-aware interface conventions: skip links,
semantic headings and tables, explicit labels, live regions, focus restoration, and native
dialogs and controls. State must never depend on color alone. Maintain readable contrast,
usable narrow-screen layouts, and reduced-motion behavior across all authenticated surfaces.
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ Each workspace includes Rust, Node.js, Python, `uv`, GitHub CLI, Codex CLI, Clau
- A Rust `devbox` CLI for create, list, inspect, SSH, start, stop, delete, custom-image profiles, and opt-in Insights workflows.
- A FastAPI controller with an authenticated API, accessible browser workbench, Insights dashboard, documentation, metrics, health checks, and TTL cleanup.
- A versioned Helm chart with values schema validation and namespace-scoped RBAC.
- Optional namespace-per-tenant isolation with principal roles, quotas, policy catalogs, and
tenant-scoped Insights.
- Optional operator-approved GPU profiles for NVIDIA, AMD, Intel, partitioned, or shared accelerators.
- Optional operator-approved custom image profiles for pod-local services or vetted workspace derivatives.
- Multi-architecture controller and workspace images for `linux/amd64` and `linux/arm64`.
- Persistent SSH host identity, shell state, tool installs, account state, and source under `/home/dev`.
- GitHub Releases with macOS and Linux CLI binaries and SHA-256 checksums.

Devboxes is currently a single-operator system: one shared token controls every box in one installation. It is suitable for a trusted personal cluster or trusted operator group, not mutually untrusted tenants.
Devboxes defaults to a backward-compatible single-operator installation. Operators can
enable [multi-tenancy](docs/multi-tenancy.md) to give users or teams independent Kubernetes
namespaces, credentials, storage, SSH identity, policy catalogs, resource quotas, and
tenant-scoped lifecycle and Insights access. The master token remains an
installation-wide operator credential.

## Requirements

Expand Down Expand Up @@ -94,6 +100,53 @@ helm install devboxes oci://ghcr.io/vicotrbb/charts/devboxes \

The chart never embeds credential values. It references existing Kubernetes Secrets so it also works with External Secrets Operator, Sealed Secrets, SOPS, Infisical, Vault, and other secret-management workflows.

### Enable multi-tenancy

Multi-tenancy is opt-in and uses one Kubernetes namespace per tenant. Operators define
principal memberships and roles in Helm while storing every principal token in an existing
Secret:

```yaml
tenancy:
enabled: true
defaultTenant: platform
existingPrincipalSecret: devboxes-principals
tenants:
- id: platform
displayName: Platform Engineering
namespace: devboxes-platform
createNamespace: true
workspaceSecretName: devboxes-platform-workspace
allowedPresets: [small, medium]
maxTtlHours: 72
resourceQuota:
enabled: true
hard:
requests.cpu: "8"
requests.memory: 32Gi
requests.storage: 500Gi
principals:
- subject: alice@example.com
displayName: Alice
tokenKey: alice-token
defaultTenant: platform
memberships:
- tenant: platform
role: admin
```

Users can switch their authorized context consistently in the CLI or dashboard:

```bash
devbox tenant list
devbox tenant use platform
devbox --tenant platform create atlas --preset medium --ssh
```

Read [multi-tenancy](docs/multi-tenancy.md) before rollout for the complete role matrix,
Secret provisioning, isolation guarantees, quota behavior, Insights migration, and
rollback contract.

### Configure dashboard ingress

This example uses a generic nginx ingress class and an existing TLS Secret:
Expand Down Expand Up @@ -322,7 +375,10 @@ Devboxes controller ─── Kubernetes API
└─ Insights SQLite PVC (optional central history)
```

The controller watches only its release namespace and receives namespace-scoped RBAC. Workspace pods do not receive Kubernetes service-account tokens. See [architecture](docs/architecture.md) for resource ownership, persistence, readiness, and threat boundaries.
The controller receives only namespaced RBAC: the release namespace in default mode or each
explicitly configured tenant namespace in multi-tenant mode. Workspace pods do not receive
Kubernetes service-account tokens. See [architecture](docs/architecture.md) for resource
ownership, persistence, readiness, and threat boundaries.

## Development

Expand Down
26 changes: 25 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,31 @@ You should receive an acknowledgement within seven days. Please allow maintainer

## Security model

Devboxes assumes a trusted single operator or trusted operator group. The shared access token can control and permanently purge every devbox in an installation. Workspaces are development machines with passwordless `sudo`; they are not sandboxes for untrusted code or mutually untrusted users.
Devboxes defaults to a trusted single operator or operator group. Opt-in multi-tenancy adds
principal roles and a dedicated Kubernetes namespace, credentials, storage, quota, and
telemetry scope per tenant. The master access token remains an installation-wide operator
credential that can control and permanently purge every tenant.

Workspaces are development machines with passwordless `sudo`; they are not sandboxes for
hostile code. Namespace isolation, application authorization, RBAC, ResourceQuota, and
NetworkPolicy provide administrative tenant separation and defense in depth, not a hardened
kernel or node boundary. Use separate clusters or appropriately sandboxed runtimes when
tenants must run mutually hostile workloads.

Recommended deployment controls:

- expose the controller only through HTTPS or local port-forwarding;
- restrict controller ingress and SSH Services to trusted client networks;
- use a 256-bit or stronger controller token and rotate it after suspected exposure;
- give tenant users unique principal tokens and least-privilege `viewer`, `member`, or
`admin` memberships instead of the master token;
- use one namespace per person when independent ownership or quota is required, and keep
tenant workspace Secrets separate;
- use fine-grained, least-privilege provider tokens;
- enable Kubernetes Secret encryption at rest and an auditable secret manager;
- enforce the chart's namespace-scoped RBAC and tokenless workspace service account;
- use a NetworkPolicy-capable CNI, review the chart's SSH-only ingress policy, and add
environment-specific source and egress restrictions where required;
- expose GPU devices only through reviewed operator-owned profiles and trusted derived images;
- back up important PVCs and test restore procedures;
- keep Kubernetes, ingress, CSI, images, chart, and CLI releases current;
Expand All @@ -42,6 +57,15 @@ tmux starts.

GPU profiles preserve this trust model. Clients can select only a configured name; they cannot inject an image, extended resource, RuntimeClass, supplemental group, selector, toleration, privileged mode, host path, or device path. The assigned device is fully available to the trusted workspace user, including through passwordless `sudo`, so GPU profiles do not make Devboxes suitable for untrusted workloads. Vendor drivers, device plugins, runtimes, and derived GPU images are part of the operator's trusted supply chain.

Multi-tenant clients select only a configured tenant ID and an operator-approved policy
catalog. Unknown and unauthorized IDs use the same forbidden response. The controller has
one namespaced RoleBinding per configured tenant and no ClusterRole; workspaces use
ServiceAccounts without API tokens or RBAC bindings. Tenant Insights exports, including
SQLite backups, are sanitized to the active tenant before leaving the controller.
Application roles do not replace SSH authorization: shell access is controlled by the public
keys in each tenant workspace Secret. A viewer with an authorized key can modify workspace
files but cannot change the workspace lifecycle through the Devboxes API.

## Dependency and supply-chain policy

Dependabot monitors npm, Cargo, Python, Docker, and GitHub Actions dependencies. Pull requests receive dependency review, and CI audits npm, Python, and Cargo dependencies, builds both images, performs strict JavaScript, documentation, controller, and CLI checks, validates Helm output, and installs into a clean Kind cluster. Release images are published for amd64 and arm64 with GitHub artifact attestations; CLI archives include SHA-256 checksums.
25 changes: 25 additions & 0 deletions charts/devboxes/templates/NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,22 @@ Devboxes is installed in namespace {{ .Release.Namespace }}.
kubectl -n {{ .Release.Namespace }} create secret generic {{ .Values.controller.existingSecret }} \
--from-literal={{ .Values.controller.accessTokenKey }}='<strong-random-token>'

{{- if .Values.tenancy.enabled }}
{{- if gt (len .Values.tenancy.principals) 0 }}

kubectl -n {{ .Release.Namespace }} create secret generic {{ .Values.tenancy.existingPrincipalSecret }} \
--from-literal={{ (index .Values.tenancy.principals 0).tokenKey }}='<strong-random-principal-token>'
{{- end }}
{{- range $tenant := .Values.tenancy.tenants }}

kubectl -n {{ $tenant.namespace }} create secret generic {{ default $.Values.workspace.existingSecret $tenant.workspaceSecretName }} \
--from-file=SSH_AUTHORIZED_KEYS="$HOME/.ssh/id_ed25519.pub"
{{- end }}
{{- else }}

kubectl -n {{ .Release.Namespace }} create secret generic {{ .Values.workspace.existingSecret }} \
--from-file=SSH_AUTHORIZED_KEYS="$HOME/.ssh/id_ed25519.pub"
{{- end }}

2. Wait for the controller:

Expand All @@ -26,6 +40,17 @@ Devboxes is installed in namespace {{ .Release.Namespace }}.
Important: every workspace needs an externally reachable SSH service. Configure
workspace.sshService for your cluster's LoadBalancer implementation or use
NodePort with workspace.sshService.host.
{{- if .Values.tenancy.enabled }}

Multi-tenancy is enabled with {{ len .Values.tenancy.tenants }} namespace-isolated tenant(s).
Verify principal membership and select a tenant with:

devbox tenant list
devbox tenant use {{ .Values.tenancy.defaultTenant }}

Review ResourceQuota and NetworkPolicy status in every tenant namespace before onboarding
users. The master controller token remains an installation-wide operator credential.
{{- end }}
{{- if .Values.gpu.enabled }}

GPU acceleration is enabled with {{ len .Values.gpu.profiles }} operator-approved profile(s).
Expand Down
4 changes: 4 additions & 0 deletions charts/devboxes/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ app.kubernetes.io/component: controller
{{- end }}
{{- end }}

{{- define "devboxes.tenantWorkspaceServiceAccountName" -}}
{{- default (include "devboxes.workspaceServiceAccountName" .root) .tenant.workspaceServiceAccountName }}
{{- end }}

{{- define "devboxes.controllerImage" -}}
{{- printf "%s:%s" .Values.controller.image.repository (default .Chart.AppVersion .Values.controller.image.tag) }}
{{- end }}
Expand Down
23 changes: 22 additions & 1 deletion charts/devboxes/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ spec:
value: {{ .Values.controller.displayName | quote }}
- name: DEVBOXES_CLUSTER_NAME
value: {{ .Values.controller.clusterName | quote }}
- name: DEVBOXES_MULTI_TENANCY_ENABLED
value: {{ .Values.tenancy.enabled | quote }}
- name: DEVBOXES_DEFAULT_TENANT
value: {{ .Values.tenancy.defaultTenant | quote }}
- name: DEVBOXES_TENANTS
value: {{ .Values.tenancy.tenants | toJson | quote }}
- name: DEVBOXES_PRINCIPALS
value: {{ .Values.tenancy.principals | toJson | quote }}
- name: DEVBOXES_PRINCIPAL_TOKEN_DIRECTORY
value: {{ .Values.tenancy.principalTokenMountPath | quote }}
- name: DEVBOXES_COOKIE_SECURE
value: {{ .Values.controller.cookieSecure | quote }}
- name: DEVBOXES_DEFAULT_TTL_HOURS
Expand Down Expand Up @@ -157,7 +167,7 @@ spec:
- name: DEVBOXES_INSIGHTS_DATABASE_WARNING_BYTES
value: {{ int .Values.insights.storage.warningBytes | quote }}
- name: DEVBOXES_INSIGHTS_CONTROLLER_URL
value: {{ printf "http://%s:%v" (include "devboxes.fullname" .) .Values.service.port | quote }}
value: {{ printf "http://%s.%s.svc:%v" (include "devboxes.fullname" .) .Release.Namespace .Values.service.port | quote }}
- name: DEVBOXES_INSIGHTS_RETENTION_RAW_DAYS
value: {{ int .Values.insights.retention.rawDays | quote }}
- name: DEVBOXES_INSIGHTS_RETENTION_HOURLY_DAYS
Expand Down Expand Up @@ -229,13 +239,24 @@ spec:
volumeMounts:
- name: tmp
mountPath: /tmp
{{- if gt (len .Values.tenancy.principals) 0 }}
- name: principal-tokens
mountPath: {{ .Values.tenancy.principalTokenMountPath }}
readOnly: true
{{- end }}
{{- if .Values.insights.enabled }}
- name: insights
mountPath: /var/lib/devboxes
{{- end }}
volumes:
- name: tmp
emptyDir: {}
{{- if gt (len .Values.tenancy.principals) 0 }}
- name: principal-tokens
secret:
secretName: {{ .Values.tenancy.existingPrincipalSecret }}
defaultMode: 0440
{{- end }}
{{- if .Values.insights.enabled }}
- name: insights
persistentVolumeClaim:
Expand Down
Loading