From cd75c2fac5fb80330534a1016cdee6c8fab9b587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= Date: Mon, 13 Jul 2026 14:26:25 +0200 Subject: [PATCH] build: consume guest-components CoCo artefacts instead of building locally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit guest-components' coco-extension-image workflow now publishes two artefacts from the same assembled rootfs, and kata consumes each on the matching path: * Monolithic confidential rootfs (Go runtime): the scratch OCI container image from the "Publish OCI container image" step (ghcr.io/confidential-containers/guest-components/coco-extension). install_coco_guest_components() resolves the per-arch manifest digest, verifies provenance, and exports the filesystem into kata-static-coco-guest-components.tar.zst (binaries, cryptsetup, pause bundle, ocicrypt config). This replaces the local guest-components compile and removes the separate pause-image dependency from confidential rootfs targets. * Composable extension (runtime-rs): the EROFS + dm-verity disk image from the "Publish disk image with ORAS" step (ghcr.io/confidential-containers/guest-components/coco-extension-disk). install_image_coco_extension() resolves the per-arch digest, verifies provenance, and oras-pulls that exact digest into kata-static. Both paths pin the guest-components revision under .externals.coco-guest-components in versions.yaml (version, container_image, and extension_image must stay in sync). Provenance verification uses gh attestation verify --bundle-from-oci and fails the build by default (VERIFY_COCO_EXTENSION_PROVENANCE=no to bypass; skipped on s390x where gh has no binary). The build container installs the GitHub CLI and forwards GITHUB_TOKEN from the runner into the container. Signed-off-by: Fabiano Fidêncio Assisted-by: Cursor --- .../build-kata-static-tarball-amd64.yaml | 6 +- .../build-kata-static-tarball-arm64.yaml | 6 +- .../build-kata-static-tarball-s390x.yaml | 3 +- docs/design/composable-vm-images.md | 70 +++- .../kata-deploy/local-build/Makefile | 11 +- .../local-build/dockerbuild/Dockerfile | 6 +- .../local-build/dockerbuild/install_gh.sh | 52 +++ .../kata-deploy-binaries-in-docker.sh | 2 + .../local-build/kata-deploy-binaries.sh | 307 +++++++++++------- versions.yaml | 11 +- 10 files changed, 316 insertions(+), 158 deletions(-) create mode 100755 tools/packaging/kata-deploy/local-build/dockerbuild/install_gh.sh diff --git a/.github/workflows/build-kata-static-tarball-amd64.yaml b/.github/workflows/build-kata-static-tarball-amd64.yaml index e57ce85670..bcf026c848 100644 --- a/.github/workflows/build-kata-static-tarball-amd64.yaml +++ b/.github/workflows/build-kata-static-tarball-amd64.yaml @@ -57,7 +57,6 @@ jobs: - ovmf - ovmf-sev - ovmf-tdx - - pause-image - qemu - qemu-snp-experimental - qemu-tdx-experimental @@ -108,6 +107,8 @@ jobs: RELEASE: ${{ inputs.stage == 'release' && 'yes' || 'no' }} KBUILD_SIGN_PIN: ${{ contains(matrix.asset, 'nvidia') && secrets.KBUILD_SIGN_PIN || '' }} USE_DEVMAPPER: ${{ matrix.asset == 'agent' && 'yes' || 'no' }} + GH_TOKEN: ${{ github.token }} + VERIFY_COCO_EXTENSION_PROVENANCE: "yes" - name: Parse OCI image name and digest id: parse-oci-segments @@ -266,6 +267,9 @@ jobs: TARGET_BRANCH: ${{ inputs.target-branch }} RELEASE: ${{ inputs.stage == 'release' && 'yes' || 'no' }} KBUILD_SIGN_PIN: ${{ contains(matrix.asset, 'nvidia') && secrets.KBUILD_SIGN_PIN || '' }} + # Used to verify the guest-components CoCo extension provenance. + GH_TOKEN: ${{ github.token }} + VERIFY_COCO_EXTENSION_PROVENANCE: "yes" - name: store-artifact ${{ matrix.asset }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 diff --git a/.github/workflows/build-kata-static-tarball-arm64.yaml b/.github/workflows/build-kata-static-tarball-arm64.yaml index 160c27846a..f4e5fd17e2 100644 --- a/.github/workflows/build-kata-static-tarball-arm64.yaml +++ b/.github/workflows/build-kata-static-tarball-arm64.yaml @@ -55,7 +55,6 @@ jobs: - kernel-nvidia-gpu - nydus - ovmf - - pause-image - qemu - virtiofsd concurrency: @@ -101,6 +100,8 @@ jobs: RELEASE: ${{ inputs.stage == 'release' && 'yes' || 'no' }} KBUILD_SIGN_PIN: ${{ contains(matrix.asset, 'nvidia') && secrets.KBUILD_SIGN_PIN || '' }} USE_DEVMAPPER: ${{ matrix.asset == 'agent' && 'yes' || 'no' }} + GH_TOKEN: ${{ github.token }} + VERIFY_COCO_EXTENSION_PROVENANCE: "yes" - name: Parse OCI image name and digest id: parse-oci-segments @@ -255,6 +256,8 @@ jobs: TARGET_BRANCH: ${{ inputs.target-branch }} RELEASE: ${{ inputs.stage == 'release' && 'yes' || 'no' }} KBUILD_SIGN_PIN: ${{ contains(matrix.asset, 'nvidia') && secrets.KBUILD_SIGN_PIN || '' }} + GH_TOKEN: ${{ github.token }} + VERIFY_COCO_EXTENSION_PROVENANCE: "yes" - name: store-artifact ${{ matrix.asset }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 @@ -275,7 +278,6 @@ jobs: - busybox - coco-guest-components - kernel-nvidia-gpu-modules - - pause-image concurrency: group: ${{ github.workflow }}-${{ github.job }}-${{ github.event.pull_request.number || github.ref }}-arm-${{ toJSON(matrix) }} cancel-in-progress: true diff --git a/.github/workflows/build-kata-static-tarball-s390x.yaml b/.github/workflows/build-kata-static-tarball-s390x.yaml index b5f56dfd87..653bd7ecce 100644 --- a/.github/workflows/build-kata-static-tarball-s390x.yaml +++ b/.github/workflows/build-kata-static-tarball-s390x.yaml @@ -47,7 +47,6 @@ jobs: - coco-guest-components - fake-boot-image-se - kernel - - pause-image - qemu - virtiofsd concurrency: @@ -99,6 +98,7 @@ jobs: TARGET_BRANCH: ${{ inputs.target-branch }} RELEASE: ${{ inputs.stage == 'release' && 'yes' || 'no' }} USE_DEVMAPPER: ${{ matrix.asset == 'agent' && 'yes' || 'no' }} + GH_TOKEN: ${{ github.token }} - name: Parse OCI image name and digest id: parse-oci-segments @@ -237,6 +237,7 @@ jobs: ARTEFACT_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} TARGET_BRANCH: ${{ inputs.target-branch }} RELEASE: ${{ inputs.stage == 'release' && 'yes' || 'no' }} + GH_TOKEN: ${{ github.token }} - name: store-artifact ${{ matrix.asset }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 diff --git a/docs/design/composable-vm-images.md b/docs/design/composable-vm-images.md index db9ee26c97..977ef8ea81 100644 --- a/docs/design/composable-vm-images.md +++ b/docs/design/composable-vm-images.md @@ -568,27 +568,61 @@ arrive as packages — they are copied into the base layout explicitly: `veritysetup` and its library closure unconditionally, and the `mke2fs`/`mkfs.ext4`/`dd`/`mke2fs.conf` plain-storage tooling alongside it. +#### CoCo guest components (monolithic confidential images) + +The Go runtime still boots a **monolithic** confidential image that bakes the +CoCo guest components directly into the rootfs. Rather than compiling +guest-components on the kata side, `install_coco_guest_components` pulls the +scratch OCI **container** image guest-components publishes in the +`coco-extension-image` workflow ("Publish OCI container image" step) at +`ghcr.io/confidential-containers/guest-components/coco-extension` +(`.externals.coco-guest-components.container_image`), verifies its provenance, +and exports the filesystem into `kata-static-coco-guest-components.tar.zst`. +This is distinct from the EROFS disk image (`extension_image`) used by the +runtime-rs composable extension path. The container payload includes the +guest-component binaries, `cryptsetup`, the pause bundle, and +`etc/ocicrypt_config.json`, so confidential rootfs builds no longer need a +separate `pause-image` tarball or a local guest-components compile. + #### CoCo guest extension image -The `kata-containers-coco-extension.img` is built by: +The `kata-containers-coco-extension.img` is built and published by +[guest-components](https://github.com/confidential-containers/guest-components) +(`tools/coco-extension/`) as a multi-arch OCI index at +`ghcr.io/confidential-containers/guest-components/coco-extension-disk`. Kata pins +the guest-components revision in `versions.yaml` under +`.externals.coco-guest-components` (`version`, `container_image`, and +`extension_image` must stay in sync) and, at build time, `install_image_coco_extension`: -1. Unpacking the CoCo guest components tarball into a temporary rootfs. Besides - the agent-launched binaries (attestation-agent, attestation-agent-nv, - confidential-data-hub, api-server-rest) this tarball also carries: - - `cryptsetup` under `usr/sbin` — the encrypted-storage binary for CDH - `secure_mount` (its shared libraries are resolved against the base; see - "Runtime dependencies"). - - the NVIDIA attester libraries under `usr/local/lib` — `libnvat.so` plus its - non-glibc transitive closure (libxml2, zlib, lzma, the C++ runtime). -2. Unpacking the pause image tarball into the same rootfs. -3. Writing the component manifest to `etc/kata-extensions/components.toml`. -4. Running the image builder with: - - `FS_TYPE=erofs` — EROFS filesystem for compact, read-only storage. - - `MEASURED_ROOTFS=yes` — creates a dm-verity hash partition. - - `SKIP_DAX_HEADER=yes` — no DAX header (virtio-blk, not NVDIMM). - - `SKIP_ROOTFS_CHECK=yes` — the extension has no `/sbin/init`. - - `BUILD_VARIANT=coco-extension` — produces a correctly named root hash file - (`root_hash_coco-extension.txt`). +1. Resolves the per-architecture manifest digest from the index + (`oras resolve --platform linux/`). +2. Verifies the provenance attestation guest-components published for that digest + (`gh attestation verify --repo confidential-containers/guest-components + --bundle-from-oci`). On amd64/arm64 CI sets + `VERIFY_COCO_EXTENSION_PROVENANCE=yes` to require it when `GH_TOKEN` is + available; locally it is skipped when no GitHub token is set. s390x always + skips because the GitHub CLI ships no s390x build. +3. Pulls that exact digest (`oras pull`) so the installed image is byte-for-byte + the artefact whose provenance was verified. + +!!! note + Provenance verification runs when `GH_TOKEN` or `GITHUB_TOKEN` is available + (CI forwards the runner token automatically). Local builds without a token + skip verification with a warning. Set `VERIFY_COCO_EXTENSION_PROVENANCE=yes` + to require it, or `no` to disable it explicitly. + +The guest-components build assembles a rootfs that carries the agent-launched +binaries (attestation-agent, attestation-agent-nv, confidential-data-hub, +api-server-rest), `cryptsetup` under `usr/sbin`, the NVIDIA attester libraries +under `usr/local/lib`, the pause bundle, and the component manifest at +`etc/kata-extensions/components.toml`, then formats it as: + +- `FS_TYPE=erofs` — EROFS filesystem for compact, read-only storage. +- `MEASURED_ROOTFS=yes` — creates a dm-verity hash partition (except on s390x). +- `SKIP_DAX_HEADER=yes` — no DAX header (virtio-blk, not NVDIMM). +- `SKIP_ROOTFS_CHECK=yes` — the extension has no `/sbin/init`. +- `BUILD_VARIANT=coco-extension` — produces a correctly named root hash file + (`root_hash_coco-extension.txt`). The resulting image is a two-partition disk: diff --git a/tools/packaging/kata-deploy/local-build/Makefile b/tools/packaging/kata-deploy/local-build/Makefile index 3680e16a7a..c0990166a2 100644 --- a/tools/packaging/kata-deploy/local-build/Makefile +++ b/tools/packaging/kata-deploy/local-build/Makefile @@ -77,7 +77,6 @@ NVGPU_BASE_TARBALLS = \ ovmf-sev-tarball \ ovmf-tdx-tarball \ ovmf-tarball \ - pause-image-tarball \ qemu-snp-experimental-tarball \ qemu-tdx-experimental-tarball \ qemu-tarball \ @@ -91,7 +90,6 @@ NVGPU_BASE_TARBALLS = \ $(PUBLISH_COMPONENT_TARBALLS) \ kernel-nvidia-gpu-tarball \ ovmf-tarball \ - pause-image-tarball \ qemu-tarball \ virtiofsd-tarball \ serial-targets @@ -254,19 +252,18 @@ DEPS := agent-tarball rootfs-image-tarball: $(DEPS) ${MAKE} $@-build -DEPS := agent-tarball pause-image-tarball coco-guest-components-tarball kernel-tarball +DEPS := agent-tarball coco-guest-components-tarball kernel-tarball rootfs-image-confidential-tarball: $(DEPS) ${MAKE} $@-build -DEPS := pause-image-tarball coco-guest-components-tarball -rootfs-image-coco-extension-tarball: $(DEPS) +rootfs-image-coco-extension-tarball: ${MAKE} $@-build DEPS := agent-tarball rootfs-image-mariner-tarball: $(DEPS) ${MAKE} $@-build -DEPS := agent-tarball pause-image-tarball coco-guest-components-tarball kernel-tarball +DEPS := agent-tarball coco-guest-components-tarball kernel-tarball rootfs-initrd-confidential-tarball: $(DEPS) ${MAKE} $@-build @@ -278,7 +275,7 @@ DEPS := agent-tarball busybox-tarball kernel-nvidia-gpu-tarball rootfs-image-nvidia-gpu-tarball: $(DEPS) ${MAKE} $@-build -DEPS := agent-tarball busybox-tarball pause-image-tarball coco-guest-components-tarball kernel-nvidia-gpu-tarball +DEPS := agent-tarball busybox-tarball coco-guest-components-tarball kernel-nvidia-gpu-tarball rootfs-image-nvidia-gpu-confidential-tarball: $(DEPS) ${MAKE} $@-build diff --git a/tools/packaging/kata-deploy/local-build/dockerbuild/Dockerfile b/tools/packaging/kata-deploy/local-build/dockerbuild/Dockerfile index 68dbcae65f..cae7ffa250 100644 --- a/tools/packaging/kata-deploy/local-build/dockerbuild/Dockerfile +++ b/tools/packaging/kata-deploy/local-build/dockerbuild/Dockerfile @@ -10,10 +10,11 @@ ENV TZ=Etc/UTC COPY install_yq.sh /usr/bin/install_yq.sh COPY install_oras.sh /usr/bin/install_oras.sh +COPY install_gh.sh /usr/bin/install_gh.sh SHELL ["/bin/bash", "-o", "pipefail", "-c"] -# Install yq, oras, and docker +# Install yq, oras, gh, and docker RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ @@ -25,7 +26,8 @@ RUN apt-get update && \ sudo && \ apt-get clean && rm -rf /var/lib/apt/lists/ && \ install_yq.sh && \ - install_oras.sh + install_oras.sh && \ + install_gh.sh ARG IMG_USER=kata-builder ARG UID=1000 diff --git a/tools/packaging/kata-deploy/local-build/dockerbuild/install_gh.sh b/tools/packaging/kata-deploy/local-build/dockerbuild/install_gh.sh new file mode 100755 index 0000000000..bde865157e --- /dev/null +++ b/tools/packaging/kata-deploy/local-build/dockerbuild/install_gh.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# +# Copyright (c) 2026 Kata Containers contributors +# +# SPDX-License-Identifier: Apache-2.0 +# +# Install the GitHub CLI, used to verify the provenance attestation of the +# guest-components CoCo extension image (see kata-deploy-binaries.sh). + +set -o errexit +set -o nounset +set -o pipefail + +install_dest="/usr/local/bin" + +# Keep in sync with a version that ships "gh attestation verify". +gh_required_version="2.62.0" + +if command -v gh &>/dev/null; then + echo "gh is already installed in the system" + exit 0 +fi + +arch=$(uname -m) +case "${arch}" in + x86_64) arch="amd64" ;; + aarch64) arch="arm64" ;; + s390x) + # The GitHub CLI publishes no linux/s390x build, so provenance + # verification is skipped on s390x by the caller. + echo "gh CLI has no s390x build; skipping installation" + exit 0 + ;; + *) + echo "Unsupported architecture for gh CLI: ${arch}" + exit 1 + ;; +esac + +gh_tarball="gh_${gh_required_version}_linux_${arch}.tar.gz" + +echo "Downloading gh ${gh_required_version}" +tmp_dir="$(mktemp -d)" +curl -fsSL -o "${tmp_dir}/${gh_tarball}" \ + "https://github.com/cli/cli/releases/download/v${gh_required_version}/${gh_tarball}" + +echo "Installing gh to ${install_dest}" +tar -C "${tmp_dir}" -xzf "${tmp_dir}/${gh_tarball}" +sudo install -D --mode 0755 \ + "${tmp_dir}/gh_${gh_required_version}_linux_${arch}/bin/gh" \ + "${install_dest}/gh" +rm -rf "${tmp_dir}" diff --git a/tools/packaging/kata-deploy/local-build/kata-deploy-binaries-in-docker.sh b/tools/packaging/kata-deploy/local-build/kata-deploy-binaries-in-docker.sh index 88570f03e0..a294ba00e4 100755 --- a/tools/packaging/kata-deploy/local-build/kata-deploy-binaries-in-docker.sh +++ b/tools/packaging/kata-deploy/local-build/kata-deploy-binaries-in-docker.sh @@ -131,6 +131,8 @@ docker run \ --env REPO_URL_X86_64="${REPO_URL_X86_64}" \ --env REPO_COMPONENTS="${REPO_COMPONENTS}" \ --env AGENT_POLICY="${AGENT_POLICY}" \ + --env GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}" \ + --env VERIFY_COCO_EXTENSION_PROVENANCE="${VERIFY_COCO_EXTENSION_PROVENANCE:-}" \ --env USE_DEVMAPPER="${USE_DEVMAPPER}" \ --env RUNTIME_CHOICE="${RUNTIME_CHOICE}" \ --env STATIC_RUNTIME="${STATIC_RUNTIME:-}" \ diff --git a/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh b/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh index f212554ad5..ab68e318b2 100755 --- a/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh +++ b/tools/packaging/kata-deploy/local-build/kata-deploy-binaries.sh @@ -29,7 +29,6 @@ readonly versions_yaml="${repo_root_dir}/versions.yaml" readonly busybox_builder="${static_build_dir}/busybox/build.sh" readonly agent_builder="${static_build_dir}/agent/build.sh" -readonly coco_guest_components_builder="${static_build_dir}/coco-guest-components/build.sh" readonly clh_builder="${static_build_dir}/cloud-hypervisor/build-static-clh.sh" readonly firecracker_builder="${static_build_dir}/firecracker/build-static-firecracker.sh" readonly kernel_builder="${static_build_dir}/kernel/build.sh" @@ -352,15 +351,144 @@ get_coco_guest_components_tarball_path() { } get_latest_coco_guest_components_artefact_and_builder_image_version() { - local coco_guest_components_version - coco_guest_components_version=$(get_from_kata_deps ".externals.coco-guest-components.version") - local coco_guest_components_toolchain - coco_guest_components_toolchain=$(get_from_kata_deps ".externals.coco-guest-components.toolchain") - local latest_coco_guest_components_artefact="${coco_guest_components_version}-${coco_guest_components_toolchain}" - local latest_coco_guest_components_builder_image - latest_coco_guest_components_builder_image="$(get_coco_guest_components_image_name)" + echo "$(get_from_kata_deps ".externals.coco-guest-components.version")-$(get_coco_extension_oci_arch)" +} - echo "${latest_coco_guest_components_artefact}-${latest_coco_guest_components_builder_image}" +get_coco_extension_oci_arch() { + arch_to_golang "$(uname -m)" +} + +# Multi-arch scratch OCI container image from guest-components' "Publish OCI +# container image" step (coco-extension-image.yml). This is the assembled guest +# components rootfs as a container, not the EROFS disk image (extension_image). +get_coco_guest_components_container_image_ref() { + local version image + version="$(get_from_kata_deps ".externals.coco-guest-components.version")" + image="$(get_from_kata_deps ".externals.coco-guest-components.container_image")" + [[ -n "${version}" ]] || die "Failed to get coco-guest-components version from versions.yaml" + [[ -n "${image}" ]] || die "Failed to get coco-guest-components container_image from versions.yaml" + + echo "${image}:${version}" +} + +# The extension disk image is published as a multi-arch OCI index tagged with the +# guest-components commit; the per-arch selection happens at pull time. +get_coco_extension_disk_image_ref() { + local version image + version="$(get_from_kata_deps ".externals.coco-guest-components.version")" + image="$(get_from_kata_deps ".externals.coco-guest-components.extension_image")" + [[ -n "${version}" ]] || die "Failed to get coco-guest-components version from versions.yaml" + [[ -n "${image}" ]] || die "Failed to get coco-guest-components extension_image from versions.yaml" + + echo "${image}:${version}" +} + +# GitHub "owner/repo" that owns the provenance attestation, derived from the +# guest-components URL so it never drifts from the pinned source. +get_coco_extension_provenance_repo() { + local url + url="$(get_from_kata_deps ".externals.coco-guest-components.url")" + url="${url%/}" + echo "${url#https://github.com/}" +} + +get_latest_coco_extension_artefact_version() { + echo "$(get_from_kata_deps ".externals.coco-guest-components.version")-$(get_coco_extension_oci_arch)" +} + +ensure_oras_installed() { + if command -v oras &>/dev/null; then + return 0 + fi + + local install_oras_script="${repo_root_dir}/tools/packaging/kata-deploy/local-build/dockerbuild/install_oras.sh" + [[ -f "${install_oras_script}" ]] || die "oras is required to pull the CoCo extension image" + "${install_oras_script}" +} + +ensure_gh_installed() { + if command -v gh &>/dev/null; then + return 0 + fi + + local install_gh_script="${repo_root_dir}/tools/packaging/kata-deploy/local-build/dockerbuild/install_gh.sh" + [[ -f "${install_gh_script}" ]] || return 1 + "${install_gh_script}" || return 1 + command -v gh &>/dev/null +} + +# Verify the guest-components provenance attestation bound to a per-arch OCI +# manifest digest (container or disk image). +# +# Verification policy: +# VERIFY_COCO_EXTENSION_PROVENANCE=no — always skip +# VERIFY_COCO_EXTENSION_PROVENANCE=yes — always verify (CI); fail if gh/token missing +# unset (default) — verify when GH_TOKEN/GITHUB_TOKEN is set, +# otherwise skip with a warning (local builds) +verify_guest_components_oci_provenance() { + local image="$1" + local digest="$2" + local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}" + + if [[ "${VERIFY_COCO_EXTENSION_PROVENANCE:-}" == "no" ]]; then + warn "guest-components provenance verification disabled (VERIFY_COCO_EXTENSION_PROVENANCE=no)" + return 0 + fi + + # The GitHub CLI has no s390x build; provenance cannot be verified there even + # when CI sets VERIFY_COCO_EXTENSION_PROVENANCE=yes. + if [[ "$(uname -m)" == "s390x" ]]; then + warn "skipping guest-components provenance verification on s390x for ${image}@${digest}" + return 0 + fi + + if [[ -z "${token}" ]]; then + if [[ "${VERIFY_COCO_EXTENSION_PROVENANCE:-}" == "yes" ]]; then + die "VERIFY_COCO_EXTENSION_PROVENANCE=yes requires GH_TOKEN or GITHUB_TOKEN" + fi + warn "No GH_TOKEN/GITHUB_TOKEN set; skipping guest-components provenance verification for ${image}@${digest}" + return 0 + fi + + if ! ensure_gh_installed; then + die "gh CLI is required to verify guest-components provenance (set VERIFY_COCO_EXTENSION_PROVENANCE=no to bypass)" + fi + + local repo + repo="$(get_coco_extension_provenance_repo)" + info "Verifying provenance of ${image}@${digest} against ${repo}" + export GH_TOKEN="${token}" + # --bundle-from-oci reads the attestation guest-components pushed alongside the + # image (actions/attest push-to-registry) instead of the GitHub API. + gh attestation verify "oci://${image}@${digest}" \ + --repo "${repo}" \ + --bundle-from-oci \ + || die "Provenance verification failed for ${image}@${digest}" +} + +# Pull the published scratch OCI container image (coco-extension), verify its +# provenance, and export the rootfs into destdir for monolithic confidential images. +install_coco_guest_components_from_oci() { + ensure_oras_installed + + local image_ref image go_arch digest cid + image_ref="$(get_coco_guest_components_container_image_ref)" + image="${image_ref%%:*}" + go_arch="$(get_coco_extension_oci_arch)" + + digest="$(oras resolve --platform "linux/${go_arch}" "${image_ref}")" \ + || die "Failed to resolve ${image_ref} for linux/${go_arch}; bump .externals.coco-guest-components.version in versions.yaml once guest-components has published the image" + + verify_guest_components_oci_provenance "${image}" "${digest}" + + info "Pull and extract CoCo guest components from ${image}@${digest} (linux/${go_arch})" + + docker pull "${image}@${digest}" + # coco-extension is FROM scratch with no CMD/ENTRYPOINT; docker create needs + # an executable that exists in the image rootfs (we never run the container). + cid="$(docker create "${image}@${digest}" /usr/local/bin/attestation-agent --help)" + docker export "${cid}" | tar -xf - -C "${destdir}" + docker rm -f "${cid}" >/dev/null } get_pause_image_tarball_path() { @@ -481,10 +609,8 @@ install_image() { fi # Both the standard and NVIDIA confidential images bake the CoCo - # guest components + pause image into the rootfs, so factor them - # into the cache key. + # guest components (including the pause bundle) into the rootfs. latest_artefact+="-$(get_latest_coco_guest_components_artefact_and_builder_image_version)" - latest_artefact+="-$(get_latest_pause_image_artefact_and_builder_image_version)" fi if [[ "${variant}" == "nvidia-gpu" || "${variant}" == "nvidia-gpu-extension" ]]; then @@ -534,15 +660,13 @@ install_image() { if [[ -n "${variant}" ]]; then # Both the standard confidential image and the NVIDIA confidential - # image bake the CoCo guest components + pause image into the - # rootfs, so each stays a usable standalone monolithic CoCo image. - # The runtime-rs split path instead ships these in the separate + # image bake the CoCo guest components (including the pause bundle) + # into the rootfs, so each stays a usable standalone monolithic CoCo image. + # The runtime-rs split path instead ships these in the separately published # CoCo extension image (rootfs-image-coco-extension). if [[ "${variant}" == *confidential ]]; then COCO_GUEST_COMPONENTS_TARBALL="$(get_coco_guest_components_tarball_path)" export COCO_GUEST_COMPONENTS_TARBALL - PAUSE_IMAGE_TARBALL="$(get_pause_image_tarball_path)" - export PAUSE_IMAGE_TARBALL fi fi @@ -611,15 +735,15 @@ install_image_confidential() { } #Install CoCo extension image (erofs+verity, contains CoCo guest components + pause) +# +# The disk image is built and published by guest-components +# (ghcr.io/confidential-containers/guest-components/coco-extension-disk) as a +# multi-arch OCI index. Kata resolves the per-arch manifest digest, verifies its +# provenance attestation, and pulls exactly that digest into kata-static. install_image_coco_extension() { local component="rootfs-image-coco-extension" - local coco_last_commit - coco_last_commit="$(get_latest_coco_guest_components_artefact_and_builder_image_version)" - local pause_last_commit - pause_last_commit="$(get_latest_pause_image_artefact_and_builder_image_version)" - - latest_artefact="$(get_kata_version)-coco-extension-${coco_last_commit}-${pause_last_commit}" + latest_artefact="$(get_kata_version)-coco-extension-$(get_latest_coco_extension_artefact_version)" latest_builder_image="" install_cached_tarball_component \ @@ -630,114 +754,50 @@ install_image_coco_extension() { "${final_tarball_path}" \ && return 0 - info "Create CoCo extension image" + ensure_oras_installed - # Use a temp dir under the repo root so the path is valid both inside - # the outer build-kata-deploy container and in the nested image-builder - # container (Docker-in-Docker mounts use host paths). - local extension_rootfs - extension_rootfs="$(mktemp -d "${repo_root_dir}/.coco-extension-rootfs.XXXX")" + local disk_image_ref image go_arch + disk_image_ref="$(get_coco_extension_disk_image_ref)" + image="${disk_image_ref%%:*}" + go_arch="$(get_coco_extension_oci_arch)" - COCO_GUEST_COMPONENTS_TARBALL="$(get_coco_guest_components_tarball_path)" - PAUSE_IMAGE_TARBALL="$(get_pause_image_tarball_path)" + # Resolve the per-arch manifest digest out of the multi-arch index. This is the + # subject guest-components' provenance attestation is bound to, so we verify it + # and then pull that exact digest (avoiding a resolve/pull TOCTOU window). + local digest + digest="$(oras resolve --platform "linux/${go_arch}" "${disk_image_ref}")" \ + || die "Failed to resolve ${disk_image_ref} for linux/${go_arch}; bump .externals.coco-guest-components.version in versions.yaml once guest-components has published the image" - info "Unpacking CoCo guest components into extension rootfs" - tar --zstd -xvf "${COCO_GUEST_COMPONENTS_TARBALL}" -C "${extension_rootfs}" + verify_guest_components_oci_provenance "${image}" "${digest}" - info "Unpacking pause image into extension rootfs" - tar --zstd -xvf "${PAUSE_IMAGE_TARBALL}" -C "${extension_rootfs}" + info "Pull CoCo extension disk image from ${image}@${digest} (linux/${go_arch})" - # Data-driven extension manifest consumed by kata-agent. It describes the - # components shipped in this extension so the agent needs no per-bundle code - # changes. All paths are relative to the extension mount point - # (/run/kata-extensions/coco). The "${var}" tokens in the [[process]] entries are - # substituted by kata-agent from its runtime context. - info "Writing CoCo extension component manifest" - local manifest_dir="${extension_rootfs}/etc/kata-extensions" - mkdir -p "${manifest_dir}" - cat > "${manifest_dir}/components.toml" <<'EOF' -schema_version = 1 + local pull_dir + pull_dir="$(mktemp -d)" + pushd "${pull_dir}" >/dev/null + if ! oras pull "${image}@${digest}" --no-tty; then + popd >/dev/null + rm -rf "${pull_dir}" + die "Failed to pull CoCo extension image ${image}@${digest}" + fi -[paths] -"ocicrypt-config" = "etc/ocicrypt_config.json" -"pause-bundle" = "pause_bundle" - -[[process]] -id = "attestation-agent" -level = 1 -args = ["--attestation_sock", "${aa_attestation_uri}"] -optional_args = [{ when = "initdata_toml_path", args = ["--initdata-toml", "${initdata_toml_path}"] }] -config = "${aa_config_path}" -wait_socket = "${aa_attestation_socket}" -timeout_secs = "${launch_process_timeout}" -# The extension ships both the stock attestation-agent and the NVIDIA-attester -# build; the consumer selects one via the "attester_variant" context value -# (kata-agent uses "default", NVRC uses "nvidia"). -select = "${attester_variant}" - - [process.variants.default] - path = "usr/local/bin/attestation-agent" - - [process.variants.nvidia] - path = "usr/local/bin/attestation-agent-nv" - # attestation-agent-nv links libnvat.so (bundled in this CoCo extension under - # usr/local/lib), which dlopens libnvidia-ml.so.1 for GPU attestation evidence. - # Only NVAT's own libs need LD_LIBRARY_PATH: NVML lives in the GPU extension, - # which NVRC folds into the guest loader cache before starting kata-agent, so it - # resolves without a path here (see NVRC gpu::setup). - env = { LD_LIBRARY_PATH = "${extension_root}/usr/local/lib" } - -[[process]] -id = "confidential-data-hub" -level = 2 -path = "usr/local/bin/confidential-data-hub" -config = "${cdh_config_path}" -# CDH's secure_mount shells out (by PATH lookup) to cryptsetup for encrypted -# storage and to mke2fs/mkfs.ext4/dd for the filesystem. cryptsetup is CoCo-only -# and ships in this extension under usr/sbin (see -# build-static-coco-guest-components.sh); the plain mkfs/dd tooling lives in the -# nvidia base image's /sbin and /bin. The agent launches CDH with -# PATH=/bin:/sbin:/usr/bin:/usr/sbin, but setting any env here overrides it -# wholesale, so prepend the extension's usr/sbin and restore the base dirs. -env = { OCICRYPT_KEYPROVIDER_CONFIG = "${ocicrypt_config_path}", PATH = "${extension_root}/usr/sbin:/bin:/sbin:/usr/bin:/usr/sbin" } -wait_socket = "${cdh_socket}" -timeout_secs = "${launch_process_timeout}" - -[[process]] -id = "api-server-rest" -level = 3 -path = "usr/local/bin/api-server-rest" -args = ["--features", "${rest_api_features}"] -timeout_secs = "0" -EOF + [[ -f kata-containers-coco-extension.img ]] || die "CoCo extension pull did not contain kata-containers-coco-extension.img" local install_dir="${destdir}/${prefix}/share/kata-containers/" mkdir -p "${install_dir}" + install -D --mode 0644 kata-containers-coco-extension.img \ + "${install_dir}/kata-containers-coco-extension.img" - local image_builder="${repo_root_dir}/tools/osbuilder/image-builder/image_builder.sh" - - export USE_DOCKER="1" - export BUILD_VARIANT="coco-extension" - export FS_TYPE="erofs" - # Mirror the base/confidential images: s390x does not use a measured rootfs - # (Secure Execution measures the guest through a different mechanism), so the - # extension carries no dm-verity hash there and is mounted off its raw - # partition instead. - if [[ "${ARCH}" == "s390x" ]]; then - export MEASURED_ROOTFS="no" - else - export MEASURED_ROOTFS="yes" - fi - export SKIP_DAX_HEADER="yes" - export SKIP_ROOTFS_CHECK="yes" - - "${image_builder}" -o "${install_dir}/kata-containers-coco-extension.img" "${extension_rootfs}" - - if [[ -e "${install_dir}/root_hash_coco-extension.txt" ]]; then + if [[ -f root_hash_coco-extension.txt ]]; then + install -D --mode 0644 root_hash_coco-extension.txt \ + "${install_dir}/root_hash_coco-extension.txt" info "Root hash file: ${install_dir}/root_hash_coco-extension.txt" + elif [[ "${ARCH}" != "s390x" ]]; then + die "Expected root_hash_coco-extension.txt in ${disk_image_ref}" fi - rm -rf "${extension_rootfs}" + popd >/dev/null + rm -rf "${pull_dir}" } #Install cbl-mariner guest image @@ -792,7 +852,6 @@ install_initrd() { latest_artefact+="-$(get_latest_kernel_artefact_and_builder_image_version)" fi latest_artefact+="-$(get_latest_coco_guest_components_artefact_and_builder_image_version)" - latest_artefact+="-$(get_latest_pause_image_artefact_and_builder_image_version)" fi if [[ "${variant}" == "nvidia-gpu" ]]; then @@ -819,8 +878,6 @@ install_initrd() { if [[ "${variant}" == *confidential ]]; then COCO_GUEST_COMPONENTS_TARBALL="$(get_coco_guest_components_tarball_path)" export COCO_GUEST_COMPONENTS_TARBALL - PAUSE_IMAGE_TARBALL="$(get_pause_image_tarball_path)" - export PAUSE_IMAGE_TARBALL fi else # Vanilla initrd uses kata-agent as /sbin/init (no systemd). @@ -1445,9 +1502,9 @@ install_agent() { } install_coco_guest_components() { - latest_artefact="$(get_from_kata_deps ".externals.coco-guest-components.version")-$(get_from_kata_deps ".externals.coco-guest-components.toolchain")" + latest_artefact="$(get_latest_coco_guest_components_artefact_and_builder_image_version)" artefact_tag="$(get_from_kata_deps ".externals.coco-guest-components.version")" - latest_builder_image="$(get_coco_guest_components_image_name)" + latest_builder_image="" install_cached_tarball_component \ "${build_target}" \ @@ -1457,8 +1514,8 @@ install_coco_guest_components() { "${final_tarball_path}" \ && return 0 - info "build static coco-guest-components" - DESTDIR="${destdir}" "${coco_guest_components_builder}" + info "Pull published CoCo guest components OCI container image" + install_coco_guest_components_from_oci } install_pause_image() { diff --git a/versions.yaml b/versions.yaml index 0b893ee72a..265001160b 100644 --- a/versions.yaml +++ b/versions.yaml @@ -272,8 +272,15 @@ externals: coco-guest-components: description: "Provides attested key unwrapping for image decryption" url: "https://github.com/confidential-containers/guest-components/" - version: "da8d93f2797088a5f0636c8c1eeb31da73784fe8" - toolchain: "1.90.0" + version: "fff3a966b7b1d1654d172a7cd81c0f563721ff0b" + # Scratch OCI container image (guest-components "Publish OCI container image" + # step). Consumed by monolithic confidential rootfs builds. + # yamllint disable-line rule:line-length + container_image: "ghcr.io/confidential-containers/guest-components/coco-extension" + # EROFS + dm-verity disk image (guest-components "Publish disk image with ORAS" + # step). Consumed by the runtime-rs composable extension path. + # yamllint disable-line rule:line-length + extension_image: "ghcr.io/confidential-containers/guest-components/coco-extension-disk" coco-trustee: description: "Provides attestation and secret delivery components"