Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
dfaa4ad
feat(vm): launch capability-free guest sandbox
drew Sep 5, 2026
8112482
fix(vm): satisfy portable supervisor lookup lint
drew Sep 5, 2026
b08e1c2
test(vm): skip recovery without e2fsck
drew Sep 5, 2026
4a51f29
ci(vm): embed dedicated supervisor runtime
drew Sep 5, 2026
ed3f7e3
ci(vm): package trusted helper runtime
drew Sep 5, 2026
c641c5e
fix(vm): surface guest console failures
drew Sep 5, 2026
dbfc135
fix(vm): embed native host supervisor
drew Sep 5, 2026
8a4f708
fix(vm): activate Landlock in guest kernel
drew Sep 5, 2026
e27c519
fix(vm): retire obsolete kernel network checks
drew Sep 5, 2026
8796af6
build(vm): pin capability-free runtime
drew Sep 5, 2026
6089614
chore(stack): merge supervisor runtime fix
drew Sep 5, 2026
c7edab1
chore(stack): merge supervisor DNS fix
drew Sep 5, 2026
598352c
chore(stack): merge supervisor route probe fix
drew Sep 5, 2026
fa68198
chore(stack): merge supervisor route probe test fix
drew Sep 5, 2026
96b686d
chore(stack): merge supervisor DNS relay fix
drew Sep 5, 2026
a9ca78d
chore(stack): merge gateway callback startup fix
drew Sep 5, 2026
2c02412
chore(stack): merge supervisor readiness fix
drew Sep 6, 2026
b846483
chore(stack): merge supervisor restart fencing
drew Sep 6, 2026
292d5ad
chore(stack): merge supervisor restart fixture
drew Sep 6, 2026
894f4f0
chore(stack): merge supervisor reconciliation fixtures
drew Sep 6, 2026
ea3dda9
chore(stack): merge supervisor transient fixture
drew Sep 6, 2026
2d402d7
chore(stack): merge supervisor restart assertion
drew Sep 6, 2026
b529024
chore(stack): merge supervisor bootstrap recovery
drew Sep 6, 2026
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
105 changes: 105 additions & 0 deletions .github/workflows/build-vm-driver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,88 @@ permissions:
contents: read

jobs:
host-supervisor-macos:
name: native host supervisor (aarch64-apple-darwin)
permissions:
contents: read
uses: ./.github/workflows/build-binaries.yml
with:
package: openshell-supervisor
binary: openshell-supervisor
triple: aarch64-apple-darwin
runner: macos-15-xlarge
dev-shell: .#devShells.aarch64-darwin.default
cargo-version: ${{ inputs.cargo-version }}
image-tag: ${{ inputs.image-tag }}
checkout-ref: ${{ inputs.checkout-ref }}
secrets: inherit

helper-runtime:
name: helper runtime (${{ matrix.arch }})
strategy:
matrix:
include:
- arch: x86_64
docker_arch: amd64
runner: linux-amd64-cpu8
- arch: aarch64
docker_arch: arm64
runner: linux-arm64-cpu8
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs['checkout-ref'] || github.sha }}

- name: Download openshell-sandbox
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl
path: sandbox

- name: Download openshell-supervisor
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-supervisor-${{ matrix.arch }}-unknown-linux-musl
path: supervisor

- name: Assemble trusted helper runtime
shell: bash
run: |
binary_dir="deploy/docker/.build/prebuilt-binaries/${{ matrix.docker_arch }}"
install -d "$binary_dir" artifacts
install -m 0555 sandbox/openshell-sandbox "$binary_dir/openshell-sandbox"
install -m 0555 supervisor/openshell-supervisor "$binary_dir/openshell-supervisor"

image="openshell-vm-helper-runtime:${{ matrix.docker_arch }}-${GITHUB_RUN_ID}"
container=""
cleanup() {
if [ -n "$container" ]; then docker rm -f "$container" >/dev/null 2>&1 || true; fi
docker image rm "$image" >/dev/null 2>&1 || true
}
trap cleanup EXIT

docker build \
--build-arg "TARGETARCH=${{ matrix.docker_arch }}" \
--file deploy/docker/Dockerfile.supervisor \
--tag "$image" \
.
container="$(docker create "$image")"
docker cp "$container:/openshell-runtime" - \
| zstd -19 -T0 -o artifacts/openshell-runtime.tar.zst
test -s artifacts/openshell-runtime.tar.zst

- name: Upload trusted helper runtime
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: openshell-vm-helper-runtime-${{ matrix.arch }}
path: artifacts/openshell-runtime.tar.zst
if-no-files-found: error

build:
name: openshell-driver-vm (${{ matrix.triple }})
needs: [helper-runtime, host-supervisor-macos]
strategy:
matrix:
include:
Expand Down Expand Up @@ -64,15 +144,40 @@ jobs:
name: openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl
path: sandbox

- name: Download openshell-supervisor
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-supervisor-${{ matrix.arch }}-unknown-linux-musl
path: supervisor

- name: Download native macOS host supervisor
if: endsWith(matrix.triple, '-apple-darwin')
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-supervisor-aarch64-apple-darwin
path: host-supervisor

- name: Download trusted helper runtime
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-vm-helper-runtime-${{ matrix.arch }}
path: helper-runtime

- name: Build VM runtime
run: nix build .#vm-runtime

- name: Assemble compressed VM runtime
run: |
compressed_dir="${RUNNER_TEMP}/vm-runtime-compressed"
host_supervisor="supervisor/openshell-supervisor"
if [[ "${{ matrix.triple }}" == *-apple-darwin ]]; then
host_supervisor="host-supervisor/openshell-supervisor"
fi
install -d "$compressed_dir"
cp result/compressed/*.zst "$compressed_dir/"
zstd -19 -T1 sandbox/openshell-sandbox -o "$compressed_dir/openshell-sandbox.zst"
zstd -19 -T1 "$host_supervisor" -o "$compressed_dir/openshell-supervisor.zst"
cp helper-runtime/openshell-runtime.tar.zst "$compressed_dir/"

- name: Build openshell-driver-vm
uses: ./.github/actions/build-rust-binary
Expand Down
29 changes: 20 additions & 9 deletions .github/workflows/release-vm-kernel.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Release VM Kernel

# Build custom libkrunfw (kernel firmware) + libkrun (VMM) + gvproxy for all
# Build custom libkrunfw (kernel firmware) + libkrun (VMM) for all
# supported openshell-driver-vm platforms. Artifacts are uploaded to the
# rolling "vm-runtime" GitHub Release and consumed by normal dev/tag release
# workflows when building the openshell-driver-vm binary.
Expand All @@ -16,14 +16,20 @@ name: Release VM Kernel

on:
workflow_dispatch:
inputs:
release-tag:
description: Rolling prerelease tag to create or update
required: false
default: vm-runtime
type: string

permissions:
contents: write
packages: read

# Serialize runtime release updates.
concurrency:
group: vm-runtime-release
group: vm-runtime-release-${{ inputs.release-tag || 'vm-runtime' }}
cancel-in-progress: false

defaults:
Expand Down Expand Up @@ -194,23 +200,28 @@ jobs:
release/vm-runtime-darwin-aarch64.tar.zst

- name: Ensure vm-runtime tag exists
env:
RELEASE_TAG: ${{ inputs.release-tag || 'vm-runtime' }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -fa vm-runtime -m "VM Runtime Development Build" "${GITHUB_SHA}"
git push --force origin vm-runtime
git tag -fa "$RELEASE_TAG" -m "VM Runtime Development Build" "${GITHUB_SHA}"
git push --force origin "$RELEASE_TAG"

- name: Prune stale runtime assets from vm-runtime release
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
RELEASE_TAG: ${{ inputs.release-tag || 'vm-runtime' }}
with:
script: |
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
const tag = process.env.RELEASE_TAG;
let release;
try {
release = await github.rest.repos.getReleaseByTag({ owner, repo, tag: 'vm-runtime' });
release = await github.rest.repos.getReleaseByTag({ owner, repo, tag });
} catch (err) {
if (err.status === 404) {
core.info('No existing vm-runtime release; will create fresh.');
core.info(`No existing ${tag} release; will create fresh.`);
return;
}
throw err;
Expand All @@ -228,7 +239,7 @@ jobs:
with:
name: OpenShell VM Runtime
prerelease: true
tag_name: vm-runtime
tag_name: ${{ inputs.release-tag || 'vm-runtime' }}
target_commitish: ${{ github.sha }}
body: |
Build of the OpenShell VM runtime artifacts used by `openshell-driver-vm`.
Expand All @@ -237,7 +248,7 @@ jobs:

### Kernel Runtime Artifacts

Pre-built kernel runtime (libkrunfw + libkrun + gvproxy + umoci) for embedding
Pre-built kernel runtime (libkrunfw + libkrun + umoci) for embedding
into the `openshell-driver-vm` binary. These are rebuilt on demand when the
kernel config or pinned dependency versions change.

Expand All @@ -250,7 +261,7 @@ jobs:
### Verify

```bash
gh release download vm-runtime -R NVIDIA/OpenShell -p vm-runtime-linux-x86_64.tar.zst
gh release download ${{ inputs.release-tag || 'vm-runtime' }} -R NVIDIA/OpenShell -p vm-runtime-linux-x86_64.tar.zst
gh attestation verify vm-runtime-linux-x86_64.tar.zst -R NVIDIA/OpenShell
```

Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions architecture/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,12 @@ file and builds the `Proxy-Authorization: Basic` header; a credential that is
empty, contains control characters, or is not in `user:pass` form is fatal on
both sides.

The VM driver runs `openshell-supervisor` on the host. Corporate-proxy
credentials, private CA keys, policy, and gateway credentials never enter the
guest. The NIC-less guest reaches the host supervisor only through the
authenticated vsock channel; the host supervisor performs DNS and upstream
connections.
The VM driver starts `openshell-supervisor` on the host and
`openshell-sandbox` as capability-free guest PID 1. Corporate proxy arguments,
credentials, private CA keys, policy, and gateway credentials stay host-side.
Both libkrun and QEMU guests are NIC-less; intercepted workload connections
cross the authenticated vsock channel. A gateway-host proxy is addressed as
`host.openshell.internal`, which the host supervisor normalizes to `127.0.0.1`.

The Docker driver runs `openshell-supervisor` in a separate companion container.
Its private named volume contains supervisor bootstrap and channel material.
Expand Down
28 changes: 0 additions & 28 deletions crates/openshell-core/src/container_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,31 +66,6 @@ pub const VM_GUEST_SANDBOX_TOKEN_PATH: &str = "/opt/openshell/auth/sandbox.jwt";
pub const VM_GUEST_INIT_DROPIN_DIR: &str = "/opt/openshell/init.d";
pub const VM_GUEST_INIT_DROPIN_MANIFEST: &str = "/opt/openshell/init.d.manifest";

/// Guest path for the corporate upstream-proxy credential in VM sandboxes.
///
/// The VM driver stages the `user:pass` credential here (mode `0600`,
/// root-only) inside the per-sandbox overlay upperdir, and passes only this
/// path on the supervisor's argv. A microVM has no bind mounts or container
/// secrets, so this is the same delivery the per-sandbox JWT already uses.
pub const VM_GUEST_UPSTREAM_PROXY_AUTH_PATH: &str = "/opt/openshell/auth/upstream-proxy";

/// Guest path for the corporate proxy CA bundle in VM sandboxes.
///
/// A CA certificate is not secret, so unlike the credential this is staged
/// world-readable. The supervisor trusts it for the handshake with an
/// `https://` proxy and for server certificates re-signed by a
/// TLS-intercepting proxy.
pub const VM_GUEST_PROXY_CA_PATH: &str = "/opt/openshell/tls/proxy-ca.pem";

/// Guest path for the driver-authored supervisor argument list in VM sandboxes.
///
/// Podman and Kubernetes build the supervisor's command line directly; the VM
/// guest init script execs a fixed argv, so driver-owned arguments travel
/// through this file instead. The driver writes it into the overlay upperdir
/// on every launch — empty when it has no arguments to pass — so a sandbox
/// image can neither forge entries nor shadow the driver's copy, and the
/// guest appends exactly what it finds there and nothing else.
pub const VM_GUEST_SUPERVISOR_ARGS_PATH: &str = "/opt/openshell/supervisor-args";
pub const VM_UMOCI_PATH: &str = "/opt/openshell/bin/umoci";
pub const VM_SANDBOX_OWNER_NORMALIZED_MARKER: &str = "/opt/openshell/.sandbox-owner-normalized";

Expand Down Expand Up @@ -129,9 +104,6 @@ mod tests {
VM_GUEST_SANDBOX_TOKEN_PATH,
VM_GUEST_INIT_DROPIN_DIR,
VM_GUEST_INIT_DROPIN_MANIFEST,
VM_GUEST_UPSTREAM_PROXY_AUTH_PATH,
VM_GUEST_PROXY_CA_PATH,
VM_GUEST_SUPERVISOR_ARGS_PATH,
VM_UMOCI_PATH,
VM_SANDBOX_OWNER_NORMALIZED_MARKER,
];
Expand Down
Loading
Loading