Multi-Tenant Session Architecture¶
This document describes the multi-tenant session model introduced by the KubeCopilotSession custom resource. It enables multiple users (tenants) to share a single kube-copilot-agent installation while maintaining strict data privacy and isolation.
Design Decisions¶
| Decision | Approach |
|---|---|
| Isolation boundary | Namespace-per-session — every KubeCopilotSession gets its own Kubernetes namespace. |
| Network isolation | A deny-all ingress NetworkPolicy is installed in each session namespace (configurable via isolationLevel). |
| Access control | A Role + RoleBinding scoped to the session namespace limits the tenant to kubecopilot.io resources only. |
| Lifecycle | A finalizer on the session object ensures the namespace (and all its contents) is deleted when the session is removed. |
How It Works¶
┌──────────────────────────────────────────────────────────────────────┐
│ Namespace: kube-copilot-agent (operator namespace) │
│ │
│ KubeCopilotSession KubeCopilotAgent │
│ ┌───────────────────┐ ┌────────────────────┐ │
│ │ name: tenant-alice │──ref─▶│ name: my-agent │ │
│ │ tenantID: alice │ └────────────────────┘ │
│ │ isolationLevel: │ │
│ │ strict │ │
│ └───────────────────┘ │
│ │ │
│ │ creates │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Namespace: kc-session-tenant-alice │ │
│ │ Labels: │ │
│ │ kubecopilot.io/tenant-id: alice │ │
│ │ kubecopilot.io/session: tenant-alice │ │
│ │ │ │
│ │ NetworkPolicy: tenant-isolation (deny-all) │ │
│ │ Role: tenant-session-role │ │
│ │ RoleBinding: tenant-session-binding │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Quick Start¶
1. Create an agent (if you haven't already)¶
apiVersion: kubecopilot.io/v1
kind: KubeCopilotAgent
metadata:
name: my-agent
namespace: kube-copilot-agent
spec:
githubTokenSecretRef:
name: github-token
2. Create a session for a tenant¶
apiVersion: kubecopilot.io/v1
kind: KubeCopilotSession
metadata:
name: tenant-alice
namespace: kube-copilot-agent
spec:
tenantID: alice
agentRef: my-agent
isolationLevel: strict # default; use "none" to skip NetworkPolicy
3. List sessions¶
NAME PHASE TENANTID NAMESPACE
tenant-alice Active alice kc-session-tenant-alice
tenant-bob Active bob kc-session-tenant-bob
4. Destroy a session¶
The finalizer ensures the session namespace (kc-session-tenant-alice) and all
resources inside it are deleted automatically.
Session Spec Reference¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
tenantID |
string | ✅ | — | Unique tenant identifier (1–63 chars, DNS-label format). |
agentRef |
string | ✅ | — | Name of the KubeCopilotAgent in the same namespace. |
isolationLevel |
enum | ❌ | strict |
strict installs a deny-all NetworkPolicy; none skips it. |
Session Status¶
| Field | Description |
|---|---|
phase |
Pending, Active, Error, or Terminating. |
namespace |
The isolated namespace created for the session (e.g. kc-session-tenant-alice). |
conditions |
Standard Kubernetes conditions. |
Security Model¶
- Namespace isolation — each tenant's resources live in a separate
namespace, preventing cross-tenant
kubectl getorkubectl exec. - NetworkPolicy — by default, a
deny-all ingresspolicy prevents pods in one session namespace from receiving traffic originating in another session's namespace. - RBAC — a tenant-scoped
Rolerestricts access tokubecopilot.io/*resources within the session namespace. TheRoleBindingbinds to the groupkubecopilot:tenant:<tenantID>, allowing operators to grant access through standard Kubernetes group-based authentication. - Finalizer cleanup — when a session is deleted the controller removes the entire namespace, guaranteeing no orphaned data.
Developer Guide¶
Running tests¶
The controller tests include:
- Basic reconciliation (namespace, NetworkPolicy, RBAC creation)
- Session isolation (two tenants get distinct namespaces)
isolationLevel=noneskips NetworkPolicy- Error handling (missing agent reference)
Regenerating manifests¶
After changing api/v1/kubecopilotsession_types.go: