#!/usr/bin/env bash
# Build the shellshock target — Alpine 3.21 + bash 4.2 + Apache mod_cgi.
#
# Inputs (env, set by tools/build_target.py):
#   OUT_PATH          — staging qcow2 path; we write the final image here
#   BASE_IMAGE_NAME   — "alpine-3.21-virt" per spec.toml
#
# Build strategy:
#   1. Fetch the alpine-virt cloud image (sha256-pinned); cache to
#      /var/cache/cis490/base-images/.
#   2. Create a CoW overlay at $OUT_PATH so we don't mutate the base.
#   3. Build a cidata ISO with cloud-init user-data that, on first
#      boot:
#        - apk add apache2 + apache2-utils + bash-builtins (we'll
#          replace /bin/bash with the compiled-from-source 4.2)
#        - download bash-4.2 source (sha256-pinned) + compile
#        - drop the vulnerable bash at /usr/local/bin/bash, symlink
#          /bin/sh -> /usr/local/bin/bash
#        - drop a CGI script at /var/www/localhost/cgi-bin/test.cgi
#          that prints a benign greeting (the exploit doesn't need
#          anything more — it just needs ANY CGI script the User-Agent
#          header reaches)
#        - enable apache + cis490-agent at boot
#        - touch /var/lib/cis490-build-complete and shut down
#   4. Boot the overlay+cidata in qemu, wait for shutdown, snapshot
#      a fresh state.
#
# Idempotent: re-running with a present $OUT_PATH starts from scratch.

set -euo pipefail

if [[ -z "${OUT_PATH:-}" ]]; then
    echo "OUT_PATH not set" >&2
    exit 2
fi

REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
TARGET_DIR="$(cd "$(dirname "$0")" && pwd)"
CACHE_DIR="${CIS490_BASE_IMAGE_CACHE:-/var/cache/cis490/base-images}"
mkdir -p "$CACHE_DIR"

# alpine-virt-3.21.0-x86_64.iso would be the kernel+initramfs; we want
# the cloud-init-aware qcow2. Alpine ships nocloud images at
# https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/cloud/.
BASE_URL="https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/cloud/nocloud_alpine-3.21.0-x86_64-uefi-cloudinit-r0.qcow2"
BASE_FILE="$CACHE_DIR/alpine-3.21-cloudinit.qcow2"
# Pin the published sha256 of the alpine-3.21 cloud image. Update
# alongside any base-image bump (substantive §16 amendment).
BASE_SHA256="ee0b8c2e1ce8d5fa5e3fb9968fdfee9c8b1f01ae9ee8ed3b3c7c3bd9b7e1e9c8"

if [[ ! -f "$BASE_FILE" ]]; then
    echo "[build:shellshock] fetching base image $BASE_URL"
    curl -fsSL -o "$BASE_FILE.partial" "$BASE_URL"
    mv "$BASE_FILE.partial" "$BASE_FILE"
fi

actual_sha=$(sha256sum "$BASE_FILE" | awk '{print $1}')
if [[ "$actual_sha" != "$BASE_SHA256" ]]; then
    echo "[build:shellshock] WARN: base image sha256 mismatch" >&2
    echo "  expected: $BASE_SHA256" >&2
    echo "  got:      $actual_sha" >&2
    echo "  Base image hash drifted — investigate before trusting." >&2
    # Don't auto-update the pin; that's a §16 amendment.
    exit 1
fi

# Always start clean.
rm -f "$OUT_PATH"

# 6 GiB upper bound; bash source + compile artifacts + apache.
qemu-img create -f qcow2 -F qcow2 -b "$BASE_FILE" "$OUT_PATH" 6G >/dev/null

# Build cidata seed.
CIDATA_DIR="$(mktemp -d)"
trap 'rm -rf "$CIDATA_DIR"' EXIT

cat > "$CIDATA_DIR/meta-data" <<EOF
instance-id: cis490-shellshock
local-hostname: shellshock
EOF

cat > "$CIDATA_DIR/user-data" <<'EOF'
#cloud-config
hostname: shellshock
manage_etc_hosts: true
users:
  - name: cis490
    plain_text_passwd: cis490
    lock_passwd: false
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/sh
ssh_pwauth: true
chpasswd:
  expire: false
  list: |
    root:cis490

packages:
  - apache2
  - apache2-utils
  - python3
  - curl
  - gcc
  - make
  - musl-dev
  - patch

write_files:
  - path: /var/www/localhost/cgi-bin/test.cgi
    permissions: '0755'
    content: |
      #!/usr/local/bin/bash
      echo "Content-type: text/plain"
      echo
      echo "shellshock target up; bash --version: $(/usr/local/bin/bash --version | head -1)"
  - path: /etc/apache2/conf.d/cgi.conf
    content: |
      LoadModule cgi_module modules/mod_cgi.so
      ScriptAlias "/cgi-bin/" "/var/www/localhost/cgi-bin/"
      <Directory "/var/www/localhost/cgi-bin">
        AllowOverride None
        Options +ExecCGI
        Require all granted
        AddHandler cgi-script .cgi .sh
      </Directory>
  - path: /usr/local/sbin/build-vulnerable-bash.sh
    permissions: '0755'
    content: |
      #!/bin/sh
      set -e
      cd /tmp
      # bash-4.2 source. GNU mirrors are sha256-pinned by upstream.
      BASH_URL="https://ftp.gnu.org/gnu/bash/bash-4.2.tar.gz"
      curl -fsSL -o bash-4.2.tar.gz "$BASH_URL"
      echo "a27a1179ec9c0830c65c6aa5d7dab60f7ce1a2a608618570f96bfa72e95ab3d8  bash-4.2.tar.gz" | sha256sum -c -
      tar xzf bash-4.2.tar.gz
      cd bash-4.2
      ./configure --prefix=/usr/local --without-bash-malloc
      make -j2
      make install
      # Confirm the vulnerable function-export parser is present.
      env x='() { :;}; echo VULN_OK' /usr/local/bin/bash -c 'echo done' \
        | grep -q VULN_OK || { echo "bash 4.2 build failed shellshock-positive check" >&2; exit 1; }

runcmd:
  - [ sh, -c, "echo CIS490_BOOT_OK > /tmp/.cis490-boot" ]
  - [ /usr/local/sbin/build-vulnerable-bash.sh ]
  - [ rc-update, add, apache2, default ]
  - [ rc-service, apache2, start ]
  - [ touch, /var/lib/cis490-build-complete ]
  - [ poweroff ]
EOF

CIDATA_ISO="$CIDATA_DIR/cidata.iso"
"$REPO_ROOT/.venv/bin/python" "$REPO_ROOT/tools/build_cidata.py" \
    --user-data "$CIDATA_DIR/user-data" \
    --meta-data "$CIDATA_DIR/meta-data" \
    --no-embed-agent \
    "$CIDATA_ISO"

echo "[build:shellshock] booting overlay for first-boot install (~5-10 min)"

# Boot with no upstream egress (restrict=on prevents the cidata script
# from reaching the open internet during the install — but cloud-init
# needs to fetch packages, which doesn't work under restrict. So we
# permit egress ONLY during build, then re-verify under restrict in
# verify.sh. The build is supply-chain attested via sha256 pins on
# the base image and bash source; the operator runs build_target on
# a trusted host).
qemu-system-x86_64 \
    -name cis490-build-shellshock \
    -machine q35,accel=kvm \
    -cpu host \
    -smp 2 \
    -m 1024 \
    -drive file="$OUT_PATH",format=qcow2,if=virtio \
    -drive file="$CIDATA_ISO",format=raw,if=virtio,readonly=on \
    -netdev user,id=n0 \
    -device virtio-net-pci,netdev=n0 \
    -nographic \
    -serial mon:stdio \
    -no-reboot \
    > "$OUT_PATH.boot.log" 2>&1 || true

# Verify the install reached completion.
# (The poweroff in runcmd ends qemu cleanly; -no-reboot exits.)
if ! grep -q "CIS490_BOOT_OK" "$OUT_PATH.boot.log"; then
    echo "[build:shellshock] first-boot install did not complete; see $OUT_PATH.boot.log" >&2
    exit 1
fi

# Compact the qcow2 (cloud-init artifacts, package caches).
qemu-img convert -O qcow2 -c "$OUT_PATH" "$OUT_PATH.compact"
mv "$OUT_PATH.compact" "$OUT_PATH"

echo "[build:shellshock] complete: $OUT_PATH"
