diff --git a/AGENTS.md b/AGENTS.md index 555f9b4..dc78993 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,6 +119,30 @@ **Compliance**: NIST SP 800-63B, CIS Benchmarks for Debian +### Host System FDE - MANDATORY +**Requirement**: The host system used to build or test the ISO MUST have FDE enabled + +**Why This Matters**: +- Building a secure OS on an unencrypted host creates supply chain risk +- An unencrypted test host exposes the secure OS to attacks +- Defense in depth requires protection at every layer + +**Enforcement**: +- `./run.sh iso` will FAIL if host FDE not detected +- `./run.sh test:iso` commands will FAIL if host FDE not detected +- This check CANNOT be bypassed + +**Detection Methods**: +- Checks for LUKS devices via `lsblk` +- Checks `/etc/crypttab` for encrypted partitions +- Checks if root filesystem is on dm-crypt device +- Checks `/sys/block/dm-*` for LUKS devices + +**Implementation**: +- `run.sh` - `check_host_fde()` function + +**Compliance**: Supply chain security best practice + --- ## 📁 PROJECT STRUCTURE diff --git a/docs/PRD.md b/docs/PRD.md index 175d273..a3c4c9d 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -291,6 +291,38 @@ The system MUST implement full disk encryption using LUKS (Linux Unified Key Set - MD5 checksum file - Build report (optional) +### FR-011: Host System Full Disk Encryption (MANDATORY) + +**Priority:** P0 (Critical) +**Status:** Required + +**Description:** +The host system used to build or test KNEL-Football ISO images MUST have full disk encryption enabled. Building a secure operating system on an unencrypted host defeats the entire security model and creates a supply chain risk. + +**Requirements:** +1. **LUKS Encryption Required** - Host must use LUKS for disk encryption +2. **Build Enforcement** - `./run.sh iso` command MUST fail if host FDE not detected +3. **VM Test Enforcement** - `./run.sh test:iso` commands MUST fail if host FDE not detected +4. **No Bypass** - This check cannot be disabled or bypassed +5. **Clear Error Message** - Users receive clear guidance on how to enable FDE + +**Detection Methods:** +- Check for LUKS devices via `lsblk -o TYPE,FSTYPE` +- Check `/etc/crypttab` for configured encrypted partitions +- Check if root filesystem is on a dm-crypt device +- Check for dm-crypt devices in `/sys/block/dm-*` + +**Rationale:** +- An unencrypted build host could be compromised, affecting all built ISOs +- An unencrypted test host exposes the secure OS to attacks during testing +- Supply chain security requires securing the entire build pipeline +- Defense in depth requires protection at every layer + +**User Guidance (if FDE not detected):** +1. Backup all data +2. Reinstall with "Guided - use entire disk and set up encrypted LVM" +3. Or use tools like encrypt-existing-debian for in-place encryption + --- ## Non-Functional Requirements diff --git a/run.sh b/run.sh index 5f4fa6f..79e55b1 100755 --- a/run.sh +++ b/run.sh @@ -39,6 +39,86 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # Create output and build directories if they don't exist mkdir -p "${OUTPUT_DIR}" "${BUILD_DIR}" +# ============================================================================ +# HOST FDE CHECK (MANDATORY) +# ============================================================================ + +# Check if host system has full disk encryption enabled +# This is MANDATORY - building or testing a secure OS on an unencrypted host +# defeats the entire security model +check_host_fde() { + log_info "Checking host system for Full Disk Encryption..." + + local has_luks=false + local encrypted_root=false + + # Method 1: Check for LUKS devices via lsblk + if lsblk -o TYPE,FSTYPE 2>/dev/null | grep -q "crypt"; then + has_luks=true + log_info "Found LUKS encrypted partitions" + fi + + # Method 2: Check if root filesystem is on a dm-crypt device + if [[ -e /dev/mapper/root ]] || [[ -e /dev/mapper/rootfs ]]; then + encrypted_root=true + log_info "Root filesystem appears to be on encrypted device" + fi + + # Method 3: Check /etc/crypttab for configured encrypted partitions + if [[ -f /etc/crypttab ]] && grep -qE "^[^#]" /etc/crypttab 2>/dev/null; then + has_luks=true + log_info "Found encrypted partitions in /etc/crypttab" + fi + + # Method 4: Check for dm-crypt devices in /sys/block + if ls /sys/block/dm-* 2>/dev/null | head -1 | grep -q .; then + for dm_dev in /sys/block/dm-*; do + if [[ -f "${dm_dev}/dm/name" ]]; then + local dm_name + dm_name=$(cat "${dm_dev}/dm/name" 2>/dev/null) + # Check if this is a LUKS device + if [[ -f "${dm_dev}/dm/uuid" ]] && grep -qi "CRYPT-LUKS" "${dm_dev}/dm/uuid" 2>/dev/null; then + has_luks=true + log_info "Found LUKS device: ${dm_name}" + fi + fi + done + fi + + # Method 5: Check root mount point for encryption + local root_device + root_device=$(findmnt -n -o SOURCE / 2>/dev/null || echo "") + if [[ "$root_device" == /dev/mapper/* ]] || [[ "$root_device" == *"crypt"* ]]; then + encrypted_root=true + log_info "Root filesystem is on encrypted device: $root_device" + fi + + # Require at least one indicator of FDE + if [[ "$has_luks" == "true" || "$encrypted_root" == "true" ]]; then + log_info "Host FDE check PASSED" + return 0 + fi + + # FDE not detected - this is a FATAL error + log_error "============================================================" + log_error "SECURITY REQUIREMENT VIOLATION" + log_error "============================================================" + log_error "Host system does NOT have Full Disk Encryption enabled." + log_error "" + log_error "Building or testing KNEL-Football Secure OS requires the" + log_error "host system to be encrypted with LUKS. An unencrypted host" + log_error "defeats the entire security model." + log_error "" + log_error "To enable FDE on Debian/Ubuntu:" + log_error " 1. Backup all data" + log_error " 2. Reinstall with 'Guided - use entire disk and set up encrypted LVM'" + log_error " 3. Or use: https://github.com/The Firefoxlyer/encrypt-existing-debian" + log_error "" + log_error "This check is MANDATORY and cannot be bypassed." + log_error "============================================================" + return 1 +} + # ============================================================================ # VM TESTING FUNCTIONS (merged from test-iso.sh) # ============================================================================ @@ -485,6 +565,7 @@ main() { bash ;; iso) + check_host_fde || exit 1 echo "Building KNEL-Football secure ISO..." echo "ALL operations run inside Docker container" echo "Timezone: America/Chicago" @@ -551,6 +632,7 @@ fi monitor_build "${2:-180}" ;; test:iso) + check_host_fde || exit 1 shift # Remove 'test:iso' from args local subcmd="${1:-help}" case "$subcmd" in diff --git a/tests/simple_test.bats b/tests/simple_test.bats index 80c8fd7..c758d49 100644 --- a/tests/simple_test.bats +++ b/tests/simple_test.bats @@ -1,10 +1,157 @@ #!/usr/bin/env bats -# Simple test to validate bats setup +# KNEL-Football Basic Tests - BATS Framework Validation +# Reference: PRD.md FR-001 through FR-010 +# Copyright © 2026 Known Element Enterprises LLC +# License: GNU Affero General Public License v3.0 only -@test "bats is working" { - true +# ============================================================================= +# BATS Framework Validation Tests +# ============================================================================= + +@test "bats framework is working" { + # Verify bats can execute tests + run echo "bats works" + [ "$status" -eq 0 ] + [ "$output" = "bats works" ] } -@test "basic assertion works" { +@test "basic arithmetic assertions work" { + # Verify basic test assertions [ 1 -eq 1 ] + [ 2 -gt 1 ] + [ 0 -lt 1 ] +} + +@test "string comparison assertions work" { + # Verify string comparisons + [ "hello" = "hello" ] + [ "hello" != "world" ] +} + +@test "file existence assertions work" { + # Verify file test operators + [ -f "run.sh" ] + [ -d "config" ] + [ -d "tests" ] +} + +@test "run command and check status works" { + # Verify run command captures exit status + run true + [ "$status" -eq 0 ] +} + +@test "run command captures output works" { + # Verify run command captures stdout + run echo "test output" + [ "$status" -eq 0 ] + [ "$output" = "test output" ] +} + +@test "run command captures stderr works" { + # Verify run command captures stderr + run bash -c 'echo "error message" >&2' + [ "$status" -eq 0 ] + [ "$output" = "error message" ] +} + +@test "run command captures failure status works" { + # Verify run command captures non-zero exit + run false + [ "$status" -eq 1 ] +} + +# ============================================================================= +# Project Structure Validation Tests +# ============================================================================= + +@test "project root directory exists" { + [ -d "/workspace" ] +} + +@test "essential directories exist" { + [ -d "/workspace/config" ] + [ -d "/workspace/src" ] + [ -d "/workspace/tests" ] + [ -d "/workspace/docs" ] +} + +@test "essential files exist" { + [ -f "/workspace/run.sh" ] + [ -f "/workspace/Dockerfile" ] + [ -f "/workspace/AGENTS.md" ] + [ -f "/workspace/README.md" ] + [ -f "/workspace/docs/PRD.md" ] +} + +@test "run.sh is executable" { + [ -x "/workspace/run.sh" ] +} + +@test "config directory structure is correct" { + [ -d "/workspace/config/hooks" ] + [ -d "/workspace/config/hooks/live" ] + [ -d "/workspace/config/hooks/installed" ] + [ -d "/workspace/config/package-lists" ] +} + +@test "test directory structure is correct" { + [ -d "/workspace/tests/unit" ] + [ -d "/workspace/tests/integration" ] + [ -d "/workspace/tests/security" ] + [ -d "/workspace/tests/system" ] + [ -d "/workspace/tests/test_helper" ] +} + +# ============================================================================= +# Shell Script Syntax Validation +# ============================================================================= + +@test "run.sh has valid bash syntax" { + run bash -n /workspace/run.sh + [ "$status" -eq 0 ] +} + +@test "security-hardening.sh has valid bash syntax" { + [ -f "/workspace/src/security-hardening.sh" ] + run bash -n /workspace/src/security-hardening.sh + [ "$status" -eq 0 ] +} + +@test "firewall-setup.sh has valid bash syntax" { + [ -f "/workspace/src/firewall-setup.sh" ] + run bash -n /workspace/src/firewall-setup.sh + [ "$status" -eq 0 ] +} + +@test "all hook scripts have valid bash syntax" { + for script in /workspace/config/hooks/live/*.sh; do + [ -f "$script" ] + run bash -n "$script" + [ "$status" -eq 0 ] + done + for script in /workspace/config/hooks/installed/*.sh; do + [ -f "$script" ] + run bash -n "$script" + [ "$status" -eq 0 ] + done +} + +# ============================================================================= +# Configuration File Validation +# ============================================================================= + +@test "preseed.cfg exists and is readable" { + [ -f "/workspace/config/preseed.cfg" ] + [ -r "/workspace/config/preseed.cfg" ] +} + +@test "package list exists and is readable" { + [ -f "/workspace/config/package-lists/knel-football.list.chroot" ] + [ -r "/workspace/config/package-lists/knel-football.list.chroot" ] +} + +@test "Dockerfile exists and is readable" { + [ -f "/workspace/Dockerfile" ] + [ -r "/workspace/Dockerfile" ] } diff --git a/tests/unit/run_comprehensive_test.bats b/tests/unit/run_comprehensive_test.bats index e87213c..b5ab15e 100644 --- a/tests/unit/run_comprehensive_test.bats +++ b/tests/unit/run_comprehensive_test.bats @@ -1,6 +1,328 @@ #!/usr/bin/env bats -# Minimal unit test +# KNEL-Football Unit Tests - run.sh Main Entry Point +# Reference: PRD.md FR-010 (ISO Build Process) +# Copyright © 2026 Known Element Enterprises LLC +# License: GNU Affero General Public License v3.0 only -@test "test file is working" { - true +# ============================================================================= +# File Existence and Basic Properties +# ============================================================================= + +@test "run.sh exists" { + [ -f "/workspace/run.sh" ] +} + +@test "run.sh is executable" { + [ -x "/workspace/run.sh" ] +} + +@test "run.sh is a valid bash script" { + run bash -n /workspace/run.sh + [ "$status" -eq 0 ] +} + +@test "run.sh uses strict mode" { + grep -q "set -euo pipefail" /workspace/run.sh +} + +# ============================================================================= +# Script Structure and Configuration +# ============================================================================= + +@test "run.sh defines SCRIPT_DIR variable" { + grep -q "SCRIPT_DIR=" /workspace/run.sh +} + +@test "run.sh defines DOCKER_IMAGE variable" { + grep -q "DOCKER_IMAGE=" /workspace/run.sh +} + +@test "run.sh defines OUTPUT_DIR variable" { + grep -q "OUTPUT_DIR=" /workspace/run.sh +} + +@test "run.sh defines BUILD_DIR variable" { + grep -q "BUILD_DIR=" /workspace/run.sh +} + +@test "run.sh defines BUILD_LOG variable" { + grep -q "BUILD_LOG=" /workspace/run.sh +} + +# ============================================================================= +# Logging Functions +# ============================================================================= + +@test "run.sh defines log_info function" { + grep -q "log_info()" /workspace/run.sh +} + +@test "run.sh defines log_warn function" { + grep -q "log_warn()" /workspace/run.sh +} + +@test "run.sh defines log_error function" { + grep -q "log_error()" /workspace/run.sh +} + +# ============================================================================= +# Build Commands +# ============================================================================= + +@test "run.sh has build command" { + grep -q '"build")' /workspace/run.sh +} + +@test "run.sh has iso command" { + grep -q '"iso")' /workspace/run.sh +} + +@test "run.sh has monitor command" { + grep -q '"monitor")' /workspace/run.sh +} + +@test "run.sh has clean command" { + grep -q '"clean")' /workspace/run.sh +} + +# ============================================================================= +# Test Commands +# ============================================================================= + +@test "run.sh has test command" { + grep -q '"test")' /workspace/run.sh +} + +@test "run.sh has test:unit command" { + grep -q '"test:unit")' /workspace/run.sh +} + +@test "run.sh has test:integration command" { + grep -q '"test:integration")' /workspace/run.sh +} + +@test "run.sh has test:security command" { + grep -q '"test:security")' /workspace/run.sh +} + +@test "run.sh has test:system command" { + grep -q '"test:system")' /workspace/run.sh +} + +@test "run.sh has lint command" { + grep -q '"lint")' /workspace/run.sh +} + +# ============================================================================= +# VM Testing Commands +# ============================================================================= + +@test "run.sh has test:iso command" { + grep -q '"test:iso")' /workspace/run.sh +} + +@test "run.sh defines vm_check_prerequisites function" { + grep -q "vm_check_prerequisites()" /workspace/run.sh +} + +@test "run.sh defines vm_create function" { + grep -q "vm_create()" /workspace/run.sh +} + +@test "run.sh defines vm_console function" { + grep -q "vm_console()" /workspace/run.sh +} + +@test "run.sh defines vm_status function" { + grep -q "vm_status()" /workspace/run.sh +} + +@test "run.sh defines vm_destroy function" { + grep -q "vm_destroy()" /workspace/run.sh +} + +@test "run.sh defines vm_is_running function" { + grep -q "vm_is_running()" /workspace/run.sh +} + +# ============================================================================= +# Help and Usage +# ============================================================================= + +@test "run.sh has help command" { + grep -q '"help")' /workspace/run.sh || grep -q '"help"|' /workspace/run.sh +} + +@test "run.sh has usage function" { + grep -q "usage()" /workspace/run.sh +} + +@test "run.sh usage shows available commands" { + run bash /workspace/run.sh help + [ "$status" -eq 1 ] # usage() exits with 1 + [[ "$output" == *"build"* ]] + [[ "$output" == *"test"* ]] + [[ "$output" == *"iso"* ]] +} + +@test "run.sh help mentions Docker" { + run bash /workspace/run.sh help + [[ "$output" == *"docker"* ]] || [[ "$output" == *"Docker"* ]] +} + +@test "run.sh help mentions test commands" { + run bash /workspace/run.sh help + [[ "$output" == *"test:unit"* ]] + [[ "$output" == *"test:integration"* ]] + [[ "$output" == *"test:security"* ]] +} + +# ============================================================================= +# Docker Integration +# ============================================================================= + +@test "run.sh iso command uses Docker" { + grep -A 50 '"iso")' /workspace/run.sh | grep -q "docker run" +} + +@test "run.sh test command uses Docker" { + grep -A 10 '"test")' /workspace/run.sh | grep -q "docker run" +} + +@test "run.sh mounts workspace as read-only in Docker" { + grep -q "/workspace:ro" /workspace/run.sh +} + +@test "run.sh uses correct Docker image name" { + grep -q "knel-football-dev" /workspace/run.sh +} + +# ============================================================================= +# Build Configuration +# ============================================================================= + +@test "run.sh configures live-build for Debian testing" { + grep -q "\-\-distribution testing" /workspace/run.sh +} + +@test "run.sh configures live-build for AMD64" { + grep -q "\-\-architectures amd64" /workspace/run.sh +} + +@test "run.sh configures live-build for ISO hybrid" { + grep -q "\-\-binary-images iso-hybrid" /workspace/run.sh +} + +@test "run.sh sets correct ISO application name" { + grep -q "KNEL-Football Secure OS" /workspace/run.sh +} + +@test "run.sh enables Debian installer" { + grep -q "\-\-debian-installer" /workspace/run.sh +} + +# ============================================================================= +# Checksum Generation +# ============================================================================= + +@test "run.sh generates SHA256 checksums" { + grep -q "sha256sum" /workspace/run.sh +} + +@test "run.sh generates MD5 checksums" { + grep -q "md5sum" /workspace/run.sh +} + +# ============================================================================= +# VM Configuration +# ============================================================================= + +@test "run.sh defines VM name" { + grep -q 'VM_NAME=' /workspace/run.sh +} + +@test "run.sh defines VM RAM size" { + grep -q 'VM_RAM=' /workspace/run.sh +} + +@test "run.sh defines VM CPU count" { + grep -q 'VM_CPUS=' /workspace/run.sh +} + +@test "run.sh defines VM disk size" { + grep -q 'VM_DISK_SIZE=' /workspace/run.sh +} + +@test "run.sh uses system libvirt URI" { + grep -q 'qemu:///system' /workspace/run.sh +} + +# ============================================================================= +# Main Entry Point +# ============================================================================= + +@test "run.sh has main function" { + grep -q "main()" /workspace/run.sh +} + +@test "run.sh calls main with arguments" { + grep -q 'main "\$@"' /workspace/run.sh +} + +@test "run.sh uses case statement for command dispatch" { + grep -q "case.*command" /workspace/run.sh +} + +# ============================================================================= +# Shell Compatibility +# ============================================================================= + +@test "run.sh shebang is bash" { + head -1 /workspace/run.sh | grep -q "#!/bin/bash" +} + +@test "run.sh handles missing arguments gracefully" { + run bash /workspace/run.sh + [ "$status" -eq 1 ] # Should show usage and exit 1 +} + +# ============================================================================= +# Host FDE Requirements (FR-011) +# ============================================================================= + +@test "run.sh has check_host_fde function" { + grep -q "check_host_fde()" /workspace/run.sh +} + +@test "run.sh checks for LUKS devices" { + grep -q "lsblk.*crypt" /workspace/run.sh || grep -q "CRYPT-LUKS" /workspace/run.sh +} + +@test "run.sh checks /etc/crypttab" { + grep -q "/etc/crypttab" /workspace/run.sh +} + +@test "run.sh checks root filesystem encryption" { + grep -q "findmnt" /workspace/run.sh || grep -q "dm-crypt" /workspace/run.sh +} + +@test "run.sh iso command calls check_host_fde" { + grep -A 5 'iso)' /workspace/run.sh | grep -q "check_host_fde" +} + +@test "run.sh test:iso command calls check_host_fde" { + grep -A 5 'test:iso)' /workspace/run.sh | grep -q "check_host_fde" +} + +@test "run.sh host FDE check cannot be bypassed" { + # Should exit with error if check fails + grep -q "check_host_fde || exit 1" /workspace/run.sh +} + +@test "run.sh provides clear FDE error message" { + grep -q "SECURITY REQUIREMENT VIOLATION" /workspace/run.sh +} + +@test "run.sh provides FDE setup guidance" { + grep -q "encrypted LVM" /workspace/run.sh || grep -q "Full Disk Encryption" /workspace/run.sh }