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
112 changes: 75 additions & 37 deletions .github/workflows/build-packages-self-hosted.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ on:
build_type:
description: "Select build type"
required: true
default: "BOTH"
default: "SEV+TDX"
type: choice
options:
- TDX
- SNP
- BOTH
- SEV+TDX
- libvirt-ubuntu24
- libvirt-ubuntu26

jobs:
build:
Expand All @@ -23,7 +25,7 @@ jobs:
- name: Setup clean workspace
run: |
WORK_DIR="/home/gh-runner/builds/run-${{ github.run_number }}"
echo "WORK_DIR=$WORK_DIR" >> $GITHUB_ENV
echo "WORK_DIR=$WORK_DIR" >> "$GITHUB_ENV"

mkdir -p /home/gh-runner/builds

Expand All @@ -35,72 +37,108 @@ jobs:
cd "$WORK_DIR"

git init .
git remote add origin https://github.com/${{ github.repository }}.git
git fetch --depth 1 origin ${{ github.sha }}
git checkout ${{ github.sha }}
git remote add origin "https://github.com/${{ github.repository }}.git"
git fetch --depth 1 origin "${{ github.sha }}"
git checkout "${{ github.sha }}"

- name: Set build type and runner ID
working-directory: ${{ env.WORK_DIR }}
run: |
echo "BUILD_TYPE=${{ github.event.inputs.build_type }}" >> $GITHUB_ENV
echo "RUNNER_ID=${{ github.run_number }}" >> $GITHUB_ENV
echo "BUILD_TYPE=${{ github.event.inputs.build_type }}" >> "$GITHUB_ENV"
echo "RUNNER_ID=${{ github.run_number }}" >> "$GITHUB_ENV"

- name: Run TDX docker build
if: ${{ github.event.inputs.build_type == 'TDX' || github.event.inputs.build_type == 'BOTH' }}
if: ${{ github.event.inputs.build_type == 'TDX' || github.event.inputs.build_type == 'SEV+TDX' }}
working-directory: ${{ env.WORK_DIR }}
run: |
NON_INTERACTIVE=1 FORCE_REBUILD_CONTAINER=1 ./build/build_in_docker.sh tdx

- name: Run SNP docker build
if: ${{ github.event.inputs.build_type == 'SNP' || github.event.inputs.build_type == 'BOTH' }}
if: ${{ github.event.inputs.build_type == 'SNP' || github.event.inputs.build_type == 'SEV+TDX' }}
working-directory: ${{ env.WORK_DIR }}
run: |
NON_INTERACTIVE=1 FORCE_REBUILD_CONTAINER=1 ./build/build_in_docker.sh snp

- name: Build libvirt packages
if: ${{ startsWith(github.event.inputs.build_type, 'libvirt-ubuntu') }}
working-directory: ${{ env.WORK_DIR }}
run: |
case "${BUILD_TYPE}" in
libvirt-ubuntu24)
target="ubuntu24"
output_directory="ubuntu-24.04"
;;
libvirt-ubuntu26)
target="ubuntu26"
output_directory="ubuntu-26.04"
;;
*)
echo "Error: unsupported libvirt build type: ${BUILD_TYPE}" >&2
exit 1
;;
esac

archive_name="${BUILD_TYPE}.tar.gz"
archive_path="${RUNNER_TEMP}/${archive_name}"

./build/libvirt/build.sh "${target}"

tar \
--create \
--gzip \
--file "${archive_path}" \
--directory "${WORK_DIR}/build/libvirt/out" \
"${output_directory}"

tar --list --gzip --file "${archive_path}" >/dev/null
echo "LIBVIRT_ARCHIVE_NAME=${archive_name}" >> "$GITHUB_ENV"
echo "LIBVIRT_ARCHIVE_PATH=${archive_path}" >> "$GITHUB_ENV"

- name: Set release name
id: release-name
run: |
if [[ "${BUILD_TYPE}" == "BOTH" ]]; then
echo "RELEASE_NAME=${RUNNER_ID}-tdx+snp" >> $GITHUB_ENV
if [[ "${BUILD_TYPE}" == "SEV+TDX" ]]; then
echo "RELEASE_NAME=${RUNNER_ID}-sev+tdx" >> "$GITHUB_ENV"
elif [[ "${BUILD_TYPE}" == "TDX" ]]; then
echo "RELEASE_NAME=${RUNNER_ID}-tdx" >> $GITHUB_ENV
echo "RELEASE_NAME=${RUNNER_ID}-tdx" >> "$GITHUB_ENV"
elif [[ "${BUILD_TYPE}" == "SNP" ]]; then
echo "RELEASE_NAME=${RUNNER_ID}-snp" >> $GITHUB_ENV
echo "RELEASE_NAME=${RUNNER_ID}-snp" >> "$GITHUB_ENV"
elif [[ "${BUILD_TYPE}" == libvirt-ubuntu* ]]; then
echo "RELEASE_NAME=${RUNNER_ID}-${BUILD_TYPE}" >> "$GITHUB_ENV"
else
echo "Error: Unknown BUILD_TYPE ${BUILD_TYPE}" >&2
exit 1
fi

- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
- name: Publish TDX Release Asset
if: ${{ github.event.inputs.build_type == 'TDX' || github.event.inputs.build_type == 'SEV+TDX' }}
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

with:
tag_name: "${{ env.RELEASE_NAME }}"
release_name: "Release ${{ env.RELEASE_NAME }}"
draft: false
tag_name: ${{ env.RELEASE_NAME }}
name: Release ${{ env.RELEASE_NAME }}
prerelease: true

- name: Upload TDX Release Asset
if: ${{ github.event.inputs.build_type == 'TDX' || github.event.inputs.build_type == 'BOTH' }}
uses: actions/upload-release-asset@v1
files: ${{ env.WORK_DIR }}/build/out/tdx/package-tdx.tar.gz

- name: Publish SNP Release Asset
if: ${{ github.event.inputs.build_type == 'SNP' || github.event.inputs.build_type == 'SEV+TDX' }}
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.WORK_DIR }}/build/out/tdx/package-tdx.tar.gz
asset_name: package-tdx.tar.gz
asset_content_type: application/gzip
- name: Upload SNP Release Asset
if: ${{ github.event.inputs.build_type == 'SNP' || github.event.inputs.build_type == 'BOTH' }}
uses: actions/upload-release-asset@v1
tag_name: ${{ env.RELEASE_NAME }}
name: Release ${{ env.RELEASE_NAME }}
prerelease: true
files: ${{ env.WORK_DIR }}/build/out/snp/package-snp.tar.gz

- name: Publish libvirt Release Asset
if: ${{ startsWith(github.event.inputs.build_type, 'libvirt-ubuntu') }}
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.WORK_DIR }}/build/out/snp/package-snp.tar.gz
asset_name: package-snp.tar.gz
asset_content_type: application/gzip
tag_name: ${{ env.RELEASE_NAME }}
name: Release ${{ env.RELEASE_NAME }}
prerelease: true
files: ${{ env.LIBVIRT_ARCHIVE_PATH }}
65 changes: 60 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Utilities for bootstrapping a Confidential Computing host (Intel **TDX** or AMD
| `scripts/bootstrap_tdx.sh` | Turn an Ubuntu host into a TDX-capable hypervisor (kernel, QEMU, OVMF, attestation, GPU passthrough). |
| `scripts/bootstrap_snp.sh` | Turn an Ubuntu host into a SEV-SNP-capable hypervisor (firmware, modules, GPU passthrough). |
| `scripts/start_super_protocol.sh` | Start a confidential VM (TDX / SEV-SNP / untrusted) from a Super Protocol release image. |
| `scripts/start_super_protocol_libvirt.sh` | Start the same VM as a transient `qemu:///system` domain through libvirt-python (Ubuntu 24.04 or 26.04). |
| `scripts/swarm-cluster.sh` | Bring up a 3-node Swarm cluster on a single host. |
| `scripts/check_configuration.sh`, `get_super_running_vms.sh` | Auxiliary tooling. |

Expand All @@ -35,6 +36,49 @@ This is the main path: take a bare Ubuntu host, turn it into a confidential hype

For the exact commands to clone the repository, run the bootstrap scripts, and launch a VM, see [docs/swarm.md](docs/swarm.md).

### Libvirt launcher (Ubuntu 24.04 and 26.04)

`scripts/start_super_protocol_libvirt.sh` reuses the release, disk, provider-config, and VFIO preparation from the direct QEMU launcher, then builds domain XML and starts a transient domain through `libvirt-python`. GPU passthrough uses IOMMUFD. The TDX and SEV-SNP bootstrap scripts install libvirt **12.5.0** when the system version is older, configure AppArmor, grant `passt` the capability required for privileged ports, and validate the daemon and domain capabilities before VFIO devices are bound.

The command line is the same as for `start_super_protocol.sh`, with an optional domain name:

```bash
sudo ./scripts/start_super_protocol_libvirt.sh \
--name super-protocol-3 \
--provider_config /path/to/provider-configs \
--mode tdx
```

With `--debug false` the command returns after the domain starts. With `--debug true --log_file /path/to/boot.log`, it follows the domain serial log and copies it to the log file; `Ctrl-C` detaches without stopping the VM. The domain always records its console to `/var/log/libvirt/qemu/<domain>-serial.log` from the first byte, so a VM that fails early can still be diagnosed. The serial port is a file sink rather than a pty, because a pty nobody reads fills up and stalls the guest inside console output. Use `virsh -c qemu:///system list`, `shutdown`, or `destroy` to manage it. `--gpu none` disables GPU, NVSwitch, and CX7 passthrough for diagnostics.

#### Libvirt host configuration

Run the bootstrap matching the host CPU before using the libvirt launcher:

```bash
sudo ./scripts/bootstrap_tdx.sh
# or
sudo ./scripts/bootstrap_snp.sh
```

The bootstrap performs the host-wide work that previously required manual fixes:

- installs the complete project libvirt 12.5 package set, including `libvirt-dev`, when the installed version is older or any required split package is missing;
- preserves already installed libvirt split drivers during the package transaction;
- enables executable mmap and the libvirt socket in the nested AppArmor `passt` profile;
- permits QEMU to contact TDX QGS through VSOCK;
- applies `CAP_NET_BIND_SERVICE` to every installed `passt` binary;
- prepares `/var/lib/libvirt/images/superprotocol` and validates `qemu:///system`.

The bootstrap deliberately does not change `net.ipv4.ip_unprivileged_port_start`. File capabilities can be removed when the administrator upgrades or reinstalls `passt`; re-run the same bootstrap to restore them. The launcher detects missing AppArmor rules or capabilities before preparing VM disks and prints the appropriate bootstrap command.

On Ubuntu 24.04, the bootstrap adapts only the verified temporary copy of the
project `libvirt-daemon-driver-qemu` package to the `systemd-sysusers` syntax
supported by that release. The downloaded release archive itself is not
modified.

Do not disable AppArmor globally. For diagnostics, inspect recent denials with `journalctl -k --since '-10 min' --no-pager`.

### 1. Clone the repo

Clone the repository onto the target host. See [docs/swarm.md](docs/swarm.md) for the exact command.
Expand All @@ -48,11 +92,12 @@ Pick the script that matches your CPU vendor. See [docs/swarm.md](docs/swarm.md)
What it does:

1. Verifies Ubuntu version and root privileges.
2. Runs `setup_tdx.sh` to install the Canonical TDX 3.3 stack and PCCS attestation host components.
2. Runs `setup_tdx.sh` to install the project-matched TDX kernel/QEMU bundle and PCCS attestation host components.
3. Verifies BIOS/CPU TDX settings (TME, TME-MT, SEAM, TXT, SGX, …).
4. Runs the official `setup-tdx-host.sh` from `canonical/tdx`.
4. Installs the required QGS/PCCS attestation packages directly, without running Canonical's host-setup script or enabling global package downgrades.
5. Updates the Intel TDX-Module to a known-good version.
6. Configures NVIDIA GPUs for Confidential Computing (CC mode + `vfio-pci` binding) and, on B200 systems, sets up ConnectX-7 bridges for VFIO passthrough.
7. Installs and validates libvirt 12.5, AppArmor policy, VSOCK access, and `passt` capabilities before binding devices to the VM stack.

> **Note:** Some steps require manual action to take effect. The script may stop and ask you to do something, then need to be re-run — this is expected. Follow the on-screen instructions and re-run to finish.

Expand All @@ -65,6 +110,7 @@ What it does:
3. Downloads and installs the matching AMD SEV firmware blob to `/lib/firmware/amd/` and reloads `ccp` / `kvm_amd`.
4. Runs SNP status checks (RMP table, SEV / SEV-SNP API versions, ASID allocation, IOMMU groups, hugepages, CPU governor).
5. Configures NVIDIA GPUs for CC mode and binds them to `vfio-pci`.
6. Installs and validates libvirt 12.5, AppArmor policy, and `passt` capabilities before binding devices to the VM stack.

> **Ubuntu 24.04 note:** the SNP bootstrap installs a bundled Linux **6.16** kernel. On some systems, network interfaces may be renamed after reboot, which can affect networking and remote SSH access. Make sure you have iKVM or other interactive console access before rebooting, so you can reconfigure networking for the new interface names if needed.

Expand All @@ -76,13 +122,22 @@ A reboot is required partway through bootstrap. After reboot, re-run the same bo

`scripts/check_configuration.sh` prints a hardware overview (CPU, memory, network, disks, RAID/SMART) you can compare against the [Requirements](#requirements). See [docs/swarm.md](docs/swarm.md) for how to run it.

Hardware acceptance remains a manual step because containers cannot validate KVM, IOMMUFD, QGS, VSOCK, or physical GPU assignment. On each prepared host verify:

- a transient VM starts in release and debug modes;
- TCP/UDP forwarding works on host ports 53, 80, and 443;
- TDX measurement returns a non-empty quote and PKI/gossip become ready;
- SEV-SNP launch security is active;
- `--gpu none` works and an enabled GPU is attached through IOMMUFD;
- the kernel audit log contains no new `passt`, libvirt, or VSOCK AppArmor denial.

## Running a Swarm cluster

There are two ways to run a Super Protocol Swarm cluster.

### Single-host cluster (quick start)

`scripts/swarm-cluster.sh` brings up a **3-node Swarm cluster on a single host** — no multi-machine setup. It creates an isolated bridge network, launches one bootstrap + two join VMs in separate `tmux` sessions, auto-configures provider configs, and sets up ingress via HAProxy. You still need to set `gateway_hostname` in the provider template to point to the machine's public IP.
`scripts/swarm-cluster.sh` brings up a **3-node Swarm cluster on a single host** — no multi-machine setup. It creates an isolated bridge network, launches one bootstrap + two join VMs as transient libvirt domains, auto-configures provider configs, and sets up ingress via HAProxy. In debug mode their serial consoles remain attached in separate `tmux` sessions. You still need to set `gateway_hostname` in the provider template to point to the machine's public IP.

Prerequisites: a bootstrapped host (TDX or SEV-SNP), a populated provider config template (see [config.yaml reference](docs/swarm.md#configyaml-reference) for an example), and `tmux` / `nftables` / `curl` installed.

Expand Down Expand Up @@ -118,7 +173,7 @@ BIOS settings:
|---|---|
| `CPU PA limit to 46 bits` | Disabled |
| `SMT` | Enabled |
| `TXT` | Enabled |
| `TXT` | Optional for TDX; status is reported but does not block setup |
| `SGX` | Enabled |
| `TME` | Enabled |
| `TME-MT (Multi-Tenant)` | Enabled, KeyIDs configured (non-zero key split) |
Expand Down Expand Up @@ -157,4 +212,4 @@ Planned hardware support. These items are **not yet supported** and are listed f

## License

See [LICENSE](LICENSE).
See [LICENSE](LICENSE).
1 change: 1 addition & 0 deletions build/libvirt/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
out
29 changes: 29 additions & 0 deletions build/libvirt/Dockerfile.ubuntu
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1

ARG UBUNTU_VERSION=24.04
FROM ubuntu:${UBUNTU_VERSION}

ARG LIBVIRT_VERSION=12.5.0
ARG DEBIAN_REVISION=1
ARG PACKAGING_COMMIT=a8f73eb070c24b72f9d6dfbeffc28a334f29e076

ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
devscripts \
dpkg-dev \
equivs \
git \
libdistro-info-perl \
&& rm -rf /var/lib/apt/lists/*

COPY docker/prepare-source.sh /usr/local/bin/prepare-libvirt-source
RUN prepare-libvirt-source "${LIBVIRT_VERSION}" "${DEBIAN_REVISION}" "${PACKAGING_COMMIT}"

COPY docker/build-packages.sh /usr/local/bin/build-libvirt-packages

ENTRYPOINT ["/usr/local/bin/build-libvirt-packages"]
Loading