banger/scripts/make-generic-kernel.sh
Thales Maciel 25a1466947
supply chain: verify signatures and pins across image + kernel builds
Three independent hardenings, addressing a review finding that the
kernel and image build pipelines were relying on HTTPS alone for
artifact integrity.

scripts/make-generic-kernel.sh
- Fetch the detached PGP signature (linux-<ver>.tar.sign) alongside
  the tarball and verify it with gpg before extraction. An isolated
  $GNUPGHOME under the tempdir keeps the kernel signers out of the
  invoking user's keyring.
- Import the three kernel.org release signing keys (Greg KH / Linus /
  Sasha Levin) from keyserver.ubuntu.com, falling back to
  keys.openpgp.org. Ubuntu comes first because keys.openpgp.org strips
  unverified UIDs on upload, leaving gpg with UID-less keys it
  refuses to trust.
- Require VALIDSIG (cryptographic proof) rather than GOODSIG
  (printed even for expired keys) before proceeding. Verified
  end-to-end against a clean tarball (accepts) and a byte-flipped
  tampered copy (rejects with BADSIG).
- gpg + gpgv + xz added to the required-tools check.

images/golden/Dockerfile
- Pin Docker's apt signing key by fingerprint. After downloading
  /etc/apt/keyrings/docker.asc we gpg --show-keys --with-colons it,
  extract the fpr, and compare against the expected
  9DC858229FC7DD38854AE2D88D81803C0EBFCD88. A tampered or swapped key
  aborts the build before any apt repo metadata is fetched.
- Replace `curl https://mise.run | sh` with a pinned GitHub release
  binary (mise v2026.4.18, linux-x64) verified against its published
  sha256. Refuses to build on unknown architectures rather than
  silently installing a binary we have no hash for.
- Add gnupg to the ESSENTIAL apt-get install so the fingerprint check
  has gpg available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:38:13 -03:00

148 lines
5.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# make-generic-kernel.sh
#
# Build a minimal Firecracker-optimized vmlinux from upstream kernel.org
# sources using the vendored Firecracker config. All essential drivers
# (virtio_blk, virtio_net, ext4, vsock) are compiled in — no modules,
# no initramfs needed. The result boots any OCI-pulled rootfs directly.
#
# Usage:
# scripts/make-generic-kernel.sh [--version 6.12.8]
#
# Output:
# build/manual/generic-kernel/boot/vmlinux-<version>
# build/manual/generic-kernel/metadata.json
set -euo pipefail
log() { printf '[make-generic-kernel] %s\n' "$*" >&2; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OUT_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}/generic-kernel"
CONFIG="$REPO_ROOT/configs/firecracker-x86_64-6.1.config"
KERNEL_VERSION="${KERNEL_VERSION:-6.12.8}"
KERNEL_MAJOR="${KERNEL_VERSION%%.*}"
JOBS="${JOBS:-$(nproc)}"
usage() {
cat <<EOF
usage: scripts/make-generic-kernel.sh [--version <ver>]
Downloads kernel <ver> from kernel.org, applies the vendored Firecracker
config, and builds a minimal vmlinux. Default version: $KERNEL_VERSION
Output: $OUT_DIR/boot/vmlinux-<ver>
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--version) KERNEL_VERSION="$2"; KERNEL_MAJOR="${KERNEL_VERSION%%.*}"; shift 2;;
-h|--help) usage; exit 0;;
*) log "unknown arg: $1"; exit 1;;
esac
done
for tool in curl tar xz make gcc gpg gpgv; do
command -v "$tool" >/dev/null 2>&1 || { log "missing required tool: $tool"; exit 1; }
done
[[ -f "$CONFIG" ]] || { log "config not found: $CONFIG"; exit 1; }
# kernel.org release signing keys. Stable (Greg KH) signs most point
# releases; mainline (Linus) signs .0 drops; Sasha Levin sometimes
# signs longterm backports. Listing all three keeps the script
# working across every release channel the user might pick. Rotations
# are rare and announced; update this list if gpg complains.
#
# Fingerprints verified against kernel.org:
# https://www.kernel.org/signature.html
KERNEL_SIGNING_KEYS=(
647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman
ABAF11C65A2970B130ABE3C479BE3E4300411886 # Linus Torvalds
E27E5D8A3403A2EF66873BBCDEA66FF797772CDC # Sasha Levin
)
TARBALL="linux-${KERNEL_VERSION}.tar.xz"
SIGNATURE="linux-${KERNEL_VERSION}.tar.sign"
BASE_URL="https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_MAJOR}.x"
SRC_DIR="$(mktemp -d)"
trap 'rm -rf "$SRC_DIR"' EXIT
# Isolated GNUPGHOME so the verification step can't accidentally
# trust whatever the invoking user already has in their keyring. The
# trap above cleans the whole SRC_DIR, including this.
GPG_HOME="$SRC_DIR/gnupg"
install -d -m 0700 "$GPG_HOME"
export GNUPGHOME="$GPG_HOME"
log "importing kernel.org signing keys"
# keyserver.ubuntu.com first: it returns keys with user IDs intact,
# which gpg needs to mark the key as usable. keys.openpgp.org (the
# current SKS successor) strips unverified UIDs on upload, and the
# kernel.org devs haven't all completed its email verification flow,
# so pulling from there returns UID-less keys that gpg then refuses
# to trust. We fall back to it anyway in case ubuntu is unreachable.
if ! gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys "${KERNEL_SIGNING_KEYS[@]}" 2>/dev/null; then
log "key fetch from keyserver.ubuntu.com failed; trying keys.openpgp.org"
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "${KERNEL_SIGNING_KEYS[@]}"
fi
log "downloading kernel $KERNEL_VERSION from $BASE_URL/$TARBALL"
curl -fSL --progress-bar -o "$SRC_DIR/$TARBALL" "$BASE_URL/$TARBALL"
curl -fSL --progress-bar -o "$SRC_DIR/$SIGNATURE" "$BASE_URL/$SIGNATURE"
log "verifying signature"
# The .tar.sign is a detached signature over the *uncompressed* tar,
# per kernel.org convention. Pipe the xz-decompressed stream into
# gpg --verify so we never materialise an unverified tarball on disk.
# Require VALIDSIG (the cryptographic proof — GOODSIG alone is
# printed even for expired/revoked keys, VALIDSIG requires a usable
# key and a mathematically valid signature).
VERIFY_STATUS="$SRC_DIR/verify.status"
xz -cd "$SRC_DIR/$TARBALL" | gpg --batch --status-fd 3 --verify "$SRC_DIR/$SIGNATURE" - 3>"$VERIFY_STATUS" 2>/dev/null || true
if ! grep -qE '^\[GNUPG:\] VALIDSIG' "$VERIFY_STATUS"; then
log "signature verification FAILED — refusing to build"
log "gpg status:"
cat "$VERIFY_STATUS" >&2 || true
exit 1
fi
log "signature OK (signed by $(awk '/^\[GNUPG:\] VALIDSIG/ {print $3}' "$VERIFY_STATUS"))"
log "extracting"
tar -xf "$SRC_DIR/$TARBALL" -C "$SRC_DIR" --strip-components=1
log "applying firecracker config"
cp "$CONFIG" "$SRC_DIR/.config"
# Adapt the 6.1 config to whatever version we're building. make olddefconfig
# fills in any new symbols with defaults.
make -C "$SRC_DIR" olddefconfig >/dev/null 2>&1
log "building vmlinux (jobs=$JOBS)"
make -C "$SRC_DIR" -j"$JOBS" vmlinux 2>&1 | tail -5
VMLINUX="$SRC_DIR/vmlinux"
if [[ ! -f "$VMLINUX" ]]; then
log "vmlinux not found after build; check build output above"
exit 1
fi
mkdir -p "$OUT_DIR/boot"
DEST="$OUT_DIR/boot/vmlinux-${KERNEL_VERSION}"
cp "$VMLINUX" "$DEST"
log "verifying: $(file -b "$DEST" | head -c 80)"
cat > "$OUT_DIR/metadata.json" <<EOF
{
"kernel_path": "$DEST",
"kernel_version": "$KERNEL_VERSION",
"config": "firecracker-x86_64-6.1"
}
EOF
log "done: $DEST ($(du -h "$DEST" | cut -f1))"
log "no initrd or modules needed — all drivers are built-in"
log ""
log "next steps:"
log " banger kernel import generic-${KERNEL_VERSION%%.*}.${KERNEL_VERSION#*.} --from $OUT_DIR --distro generic --arch x86_64"