diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a14de0..81bea10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: curl -sfL "https://github.com/gitleaks/gitleaks/releases/download/${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION#v}_linux_x64.tar.gz" \ | tar -xz -C /usr/local/bin gitleaks + - name: Verify extension pins and paths + run: ./hack/verify.sh + - name: Gitleaks secret scan run: gitleaks detect --source . --redact diff --git a/.github/workflows/export-cosign-public-key.yml b/.github/workflows/export-cosign-public-key.yml new file mode 100644 index 0000000..b559461 --- /dev/null +++ b/.github/workflows/export-cosign-public-key.yml @@ -0,0 +1,21 @@ +name: Export Cosign public key + +on: + workflow_dispatch: + +jobs: + export: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v3.8.1 + + - name: Export public key + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + cosign public-key --key env://COSIGN_PRIVATE_KEY diff --git a/Dockerfile b/Dockerfile index e9a4989..1fcbe44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,24 +5,32 @@ ARG FIRECRACKER_VERSION=v1.15.0 ARG FIRECRACKER_SHA256_X86_64=00cadf7f21e709e939dc0c8d16e2d2ce7b975a62bec6c50f74b421cc8ab3cab4 ARG FIRECRACKER_SHA256_AARCH64=58325e6c3c539482a412ec0b60e6f539c3320adebcf8179c7629d06736aee0bd +ARG FIRECRACKER_CI_PREFIX=https://s3.amazonaws.com/spec.ccfc.min/firecracker-ci/v1.15 +ARG KERNEL_VERSION=6.1.155 +ARG KERNEL_SHA256_X86_64=e20e46d0c36c55c0d1014eb20576171b3f3d922260d9f792017aeff53af3d4f2 +ARG KERNEL_SHA256_AARCH64=e3544b10603acbf3db492cb52e000d22ba202cb4b63b9add027565683e11c591 # -- download stage ----------------------------------------------------------- FROM --platform=${BUILDPLATFORM} alpine:3.21 AS download ARG FIRECRACKER_VERSION ARG FIRECRACKER_SHA256_X86_64 ARG FIRECRACKER_SHA256_AARCH64 +ARG FIRECRACKER_CI_PREFIX +ARG KERNEL_VERSION +ARG KERNEL_SHA256_X86_64 +ARG KERNEL_SHA256_AARCH64 ARG TARGETARCH # hadolint ignore=DL3018 RUN apk add --no-cache curl && \ case "${TARGETARCH}" in \ - amd64) ARCH="x86_64"; SHA256="${FIRECRACKER_SHA256_X86_64}" ;; \ - arm64) ARCH="aarch64"; SHA256="${FIRECRACKER_SHA256_AARCH64}" ;; \ + amd64) ARCH="x86_64"; FIRECRACKER_SHA256="${FIRECRACKER_SHA256_X86_64}"; KERNEL_SHA256="${KERNEL_SHA256_X86_64}" ;; \ + arm64) ARCH="aarch64"; FIRECRACKER_SHA256="${FIRECRACKER_SHA256_AARCH64}"; KERNEL_SHA256="${KERNEL_SHA256_AARCH64}" ;; \ *) echo "Unsupported architecture: ${TARGETARCH}" >&2 && exit 1 ;; \ esac && \ URL="https://github.com/firecracker-microvm/firecracker/releases/download/${FIRECRACKER_VERSION}/firecracker-${FIRECRACKER_VERSION}-${ARCH}.tgz" && \ curl -fsSL "${URL}" -o /tmp/firecracker.tgz && \ - printf '%s %s\n' "${SHA256}" "/tmp/firecracker.tgz" > /tmp/firecracker.sha256 && \ + printf '%s %s\n' "${FIRECRACKER_SHA256}" "/tmp/firecracker.tgz" > /tmp/firecracker.sha256 && \ sha256sum -c /tmp/firecracker.sha256 && \ tar -xzf /tmp/firecracker.tgz -C /tmp && \ install -Dm755 \ @@ -30,7 +38,13 @@ RUN apk add --no-cache curl && \ /out/rootfs/usr/local/bin/firecracker && \ install -Dm755 \ "/tmp/release-${FIRECRACKER_VERSION}-${ARCH}/jailer-${FIRECRACKER_VERSION}-${ARCH}" \ - /out/rootfs/usr/local/bin/jailer + /out/rootfs/usr/local/bin/jailer && \ + KERNEL_URL="${FIRECRACKER_CI_PREFIX}/${ARCH}/vmlinux-${KERNEL_VERSION}" && \ + curl -fsSL "${KERNEL_URL}" -o /tmp/vmlinux && \ + printf '%s %s\n' "${KERNEL_SHA256}" /tmp/vmlinux > /tmp/vmlinux.sha256 && \ + sha256sum -c /tmp/vmlinux.sha256 && \ + test -s /tmp/vmlinux && \ + install -Dm0644 /tmp/vmlinux /out/rootfs/usr/local/share/imp/vmlinux # -- extension image ---------------------------------------------------------- # Files under /rootfs/ are overlaid onto the Talos node filesystem at boot. diff --git a/README.md b/README.md index 2a7063f..cf48f86 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Publish](https://github.com/syscode-labs/talos-ext-firecracker/actions/workflows/release.yml/badge.svg)](https://github.com/syscode-labs/talos-ext-firecracker/actions/workflows/release.yml) [![GHCR](https://img.shields.io/badge/registry-ghcr.io-blue)](https://ghcr.io/syscode-labs/talos-ext-firecracker) [![Talos](https://img.shields.io/badge/talos-%3E%3D1.7-0f62fe)](https://www.talos.dev/) -[![Firecracker](https://img.shields.io/badge/firecracker-v1.9.0-orange)](https://github.com/firecracker-microvm/firecracker/releases/tag/v1.9.0) +[![Firecracker](https://img.shields.io/badge/firecracker-v1.15.0-orange)](https://github.com/firecracker-microvm/firecracker/releases/tag/v1.15.0) [![Platforms](https://img.shields.io/badge/platforms-amd64%20%7C%20arm64-brightgreen)](#) [![License](https://img.shields.io/badge/license-Apache--2.0-green)](https://www.apache.org/licenses/LICENSE-2.0) @@ -13,14 +13,15 @@ This repo builds a **Talos system extension** that adds: - `firecracker` - `jailer` +- `/usr/local/share/imp/vmlinux` (Firecracker CI guest kernel, `6.1.155`) In plain terms: it gives a Talos node the Firecracker binaries so that higher-level systems can start lightweight microVMs. ## What This Does -1. Downloads Firecracker release tarballs for `amd64` and `arm64` -2. Verifies tarball checksums before extracting -3. Packages binaries into a Talos extension image +1. Downloads Firecracker release tarballs and the pinned Firecracker CI guest kernel for `amd64` and `arm64` +2. Verifies all artifact checksums before installing them +3. Packages Firecracker, Jailer, and the Imp guest kernel into a Talos extension image 4. Publishes multi-arch images to GHCR on tagged releases 5. Signs the pushed image digest recursively with Cosign so Image Factory can verify it @@ -54,6 +55,19 @@ retired key. ## Quick Start +## Use With Imp + +The Imp Helm chart must mount the kernel installed by this extension: + +```yaml +agent: + env: + kernelPath: /usr/local/share/imp/vmlinux +``` + +This path is Talos-specific. Local and Kind-based Imp tests provision their own +kernel at `/var/lib/imp/vmlinux` and should retain that configuration. + Build locally: ```bash @@ -63,6 +77,7 @@ docker buildx build --platform linux/amd64,linux/arm64 -t talos-ext-firecracker: Run local checks: ```bash +./hack/verify.sh pre-commit run --all-files ``` @@ -75,8 +90,9 @@ pre-commit install --hook-type commit-msg ## Key Files -- [`Dockerfile`](Dockerfile): downloads, verifies, and installs Firecracker binaries +- [`Dockerfile`](Dockerfile): downloads, verifies, and installs Firecracker, Jailer, and the pinned Imp guest kernel - [`manifest.yaml`](manifest.yaml): Talos extension metadata +- [`hack/verify.sh`](hack/verify.sh): validates artifact pins and installed paths - [`.github/workflows/ci.yml`](.github/workflows/ci.yml): lint/build checks - [`.github/workflows/release-please.yml`](.github/workflows/release-please.yml): automated release PRs - [`.github/workflows/release.yml`](.github/workflows/release.yml): publish on tags diff --git a/hack/verify.sh b/hack/verify.sh new file mode 100755 index 0000000..f8f2a24 --- /dev/null +++ b/hack/verify.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DOCKERFILE="${ROOT_DIR}/Dockerfile" +MANIFEST="${ROOT_DIR}/manifest.yaml" + +fail() { + echo "verification failed: $*" >&2 + exit 1 +} + +require_file() { + [[ -f "$1" ]] || fail "missing file: $1" +} + +require_file "${DOCKERFILE}" +require_file "${MANIFEST}" + +grep -q '^ name: firecracker$' "${MANIFEST}" || fail "manifest name is not pinned" +grep -Eq '^ version: [0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)?$' "${MANIFEST}" \ + || fail "manifest version is not a semantic version" +grep -q 'pinned Imp guest vmlinux' "${MANIFEST}" || fail "manifest description is missing the guest kernel" + +grep -q 'FIRECRACKER_VERSION=v1\.15\.0' "${DOCKERFILE}" || fail "Firecracker version is not pinned" +grep -q 'FIRECRACKER_CI_PREFIX=https://s3\.amazonaws\.com/spec\.ccfc\.min/firecracker-ci/v1\.15' "${DOCKERFILE}" \ + || fail "guest kernel source is not pinned" +grep -q 'KERNEL_VERSION=6\.1\.155' "${DOCKERFILE}" || fail "guest kernel version is not pinned" + +for digest in \ + 00cadf7f21e709e939dc0c8d16e2d2ce7b975a62bec6c50f74b421cc8ab3cab4 \ + 58325e6c3c539482a412ec0b60e6f539c3320adebcf8179c7629d06736aee0bd \ + e20e46d0c36c55c0d1014eb20576171b3f3d922260d9f792017aeff53af3d4f2 \ + e3544b10603acbf3db492cb52e000d22ba202cb4b63b9add027565683e11c591; do + grep -q "${digest}" "${DOCKERFILE}" || fail "missing SHA-256 pin: ${digest}" +done + +grep -q 'amd64) ARCH="x86_64"' "${DOCKERFILE}" || fail "amd64 architecture mapping is missing" +grep -q 'arm64) ARCH="aarch64"' "${DOCKERFILE}" || fail "arm64 architecture mapping is missing" + +for path in \ + /usr/local/bin/firecracker \ + /usr/local/bin/jailer \ + /usr/local/share/imp/vmlinux; do + grep -q "${path}" "${DOCKERFILE}" || fail "required installed path is missing: ${path}" +done + +if [[ "${VERIFY_REMOTE:-false}" == "true" ]]; then + command -v curl >/dev/null 2>&1 || fail "curl is required for VERIFY_REMOTE=true" + command -v sha256sum >/dev/null 2>&1 || fail "sha256sum is required for VERIFY_REMOTE=true" + + tmp_dir="$(mktemp -d)" + trap 'rm -rf "${tmp_dir}"' EXIT + base="https://s3.amazonaws.com/spec.ccfc.min/firecracker-ci/v1.15" + curl -fsSL "${base}/x86_64/vmlinux-6.1.155" -o "${tmp_dir}/x86_64" + curl -fsSL "${base}/aarch64/vmlinux-6.1.155" -o "${tmp_dir}/aarch64" + printf '%s %s\n' e20e46d0c36c55c0d1014eb20576171b3f3d922260d9f792017aeff53af3d4f2 "${tmp_dir}/x86_64" | sha256sum -c - + printf '%s %s\n' e3544b10603acbf3db492cb52e000d22ba202cb4b63b9add027565683e11c591 "${tmp_dir}/aarch64" | sha256sum -c - +fi + +echo "Firecracker extension pins and installed paths verified" diff --git a/manifest.yaml b/manifest.yaml index a35889d..aad88e8 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -3,7 +3,7 @@ metadata: name: firecracker version: 1.12.0 author: syscode-labs - description: Firecracker microVM and Jailer binaries for Talos Linux + description: Firecracker microVM, Jailer, and pinned Imp guest vmlinux for Talos Linux compatibility: talos: version: '>= 1.7.0'