* refactor(driver): extract shared supervisor binary helpers Move supervisor binary extraction, caching, and validation helpers from the Docker driver into openshell-core::driver_utils so both Docker and Podman drivers can reuse them. Moved helpers: extract_first_tar_entry, write_cache_binary_atomic, supervisor_cache_path, temp_extract_container_name, and validate_linux_elf_binary. The shared extract_first_tar_entry gains entry-type and empty-payload checks that the Docker-local version lacked. supervisor_cache_path takes a driver_subdir parameter so each driver caches under its own namespace (docker-supervisor vs podman-supervisor). Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com> * feat(driver-podman): add userns config Add a `userns` option to the Podman compute driver that maps to Podman's user namespace modes. The mode string is split on the first colon into the API's `nsmode` and `value` fields so parameterized values like `auto:size=65536` and `keep-id:uid=1000,gid=1000` are forwarded correctly. When the mode is `auto`, the container spec also sets `idmappings.AutoUserNs = true` as required by the API. An allowlist validates the mode at startup: `auto` and `keep-id` accept optional parameters; `host`, `private`, and `nomap` reject them; everything else is an error. Podman image volumes use overlay mounts internally and the kernel does not support idmapped mounts on overlay (`mount_setattr` returns EINVAL). When userns is configured (any mode except `host`), the driver extracts the supervisor binary from the image to a host-side cache and bind-mounts it instead of using an image volume. Configurable via TOML `userns = "auto"`, CLI `--userns`, or environment variable `OPENSHELL_PODMAN_USERNS`. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com> --------- Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
openshell-driver-docker
Docker-backed compute driver for local OpenShell gateways.
The driver manages sandbox containers through the local Docker daemon with the
bollard client. It is intended for developer environments where Docker is
already available and running Kubernetes would be unnecessary.
The driver connects to [openshell.drivers.docker].socket_path when configured.
Otherwise, it uses the first standard local Docker socket that responds to an
API ping, which is the same selection mechanism used by gateway auto-detection.
An explicitly selected Docker driver falls back to /var/run/docker.sock when
no candidate responds.
Runtime Model
The gateway runs as a host process. The Docker driver creates one container per
sandbox and starts the openshell-sandbox supervisor inside that container. The
supervisor then creates the nested sandbox namespace for the agent process.
Stop and Start
Stop stops the managed container without removing it. Docker retains the container writable layer, attached volumes, labels, token material, and restart policy. Start starts that same container, so files in the resolved OCI workspace remain available. A durably stopped sandbox is excluded from gateway startup recovery and stays stopped across gateway restarts. Delete continues to force-remove the container and clean up driver-owned material.
Before creating the container, the driver inspects the final sandbox image and
captures its immutable image ID, raw OCI Config.User, and OCI
Config.WorkingDir. Container creation uses that image ID, preventing a
mutable tag from changing between inspection and launch. The supervisor runs as
root, resolves omitted policy identity fields from the image declaration, and
drops only agent children to the resulting identity. Named OCI components
remain names after validation; a missing group is filled with the user's
numeric primary GID. Explicit process.run_as_user and
process.run_as_group values take precedence independently.
An absolute OCI working directory becomes the agent workspace. An empty,
root (/), or explicit /sandbox declaration uses /sandbox, which OpenShell
creates when necessary and owns as a compatibility workspace. Any other image
workdir must already exist without symlink components. The completed identity,
including supplementary groups, must already be able to traverse every parent
and write and enter the workdir. OpenShell does not change its ownership or
mode.
OpenShell deliberately asks the Linux kernel to make this access decision under the completed sandbox identity instead of reproducing permission rules from ownership and mode bits. Mode-bit inspection alone can reject authority granted by a POSIX ACL or overlook a denial imposed by a Linux Security Module such as SELinux or AppArmor. OpenShell does not configure or otherwise manage ACLs or LSM policy here; the one-shot validator only observes the kernel's effective decision. This keeps the no-authority-expansion invariant aligned with the access the eventual workload will receive without adding a separate, incomplete permission model to OpenShell.
Image VOLUME declarations must not cover the workdir or one of its parents
because Docker would mount the volume before the supervisor could validate the
immutable image path.
Workdirs under the standard OCI runtime namespaces /proc, /sys, and /dev
are rejected, as are paths that overlap concrete OpenShell control resources.
The workspace is the child cwd and HOME. The supervisor starts from /, then
reports an invalid workdir as a readiness failure.
Docker containers join an OpenShell-managed bridge network. The driver injects
host.openshell.internal and host.docker.internal so supervisors have stable
names for reaching the gateway host. On Docker Desktop, Colima, Rancher
Desktop, OrbStack, and macOS-hosted gateways, those names use Docker's
host-gateway alias. The driver requests a separate IPv4 loopback callback
listener when the primary listener does not already cover it. On native Linux
Docker, the gateway also binds the bridge gateway IP so containers can call
back to the host process.
Container Contract
The driver-controlled container settings are part of the sandbox security contract:
| Setting | Purpose |
|---|---|
user = "0" |
The supervisor needs root inside the container to prepare namespaces, mounts, Landlock, and seccomp. |
network_mode = openshell |
Places the supervisor on the managed Docker bridge network. |
cap_add |
Grants supervisor-only capabilities required for namespace setup and process inspection. |
apparmor=unconfined |
Avoids Docker's default profile blocking required mount operations. |
restart_policy = unless-stopped |
Keeps managed sandboxes resumable across daemon or gateway restarts. |
PidsLimit |
Enforces the sandbox PID budget at the Docker cgroup layer. Set [openshell.drivers.docker].sandbox_pids_limit = 0 to inherit the Docker/runtime default. |
| CDI GPU request | Uses opaque driver_config.cdi_devices values when set; otherwise selects the requested count of NVIDIA CDI GPUs in round-robin order when daemon CDI support is detected. Docker daemon /info can permit nvidia.com/gpu=all as a WSL2 all-only compatibility fallback, where it counts as one selectable device. Exact CDI device lists must not contain duplicates and must match the effective GPU count. |
The agent child process does not retain these supervisor privileges.
Driver Config Mounts
The gateway forwards the docker block from --driver-config-json to this
driver. The driver accepts user-supplied mounts entries with these Docker
mount types:
bind: mounts an absolute host path when[openshell.drivers.docker]hasenable_bind_mounts = true.volume: mounts an existing Docker named volume. The driver validates that the volume exists before provisioning and never creates or removes it. Docker local-driver volumes created with bind options are treated as host bind mounts and requireenable_bind_mounts = true.tmpfs: mounts an in-memory filesystem with optionaloptions,size_bytes, andmode.
Host bind mounts are disabled by default because they expose gateway host paths to sandbox requests. Image mounts are not part of the Docker driver-config schema. The driver still uses internal bind mounts for OpenShell-owned supervisor, token, and TLS material.
Docker bind mounts accept source, target, optional read_only, and an
optional selinux_label of shared (applies :z) or private (applies
:Z) for SELinux-enforcing hosts. Docker volume mounts may include
subpath. User-supplied bind and volume mounts are read-only by default; set
read_only: false to make them writable. Mount source, target, and
subpath values must not contain surrounding whitespace. Mount targets must be
absolute container paths and must not replace or contain the resolved workspace
root. Nested workspace mounts remain valid. Mounts also must not overlap the
configured SSH socket or the reserved /opt/openshell, /etc/openshell,
/etc/openshell-tls, /run/openshell, /run/openshell-sidecar, and network
namespace roots.
Example named-volume usage:
docker volume create openshell-work
openshell sandbox create \
--driver-config-json '{"docker":{"mounts":[{"type":"volume","source":"openshell-work","target":"/sandbox/work"}]}}' \
-- claude
Supervisor Binary Resolution
The Docker driver bind-mounts a host-side Linux openshell-sandbox binary into
each sandbox container. Resolution order is:
supervisor_binin[openshell.drivers.docker].supervisor_imagein[openshell.drivers.docker], extracting/openshell-sandboxfrom that image.- A sibling
openshell-sandboxnext to the runningopenshell-gatewaybinary. - A local Linux cargo target build for the Docker daemon architecture.
- The release-matched default supervisor image, extracting
/openshell-sandbox.
Release and Docker-image gateway builds bake the matching supervisor image tag
into the binary at compile time. The default Docker supervisor image is not
:latest unless a custom build explicitly sets that tag.
Callback and TLS
OPENSHELL_ENDPOINT is injected from the gateway's configured gRPC endpoint.
When no endpoint is configured, the driver uses
host.openshell.internal:<gateway-port> with the appropriate HTTP or HTTPS
scheme. Set host_gateway_ip only when the host has an explicit, locally
assigned address that containers should use for callbacks; package-managed
macOS gateways should leave it unset.
For HTTPS endpoints, the server certificate must include the endpoint host as a subject alternative name. Docker sandboxes also need the client TLS bundle mounted into the container and exposed with:
OPENSHELL_TLS_CAOPENSHELL_TLS_CERTOPENSHELL_TLS_KEY
HTTP endpoints reject TLS material because the supervisor would not use it.
Environment Ownership
The driver merges template environment and sandbox spec environment first, then overwrites security-critical keys:
OPENSHELL_ENDPOINTOPENSHELL_SANDBOX_IDOPENSHELL_SANDBOXOPENSHELL_SSH_SOCKET_PATHOPENSHELL_SANDBOX_COMMAND- TLS path variables when HTTPS is enabled
Do not allow sandbox images or templates to override these values.