Skip to content

Add vsock port wiring + WithoutSSH option#85

Merged
JAORMX merged 5 commits into
mainfrom
wp-3.1/vsock-and-no-ssh
May 23, 2026
Merged

Add vsock port wiring + WithoutSSH option#85
JAORMX merged 5 commits into
mainfrom
wp-3.1/vsock-and-no-ssh

Conversation

@JAORMX
Copy link
Copy Markdown
Contributor

@JAORMX JAORMX commented May 23, 2026

Summary

Adds two new public options on top of microvm.Run so callers that drive their
own guest init binary (host channel = vsock IPC, not SSH) can use go-microvm
without paying for an unused SSH bring-up:

  • microvm.WithVsock(port, socketPath) — wires a guest vsock port to a
    host UNIX socket via krun_add_vsock_port. May be called multiple times;
    entries are processed in order.
  • microvm.WithoutSSH() — host-side flag that writes
    vmconfig.Config.DisableSSH = true into /etc/go-microvm.json. The guest
    init reads it (via the matching guest/boot.WithoutSSH() option) and skips
    steps 8 (authorized_keys), 8b (host-key load), and 10 (SSH listener). All
    other hardening — mounts, networking, capability drops, no_new_privs,
    seccomp — runs unchanged.

These plumb cleanly through the existing layers: microvmhypervisor
runner → CGO krun, plus guest side vmconfigguest/boot.

Driving consumer: brood-box-k8s, which uses ttrpc-over-vsock for the
host↔guest channel and ships its own bbox-agent init binary. The brood-box
CLI does not call either option, so its SSH-based path is unchanged.

What's in here

5 commits, ~503 LOC across 15 files (250 LOC is tests).

Commit What
77423e6 Bind krun_add_vsock_port in CGO layer (vendored libkrun.h extension + Context.AddVsockPort)
d096934 Thread VsockPort through runner + hypervisor configs (incl. the duplicate Config in runner/cmd/go-microvm-runner/main.go)
62969d4 Add microvm.WithVsock and microvm.WithoutSSH public options
525eec4 Honor DisableSSH in guest/boot.Run (skip steps 8, 8b, 10; shutdown closure becomes a no-op)
7ec4267 Tests: option behavior, JSON round-trip with omitempty, drift guard against the duplicate runner Config struct, vmconfig round-trip, guest boot option propagation

Design notes

  • vsock_ports,omitempty — older runner binaries that pre-date this field
    ignore an absent vsock_ports key cleanly, so the JSON wire is
    forward-compat.
  • Duplicate runner.Config struct — the runner main binary already keeps
    a parallel Config struct (it can't import the runner package without a
    cycle). This PR keeps the duplication but adds a reflection-based test
    (runnerMainConfigMirror in runner/config_test.go) that fails if the two
    diverge on field name/JSON tag/type.
  • libkrun.h vendored declarationkrun_add_vsock_port is in upstream
    libkrun 1.18 (already pinned in versions.env); just needed in the
    vendored header. No ABI change.
  • Guest side opt-inWithoutSSH() on the host is a signal; the guest
    init must choose to honor it. guest/boot does. Consumers shipping a
    custom init binary can ignore the flag if they have other reasons to keep
    SSH on.

Backward compatibility

  • Hosts that don't call WithVsock or WithoutSSH produce byte-identical
    JSON to before this change (both fields are omitempty).
  • vmconfig.Config.DisableSSH defaults to false; guest/boot behavior is
    unchanged when the flag is absent.
  • brood-box continues to work without modification.

Test plan

  • task fmt — no diff
  • task lint — 0 issues
  • task test-nocgo — all pure-Go packages pass
  • task test (CGO) — requires libkrun-devel; defer to CI

Per-package coverage (from the worktree run that produced these commits):
microvm 82.9%, hypervisor 94.8%.

Follow-up (NOT in this PR)

guest/boot transitively links golang.org/x/crypto/ssh into any consumer
binary even when WithoutSSH() is set at runtime — the listener code is
gated at runtime but still compiled in. The fix is to split
guest/bootguest/sshd along a Go build tag so consumers can opt out at
link time. Tracked downstream; will land as a separate PR.

🤖 Generated with Claude Code

JAORMX added 5 commits May 22, 2026 14:04
Add the krun_add_vsock_port symbol to the vendored libkrun.h header and
expose it as Context.AddVsockPort in krun/context.go. This is the lowest
layer needed for the bbox-k8s ttrpc-over-vsock guest channel; higher
layers (runner.Config, microvm.WithVsock) will plumb host configuration
through to this call.

Signature matches the upstream libkrun-1.18 ABI:

    int32_t krun_add_vsock_port(uint32_t ctx_id,
                                uint32_t port,
                                const char *c_filepath);
Add VsockPort{Port, SocketPath} to runner.Config and hypervisor.VMConfig
(plus the matching duplicate in runner/cmd/go-microvm-runner/main.go,
per the CLAUDE.md duplication note). The runner subprocess loops over
VsockPorts and calls Context.AddVsockPort for each entry before starting
the VM.

JSON tags match between runner.Config and the runner main.go duplicate
so the JSON round-trip is identical. The libkrun backend maps
hypervisor.VsockPort -> runner.VsockPort one-to-one.

Top-level microvm.WithVsock and the option struct field are added in
the next commit.
WithVsock(port, socketPath) wires a guest vsock port to a host UNIX
socket. May be called multiple times to add multiple ports; entries are
processed in order by the runner subprocess via krun_add_vsock_port.
Used by the bbox-k8s ttrpc-over-vsock guest channel.

WithoutSSH signals to the guest init that the in-guest SSH server
should not be started. The signal is written into the rootfs at
/etc/go-microvm.json (vmconfig.Config.DisableSSH) and read by the
caller's init binary, which can pass guest/boot.WithoutSSH() through to
boot.Run. Purely a guest-side signal; the host process and runner
subprocess are unchanged.

The brood-box CLI does NOT set either option, so its SSH-based path is
unchanged.
Add DisableSSH field to vmconfig.Config (host->guest signal) and a
matching boot.WithoutSSH() option. When set, the guest boot sequence
skips:

  - step 8: authorized_keys parsing
  - step 8b: host-key loading
  - step 10: SSH server creation and listener

All other steps (mounts, networking, hardening, capability drops,
no_new_privs, seccomp) run normally. The shutdown closure is a no-op
when SSH is disabled.

Backward compatibility: when neither vmconfig.Config.DisableSSH nor
boot.WithoutSSH is set, behavior is identical to the previous
implementation. brood-box continues to work unchanged.
Covers:
- microvm.WithVsock appends entries in order, defaults to empty
- microvm.WithoutSSH sets disableSSH; default config leaves SSH enabled
- microvm.buildVMConfig propagates DisableSSH into vmconfig.Config
- toHypervisorVsockPorts maps nil/empty to nil
- runner.Config JSON round-trip includes vsock_ports; omitempty when nil
- VsockPort.JSON has expected snake_case keys
- runner.Config JSON decodes cleanly into a struct that mirrors the
  duplicate Config in runner/cmd/go-microvm-runner/main.go (the explicit
  drift guard for the intentional duplication)
- vmconfig.Config.DisableSSH round-trips through readFrom
- guest/boot.WithoutSSH sets the disableSSH flag

Together these guarantee:
- The new public API surface behaves as documented.
- Hosts that don't call WithVsock/WithoutSSH produce identical JSON to
  before this WP, preserving backward compatibility for brood-box.
- The duplicate runner Config struct can decode every field the
  runner.Config struct can emit (drift guard).
@JAORMX JAORMX merged commit b0ec996 into main May 23, 2026
12 of 14 checks passed
@JAORMX JAORMX deleted the wp-3.1/vsock-and-no-ssh branch May 23, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant