diff --git a/build/scripts/build-kernel.sh b/build/scripts/build-kernel.sh index c88bdfc..541ee21 100755 --- a/build/scripts/build-kernel.sh +++ b/build/scripts/build-kernel.sh @@ -96,17 +96,6 @@ echo "==> Enabling required kernel configs..." ./scripts/config --enable CONFIG_MEMCG ./scripts/config --enable CONFIG_CFS_BANDWIDTH -# Security: AppArmor LSM + Audit subsystem -echo "==> Enabling AppArmor + Audit kernel configs..." -./scripts/config --enable CONFIG_AUDIT -./scripts/config --enable CONFIG_AUDITSYSCALL -./scripts/config --enable CONFIG_SECURITY -./scripts/config --enable CONFIG_SECURITYFS -./scripts/config --enable CONFIG_SECURITY_NETWORK -./scripts/config --enable CONFIG_SECURITY_APPARMOR -./scripts/config --set-str CONFIG_LSM "lockdown,yama,apparmor" -./scripts/config --set-str CONFIG_DEFAULT_SECURITY "apparmor" - # --- Strip unnecessary subsystems for smallest footprint --- # This is a headless K8s edge appliance — no sound, GPU, wireless, etc. echo "==> Disabling unnecessary subsystems for minimal footprint..." @@ -156,13 +145,29 @@ echo "==> Disabling unnecessary subsystems for minimal footprint..." # FPGA (not needed) ./scripts/config --disable FPGA -# Resolve dependencies (olddefconfig accepts defaults for new options) +# First pass: resolve base dependencies before adding security configs. +# The stock TC config has "# CONFIG_SECURITY is not set" which causes +# olddefconfig to strip security-related options if applied in a single pass. make olddefconfig -# Verify CONFIG_CGROUP_BPF is set -if grep -q 'CONFIG_CGROUP_BPF=y' .config; then - echo " CONFIG_CGROUP_BPF=y confirmed in .config" -else +# Security: AppArmor LSM + Audit subsystem +# Applied AFTER first olddefconfig to ensure CONFIG_SECURITY dependencies +# (SYSFS, MULTIUSER) are resolved before enabling the security subtree. +echo "==> Enabling AppArmor + Audit kernel configs..." +./scripts/config --enable CONFIG_AUDIT +./scripts/config --enable CONFIG_AUDITSYSCALL +./scripts/config --enable CONFIG_SECURITY +./scripts/config --enable CONFIG_SECURITYFS +./scripts/config --enable CONFIG_SECURITY_NETWORK +./scripts/config --enable CONFIG_SECURITY_APPARMOR +./scripts/config --set-str CONFIG_LSM "lockdown,yama,apparmor" +./scripts/config --set-str CONFIG_DEFAULT_SECURITY "apparmor" + +# Second pass: resolve security config dependencies +make olddefconfig + +# Verify critical configs are set +if ! grep -q 'CONFIG_CGROUP_BPF=y' .config; then echo "ERROR: CONFIG_CGROUP_BPF not set after olddefconfig" grep 'CGROUP_BPF' .config || echo " (CGROUP_BPF not found in .config)" echo "" @@ -170,10 +175,25 @@ else grep -E 'CONFIG_BPF=|CONFIG_BPF_SYSCALL=' .config || echo " BPF not found" exit 1 fi +echo " CONFIG_CGROUP_BPF=y confirmed" -# Show what changed -echo " Config diff from stock:" -diff "$KERNEL_CFG" .config | grep '^[<>]' | head -20 || echo " (no differences beyond CGROUP_BPF)" +if ! grep -q 'CONFIG_SECURITY_APPARMOR=y' .config; then + echo "ERROR: CONFIG_SECURITY_APPARMOR not set after olddefconfig" + echo " Security-related configs:" + grep -E 'CONFIG_SECURITY=|CONFIG_SECURITYFS=|CONFIG_SECURITY_APPARMOR=' .config + exit 1 +fi +echo " CONFIG_SECURITY_APPARMOR=y confirmed" + +if ! grep -q 'CONFIG_AUDIT=y' .config; then + echo "ERROR: CONFIG_AUDIT not set after olddefconfig" + exit 1 +fi +echo " CONFIG_AUDIT=y confirmed" + +# Show what changed (security-related) +echo " Key config values:" +grep -E 'CONFIG_SECURITY=|CONFIG_SECURITY_APPARMOR=|CONFIG_AUDIT=|CONFIG_LSM=|CONFIG_CGROUP_BPF=' .config | sed 's/^/ /' # --- Build kernel + modules --- NPROC=$(nproc 2>/dev/null || echo 4) diff --git a/test/benchmark/bench-boot.sh b/test/benchmark/bench-boot.sh index f6d324b..27b7157 100755 --- a/test/benchmark/bench-boot.sh +++ b/test/benchmark/bench-boot.sh @@ -22,6 +22,8 @@ RUNS=3 SSH_PORT=2222 K8S_PORT=6443 +. "$SCRIPT_DIR/../lib/qemu-helpers.sh" + shift || true while [ $# -gt 0 ]; do case "$1" in @@ -47,6 +49,15 @@ echo "Type: $IMAGE_TYPE" >&2 echo "Runs: $RUNS" >&2 echo "" >&2 +EXTRACT_DIR="" +TEMP_DISK="" + +cleanup() { + [ -n "$TEMP_DISK" ] && rm -f "$TEMP_DISK" + [ -n "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR" +} +trap cleanup EXIT + # Build QEMU command QEMU_CMD=( qemu-system-x86_64 @@ -55,24 +66,31 @@ QEMU_CMD=( -nographic -no-reboot -serial mon:stdio - -net nic,model=virtio + -net "nic,model=virtio" -net "user,hostfwd=tcp::${SSH_PORT}-:22,hostfwd=tcp::${K8S_PORT}-:6443" ) # Add KVM if available -if [ -e /dev/kvm ] && [ -r /dev/kvm ]; then +KVM_FLAG=$(detect_kvm) +if [ -n "$KVM_FLAG" ]; then QEMU_CMD+=(-enable-kvm -cpu host) + echo "KVM: enabled" >&2 else QEMU_CMD+=(-cpu max) + echo "KVM: not available (TCG)" >&2 fi +echo "" >&2 if [ "$IMAGE_TYPE" = "iso" ]; then - QEMU_CMD+=(-cdrom "$IMAGE") + # Extract kernel/initramfs for direct boot (required for -append to work) + EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-bench-extract-XXXXXX)" + extract_kernel_from_iso "$IMAGE" "$EXTRACT_DIR" >&2 + QEMU_CMD+=(-kernel "$VMLINUZ" -initrd "$INITRAMFS") + QEMU_CMD+=(-append "console=ttyS0,115200n8 kubesolo.debug") # Add a temp disk for persistence TEMP_DISK=$(mktemp /tmp/kubesolo-bench-XXXXXX.img) qemu-img create -f qcow2 "$TEMP_DISK" 8G >/dev/null 2>&1 QEMU_CMD+=(-drive "file=$TEMP_DISK,format=qcow2,if=virtio") - trap "rm -f $TEMP_DISK" EXIT else QEMU_CMD+=(-drive "file=$IMAGE,format=raw,if=virtio") fi @@ -111,7 +129,7 @@ for run in $(seq 1 "$RUNS"); do echo "KERNEL_MS=$ELAPSED_MS" >> "$LOG.times" fi ;; - *"kubesolo-init"*"all stages complete"*|*"init complete"*) + *"KubeSolo is running"*|*"kubesolo-init"*"OK"*) if [ -z "$INIT_DONE" ]; then INIT_DONE="$ELAPSED_MS" echo " Init complete: ${ELAPSED_MS}ms" >&2 diff --git a/test/integration/test-deploy-workload.sh b/test/integration/test-deploy-workload.sh index fa70023..2872c94 100755 --- a/test/integration/test-deploy-workload.sh +++ b/test/integration/test-deploy-workload.sh @@ -5,19 +5,26 @@ set -euo pipefail ISO="${1:?Usage: $0 }" -TIMEOUT_BOOT=120 -TIMEOUT_K8S=300 -TIMEOUT_POD=120 +TIMEOUT_K8S=${TIMEOUT_K8S:-300} +TIMEOUT_POD=${TIMEOUT_POD:-120} API_PORT=6443 SERIAL_LOG=$(mktemp /tmp/kubesolo-workload-XXXXXX.log) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +. "$SCRIPT_DIR/../lib/qemu-helpers.sh" + DATA_DISK=$(mktemp /tmp/kubesolo-data-XXXXXX.img) dd if=/dev/zero of="$DATA_DISK" bs=1M count=1024 2>/dev/null mkfs.ext4 -q -L KSOLODATA "$DATA_DISK" 2>/dev/null +QEMU_PID="" +EXTRACT_DIR="" + cleanup() { - kill "$QEMU_PID" 2>/dev/null || true + [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true rm -f "$DATA_DISK" "$SERIAL_LOG" + [ -n "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR" } trap cleanup EXIT @@ -25,14 +32,22 @@ KUBECTL="kubectl --server=https://localhost:${API_PORT} --insecure-skip-tls-veri echo "==> Workload deployment test: $ISO" +# Extract kernel from ISO +EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" +extract_kernel_from_iso "$ISO" "$EXTRACT_DIR" + +KVM_FLAG=$(detect_kvm) + # Launch QEMU +# shellcheck disable=SC2086 qemu-system-x86_64 \ -m 2048 -smp 2 \ -nographic \ - -cdrom "$ISO" \ - -boot d \ + $KVM_FLAG \ + -kernel "$VMLINUZ" \ + -initrd "$INITRAMFS" \ -drive "file=$DATA_DISK,format=raw,if=virtio" \ - -net nic,model=virtio \ + -net "nic,model=virtio" \ -net "user,hostfwd=tcp::${API_PORT}-:6443" \ -serial "file:$SERIAL_LOG" \ -append "console=ttyS0,115200n8 kubesolo.data=/dev/vda" \ @@ -71,6 +86,7 @@ $KUBECTL run test-nginx --image=nginx:alpine --restart=Never 2>/dev/null || { echo " Waiting for pod to reach Running..." ELAPSED=0 POD_RUNNING=0 +STATUS="" while [ "$ELAPSED" -lt "$TIMEOUT_POD" ]; do STATUS=$($KUBECTL get pod test-nginx -o jsonpath='{.status.phase}' 2>/dev/null || echo "") if [ "$STATUS" = "Running" ]; then diff --git a/test/integration/test-k8s-ready.sh b/test/integration/test-k8s-ready.sh index 28374c2..c3539a4 100755 --- a/test/integration/test-k8s-ready.sh +++ b/test/integration/test-k8s-ready.sh @@ -5,31 +5,47 @@ set -euo pipefail ISO="${1:?Usage: $0 }" -TIMEOUT_BOOT=120 -TIMEOUT_K8S=300 +TIMEOUT_K8S=${TIMEOUT_K8S:-300} API_PORT=6443 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +. "$SCRIPT_DIR/../lib/qemu-helpers.sh" + DATA_DISK=$(mktemp /tmp/kubesolo-data-XXXXXX.img) dd if=/dev/zero of="$DATA_DISK" bs=1M count=1024 2>/dev/null mkfs.ext4 -q -L KSOLODATA "$DATA_DISK" 2>/dev/null +QEMU_PID="" +EXTRACT_DIR="" + cleanup() { - kill "$QEMU_PID" 2>/dev/null || true + [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true rm -f "$DATA_DISK" + [ -n "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR" } trap cleanup EXIT echo "==> K8s readiness test: $ISO" +# Extract kernel from ISO +EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" +extract_kernel_from_iso "$ISO" "$EXTRACT_DIR" + +KVM_FLAG=$(detect_kvm) +[ -n "$KVM_FLAG" ] && echo " KVM acceleration: enabled" + # Launch QEMU with API port forwarded +# shellcheck disable=SC2086 qemu-system-x86_64 \ -m 2048 -smp 2 \ -nographic \ - -cdrom "$ISO" \ - -boot d \ + $KVM_FLAG \ + -kernel "$VMLINUZ" \ + -initrd "$INITRAMFS" \ -drive "file=$DATA_DISK,format=raw,if=virtio" \ - -net nic,model=virtio \ - -net user,hostfwd=tcp::${API_PORT}-:6443 \ + -net "nic,model=virtio" \ + -net "user,hostfwd=tcp::${API_PORT}-:6443" \ -append "console=ttyS0,115200n8 kubesolo.data=/dev/vda" \ & QEMU_PID=$! diff --git a/test/integration/test-local-storage.sh b/test/integration/test-local-storage.sh index c4d6303..9229ff1 100755 --- a/test/integration/test-local-storage.sh +++ b/test/integration/test-local-storage.sh @@ -5,37 +5,52 @@ set -euo pipefail ISO="${1:?Usage: $0 }" -TIMEOUT_K8S=300 -TIMEOUT_PVC=120 +TIMEOUT_K8S=${TIMEOUT_K8S:-300} +TIMEOUT_PVC=${TIMEOUT_PVC:-120} API_PORT=6443 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +. "$SCRIPT_DIR/../lib/qemu-helpers.sh" + DATA_DISK=$(mktemp /tmp/kubesolo-data-XXXXXX.img) dd if=/dev/zero of="$DATA_DISK" bs=1M count=2048 2>/dev/null mkfs.ext4 -q -L KSOLODATA "$DATA_DISK" 2>/dev/null SERIAL_LOG=$(mktemp /tmp/kubesolo-storage-XXXXXX.log) +QEMU_PID="" +EXTRACT_DIR="" +KUBECTL="kubectl --server=https://localhost:${API_PORT} --insecure-skip-tls-verify" + cleanup() { # Clean up K8s resources $KUBECTL delete pod test-storage --grace-period=0 --force 2>/dev/null || true $KUBECTL delete pvc test-pvc 2>/dev/null || true - kill "$QEMU_PID" 2>/dev/null || true + [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true rm -f "$DATA_DISK" "$SERIAL_LOG" + [ -n "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR" } trap cleanup EXIT -KUBECTL="kubectl --server=https://localhost:${API_PORT} --insecure-skip-tls-verify" - echo "==> Local storage test: $ISO" +# Extract kernel from ISO +EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" +extract_kernel_from_iso "$ISO" "$EXTRACT_DIR" + +KVM_FLAG=$(detect_kvm) + # Launch QEMU +# shellcheck disable=SC2086 qemu-system-x86_64 \ -m 2048 -smp 2 \ -nographic \ - -cdrom "$ISO" \ - -boot d \ + $KVM_FLAG \ + -kernel "$VMLINUZ" \ + -initrd "$INITRAMFS" \ -drive "file=$DATA_DISK,format=raw,if=virtio" \ - -net nic,model=virtio \ + -net "nic,model=virtio" \ -net "user,hostfwd=tcp::${API_PORT}-:6443" \ -serial "file:$SERIAL_LOG" \ -append "console=ttyS0,115200n8 kubesolo.data=/dev/vda" \ @@ -98,6 +113,7 @@ YAML # Wait for pod Running echo " Waiting for storage pod..." ELAPSED=0 +STATUS="" while [ "$ELAPSED" -lt "$TIMEOUT_PVC" ]; do STATUS=$($KUBECTL get pod test-storage -o jsonpath='{.status.phase}' 2>/dev/null || echo "") if [ "$STATUS" = "Running" ]; then diff --git a/test/integration/test-network-policy.sh b/test/integration/test-network-policy.sh index 23c264c..0039ab3 100755 --- a/test/integration/test-network-policy.sh +++ b/test/integration/test-network-policy.sh @@ -6,35 +6,50 @@ set -euo pipefail ISO="${1:?Usage: $0 }" -TIMEOUT_K8S=300 -TIMEOUT_POD=120 +TIMEOUT_K8S=${TIMEOUT_K8S:-300} +TIMEOUT_POD=${TIMEOUT_POD:-120} API_PORT=6443 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +. "$SCRIPT_DIR/../lib/qemu-helpers.sh" + DATA_DISK=$(mktemp /tmp/kubesolo-data-XXXXXX.img) dd if=/dev/zero of="$DATA_DISK" bs=1M count=1024 2>/dev/null mkfs.ext4 -q -L KSOLODATA "$DATA_DISK" 2>/dev/null SERIAL_LOG=$(mktemp /tmp/kubesolo-netpol-XXXXXX.log) +QEMU_PID="" +EXTRACT_DIR="" +KUBECTL="kubectl --server=https://localhost:${API_PORT} --insecure-skip-tls-verify" + cleanup() { $KUBECTL delete namespace netpol-test 2>/dev/null || true - kill "$QEMU_PID" 2>/dev/null || true + [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true rm -f "$DATA_DISK" "$SERIAL_LOG" + [ -n "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR" } trap cleanup EXIT -KUBECTL="kubectl --server=https://localhost:${API_PORT} --insecure-skip-tls-verify" - echo "==> Network policy test: $ISO" +# Extract kernel from ISO +EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" +extract_kernel_from_iso "$ISO" "$EXTRACT_DIR" + +KVM_FLAG=$(detect_kvm) + # Launch QEMU +# shellcheck disable=SC2086 qemu-system-x86_64 \ -m 2048 -smp 2 \ -nographic \ - -cdrom "$ISO" \ - -boot d \ + $KVM_FLAG \ + -kernel "$VMLINUZ" \ + -initrd "$INITRAMFS" \ -drive "file=$DATA_DISK,format=raw,if=virtio" \ - -net nic,model=virtio \ + -net "nic,model=virtio" \ -net "user,hostfwd=tcp::${API_PORT}-:6443" \ -serial "file:$SERIAL_LOG" \ -append "console=ttyS0,115200n8 kubesolo.data=/dev/vda" \ @@ -81,6 +96,7 @@ YAML # Wait for pod ELAPSED=0 +STATUS="" while [ "$ELAPSED" -lt "$TIMEOUT_POD" ]; do STATUS=$($KUBECTL get pod -n netpol-test web -o jsonpath='{.status.phase}' 2>/dev/null || echo "") [ "$STATUS" = "Running" ] && break diff --git a/test/integration/test-security-hardening.sh b/test/integration/test-security-hardening.sh index 9df7a9d..4596413 100755 --- a/test/integration/test-security-hardening.sh +++ b/test/integration/test-security-hardening.sh @@ -12,9 +12,13 @@ set -euo pipefail ISO="${1:?Usage: $0 }" -TIMEOUT_BOOT=180 # seconds to wait for boot +TIMEOUT_BOOT=${TIMEOUT_BOOT:-180} # seconds to wait for boot SERIAL_LOG=$(mktemp /tmp/kubesolo-security-test-XXXXXX.log) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +. "$SCRIPT_DIR/../lib/qemu-helpers.sh" + # Temp data disk DATA_DISK=$(mktemp /tmp/kubesolo-security-data-XXXXXX.img) dd if=/dev/zero of="$DATA_DISK" bs=1M count=1024 2>/dev/null @@ -22,6 +26,7 @@ mkfs.ext4 -q -L KSOLODATA "$DATA_DISK" 2>/dev/null QEMU_PID="" EXTRACT_DIR="" + cleanup() { [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true rm -f "$DATA_DISK" "$SERIAL_LOG" @@ -33,68 +38,12 @@ echo "==> Security Hardening Test: $ISO" echo " Timeout: ${TIMEOUT_BOOT}s" echo " Serial log: $SERIAL_LOG" +# Extract kernel from ISO +EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" +extract_kernel_from_iso "$ISO" "$EXTRACT_DIR" + # Detect KVM -KVM_FLAG="" -[ -w /dev/kvm ] 2>/dev/null && KVM_FLAG="-enable-kvm" - -# Extract kernel + initramfs from ISO (direct kernel boot required for -append) -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}" -EXTRACT_DIR="" - -VMLINUZ="" -INITRAMFS="" - -if [ -f "$ROOTFS_DIR/vmlinuz" ] && [ -f "$ROOTFS_DIR/kubesolo-os.gz" ]; then - VMLINUZ="$ROOTFS_DIR/vmlinuz" - INITRAMFS="$ROOTFS_DIR/kubesolo-os.gz" - echo " Using kernel/initramfs from build directory" -else - EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" - EXTRACTED=0 - - echo " Extracting kernel/initramfs from ISO..." - - if [ $EXTRACTED -eq 0 ] && command -v bsdtar >/dev/null 2>&1; then - if bsdtar -xf "$ISO" -C "$EXTRACT_DIR" boot/vmlinuz boot/kubesolo-os.gz 2>/dev/null; then - EXTRACTED=1 - fi - fi - - if [ $EXTRACTED -eq 0 ] && command -v isoinfo >/dev/null 2>&1; then - mkdir -p "$EXTRACT_DIR/boot" - isoinfo -i "$ISO" -x "/BOOT/VMLINUZ;1" > "$EXTRACT_DIR/boot/vmlinuz" 2>/dev/null || true - isoinfo -i "$ISO" -x "/BOOT/KUBESOLO-OS.GZ;1" > "$EXTRACT_DIR/boot/kubesolo-os.gz" 2>/dev/null || true - if [ -s "$EXTRACT_DIR/boot/vmlinuz" ] && [ -s "$EXTRACT_DIR/boot/kubesolo-os.gz" ]; then - EXTRACTED=1 - else - rm -f "$EXTRACT_DIR/boot/vmlinuz" "$EXTRACT_DIR/boot/kubesolo-os.gz" - fi - fi - - if [ $EXTRACTED -eq 0 ] && [ "$(uname)" = "Linux" ]; then - ISO_MOUNT="$EXTRACT_DIR/mnt" - mkdir -p "$ISO_MOUNT" - if mount -o loop,ro "$ISO" "$ISO_MOUNT" 2>/dev/null; then - mkdir -p "$EXTRACT_DIR/boot" - cp "$ISO_MOUNT/boot/vmlinuz" "$EXTRACT_DIR/boot/" 2>/dev/null || true - cp "$ISO_MOUNT/boot/kubesolo-os.gz" "$EXTRACT_DIR/boot/" 2>/dev/null || true - umount "$ISO_MOUNT" 2>/dev/null || true - if [ -f "$EXTRACT_DIR/boot/vmlinuz" ] && [ -f "$EXTRACT_DIR/boot/kubesolo-os.gz" ]; then - EXTRACTED=1 - fi - fi - fi - - if [ $EXTRACTED -eq 0 ]; then - echo "ERROR: Failed to extract kernel/initramfs from ISO." - exit 1 - fi - - VMLINUZ="$EXTRACT_DIR/boot/vmlinuz" - INITRAMFS="$EXTRACT_DIR/boot/kubesolo-os.gz" -fi +KVM_FLAG=$(detect_kvm) # Launch QEMU in background with direct kernel boot # shellcheck disable=SC2086 @@ -171,7 +120,7 @@ fi echo "" echo "--- Test 2: AppArmor ---" -if grep -q "AppArmor profiles loaded" "$SERIAL_LOG" 2>/dev/null; then +if grep -q "AppArmor.*loaded.*profiles" "$SERIAL_LOG" 2>/dev/null; then check_pass "AppArmor profiles loaded" elif grep -q "AppArmor not available" "$SERIAL_LOG" 2>/dev/null; then check_skip "AppArmor not in kernel (expected before kernel rebuild)" diff --git a/test/lib/qemu-helpers.sh b/test/lib/qemu-helpers.sh new file mode 100644 index 0000000..a6ed070 --- /dev/null +++ b/test/lib/qemu-helpers.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# qemu-helpers.sh — Shared functions for QEMU-based tests +# Source this file from test scripts: . "$(dirname "$0")/../lib/qemu-helpers.sh" + +# extract_kernel_from_iso +# Sets VMLINUZ and INITRAMFS variables on success +# Falls back to build/rootfs-work/ if available +extract_kernel_from_iso() { + local iso="$1" + local extract_dir="$2" + local project_root="${PROJECT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" + local rootfs_dir="${ROOTFS_DIR:-$project_root/build/rootfs-work}" + + VMLINUZ="" + INITRAMFS="" + + # Prefer build artifacts (no extraction needed) + if [ -f "$rootfs_dir/vmlinuz" ] && [ -f "$rootfs_dir/kubesolo-os.gz" ]; then + VMLINUZ="$rootfs_dir/vmlinuz" + INITRAMFS="$rootfs_dir/kubesolo-os.gz" + echo " Using kernel/initramfs from build directory" + return 0 + fi + + local extracted=0 + + echo " Extracting kernel/initramfs from ISO..." + + # Method 1: bsdtar (ships with macOS, libarchive-tools on Linux) + if [ $extracted -eq 0 ] && command -v bsdtar >/dev/null 2>&1; then + if bsdtar -xf "$iso" -C "$extract_dir" boot/vmlinuz boot/kubesolo-os.gz 2>/dev/null; then + echo " Extracted via bsdtar" + extracted=1 + fi + fi + + # Method 2: isoinfo (genisoimage/cdrtools) + if [ $extracted -eq 0 ] && command -v isoinfo >/dev/null 2>&1; then + mkdir -p "$extract_dir/boot" + isoinfo -i "$iso" -x "/BOOT/VMLINUZ;1" > "$extract_dir/boot/vmlinuz" 2>/dev/null || true + isoinfo -i "$iso" -x "/BOOT/KUBESOLO-OS.GZ;1" > "$extract_dir/boot/kubesolo-os.gz" 2>/dev/null || true + if [ -s "$extract_dir/boot/vmlinuz" ] && [ -s "$extract_dir/boot/kubesolo-os.gz" ]; then + echo " Extracted via isoinfo" + extracted=1 + else + rm -f "$extract_dir/boot/vmlinuz" "$extract_dir/boot/kubesolo-os.gz" + fi + fi + + # Method 3: loop mount (Linux only, may need root) + if [ $extracted -eq 0 ] && [ "$(uname)" = "Linux" ]; then + local iso_mount="$extract_dir/mnt" + mkdir -p "$iso_mount" + if mount -o loop,ro "$iso" "$iso_mount" 2>/dev/null; then + mkdir -p "$extract_dir/boot" + cp "$iso_mount/boot/vmlinuz" "$extract_dir/boot/" 2>/dev/null || true + cp "$iso_mount/boot/kubesolo-os.gz" "$extract_dir/boot/" 2>/dev/null || true + umount "$iso_mount" 2>/dev/null || true + if [ -f "$extract_dir/boot/vmlinuz" ] && [ -f "$extract_dir/boot/kubesolo-os.gz" ]; then + echo " Extracted via loop mount" + extracted=1 + fi + fi + fi + + if [ $extracted -eq 0 ]; then + echo "ERROR: Failed to extract kernel/initramfs from ISO." + echo " Install one of: bsdtar (libarchive-tools), isoinfo (genisoimage), or run as root for loop mount." + return 1 + fi + + VMLINUZ="$extract_dir/boot/vmlinuz" + INITRAMFS="$extract_dir/boot/kubesolo-os.gz" + return 0 +} + +# detect_kvm — prints "-enable-kvm" if KVM available, empty string otherwise +detect_kvm() { + if [ -w /dev/kvm ] 2>/dev/null; then + echo "-enable-kvm" + fi +} diff --git a/test/qemu/test-boot.sh b/test/qemu/test-boot.sh index 2caba2f..de260f2 100755 --- a/test/qemu/test-boot.sh +++ b/test/qemu/test-boot.sh @@ -8,13 +8,17 @@ ISO="${1:?Usage: $0 }" TIMEOUT_BOOT=${TIMEOUT_BOOT:-120} # seconds to wait for boot success marker SERIAL_LOG=$(mktemp /tmp/kubesolo-boot-test-XXXXXX.log) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +. "$SCRIPT_DIR/../lib/qemu-helpers.sh" + # Temp data disk DATA_DISK=$(mktemp /tmp/kubesolo-data-XXXXXX.img) dd if=/dev/zero of="$DATA_DISK" bs=1M count=512 2>/dev/null mkfs.ext4 -q -L KSOLODATA "$DATA_DISK" 2>/dev/null -EXTRACT_DIR="" QEMU_PID="" +EXTRACT_DIR="" cleanup() { [ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true @@ -27,78 +31,15 @@ echo "==> Boot test: $ISO" echo " Timeout: ${TIMEOUT_BOOT}s" echo " Serial log: $SERIAL_LOG" -# Extract kernel + initramfs from ISO (direct kernel boot required for -append) -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -ROOTFS_DIR="${ROOTFS_DIR:-$PROJECT_ROOT/build/rootfs-work}" +# Extract kernel from ISO +EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" +extract_kernel_from_iso "$ISO" "$EXTRACT_DIR" -VMLINUZ="" -INITRAMFS="" - -if [ -f "$ROOTFS_DIR/vmlinuz" ] && [ -f "$ROOTFS_DIR/kubesolo-os.gz" ]; then - VMLINUZ="$ROOTFS_DIR/vmlinuz" - INITRAMFS="$ROOTFS_DIR/kubesolo-os.gz" - echo " Using kernel/initramfs from build directory" -else - EXTRACT_DIR="$(mktemp -d /tmp/kubesolo-extract-XXXXXX)" - EXTRACTED=0 - - echo " Extracting kernel/initramfs from ISO..." - - # Method 1: bsdtar - if [ $EXTRACTED -eq 0 ] && command -v bsdtar >/dev/null 2>&1; then - if bsdtar -xf "$ISO" -C "$EXTRACT_DIR" boot/vmlinuz boot/kubesolo-os.gz 2>/dev/null; then - echo " Extracted via bsdtar" - EXTRACTED=1 - fi - fi - - # Method 2: isoinfo - if [ $EXTRACTED -eq 0 ] && command -v isoinfo >/dev/null 2>&1; then - mkdir -p "$EXTRACT_DIR/boot" - isoinfo -i "$ISO" -x "/BOOT/VMLINUZ;1" > "$EXTRACT_DIR/boot/vmlinuz" 2>/dev/null || true - isoinfo -i "$ISO" -x "/BOOT/KUBESOLO-OS.GZ;1" > "$EXTRACT_DIR/boot/kubesolo-os.gz" 2>/dev/null || true - if [ -s "$EXTRACT_DIR/boot/vmlinuz" ] && [ -s "$EXTRACT_DIR/boot/kubesolo-os.gz" ]; then - echo " Extracted via isoinfo" - EXTRACTED=1 - else - rm -f "$EXTRACT_DIR/boot/vmlinuz" "$EXTRACT_DIR/boot/kubesolo-os.gz" - fi - fi - - # Method 3: loop mount (Linux only) - if [ $EXTRACTED -eq 0 ] && [ "$(uname)" = "Linux" ]; then - ISO_MOUNT="$EXTRACT_DIR/mnt" - mkdir -p "$ISO_MOUNT" - if mount -o loop,ro "$ISO" "$ISO_MOUNT" 2>/dev/null; then - mkdir -p "$EXTRACT_DIR/boot" - cp "$ISO_MOUNT/boot/vmlinuz" "$EXTRACT_DIR/boot/" 2>/dev/null || true - cp "$ISO_MOUNT/boot/kubesolo-os.gz" "$EXTRACT_DIR/boot/" 2>/dev/null || true - umount "$ISO_MOUNT" 2>/dev/null || true - if [ -f "$EXTRACT_DIR/boot/vmlinuz" ] && [ -f "$EXTRACT_DIR/boot/kubesolo-os.gz" ]; then - echo " Extracted via loop mount" - EXTRACTED=1 - fi - fi - fi - - if [ $EXTRACTED -eq 0 ]; then - echo "ERROR: Failed to extract kernel/initramfs from ISO." - exit 1 - fi - - VMLINUZ="$EXTRACT_DIR/boot/vmlinuz" - INITRAMFS="$EXTRACT_DIR/boot/kubesolo-os.gz" -fi - -# Detect KVM -KVM_FLAG="" -if [ -w /dev/kvm ] 2>/dev/null; then - KVM_FLAG="-enable-kvm" - echo " KVM acceleration: enabled" -fi +KVM_FLAG=$(detect_kvm) +[ -n "$KVM_FLAG" ] && echo " KVM acceleration: enabled" # Launch QEMU in background with direct kernel boot +# shellcheck disable=SC2086 qemu-system-x86_64 \ -m 2048 -smp 2 \ -nographic \