Skip to content
Open
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
7 changes: 5 additions & 2 deletions .agents/skills/debug-openshell-cluster/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ kubectl -n openshell get pod -l app.kubernetes.io/name=helm-chart -o jsonpath="{
```

Sandbox pods using provider token grants should have an
`openshell.io/sandbox-id` annotation, an `openshell.ai/managed-by=openshell`
`openshell.ai/sandbox-id` annotation, an `openshell.ai/managed-by=openshell`
label, supervisor env vars `OPENSHELL_K8S_SA_TOKEN_FILE` and
`OPENSHELL_PROVIDER_SPIFFE_WORKLOAD_API_SOCKET`, plus both the projected
`openshell-sa-token` volume and the `spiffe-workload-api` CSI volume.
Expand Down Expand Up @@ -467,7 +467,10 @@ Then inspect sandbox resources in that namespace.
Check the configured sandbox service account when TokenReview bootstrap or
sandbox registration fails. Helm creates a dedicated sandbox service account by
default and writes it to `[openshell.drivers.kubernetes].service_account_name`;
the gateway rejects projected tokens from other service accounts.
the selected Kubernetes compute driver rejects projected tokens from other
service accounts. For an external driver, inspect its logs and confirm it
advertises `supports_sandbox_authentication`; the gateway delegates the opaque
credential over the driver socket and never interprets Kubernetes settings.

```bash
helm -n openshell get values openshell | grep -A3 sandboxServiceAccount
Expand Down
4 changes: 3 additions & 1 deletion .agents/skills/helm-dev-environment/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ annotations to `spiffe://openshell.local/openshell/sandbox/<sandbox-id>`.
OpenShell mounts the SPIFFE CSI Workload API socket at
`/spiffe-workload-api/spire-agent.sock` into sandbox pods for provider token
grants. Supervisor-to-gateway authentication remains on the Kubernetes
ServiceAccount bootstrap and gateway-minted sandbox JWT path.
ServiceAccount bootstrap and gateway-minted sandbox JWT path; the selected
Kubernetes compute driver validates the projected token before the gateway
mints its JWT.

---

Expand Down
26 changes: 19 additions & 7 deletions architecture/compute-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ The driver reports this behavior through
in-process and external drivers. Older drivers omit the field and retain the
conservative operator-managed behavior.

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

## Deletion Lifecycle

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

### SA Token Authentication

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

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

These checks rely on an ownership invariant. In shared and managed modes, the
gateway and its trusted Agent Sandbox controller exclusively administer the
sandbox namespace, Sandbox CRs, sandbox pods, and configured sandbox
ServiceAccount. Other principals must not create or mutate those resources or
use that ServiceAccount. In operator mode, the platform operator retains
It validates the projected token with Kubernetes `TokenReview`, checks the live
pod UID, and verifies the pod's controlling Sandbox CR UID and sandbox ID before
returning the identity to the gateway. These checks rely on an ownership
invariant. In shared and managed modes, the Kubernetes driver and its trusted
Agent Sandbox controller exclusively administer the sandbox namespace, Sandbox
CRs, sandbox pods, and configured sandbox ServiceAccount. Other principals must
not create or mutate those resources or use that ServiceAccount. In operator
mode, the platform operator retains
namespace lifecycle ownership, but must preserve the same exclusive control of
Sandbox CRs and the pods and ServiceAccount used for sandbox token bootstrap.
An allowlisted namespace is therefore a trust grant, not a tenant isolation
Expand Down
11 changes: 6 additions & 5 deletions architecture/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,12 @@ identity inspection without client-side token decoding.
Sandbox secrets are gateway-signed JWTs bound to a single sandbox ID. Docker,
Podman, and VM drivers deliver the initial token through supervisor-only
runtime material; Kubernetes supervisors exchange a projected ServiceAccount
token through `IssueSandboxToken`. The gateway validates that projected token
with Kubernetes `TokenReview`, requires the configured sandbox service account,
checks the returned pod binding against the live pod UID, and verifies the pod's
controlling `Sandbox` ownerReference against the live Sandbox CR UID and
sandbox-id label before minting the gateway JWT. The bootstrap path accepts
token through `IssueSandboxToken`. The gateway delegates that opaque credential
to the selected compute driver's `AuthenticateSandbox` RPC. A capable driver is
trusted to return the authenticated sandbox ID, while the gateway still requires
a matching durable sandbox record before minting a JWT. The Kubernetes driver
uses its own named configuration to run TokenReview and verify the live pod and
controlling Sandbox CR. The bootstrap path accepts
both `agents.x-k8s.io/v1beta1` ownerReferences from newer Agent Sandbox
controllers and `agents.x-k8s.io/v1alpha1` ownerReferences from existing
deployments. Supervisors renew gateway JWTs in memory before expiry only while
Expand Down
24 changes: 24 additions & 0 deletions crates/openshell-driver-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ impl DockerComputeDriver {
driver_version: self.config.daemon_version.clone(),
default_image: self.config.default_image.clone(),
gateway_manages_lifecycle: true,
supports_sandbox_authentication: false,
}
}

Expand Down Expand Up @@ -1713,6 +1714,19 @@ impl DockerComputeDriver {
impl ComputeDriver for ComputeDriverService {
type WatchSandboxesStream = WatchStream;

async fn authenticate_sandbox(
&self,
request: Request<openshell_core::proto::compute::v1::AuthenticateSandboxRequest>,
) -> Result<Response<openshell_core::proto::compute::v1::AuthenticateSandboxResponse>, Status>
{
self.trace_rpc(
"driver.authenticate_sandbox",
"authenticate_sandbox",
ComputeDriver::authenticate_sandbox(&self.driver, request),
)
.await
}

async fn get_capabilities(
&self,
request: Request<GetCapabilitiesRequest>,
Expand Down Expand Up @@ -1873,6 +1887,16 @@ impl ComputeDriver for ComputeDriverService {

#[tonic::async_trait]
impl ComputeDriver for DockerComputeDriver {
async fn authenticate_sandbox(
&self,
_request: Request<openshell_core::proto::compute::v1::AuthenticateSandboxRequest>,
) -> Result<Response<openshell_core::proto::compute::v1::AuthenticateSandboxResponse>, Status>
{
Err(Status::unimplemented(
"docker does not authenticate sandbox credentials",
))
}

type WatchSandboxesStream = WatchStream;

async fn get_capabilities(
Expand Down
6 changes: 4 additions & 2 deletions crates/openshell-driver-kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ annotation. An OTLP-enabled Agent Sandbox controller can therefore attach its
asynchronous reconciliation spans to the originating OpenShell create trace.

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

The gateway uses the supervisor relay for connect, exec, and file sync. Sandbox
pods do not need direct external ingress for SSH.
Expand Down
2 changes: 1 addition & 1 deletion crates/openshell-driver-kubernetes/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ pub struct KubernetesComputeConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operator_namespace_file: Option<String>,
/// Kubernetes `ServiceAccount` assigned to sandbox pods and accepted by
/// the gateway's `TokenReview` bootstrap authenticator.
/// the driver's `TokenReview` bootstrap authenticator.
pub service_account_name: String,
pub default_image: String,
pub image_pull_policy: String,
Expand Down
Loading
Loading