cockpit-kata/qcrows-verify

199 lines
7.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================================
# qcrows-verify — Verify integrity and compliance of a QCrows image archive
# ============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log() { echo -e "${GREEN}[qcrows-verify]${NC} $*"; }
warn() { echo -e "${YELLOW}[qcrows-verify]${NC} WARN: $*"; }
fail() { echo -e "${RED}[qcrows-verify]${NC} FAIL: $*"; }
FILE="${1:-}"
[[ -z "$FILE" ]] && { echo "Usage: qcrows-verify <file.qcrows>"; exit 1; }
[[ ! -f "$FILE" ]] && { echo "File not found: $FILE" >&2; exit 1; }
TMPDIR="$(mktemp -d /tmp/qcrows-verify-XXXXXX)"
trap 'rm -rf "$TMPDIR"' EXIT
# Extract archive
case "$FILE" in
*.gz) tar xzf "$FILE" -C "$TMPDIR" 2>/dev/null || tar xf "$FILE" -C "$TMPDIR" 2>/dev/null ;;
*) tar xf "$FILE" -C "$TMPDIR" 2>/dev/null ;;
esac
# Descend into subdirectory if the archive wraps everything in one
# (i.e., only one child dir and no regular files at the top level)
top_level_files=$(find "$TMPDIR" -maxdepth 1 -type f | wc -l)
top_level_dirs=$(find "$TMPDIR" -maxdepth 1 -type d ! -path "$TMPDIR" | wc -l)
if [[ $top_level_dirs -eq 1 && $top_level_files -eq 0 ]]; then
TMPDIR="$(find "$TMPDIR" -maxdepth 1 -type d ! -path "$TMPDIR" | head -1)"
fi
PASS=0
FAIL=0
WARN_COUNT=0
# ─── 1. Required files ─────────────────────────────────────────────────────
for req in metadata.toml menu.toml hashes.sha256; do
if [[ -f "${TMPDIR}/${req}" ]]; then
log "PASS: ${req} present"
((PASS++)) || true
else
fail "FAIL: ${req} missing (required)"
((FAIL++)) || true
fi
done
# ─── 2. Rootfs ─────────────────────────────────────────────────────────────
rootfs_found=false
for ext in .tar.gz .tar.xz .tar.zst .tgz; do
if ls "${TMPDIR}"/rootfs*${ext} 2>/dev/null | head -1 | grep -q .; then
rootfs_found=true
break
fi
done
if [[ "$rootfs_found" == "false" ]] && ls "${TMPDIR}"/rootfs* 2>/dev/null | head -1 | grep -q .; then
rootfs_found=true
fi
if [[ "$rootfs_found" == "true" ]]; then
log "PASS: rootfs found"
((PASS++)) || true
else
fail "FAIL: rootfs missing (required)"
((FAIL++)) || true
fi
# ─── 3. Kernel binary (required in v0.2+) ──────────────────────────────────
kernel_file=""
for name in kernel/vmlinuz kernel/vmlinux; do
if [[ -f "${TMPDIR}/${name}" ]]; then
kernel_file="${TMPDIR}/${name}"
log "PASS: kernel binary found (${name})"
((PASS++)) || true
break
fi
done
if [[ -z "$kernel_file" ]]; then
fail "FAIL: kernel missing (required in QCrows v0.2+)"
((FAIL++)) || true
else
# Kernel format validation
if file "$kernel_file" 2>/dev/null | grep -qE '(ELF|Linux.*boot)'; then
log "PASS: kernel binary format valid"
((PASS++)) || true
else
fail "FAIL: kernel binary format invalid (expected ELF or bzImage)"
((FAIL++)) || true
fi
# Kernel size warning
kernel_size=$(stat --format='%s' "$kernel_file" 2>/dev/null || echo 0)
kernel_size_mb=$(( kernel_size / (1024 * 1024) ))
if [[ $kernel_size_mb -gt 100 ]]; then
warn "WARN: kernel exceeds 100MiB (${kernel_size_mb}MiB)"
((WARN_COUNT++)) || true
fi
fi
# ─── 4. Kernel config (required) ───────────────────────────────────────────
if [[ -f "${TMPDIR}/kernel/config" ]]; then
log "PASS: kernel/config found"
((PASS++)) || true
# Required Kata config options — array-driven validation
missing_options=0
for opt in CONFIG_VSOCKETS CONFIG_VIRTIO CONFIG_VIRTIO_PCI CONFIG_DEVTMPFS CONFIG_DEVTMPFS_MOUNT; do
if ! grep -q "${opt}=y" "${TMPDIR}/kernel/config" 2>/dev/null; then
warn "WARN: required Kata option ${opt}=y not found in kernel config"
((missing_options++)) || true
fi
done
if [[ $missing_options -eq 0 ]]; then
log "PASS: all required Kata kernel options present"
((PASS++)) || true
else
warn "WARN: ${missing_options} required Kata options missing from kernel config"
((WARN_COUNT++)) || true
fi
else
fail "FAIL: kernel/config missing (required in QCrows v0.2+)"
((FAIL++)) || true
fi
# ─── 5. Boot params (optional — warn about dangerous params) ───────────────
if [[ -f "${TMPDIR}/boot-params.conf" ]]; then
log "PASS: boot-params.conf present"
((PASS++)) || true
if grep -qE 'module.sig_enforce=0|nokaslr|nopti|nosmap|nosmep' "${TMPDIR}/boot-params.conf" 2>/dev/null; then
warn "WARN: potentially dangerous kernel parameters in boot-params.conf"
((WARN_COUNT++)) || true
fi
fi
# ─── 6. Initrd ─────────────────────────────────────────────────────────────
initrd_found=false
for name in initrd.img initrd.cpio.gz initrd.cpio.lz4 initrd.cpio.xz initrd.cramfs; do
if [[ -f "${TMPDIR}/${name}" ]]; then
initrd_found=true
break
fi
done
if [[ "$initrd_found" == "true" ]]; then
log "PASS: initrd present"
((PASS++)) || true
else
warn "WARN: no initrd found (one of initrd/cramfs is recommended)"
((WARN_COUNT++)) || true
fi
# ─── 7. Hash verification ──────────────────────────────────────────────────
if [[ -f "${TMPDIR}/hashes.sha256" ]]; then
cd "$TMPDIR"
if sha256sum --quiet -c hashes.sha256 2>/dev/null; then
log "PASS: all SHA-256 checksums match"
((PASS++)) || true
else
fail "FAIL: SHA-256 checksum mismatch"
((FAIL++)) || true
fi
cd - >/dev/null
fi
# ─── 8. metadata.toml format check ─────────────────────────────────────────
if [[ -f "${TMPDIR}/metadata.toml" ]]; then
if grep -q "format_version" "${TMPDIR}/metadata.toml"; then
meta_ver=$(grep "format_version" "${TMPDIR}/metadata.toml" | head -1 | grep -oP '"[\d.]+"' | tr -d '"')
log "PASS: metadata format_version = ${meta_ver}"
((PASS++)) || true
else
fail "FAIL: metadata.toml missing format_version"
((FAIL++)) || true
fi
if grep -q "hypervisors" "${TMPDIR}/metadata.toml"; then
log "PASS: hypervisor compatibility declared"
((PASS++)) || true
else
warn "WARN: no hypervisor compatibility in metadata"
((WARN_COUNT++)) || true
fi
fi
# ─── Summary ────────────────────────────────────────────────────────────────
echo ""
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
echo -e " Passed: ${GREEN}${PASS}${NC} Failed: ${RED}${FAIL}${NC} Warnings: ${YELLOW}${WARN_COUNT}${NC}"
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
[[ $FAIL -gt 0 ]] && exit 1
exit 0