Skip to content

feat: native Linux compute driver (openshell-driver-native) #2255

Description

@ericcurtin

Problem Statement

OpenShell's four compute drivers (openshell-driver-docker, openshell-driver-podman, openshell-driver-kubernetes, openshell-driver-vm) all translate OpenShell sandbox policy into a third party's API: a container engine daemon, a cluster control plane, or libkrun. Each translation seam is a place where semantics can drift from what OpenShell actually intends:

  • Heuristic detection instead of deterministic behavior. Sandbox stuck in Provisioning on macOS with Podman (libkrun): docker driver resolves host-gateway to unreachable bridge IP #1519 (closed) shows the docker driver's uses_host_gateway_alias() fingerprinting Docker Desktop, Colima, Lima, Rancher Desktop, and OrbStack by daemon OS string/hostname/labels, with no pattern for a Podman-backed Docker-compatible API on macOS — host.openshell.internal resolved to an unreachable bridge IP and sandboxes stuck in Provisioning. This is a structural cost, not a one-off bug: the "Docker-compatible API" is several different runtimes with divergent networking realities that need continual fingerprinting.
  • Host dependency for local/default use. Every local OpenShell run currently starts with "install Docker or Podman first." feat: support bubblewrap and Apptainer sandbox drivers for rootless/NFS hosts #1393 (open, HPC-focused) independently identifies this as a blocker in locked-down environments: enterprise policies that forbid new daemons, rootless Podman's NFS//etc/subuid friction, and virtualization overhead where it isn't wanted.
  • Isolation enforcement is split across a translation boundary. The driver approximates policy through engine flags/pod specs/libkrun config, and the in-sandbox supervisor patches the rest at runtime. There is ongoing work (RFC 0012, in review) to make the supervisor's boundary-construction logic a pluggable IsolationBackend contract precisely because embedding it inline couples policy enforcement to whichever engine happens to be underneath, and blocks restricted/multi-tenant clusters that reject the elevated capabilities inline construction needs (feat: Support restricted SecurityContextConstraints for managed Kubernetes platforms #899).

Proposed Design

Add a fifth compute driver, openshell-driver-native, that constructs the sandbox from kernel primitives directly instead of delegating to an external engine. Scope, informed by what the codebase already has:

Isolation via the kernel, not a re-implementation of one. Use Linux namespaces (user, mount, PID, net, UTS, IPC, cgroup), cgroups v2 for the CPU/memory limits the gateway already accepts, and seccomp + no_new_privs. Generate an OCI runtime-spec config.json from policy and drive a bundled runc through the standard create/start/state/delete CLI contract — the same integration pattern containerd/CRI-O/Podman already use — rather than hand-rolling namespace/pivot_root orchestration or linking a runtime in-process (forking inside the gateway/driver's own multithreaded async process is a hazard regardless of which runtime is chosen, so every option here ends up out-of-process). runc was chosen over youki (Rust-native, faster per its own benchmarks, but far less production exposure and security review) because the isolation-critical path is the wrong place to prefer toolchain uniformity over the OCI reference implementation's much larger adversarial track record; the differentiated work here is mapping OpenShell policy directly onto primitives, not re-debugging a decade of container-runtime edge cases.

Isolation logic implemented as an IsolationBackend, not duplicated supervisor code. RFC 0012 explicitly anticipates a component implementing both the compute-driver and isolation-backend roles together (its "Extend the compute-driver contract" alternative calls this "natural for topologies such as MXC"). openshell-driver-native should be the Linux-native counterpart to that pattern rather than adding another one-off namespace-construction branch inside the supervisor — this is a hard dependency on RFC 0012 landing, not an optional nice-to-have. Doing anything else recreates exactly the coupling problem RFC 0012 was written to close.

Reuse the VM driver's OCI image and network machinery instead of rebuilding it. openshell-driver-vm already downloads registry layers into a valid OCI layout with a content-addressed cache under its state directory (crates/openshell-driver-vm/src/driver.rs), and already generates a per-sandbox nftables ruleset with NAT, default-deny forwarding, and an input chain scoped to the gateway port (crates/openshell-driver-vm/src/nft_ruleset.rs) — today gated behind the QEMU/GPU-passthrough path only. A native driver should extract the OCI-pull/cache logic into a shared crate and reuse it with overlayfs assembly in place of ext4 conversion, and generalize the existing nftables ruleset generator to a veth-based default path instead of writing either from scratch.

GPU via CDI directly. Apply CDI device nodes/mounts from the local CDI inventory when constructing the mount namespace, the same source Docker/Podman already read from — no translation layer needed.

Process model matching the existing driver pattern. ComputeDriver is a gRPC service (proto/compute_driver.proto), not an in-process trait; drivers are separate processes. Follow the openshell-driver-vm precedent exactly: gateway spawns the driver binary over a private Unix socket, passing its own PID so the driver can validate the peer (crates/openshell-server/src/compute/vm.rs).

Rootless by default. User namespaces with newuidmap, no root requirement, no long-running daemon.

Alternatives Considered

  • feat: support bubblewrap and Apptainer sandbox drivers for rootless/NFS hosts #1393 (bubblewrap/Apptainer daemonless driver). Solves a related but distinct problem: unprivileged HPC hosts where installing any new tooling is restricted, using existing external unprivileged sandboxing tools rather than OpenShell owning the runtime. That approach trades control (thinner policy mapping, external tool's isolation semantics) for zero new binary footprint. This proposal is a different point on the same spectrum — full ownership of the primitives for the default local/single-node experience — and the two are complementary, not competing; a locked-down-HPC driver and a flagship native driver can both exist.
  • Extend the Docker/Podman driver's heuristics further. Keeps the host dependency and only patches the current symptom (Sandbox stuck in Provisioning on macOS with Podman (libkrun): docker driver resolves host-gateway to unreachable bridge IP #1519-style detection gaps); the next unrecognized runtime reopens the same class of bug.
  • Do nothing / rely on Docker or Podman indefinitely. Keeps the ecosystem debuggability (docker ps, docker logs) and defers the engineering cost, at the price of a permanent host dependency and translated-not-constructed policy enforcement.

Non-goals

  • Replacing the Docker, Podman, Kubernetes, or VM drivers. Kubernetes and container-engine drivers remain the right choice for existing infrastructure; this targets the local/default experience.
  • Reimplementing an OCI runtime from scratch. This should bundle and drive an existing runtime (runc) through its standard CLI contract.
  • Windows support — that is RFC 0013 (native Windows via MXC), which is pursuing the same "no in-sandbox supervisor binary" direction for a different kernel.

Agent Investigation

Grounded against origin/main:

  • uses_host_gateway_alias()crates/openshell-driver-docker/src/lib.rs:2456-2486; no Podman pattern; feeds docker_gateway_route_for_host (lib.rs:2420-2442) and docker_extra_hosts (lib.rs:2488-2499).
  • VM driver OCI layout + content-addressed cache — crates/openshell-driver-vm/src/driver.rs:4293-4353 (layout writers), driver.rs:3623-3746 (digest-verified blob download), driver.rs:4273-4390 (cache root/keying), rooted under config.state_dir (driver.rs:440-446).
  • VM driver rootfs assembly — layers extracted via extract_layer_blob_to_dir (driver.rs:3795) and converted to ext4 via crates/openshell-driver-vm/src/rootfs.rs:83-129.
  • VM driver nftables ruleset (NAT + default-deny + gateway-port-only input) — crates/openshell-driver-vm/src/nft_ruleset.rs:18-61 — currently wired only through setup_tap_networking (crates/openshell-driver-vm/src/runtime.rs:402), called only from the QEMU/GPU path (runtime.rs:77-131); the default libkrun path uses a gvproxy socket instead and does not install this ruleset (runtime.rs:654-813, crates/openshell-driver-vm/src/lifecycle.rs:109-111).
  • Gateway-spawns-driver-as-subprocess-with-peer-PID-validation pattern — crates/openshell-server/src/compute/vm.rs:452-489; enforced via crates/openshell-driver-vm/src/main.rs:269-271 and main.rs:426-448.
  • ComputeDriver is tonic-generated from proto/compute_driver.proto:18-43, not a hand-written Rust trait; concrete implementations are separate processes (e.g. crates/openshell-driver-podman/src/grpc.rs:32-152).
  • RFC 0012 (Isolation Backend interface, in review, PR rfc-0012: Isolation Backend interface #2048) and RFC 0013 (native Windows via MXC, in review, PR docs(rfc): add RFC 0013 native Windows support via MXC #2071) are the closest in-flight prior art; this proposal is designed to depend on and compose with RFC 0012 rather than duplicate its scope.

Metadata

Metadata

Assignees

No one assigned

    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