Skip to content

sci: ship the SCI layer as verity-signed systemd-sysext extensions - #401

Draft
fwiesel wants to merge 5 commits into
mainfrom
sysext-virt
Draft

fwiesel wants to merge 5 commits into
mainfrom
sysext-virt

Conversation

@fwiesel

@fwiesel fwiesel commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

SCI preparation: one build, one guest, one transport

Status: draft. Local verification accepted; CI pending. Not a readiness claim.

Shape

Five logical commits; both documentation files arrive only in the last one.
The preparation path collapses two parallel stories — a build/test guest with
mode switches plus sysext-specific setup, and a container-based delivery path
— into one:

  • One shared Ubuntu/SSH preparation action serves the ordinary build and the
    sysext branch. The build action takes only image_tag; its mode,
    sysext_key, and validation inputs are gone (eight mode guards removed).
  • Integration mounts only the manifest-selected raws through virtiofs. There
    is no Docker rebuild/export, SCP tar stream, or ctr delivery step. The
    delivery/fetch/signing behavior stays inside its existing tests and product
    boundary.
  • Migration test definitions and workflow on: triggers are unchanged.

Trade-off

Not the absolute minimum. The user accepted the single-source growth: one
added file (.github/actions/test/integration/guest/action.yml) and 77 net
lines against the previous revision.

Range Files Added Removed
Original shape 87 7530 17
Previous revision (15e6ae8) 83 6179 17
This tip (1824943) 84 6272 33

Tests

120 collected: 108 quick, 11 build, 1 VM (original: 152). No new test function
and no parallel VM suite. One extra partial-set case checks that a dropped
extension fails closed before pool, network, and SSH setup. The existing
tamper VM test now requires a real dm-verity failure on valid filesystem data
rather than a format rejection.

Evidence

  • Local verifier accepted on 1824943 (tree dc8a8d32): 120 passed
    (108 quick, 11 build, 1 VM), exit 0; seven-image reproducibility; three
    fresh guest scenarios (signed, tamper, policy).
  • Root build: 52 controls passed and reviewed, including the 16,755-entry
    tree comparison (content/type/links/modes; ownership on selected paths
    only).
  • Prepared HV1: direct-QEMU boot, 16 checks passed and reviewed.
  • The earlier 15e6ae8 producer ran 119 passed
    (run),
    but the GitHub scheduler blocked the dependent jobs; it cannot prove
    1824943.

Limits

  • Guest-side read-only mount only; the host export has no read-only
    protection. This serves trusted test staging, not a hostile guest or
    production.
  • Data-only signed release; the script and image are test-only. The known
    pre-marker window is unchanged.
  • Key custody/rotation, independent host anchor and policy, and the base/suite
    re-pin remain deferred, with no owner assigned
    (#402).

Pending

  • VERIFY-FINAL: accepted local verification on 1824943 (tree dc8a8d32).
  • VERIFY-CI: pending for the automatic push run
    (run).
  • Managed libvirt, bridge, HV2, and twin migration remain unproven.

gardenlinux/gardenlinux-sci#401
remains draft at 1824943; the branch is pushed.

Node-local packages belong to the node owner, not to the shared boot
root: the engine builds every SCI extension as an EROFS-root GPT
image, standalone on the pinned published base, so a package change
rebuilds exactly its own extension and the base image stops churning
for every host. Seven extensions ship: base identities, compute,
network, storage, libvirt and the two hypervisor flavours.
Builds are byte-reproducible, so a produced raw traces to its inputs:
version-keyed timestamps, per-image seeds and three conscious-bump
pins (apt suite, toolchain manifest, resolved configuration). The
tools tree is bootstrapped from the pinned suite and accepted only
when its manifest digest matches the committed pin. The numeric name
prefixes state the merge order systemd-sysext applies, the virt
drivers exclude libvirt-owned paths through RemoveFiles= lists
because the overlay merge provides them at runtime, and every path
has exactly one owning extension. The qemu extension carries the VNC
port policy that the legacy image kept in /etc/libvirt/qemu.conf, as
a template with a tmpfiles C rule, because /etc is outside the merge.
`mkosi/build.sh --bootstrap` prepares sandbox, tools tree and base
without building images, for a caller that only needs the toolchain.

Signed-off-by: Fabian Wiesel <fabian.wiesel@sap.com>
A delivery is only safe to apply when its raws are intact and were
built for the host's base version. The step verifies SHA256SUMS before
anything lands, refuses a base-version mismatch through the manifest's
TARGET_BASE gate, reconciles idempotently and records pending activation
for replay. It runs as a privileged pod with the host root mounted.
Image mode takes the staging the image carries. Fetch mode materializes
the same staging from a published build: it verifies the detached
inventory signature, downloads only the files whose staged hash
differs, proves the complete set and prunes files the inventory no
longer lists. The manifest lists every deployable, and the step never
deploys a raw it does not list. The host tests pin the build, staging,
fetch and activation contracts.

Signed-off-by: Fabian Wiesel <fabian.wiesel@sap.com>
An unverified layer silently widens the boot root's trust boundary:
an unsigned or tampered extension could change what a host runs. Enable
the signed build: every raw gets a dm-verity hash tree and a signature
partition, the committed certificate is the anchor a merging host
checks, and the private key never enters the repository.
A three-scenario VM boot test closes the loop: the signed set must boot
the merged system, and tampered or unsigned layers must be refused. The
tamper scenario corrupts a verity-covered data block, so the guest must
fail with the kernel's dm-verity corruption verdict and not with a
format-probe error. An artifact is trusted only after the merged system
has booted, so the boot evidence is part of the delivery proof.

Signed-off-by: Fabian Wiesel <fabian.wiesel@sap.com>
Local-only proof dies with the machine that ran it. The workflow runs
the quick suite on every change and, on push, builds the extensions,
runs the full suite with the VM boot test and publishes one test
artifact: the raws as plain-file staging, including the delivery step
and the run certificate.
The essential workflow boundaries are pinned structurally on the parsed
workflows: the quick tier and its PR gate, producer ordering from base
ensure over the signed build to the staged raws upload, the publish
gate on the signing secret, and the inventory being regenerated before
it is signed while the delivery vehicle is dropped. The integration
job consumes a prior run's artifact like a test consumer would: it
restores the run certificate, bootstraps the pinned toolchain, builds
the shared Ubuntu guest and test SSH identity, exports only the
manifest-selected raws, and boots two Garden Linux hypervisors, which
mount those raws from a virtiofs share read-only. One guest action
serves the build action and the sysext branch, so the two flows cannot
drift. The boot activates the seven extensions, and the job then runs
the existing real QEMU and Cloud-Hypervisor live migration tests
against the produced raws.
The hosted runners need explicit accommodations: mkosi's unprivileged
sandbox needs the AppArmor user-namespace restriction lifted,
qemu-system-x86 provides the virtual qemu-kvm package, virtiofsd
provides the virtiofs share, /dev/kvm must be accessible for the VM
boot test, the finished qemu scenario's VM is stopped before the
cloud-hypervisor stage because its nested I/O starves the staging copy,
and the debug collection includes the HV serial consoles.

Signed-off-by: Fabian Wiesel <fabian.wiesel@sap.com>
Write down the single-owner layout, the delivery contract and the
operating procedures while they are current, so the design doc is the
single source of truth. docs/sysext-virt.md states the design as it
is: every invariant, gotcha and open item in Simplified Technical
English, no change log, no facts restated from tracked files.
delivery/README.md is the operational contract of the delivery step
and marks the product boundary: the step is the boot-test vehicle,
the signed release is the product.

Signed-off-by: Fabian Wiesel <fabian.wiesel@sap.com>

This branch has not been deployed

No deployments
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