Files
Charon/tests/security-enforcement/waf-enforcement.spec.ts
akanealw eec8c28fb3
Some checks are pending
Go Benchmark / Performance Regression Check (push) Waiting to run
Cerberus Integration / Cerberus Security Stack Integration (push) Waiting to run
Upload Coverage to Codecov / Backend Codecov Upload (push) Waiting to run
Upload Coverage to Codecov / Frontend Codecov Upload (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (go) (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Waiting to run
CrowdSec Integration / CrowdSec Bouncer Integration (push) Waiting to run
Docker Build, Publish & Test / build-and-push (push) Waiting to run
Docker Build, Publish & Test / Security Scan PR Image (push) Blocked by required conditions
Quality Checks / Auth Route Protection Contract (push) Waiting to run
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Waiting to run
Quality Checks / Backend (Go) (push) Waiting to run
Quality Checks / Frontend (React) (push) Waiting to run
Rate Limit integration / Rate Limiting Integration (push) Waiting to run
Security Scan (PR) / Trivy Binary Scan (push) Waiting to run
Supply Chain Verification (PR) / Verify Supply Chain (push) Waiting to run
WAF integration / Coraza WAF Integration (push) Waiting to run
changed perms
2026-04-22 18:19:14 +00:00

182 lines
6.2 KiB
TypeScript
Executable File

/**
* WAF (Coraza) Enforcement Tests
*
* Tests that verify the Web Application Firewall correctly blocks malicious
* requests such as SQL injection and XSS attempts.
*
* NOTE: Full WAF blocking tests require Caddy proxy with Coraza plugin.
* These tests verify the WAF configuration API and expected behavior.
*
* Pattern: Toggle-On-Test-Toggle-Off
*
* @see /projects/Charon/docs/plans/current_spec.md - WAF Enforcement Tests
*/
import { test, expect } from '../fixtures/test';
import { request } from '@playwright/test';
import type { APIRequestContext } from '@playwright/test';
import { STORAGE_STATE } from '../constants';
// CI-specific timeout multiplier: CI environments have higher I/O latency
const CI_TIMEOUT_MULTIPLIER = process.env.CI ? 3 : 1;
const BASE_PROPAGATION_WAIT = 3000;
const BASE_RETRY_INTERVAL = 1000;
const BASE_RETRY_COUNT_WAF = 5;
const BASE_RETRY_COUNT_STATUS = 10;
import {
getSecurityStatus,
setSecurityModuleEnabled,
captureSecurityState,
restoreSecurityState,
CapturedSecurityState,
} from '../utils/security-helpers';
/**
* Configure admin whitelist to allow test runner IPs.
* CRITICAL: Must be called BEFORE enabling any security modules to prevent 403 blocking.
*/
async function configureAdminWhitelist(requestContext: APIRequestContext) {
// Configure whitelist to allow test runner IPs (localhost, Docker networks)
const testWhitelist = '127.0.0.1/32,172.16.0.0/12,192.168.0.0/16,10.0.0.0/8';
const maxRetries = 5;
const retryDelayMs = 1000;
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
const response = await requestContext.patch(
`${process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:8080'}/api/v1/config`,
{
data: {
security: {
admin_whitelist: testWhitelist,
},
},
}
);
if (response.ok()) {
console.log('✅ Admin whitelist configured for test IP ranges');
return;
}
if (response.status() !== 429 || attempt === maxRetries) {
throw new Error(`Failed to configure admin whitelist: ${response.status()}`);
}
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
throw new Error('Failed to configure admin whitelist after retries');
}
test.describe('WAF Enforcement', () => {
let requestContext: APIRequestContext;
let originalState: CapturedSecurityState;
test.beforeAll(async () => {
requestContext = await request.newContext({
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:8080',
storageState: STORAGE_STATE,
});
// CRITICAL: Configure admin whitelist BEFORE enabling security modules
try {
await configureAdminWhitelist(requestContext);
} catch (error) {
console.error('Failed to configure admin whitelist:', error);
}
// Capture original state
try {
originalState = await captureSecurityState(requestContext);
} catch (error) {
console.error('Failed to capture original security state:', error);
}
// Enable Cerberus (master toggle) first
try {
await setSecurityModuleEnabled(requestContext, 'cerberus', true);
console.log('✓ Cerberus enabled');
} catch (error) {
console.error('Failed to enable Cerberus:', error);
}
// Enable WAF with extended wait for Caddy reload propagation
try {
await setSecurityModuleEnabled(requestContext, 'waf', true);
// Wait for Caddy reload and WAF status propagation (3-5 seconds)
await new Promise(r => setTimeout(r, BASE_PROPAGATION_WAIT * CI_TIMEOUT_MULTIPLIER));
// Verify WAF enabled with retry
let wafRetries = BASE_RETRY_COUNT_WAF * CI_TIMEOUT_MULTIPLIER;
let status = await getSecurityStatus(requestContext);
while (!status.waf.enabled && wafRetries > 0) {
await new Promise(r => setTimeout(r, BASE_RETRY_INTERVAL * CI_TIMEOUT_MULTIPLIER));
status = await getSecurityStatus(requestContext);
wafRetries--;
}
console.log('✓ WAF enabled');
} catch (error) {
console.error('Failed to enable WAF:', error);
}
});
test.afterAll(async () => {
// Restore original state
if (originalState) {
try {
await restoreSecurityState(requestContext, originalState);
console.log('✓ Security state restored');
} catch (error) {
console.error('Failed to restore security state:', error);
// Emergency disable WAF to prevent interference
try {
await setSecurityModuleEnabled(requestContext, 'waf', false);
await setSecurityModuleEnabled(requestContext, 'cerberus', false);
} catch {
console.error('Emergency WAF disable also failed');
}
}
}
await requestContext.dispose();
});
test('should verify WAF is enabled', async () => {
// WAF enforcement verified in integration tests (backend/integration/coraza_integration_test.go). E2E tests UI only.
// Use polling pattern to wait for WAF status propagation
let status = await getSecurityStatus(requestContext);
let retries = BASE_RETRY_COUNT_STATUS * CI_TIMEOUT_MULTIPLIER;
while ((!status.waf.enabled || !status.cerberus.enabled) && retries > 0) {
await new Promise(r => setTimeout(r, BASE_RETRY_INTERVAL * CI_TIMEOUT_MULTIPLIER));
status = await getSecurityStatus(requestContext);
retries--;
}
expect(status.waf.enabled).toBe(true);
expect(status.cerberus.enabled).toBe(true);
});
test('should return WAF configuration from security status', async () => {
const response = await requestContext.get('/api/v1/security/status');
expect(response.ok()).toBe(true);
const status = await response.json();
expect(status.waf).toBeDefined();
expect(status.waf.mode).toBeDefined();
expect(typeof status.waf.enabled).toBe('boolean');
});
test('should detect SQL injection patterns in request validation', async () => {
// SKIP: WAF blocking enforced via Coraza middleware (port 80).
// See: backend/integration/coraza_integration_test.go
});
test('should document XSS blocking behavior', async () => {
// SKIP: XSS blocking enforced via Coraza middleware (port 80).
// See: backend/integration/coraza_integration_test.go
});
});