#!/usr/bin/env bash set -euo pipefail # Brief: Integration test for Coraza WAF using Docker Compose and built image # Steps: # 1. Build the local image: docker build -t charon:local . # 2. Start docker-compose.local.yml: docker compose -f docker-compose.local.yml up -d # 3. Wait for API to be ready and then configure a ruleset that blocks a simple signature # 4. Request a path containing the signature and verify 403 (or WAF block response) echo "Starting Coraza integration test..." # Ensure we operate from repo root PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$PROJECT_ROOT" if ! command -v docker >/dev/null 2>&1; then echo "docker is not available; aborting" exit 1 fi docker build -t charon:local . # Run charon using docker run to ensure we pass CHARON_SECURITY_WAF_MODE and control network membership for integration docker rm -f charon-debug >/dev/null 2>&1 || true if ! docker network inspect containers_default >/dev/null 2>&1; then docker network create containers_default fi docker run -d --name charon-debug --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --network containers_default -p 80:80 -p 443:443 -p 8080:8080 -p 2345:2345 \ -e CHARON_ENV=development -e CHARON_DEBUG=1 -e CHARON_HTTP_PORT=8080 -e CHARON_DB_PATH=/app/data/charon.db -e CHARON_FRONTEND_DIR=/app/frontend/dist \ -e CHARON_CADDY_ADMIN_API=http://localhost:2019 -e CHARON_CADDY_CONFIG_DIR=/app/data/caddy -e CHARON_CADDY_BINARY=caddy -e CHARON_IMPORT_CADDYFILE=/import/Caddyfile \ -e CHARON_IMPORT_DIR=/app/data/imports -e CHARON_ACME_STAGING=false -e CHARON_SECURITY_WAF_MODE=block \ -v charon_data:/app/data -v caddy_data:/data -v caddy_config:/config -v /var/run/docker.sock:/var/run/docker.sock:ro -v "$(pwd)/backend:/app/backend:ro" -v "$(pwd)/frontend/dist:/app/frontend/dist:ro" charon:local echo "Waiting for Charon API to be ready..." for i in {1..30}; do if curl -s -f http://localhost:8080/api/v1/ >/dev/null 2>&1; then break fi echo -n '.' sleep 1 done echo "Skipping unauthenticated ruleset creation (will register and create with cookie later)..." echo "Creating a backend container for proxy host..." # ensure the overlay network exists (docker-compose uses containers_default) CREATED_NETWORK=0 if ! docker network inspect containers_default >/dev/null 2>&1; then docker network create containers_default CREATED_NETWORK=1 fi docker rm -f coraza-backend >/dev/null 2>&1 || true docker run -d --name coraza-backend --network containers_default kennethreitz/httpbin echo "Creating proxy host 'integration.local' pointing to backend..." PROXY_HOST_PAYLOAD=$(cat </dev/null || true curl -s -X POST -H "Content-Type: application/json" -d '{"email":"integration@example.local","password":"password123"}' -c ${TMP_COOKIE} http://localhost:8080/api/v1/auth/login >/dev/null echo "Give Caddy a moment to apply configuration..." sleep 3 echo "Creating simple WAF ruleset (XSS block)..." RULESET=$(cat <<'EOF' {"name":"integration-xss","content":"SecRule REQUEST_BODY \"" -H "Host: integration.local" http://localhost/post) if [ "$RESPONSE" = "403" ]; then echo "✓ Coraza WAF blocked payload as expected (HTTP 403) in BLOCK mode" else echo "✗ Unexpected response code: $RESPONSE (expected 403) in BLOCK mode" exit 1 fi echo "" echo "=== Testing MONITOR mode (DetectionOnly) ===" echo "Switching WAF to monitor mode..." SEC_CFG_MONITOR='{"name":"default","enabled":true,"waf_mode":"monitor","waf_rules_source":"integration-xss","admin_whitelist":"0.0.0.0/0"}' curl -s -X POST -H "Content-Type: application/json" -d "${SEC_CFG_MONITOR}" -b ${TMP_COOKIE} http://localhost:8080/api/v1/security/config echo "Wait for Caddy to apply monitor mode config..." sleep 2 echo "Inspecting ruleset file (should now have DetectionOnly)..." docker exec charon-debug cat /app/data/caddy/coraza/rulesets/integration-xss.conf | head -5 || true RESPONSE_MONITOR=$(curl -s -o /dev/null -w "%{http_code}" -d "" -H "Host: integration.local" http://localhost/post) if [ "$RESPONSE_MONITOR" = "200" ]; then echo "✓ Coraza WAF in MONITOR mode allowed payload through (HTTP 200) as expected" else echo "✗ Unexpected response code: $RESPONSE_MONITOR (expected 200) in MONITOR mode" echo " Note: Monitor mode should log but not block" exit 1 fi echo "" echo "=== All Coraza integration tests passed ===" echo "" echo "=== All Coraza integration tests passed ===" echo "Cleaning up..." # Delete the integration test proxy host from DB before stopping container echo "Removing integration test proxy host from database..." INTEGRATION_UUID=$(curl -s http://localhost:8080/api/v1/proxy-hosts | grep -o '"uuid":"[^"]*"[^}]*"domain_names":"integration.local"' | head -n1 | grep -o '"uuid":"[^"]*"' | sed 's/"uuid":"\([^"]*\)"/\1/') if [ -n "$INTEGRATION_UUID" ]; then curl -s -X DELETE -b ${TMP_COOKIE} "http://localhost:8080/api/v1/proxy-hosts/${INTEGRATION_UUID}?delete_uptime=true" >/dev/null echo "✓ Deleted integration proxy host ${INTEGRATION_UUID}" fi docker rm -f coraza-backend || true if [ "$CREATED_NETWORK" -eq 1 ]; then docker network rm containers_default || true fi docker rm -f charon-debug || true rm -f ${TMP_COOKIE} echo "Done"