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
44 changes: 32 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -336,19 +336,38 @@ jobs:
echo "pgtest: checked $(printf '%s\n' "$pkgs" | wc -l) real-Postgres packages against the service database"
exit "$rc"

- name: Enable KVM
# The tagged microVM suites open /dev/kvm; on a fresh ubuntu-latest runner
# the device is root:kvm 0660, so the invoking uid cannot open it. Write
# the udev group-perms rule the ecosystem standardizes on — the same rule
# the Determinate nix-installer-action installs — then reload+trigger so
# it applies to the live node. We install Nix via cachix/install-nix-action
# (above), not the Determinate action, so we write the rule ourselves.
- name: Enable KVM and unprivileged user namespaces
# Two ephemeral-runner relaxations the microVM boot leg needs, both the
# §E-D2 throwaway-runner class (a GHA runner is a single-tenant throwaway,
# so a looser device/kernel posture that the dev box would never take is
# harmless here). sudo is the runner's passwordless sudo; the test
# processes themselves run rootless as the invoking uid.
#
# MODE="0666" (world-RW), deliberately looser than the dev box's kvm-group
# 0660 (record §E-D2): a GHA runner is an ephemeral single-tenant throwaway
# where a world-RW /dev/kvm is harmless, so we skip the group-membership
# dance and just make it openable. sudo is the runner's passwordless sudo;
# the test processes themselves run rootless as the invoking uid.
# 1. /dev/kvm openable. The tagged microVM suites open /dev/kvm; on a
# fresh ubuntu-latest runner the device is root:kvm 0660, so the
# invoking uid cannot open it. Write the udev group-perms rule the
# ecosystem standardizes on — the same rule the Determinate
# nix-installer-action installs — then reload+trigger so it applies to
# the live node. We install Nix via cachix/install-nix-action (above),
# not the Determinate action, so we write the rule ourselves. MODE
# "0666" (world-RW) is deliberately looser than the dev box's kvm-group
# 0660: on this throwaway runner a world-RW /dev/kvm is harmless, so we
# skip the group-membership dance and just make it openable.
#
# 2. Unprivileged user namespaces allowed. ubuntu-latest is now Ubuntu
# 24.04, which ships AppArmor's apparmor_restrict_unprivileged_userns=1
# — an unconfined non-root process can no longer create a user
# namespace. passt AND virtiofsd both self-sandbox by detaching into
# their own userns (passt's --sandbox has no opt-out; virtiofsd's
# --sandbox=namespace is what the record §T4 specifies), so under the
# restriction they die at startup with "Failed to detach isolating
# namespaces: Operation not permitted" and the guest never boots. Set
# the sysctl to 0 to lift the restriction for this boot. This is a
# kernel-hardening knob, not a capability grant: the daemons still run
# rootless as the invoking uid with no added privilege — the record's
# "only privilege is the kvm group, no CAP_NET_ADMIN, no rootful
# helper" constraint holds. The setting reverts on the runner's
# teardown (it is a throwaway VM); nothing persists.
#
# Assume-KVM required-leg posture (§E-D2): the microVM step below sets
# COMPASS_REQUIRE_MICROVM=1, so a run where GitHub withholds /dev/kvm reds
Expand All @@ -358,6 +377,7 @@ jobs:
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns

- name: microVM suites
working-directory: go
Expand Down
6 changes: 6 additions & 0 deletions .moon/workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ projects:
# cache lane or V2a boot bring-up. Same affected-detection posture as
# compass-agent-image (see guest-image/moon.yml).
compass-guest-image: 'guest-image'
# The local microVM boot-test lane (RIG-2591): realises the guest image + VMM
# stack from nix and execs the KVM-gated `go test -tags microvm` suite the
# untagged compass-go:test lane never builds. Registered so its typecheck +
# unit tests ride the moon-driven CI sweep; the boot lane itself lives on
# compass-go:test-microvm (runInCI:false — it needs KVM + a nix build).
microvm-boot-test: 'tools/microvm-boot-test'
# The Compass native-app release bundle: a heavy nix build (realises the
# WebKitGTK cc/pkg-config closure) that stages the versioned tarball. Same
# affected-detection posture as compass-agent-image — registered here so the
Expand Down
12 changes: 12 additions & 0 deletions bun.lock

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

7 changes: 7 additions & 0 deletions go/internal/microvmtest/canary_microvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ func TestCanaryMicroVMEnv(t *testing.T) {
if env.KernelImage == "" {
t.Error("resolved Env.KernelImage is empty")
}
if env.InitrdImage == "" {
t.Error("resolved Env.InitrdImage is empty")
}
if env.RootfsImage == "" {
t.Error("resolved Env.RootfsImage is empty")
}
Expand All @@ -72,6 +75,9 @@ func TestCanaryMicroVMEnv(t *testing.T) {
if env.VirtiofsdPath == "" {
t.Error("resolved Env.VirtiofsdPath is empty")
}
if env.PasstPath == "" {
t.Error("resolved Env.PasstPath is empty")
}

// The two guest-image paths must exist on disk: this is what proves E3's
// attrs were realized and exported, not merely that the env vars were set to
Expand All @@ -84,6 +90,7 @@ func TestCanaryMicroVMEnv(t *testing.T) {
}{
{"guest kernel", env.KernelImage},
{"guest rootfs", env.RootfsImage},
{"guest initrd", env.InitrdImage},
} {
if _, err := os.Stat(img.path); err != nil {
t.Errorf("%s image %q does not exist on disk: %v", img.name, img.path, err)
Expand Down
22 changes: 22 additions & 0 deletions go/internal/microvmtest/microvmtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,32 @@ const KernelEnvVar = "COMPASS_TEST_GUEST_KERNEL"
// export it pointing at the `nix build .#compass-guest-rootfs` result).
const RootfsEnvVar = "COMPASS_TEST_GUEST_ROOTFS"

// InitrdEnvVar points the harness at the guest initramfs image to boot. Like
// KernelEnvVar it is supplied by the environment (the dev shell / CI KVM leg
// export it pointing at the `nix build .#compass-guest-initrd` result). The
// initrd is load-bearing, not optional: the pinned generic kernel ships virtio/
// erofs/overlay as modules, so the initrd is what loads them and mounts the
// root before switch_root (record §(a)).
const InitrdEnvVar = "COMPASS_TEST_GUEST_INITRD"

// vmmBinary is the VMM the microVM suites drive; virtiofsdBinary is the
// virtio-fs daemon they pair it with. Both are resolved from PATH (the dev shell
// and CI KVM leg put them there) when Require builds the Env.
const (
vmmBinary = "cloud-hypervisor"
virtiofsdBinary = "virtiofsd"
passtBinary = "passt"
)

// Env is the resolved microVM test environment Require hands back: the guest
// images to boot and the host binaries to drive them with.
type Env struct {
KernelImage string
InitrdImage string
RootfsImage string
VMMPath string
VirtiofsdPath string
PasstPath string
}

// kvmSource is which of the three KVM-availability paths Require takes.
Expand Down Expand Up @@ -148,20 +159,31 @@ func resolveEnv(t *testing.T) Env {
if err != nil {
t.Fatalf("microVM test requires %s on PATH: %v", virtiofsdBinary, err)
}
passtPath, err := exec.LookPath(passtBinary)
if err != nil {
t.Fatalf("microVM test requires %s on PATH: %v", passtBinary, err)
}
kernelImage := os.Getenv(KernelEnvVar)
if kernelImage == "" {
t.Fatalf("microVM test requires %s to point at the guest kernel image "+
"(exported by the dev shell / CI KVM leg from `nix build .#compass-guest-kernel`)", KernelEnvVar)
}
initrdImage := os.Getenv(InitrdEnvVar)
if initrdImage == "" {
t.Fatalf("microVM test requires %s to point at the guest initramfs image "+
"(exported by the dev shell / CI KVM leg from `nix build .#compass-guest-initrd`)", InitrdEnvVar)
}
rootfsImage := os.Getenv(RootfsEnvVar)
if rootfsImage == "" {
t.Fatalf("microVM test requires %s to point at the guest rootfs image "+
"(exported by the dev shell / CI KVM leg from `nix build .#compass-guest-rootfs`)", RootfsEnvVar)
}
return Env{
KernelImage: kernelImage,
InitrdImage: initrdImage,
RootfsImage: rootfsImage,
VMMPath: vmmPath,
VirtiofsdPath: virtiofsdPath,
PasstPath: passtPath,
}
}
Loading
Loading