diff --git a/README.md b/README.md index a6de706..d6c467e 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,8 @@ curl -sSL https://github.com/deeplethe/forkd/releases/download/v0.5.2/forkd-v0.5 # 2. Host bring-up: sudo bash scripts/setup-host.sh # KVM + tap device, one-time +# Or, checksum-verified host bring-up: +# sudo bash scripts/setup-host.sh --paranoid sudo bash scripts/netns-setup.sh 3 # per-child network namespaces # 3. Sanity-check: diff --git a/scripts/setup-host.sh b/scripts/setup-host.sh index 001d9d1..21b0dbd 100644 --- a/scripts/setup-host.sh +++ b/scripts/setup-host.sh @@ -7,6 +7,136 @@ set -euo pipefail say() { printf "\033[1;34m==>\033[0m %s\n" "$*"; } die() { printf "\033[1;31merror:\033[0m %s\n" "$*" >&2; exit 1; } +usage() { + cat <<'EOF' +Usage: scripts/setup-host.sh [--paranoid] + +Options: + --paranoid Download pinned rustup-init and Firecracker archives, verify + their sha256 sums, then install them. Default behavior is unchanged. + -h, --help Show this help text. +EOF +} + +PARANOID=0 +for arg in "$@"; do + case "$arg" in + --paranoid) PARANOID=1 ;; + -h|--help) usage; exit 0 ;; + *) usage >&2; die "unknown argument: $arg" ;; + esac +done + +RUSTUP_VERSION="1.29.0" +RUSTUP_X86_64_SHA256="4acc9acc76d5079515b46346a485974457b5a79893cfb01112423c89aeb5aa10" +RUSTUP_AARCH64_SHA256="9732d6c5e2a098d3521fca8145d826ae0aaa067ef2385ead08e6feac88fa5792" + +FC_VERSION="v1.10.1" +FC_X86_64_SHA256="36112969952b0e34fadcfca769d48a55dc22cbba99af17e02bd0e24fc35adc77" +FC_AARCH64_SHA256="9e3641071de140979afaac0c52fdc107baeba398bdb5709c12f77ee469207fcd" + +TEMP_DIRS=() +cleanup_temp_dirs() { + if [ "${#TEMP_DIRS[@]}" -eq 0 ]; then + return + fi + rm -rf "${TEMP_DIRS[@]}" +} +trap cleanup_temp_dirs EXIT + +make_temp_dir() { + local -n outvar="$1" + outvar="$(mktemp -d)" + TEMP_DIRS+=("$outvar") +} + +host_arch() { + case "$(uname -m)" in + x86_64) printf "x86_64\n" ;; + aarch64|arm64) printf "aarch64\n" ;; + *) die "unsupported architecture for setup-host.sh: $(uname -m)" ;; + esac +} + +rustup_triple() { + case "$1" in + x86_64) printf "x86_64-unknown-linux-gnu\n" ;; + aarch64) printf "aarch64-unknown-linux-gnu\n" ;; + *) die "unsupported architecture for rustup-init: $1" ;; + esac +} + +rustup_sha256() { + case "$1" in + x86_64-unknown-linux-gnu) printf "%s\n" "$RUSTUP_X86_64_SHA256" ;; + aarch64-unknown-linux-gnu) printf "%s\n" "$RUSTUP_AARCH64_SHA256" ;; + *) die "missing rustup-init sha256 for $1" ;; + esac +} + +firecracker_sha256() { + case "$1" in + x86_64) printf "%s\n" "$FC_X86_64_SHA256" ;; + aarch64) printf "%s\n" "$FC_AARCH64_SHA256" ;; + *) die "missing Firecracker sha256 for $1" ;; + esac +} + +download_and_verify() { + local url="$1" + local dest="$2" + local expected="$3" + local label="$4" + local actual + + curl -fsSL "$url" -o "$dest" + actual="$(sha256sum "$dest" | awk '{print $1}')" + if [ "$actual" != "$expected" ]; then + die "$label sha256 mismatch: expected $expected, got $actual" + fi +} + +install_rustup_paranoid() { + local arch triple expected tmp rustup_init + arch="$(host_arch)" + triple="$(rustup_triple "$arch")" + expected="$(rustup_sha256 "$triple")" + make_temp_dir tmp + rustup_init="$tmp/rustup-init" + + say "Downloading rustup-init $RUSTUP_VERSION ($triple) with sha256 verification..." + download_and_verify \ + "https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${triple}/rustup-init" \ + "$rustup_init" \ + "$expected" \ + "rustup-init ${RUSTUP_VERSION} ${triple}" + chmod 0755 "$rustup_init" + "$rustup_init" -y +} + +install_firecracker_from_archive() { + local arch="$1" + local tmp archive + make_temp_dir tmp + archive="$tmp/firecracker-${FC_VERSION}-${arch}.tgz" + + if [ "$PARANOID" -eq 1 ]; then + say "Downloading Firecracker $FC_VERSION ($arch) with sha256 verification..." + download_and_verify \ + "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-${arch}.tgz" \ + "$archive" \ + "$(firecracker_sha256 "$arch")" \ + "Firecracker ${FC_VERSION} ${arch}" + tar -xzf "$archive" -C "$tmp" + else + curl -fsSL "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-${arch}.tgz" \ + | tar -xz -C "$tmp" + fi + + install -m 0755 "$tmp/release-${FC_VERSION}-${arch}/firecracker-${FC_VERSION}-${arch}" "$HOME/.local/bin/firecracker" + install -m 0755 "$tmp/release-${FC_VERSION}-${arch}/jailer-${FC_VERSION}-${arch}" "$HOME/.local/bin/jailer" +} + say "Checking hardware virtualization support..." if [ "$(grep -Ec '(vmx|svm)' /proc/cpuinfo)" -eq 0 ]; then die "CPU does not advertise VT-x / AMD-V. forkd needs KVM." @@ -40,31 +170,23 @@ say "Installing Rust (if missing)..." # rustup binary version that lands here does NOT determine what compiler # forkd actually builds with — `rust-toolchain.toml` at the repo root # pins the channel (currently `stable`), and rustup fetches that -# toolchain on first `cargo build`. So a supply-chain compromise of -# `sh.rustup.rs` would still be bounded by what rustup-init does -# locally; the project itself remains pinned. -# See #236 for the security discussion. Future work: a `--paranoid` mode -# that downloads the rustup-init binary and verifies sha256 before -# executing. Not done now because the sha256 needs to be refreshed on -# every rustup-init release, which trades supply-chain hygiene for -# maintenance staleness. +# toolchain on first `cargo build`. The default remains curl-pipe-sh; +# pass --paranoid to verify a pinned rustup-init binary before running it. if ! command -v cargo >/dev/null; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + if [ "$PARANOID" -eq 1 ]; then + install_rustup_paranoid + else + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + fi # shellcheck disable=SC1091 source "$HOME/.cargo/env" fi -FC_VERSION="v1.10.1" -ARCH="$(uname -m)" +ARCH="$(host_arch)" say "Installing Firecracker $FC_VERSION ($ARCH)..." mkdir -p "$HOME/.local/bin" if [ ! -x "$HOME/.local/bin/firecracker" ]; then - TMP="$(mktemp -d)" - curl -fsSL "https://github.com/firecracker-microvm/firecracker/releases/download/${FC_VERSION}/firecracker-${FC_VERSION}-${ARCH}.tgz" \ - | tar -xz -C "$TMP" - install -m 0755 "$TMP/release-${FC_VERSION}-${ARCH}/firecracker-${FC_VERSION}-${ARCH}" "$HOME/.local/bin/firecracker" - install -m 0755 "$TMP/release-${FC_VERSION}-${ARCH}/jailer-${FC_VERSION}-${ARCH}" "$HOME/.local/bin/jailer" - rm -rf "$TMP" + install_firecracker_from_archive "$ARCH" fi case ":$PATH:" in