Skip to content

Commit 72845bc

Browse files
committed
feat(compute): delegate sandbox authentication to drivers
Signed-off-by: Drew Newberry <385+drew@users.noreply.github.com>
1 parent a715a90 commit 72845bc

24 files changed

Lines changed: 569 additions & 1347 deletions

File tree

.agents/skills/debug-openshell-cluster/SKILL.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,10 @@ Then inspect sandbox resources in that namespace.
460460
Check the configured sandbox service account when TokenReview bootstrap or
461461
sandbox registration fails. Helm creates a dedicated sandbox service account by
462462
default and writes it to `[openshell.drivers.kubernetes].service_account_name`;
463-
the gateway rejects projected tokens from other service accounts.
463+
the selected Kubernetes compute driver rejects projected tokens from other
464+
service accounts. For an external driver, inspect its logs and confirm it
465+
advertises `supports_sandbox_authentication`; the gateway delegates the opaque
466+
credential over the driver socket and never interprets Kubernetes settings.
464467

465468
```bash
466469
helm -n openshell get values openshell | grep -A3 sandboxServiceAccount

.agents/skills/helm-dev-environment/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ annotations to `spiffe://openshell.local/openshell/sandbox/<sandbox-id>`.
217217
OpenShell mounts the SPIFFE CSI Workload API socket at
218218
`/spiffe-workload-api/spire-agent.sock` into sandbox pods for provider token
219219
grants. Supervisor-to-gateway authentication remains on the Kubernetes
220-
ServiceAccount bootstrap and gateway-minted sandbox JWT path.
220+
ServiceAccount bootstrap and gateway-minted sandbox JWT path; the selected
221+
Kubernetes compute driver validates the projected token before the gateway
222+
mints its JWT.
221223

222224
---
223225

architecture/compute-runtimes.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ The driver reports this behavior through
169169
in-process and external drivers. Older drivers omit the field and retain the
170170
conservative operator-managed behavior.
171171

172+
Drivers that can verify a platform-native sandbox credential advertise
173+
`GetCapabilities.supports_sandbox_authentication`. On the path-scoped
174+
`IssueSandboxToken` exchange, the gateway forwards the opaque bearer credential
175+
to that selected driver through `AuthenticateSandbox`. The driver returns only
176+
the authenticated sandbox ID. The gateway then verifies that its durable
177+
sandbox record exists and mints the gateway JWT. The driver socket is therefore
178+
a sandbox-identity trust boundary, but it does not grant user or administrator
179+
authority.
180+
172181
## Deletion Lifecycle
173182

174183
Lifecycle requests use per-sandbox gates to serialize stop, start, and
@@ -457,20 +466,23 @@ watcher emits only sandbox CR changes, not platform events.
457466

458467
### SA Token Authentication
459468

460-
The gateway's `K8sServiceAccountAuthenticator` adapts its `NamespaceValidator`
461-
per mode (`crates/openshell-server/src/auth/k8s_sa.rs`):
469+
The Kubernetes driver's `AuthenticateSandbox` implementation applies its named
470+
`[openshell.drivers.kubernetes]` configuration per mode:
462471

463472
- **Shared:** `Exact` — accepts only the single configured namespace.
464473
- **Managed:** `Prefix` — accepts any namespace starting with `openshell-{gateway_id}-`.
465474
- **Operator:** `Allowlist` — accepts namespaces present in the dynamic
466475
`BTreeSet` populated by the label/file watchers. Starts empty (fail-closed)
467476
until the first watcher update.
468477

469-
These checks rely on an ownership invariant. In shared and managed modes, the
470-
gateway and its trusted Agent Sandbox controller exclusively administer the
471-
sandbox namespace, Sandbox CRs, sandbox pods, and configured sandbox
472-
ServiceAccount. Other principals must not create or mutate those resources or
473-
use that ServiceAccount. In operator mode, the platform operator retains
478+
It validates the projected token with Kubernetes `TokenReview`, checks the live
479+
pod UID, and verifies the pod's controlling Sandbox CR UID and sandbox ID before
480+
returning the identity to the gateway. These checks rely on an ownership
481+
invariant. In shared and managed modes, the Kubernetes driver and its trusted
482+
Agent Sandbox controller exclusively administer the sandbox namespace, Sandbox
483+
CRs, sandbox pods, and configured sandbox ServiceAccount. Other principals must
484+
not create or mutate those resources or use that ServiceAccount. In operator
485+
mode, the platform operator retains
474486
namespace lifecycle ownership, but must preserve the same exclusive control of
475487
Sandbox CRs and the pods and ServiceAccount used for sandbox token bootstrap.
476488
An allowlisted namespace is therefore a trust grant, not a tenant isolation

architecture/gateway.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,12 @@ identity inspection without client-side token decoding.
225225
Sandbox secrets are gateway-signed JWTs bound to a single sandbox ID. Docker,
226226
Podman, and VM drivers deliver the initial token through supervisor-only
227227
runtime material; Kubernetes supervisors exchange a projected ServiceAccount
228-
token through `IssueSandboxToken`. The gateway validates that projected token
229-
with Kubernetes `TokenReview`, requires the configured sandbox service account,
230-
checks the returned pod binding against the live pod UID, and verifies the pod's
231-
controlling `Sandbox` ownerReference against the live Sandbox CR UID and
232-
sandbox-id label before minting the gateway JWT. The bootstrap path accepts
228+
token through `IssueSandboxToken`. The gateway delegates that opaque credential
229+
to the selected compute driver's `AuthenticateSandbox` RPC. A capable driver is
230+
trusted to return the authenticated sandbox ID, while the gateway still requires
231+
a matching durable sandbox record before minting a JWT. The Kubernetes driver
232+
uses its own named configuration to run TokenReview and verify the live pod and
233+
controlling Sandbox CR. The bootstrap path accepts
233234
both `agents.x-k8s.io/v1beta1` ownerReferences from newer Agent Sandbox
234235
controllers and `agents.x-k8s.io/v1alpha1` ownerReferences from existing
235236
deployments. Supervisors renew gateway JWTs in memory before expiry only while

crates/openshell-driver-docker/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ impl DockerComputeDriver {
635635
driver_version: self.config.daemon_version.clone(),
636636
default_image: self.config.default_image.clone(),
637637
gateway_manages_lifecycle: true,
638+
supports_sandbox_authentication: false,
638639
}
639640
}
640641

@@ -1711,6 +1712,16 @@ impl DockerComputeDriver {
17111712

17121713
#[tonic::async_trait]
17131714
impl ComputeDriver for ComputeDriverService {
1715+
async fn authenticate_sandbox(
1716+
&self,
1717+
_request: Request<openshell_core::proto::compute::v1::AuthenticateSandboxRequest>,
1718+
) -> Result<Response<openshell_core::proto::compute::v1::AuthenticateSandboxResponse>, Status>
1719+
{
1720+
Err(Status::unimplemented(
1721+
"docker does not authenticate sandbox credentials",
1722+
))
1723+
}
1724+
17141725
type WatchSandboxesStream = WatchStream;
17151726

17161727
async fn get_capabilities(
@@ -1873,6 +1884,16 @@ impl ComputeDriver for ComputeDriverService {
18731884

18741885
#[tonic::async_trait]
18751886
impl ComputeDriver for DockerComputeDriver {
1887+
async fn authenticate_sandbox(
1888+
&self,
1889+
_request: Request<openshell_core::proto::compute::v1::AuthenticateSandboxRequest>,
1890+
) -> Result<Response<openshell_core::proto::compute::v1::AuthenticateSandboxResponse>, Status>
1891+
{
1892+
Err(Status::unimplemented(
1893+
"docker does not authenticate sandbox credentials",
1894+
))
1895+
}
1896+
18761897
type WatchSandboxesStream = WatchStream;
18771898

18781899
async fn get_capabilities(

crates/openshell-driver-kubernetes/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ workspace namespace modes via `workspace_mode`:
2020
Kubernetes namespace.
2121

2222
Workspace namespace modes assume exclusive control of the sandbox identity
23-
resource chain. In shared and managed modes, only the gateway and its trusted
23+
resource chain. In shared and managed modes, only the driver and its trusted
2424
Agent Sandbox controller may administer the sandbox namespace, Sandbox CRs,
2525
sandbox pods, or configured sandbox ServiceAccount. In operator mode, the
2626
platform operator owns namespace lifecycle but must prevent other principals
@@ -88,7 +88,9 @@ Sandbox pods run as `service_account_name` and keep
8888
`automountServiceAccountToken: false`. The only Kubernetes token exposed to the
8989
supervisor is an explicit, audience-bound projected token mounted at
9090
`/var/run/secrets/openshell/token` for the one-shot `IssueSandboxToken`
91-
bootstrap exchange.
91+
bootstrap exchange. The Kubernetes driver authenticates that token through the
92+
compute-driver protocol using its own `service_account_name` and workspace-mode
93+
namespace policy; the gateway receives only the verified sandbox ID.
9294

9395
The gateway uses the supervisor relay for connect, exec, and file sync. Sandbox
9496
pods do not need direct external ingress for SSH.

0 commit comments

Comments
 (0)