- Implement tests for Security Dashboard card status verification (SD-01 to SD-10) to ensure correct display of security statuses and toggle functionality. - Create error handling tests (EH-01 to EH-10) to validate error messages on API failures, toast notifications on mutation errors, and optimistic update rollback. - Develop loading overlay tests (LS-01 to LS-10) to verify the appearance of loading indicators during operations and ensure interactions are blocked appropriately.
12 KiB
Cerberus Full Integration Testing Plan
Version: 1.0 Date: 2025-12-12 Issue: #319 Status: ✅ COMPLETE
1. Overview
This plan tests all Cerberus security features working together to ensure no conflicts or ordering issues when multiple features are enabled simultaneously.
1.1 Features Under Test
| Feature | Handler Name | Purpose |
|---|---|---|
| Security Decisions | subroute (IP block) |
Manual IP blocks via API |
| CrowdSec | crowdsec |
IP reputation bouncer |
| WAF | waf (Coraza) |
Payload inspection |
| Rate Limiting | rate_limit |
Volume abuse prevention |
| ACL | subroute (IP/Geo) |
Static allow/deny lists |
1.2 Pipeline Order (from config.go)
The security handlers execute in this order per-route:
Request → [Security Decisions] → [CrowdSec] → [WAF] → [Rate Limit] → [ACL] → [Proxy]
Source: backend/internal/caddy/config.go#L216-290
2. Test Environment Setup
2.1 Prerequisites
- Docker with
charon:localimage built - Access to container ports: 8080 (API), 80/443 (proxy), 2019 (Caddy admin)
2.2 Environment Variables
# Enable all security features
export CERBERUS_SECURITY_CERBERUS_ENABLED=true
export CERBERUS_SECURITY_WAF_MODE=block
export CERBERUS_SECURITY_CROWDSEC_MODE=disabled # or local if LAPI available
export CERBERUS_SECURITY_RATELIMIT_MODE=enabled
export CERBERUS_SECURITY_ACL_ENABLED=true
2.3 Container Startup
docker run -d --name charon-cerberus-test \
-p 8080:8080 -p 80:80 -p 2019:2019 \
-e CHARON_ENV=development \
-e CERBERUS_SECURITY_CERBERUS_ENABLED=true \
-e CERBERUS_SECURITY_WAF_MODE=block \
-e CERBERUS_SECURITY_RATELIMIT_MODE=enabled \
-e CERBERUS_SECURITY_ACL_ENABLED=true \
charon:local
2.4 Test Proxy Host Creation
# Login and create test proxy host
curl -s -X POST http://localhost:8080/api/v1/proxy-hosts \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"domain_names": "cerberus.test.local",
"forward_scheme": "http",
"forward_host": "httpbin.org",
"forward_port": 80,
"enabled": true
}'
3. Test Cases
TC-1: Enable All Features Simultaneously
Objective: Verify all Cerberus features can be enabled without startup errors.
Steps:
- Start container with all env vars set
- Wait for Caddy reload
- Query
/api/v1/security/statusto confirm all features enabled
Verification:
# Check security status
curl -s http://localhost:8080/api/v1/security/status | jq
# Expected:
# {
# "enabled": true,
# "waf_mode": "block",
# "crowdsec_mode": "local|disabled",
# "acl_enabled": true,
# "rate_limit_enabled": true
# }
Pass Criteria:
- Container starts without errors
- All features report enabled in status
- No Caddy config validation errors in logs
TC-2: Verify Pipeline Handler Order
Objective: Confirm security handlers are in correct order in Caddy config.
Steps:
- Query Caddy admin API for running config
- Parse handler order for the test route
- Verify order matches expected: Decisions → CrowdSec → WAF → Rate Limit → ACL → Proxy
Verification:
# Extract handler order from Caddy config
curl -s http://localhost:2019/config | python3 -c "
import sys, json
config = json.load(sys.stdin)
servers = config.get('apps', {}).get('http', {}).get('servers', {})
for name, server in servers.items():
for route in server.get('routes', []):
hosts = []
for m in route.get('match', []):
hosts.extend(m.get('host', []))
if 'cerberus.test.local' in hosts:
handlers = [h.get('handler') for h in route.get('handle', [])]
print('Handler order:', handlers)
"
Expected Order:
subroute(if decisions exist)crowdsec(if enabled)waf(if enabled)rate_limitorsubroute(rate limit wrapper)subroute(ACL if enabled)reverse_proxy
Pass Criteria:
- Security handlers appear before
reverse_proxy - Order matches expected pipeline
TC-3: WAF Blocking Doesn't Break Rate Limiter
Objective: Ensure WAF-blocked requests don't consume rate limit quota incorrectly.
Steps:
- Configure rate limit: 5 requests / 30 seconds
- Send 3 malicious requests (WAF blocks)
- Send 5 legitimate requests
- Verify all 5 legitimate requests succeed (200)
Verification:
# Configure rate limit
curl -s -X POST http://localhost:8080/api/v1/security/config \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"rate_limit_requests": 5,
"rate_limit_window_sec": 30
}'
sleep 3
# 3 malicious requests (should be blocked by WAF with 403)
for i in 1 2 3; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Host: cerberus.test.local" \
"http://localhost/get?q=<script>alert(1)</script>")
echo "Malicious $i: HTTP $CODE (expect 403)"
done
# 5 legitimate requests (should all succeed)
for i in 1 2 3 4 5; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Host: cerberus.test.local" \
"http://localhost/get")
echo "Legitimate $i: HTTP $CODE (expect 200)"
done
Pass Criteria:
- All malicious requests return 403 (WAF block)
- All 5 legitimate requests return 200 (not rate limited)
TC-4: Rate Limiter Doesn't Break CrowdSec Decisions
Objective: Verify CrowdSec decisions are enforced even if rate limit not exceeded.
Steps:
- Create a manual security decision blocking a test IP
- Send request from that IP (via X-Forwarded-For if needed)
- Verify request is blocked with 403 before rate limit check
Verification:
# Add manual IP block decision
curl -s -X POST http://localhost:8080/api/v1/security/decisions \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"ip": "203.0.113.50",
"action": "block",
"reason": "Test block for TC-4"
}'
sleep 3
# Request should be blocked immediately (decisions come first)
CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Host: cerberus.test.local" \
-H "X-Forwarded-For: 203.0.113.50" \
"http://localhost/get")
echo "Blocked IP request: HTTP $CODE (expect 403)"
Pass Criteria:
- Request from blocked IP returns 403
- Response body contains "Blocked by security decision"
TC-5: Legitimate Traffic Flows Through All Layers
Objective: Confirm clean traffic passes through all security layers to upstream.
Steps:
- Enable all features with permissive config
- Send 10 legitimate requests
- Verify all return 200 with upstream response
Verification:
echo "=== Testing legitimate traffic flow ==="
# Send 10 legitimate requests
SUCCESS=0
for i in $(seq 1 10); do
BODY=$(curl -s -H "Host: cerberus.test.local" "http://localhost/get")
if echo "$BODY" | grep -q "httpbin.org"; then
((SUCCESS++))
echo "Request $i: ✓ Success (reached upstream)"
else
echo "Request $i: ✗ Failed"
fi
done
echo "Total successful: $SUCCESS/10"
Pass Criteria:
- All 10 requests return 200
- Response body contains upstream (httpbin) content
- No unexpected 403/429 responses
TC-6: Basic Latency Benchmark
Objective: Measure latency overhead when all Cerberus features are enabled.
Steps:
- Measure baseline latency (Cerberus disabled)
- Enable all Cerberus features
- Measure latency with full security stack
- Calculate overhead percentage
Verification:
# Baseline (security disabled)
echo "=== Baseline (Cerberus disabled) ==="
BASELINE=$(curl -s -o /dev/null -w "%{time_total}" \
-H "Host: cerberus.test.local" "http://localhost/get")
echo "Baseline: ${BASELINE}s"
# With full security (already enabled)
echo "=== With all Cerberus features ==="
SECURED=$(curl -s -o /dev/null -w "%{time_total}" \
-H "Host: cerberus.test.local" "http://localhost/get")
echo "Secured: ${SECURED}s"
# Calculate overhead (requires bc)
# OVERHEAD=$(echo "scale=2; ($SECURED - $BASELINE) / $BASELINE * 100" | bc)
# echo "Overhead: ${OVERHEAD}%"
Load Test (10 concurrent, 100 total):
# Using Apache Bench (ab)
ab -n 100 -c 10 -H "Host: cerberus.test.local" http://localhost/get
# Or using hey (Go HTTP load tester)
hey -n 100 -c 10 -host "cerberus.test.local" http://localhost/get
Pass Criteria:
- Average latency overhead < 50ms per request
- 95th percentile latency < 500ms
- No request failures under load
4. Integration Script Outline
Create scripts/cerberus_integration.sh:
#!/usr/bin/env bash
set -euo pipefail
# Integration test for full Cerberus security stack
# Tests all features enabled simultaneously
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PROJECT_ROOT"
CONTAINER_NAME="charon-cerberus-test"
TEST_DOMAIN="cerberus.test.local"
# Cleanup function
cleanup() {
echo "Cleaning up..."
docker rm -f ${CONTAINER_NAME} 2>/dev/null || true
}
trap cleanup EXIT
# Build and start
echo "=== Building and starting Charon with full Cerberus ==="
docker build -t charon:local .
docker run -d --name ${CONTAINER_NAME} \
-p 8380:8080 -p 8381:80 -p 2319:2019 \
-e CHARON_ENV=development \
-e CERBERUS_SECURITY_CERBERUS_ENABLED=true \
-e CERBERUS_SECURITY_WAF_MODE=block \
-e CERBERUS_SECURITY_RATELIMIT_MODE=enabled \
-e CERBERUS_SECURITY_ACL_ENABLED=true \
charon:local
# Wait for startup
sleep 10
# TC-1: Check status
echo "=== TC-1: Verifying all features enabled ==="
STATUS=$(curl -s http://localhost:8380/api/v1/security/status)
echo "$STATUS" | grep -q '"enabled":true' && echo "✓ Cerberus enabled" || exit 1
# TC-2: Check handler order
echo "=== TC-2: Verifying handler order ==="
# ... (parse Caddy config as shown in TC-2)
# TC-3: WAF + Rate Limit interaction
echo "=== TC-3: WAF blocking vs rate limiting ==="
# ... (implement test)
# TC-5: Legitimate traffic
echo "=== TC-5: Legitimate traffic flow ==="
# ... (implement test)
echo ""
echo "==========================================="
echo "ALL CERBERUS INTEGRATION TESTS PASSED"
echo "==========================================="
5. Go Test Wrapper
Create backend/integration/cerberus_integration_test.go:
//go:build integration
// +build integration
package integration
import (
"context"
"os/exec"
"strings"
"testing"
"time"
)
func TestCerberusIntegration(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", "../scripts/cerberus_integration.sh")
cmd.Dir = ".."
out, err := cmd.CombinedOutput()
t.Logf("cerberus_integration output:\n%s", string(out))
if err != nil {
t.Fatalf("cerberus integration failed: %v", err)
}
if !strings.Contains(string(out), "ALL CERBERUS INTEGRATION TESTS PASSED") {
t.Fatalf("unexpected output: final success message not found")
}
}
6. VS Code Task
Add to .vscode/tasks.json:
{
"label": "Cerberus: Run Full Integration Script",
"type": "shell",
"command": "bash",
"args": ["./scripts/cerberus_integration.sh"],
"group": "test"
},
{
"label": "Cerberus: Run Full Integration Go Test",
"type": "shell",
"command": "sh",
"args": [
"-c",
"cd backend && go test -tags=integration ./integration -run TestCerberusIntegration -v"
],
"group": "test"
}
7. Expected Issues & Mitigations
| Issue | Likelihood | Mitigation |
|---|---|---|
| Handler order wrong | Low | Unit test in config_test.go covers this |
| WAF blocks counting as rate limit | Medium | WAF returns 403 before rate_limit handler runs |
| ACL conflicts with decisions | Low | Both use subroutes, evaluated in order |
| CrowdSec LAPI unavailable | High | Test with crowdsec_mode=disabled first |
| High latency under load | Medium | Benchmark and profile if > 100ms overhead |
8. Success Criteria
All test cases pass:
- TC-1: All features enable without errors
- TC-2: Handler order verified in Caddy config
- TC-3: WAF blocks don't consume rate limit
- TC-4: Decisions enforced before rate limit
- TC-5: 100% legitimate traffic success rate
- TC-6: Latency overhead < 50ms average
Document Status: Complete Last Updated: 2025-12-12