chore: clean .gitignore cache
This commit is contained in:
@@ -1,225 +0,0 @@
|
||||
/**
|
||||
* Combined Security Enforcement Tests
|
||||
*
|
||||
* Tests that verify multiple security modules working together,
|
||||
* settings persistence, and audit logging integration.
|
||||
*
|
||||
* Pattern: Toggle-On-Test-Toggle-Off
|
||||
*
|
||||
* @see /projects/Charon/docs/plans/current_spec.md - Combined Enforcement Tests
|
||||
*/
|
||||
|
||||
import { test, expect } from '@bgotink/playwright-coverage';
|
||||
import { request } from '@playwright/test';
|
||||
import type { APIRequestContext } from '@playwright/test';
|
||||
import { STORAGE_STATE } from '../constants';
|
||||
import {
|
||||
getSecurityStatus,
|
||||
setSecurityModuleEnabled,
|
||||
captureSecurityState,
|
||||
restoreSecurityState,
|
||||
CapturedSecurityState,
|
||||
SecurityStatus,
|
||||
} from '../utils/security-helpers';
|
||||
|
||||
test.describe('Combined Security Enforcement', () => {
|
||||
let requestContext: APIRequestContext;
|
||||
let originalState: CapturedSecurityState;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
requestContext = await request.newContext({
|
||||
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8080',
|
||||
storageState: STORAGE_STATE,
|
||||
});
|
||||
|
||||
// Capture original state
|
||||
try {
|
||||
originalState = await captureSecurityState(requestContext);
|
||||
} catch (error) {
|
||||
console.error('Failed to capture original security state:', 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 all
|
||||
try {
|
||||
await setSecurityModuleEnabled(requestContext, 'acl', false);
|
||||
await setSecurityModuleEnabled(requestContext, 'waf', false);
|
||||
await setSecurityModuleEnabled(requestContext, 'rateLimit', false);
|
||||
await setSecurityModuleEnabled(requestContext, 'crowdsec', false);
|
||||
await setSecurityModuleEnabled(requestContext, 'cerberus', false);
|
||||
} catch {
|
||||
console.error('Emergency security disable also failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
await requestContext.dispose();
|
||||
});
|
||||
|
||||
test('should enable all security modules simultaneously', async () => {
|
||||
// This test verifies that all security modules can be enabled together.
|
||||
// Due to parallel test execution and shared database state, we need to be
|
||||
// resilient to timing issues. We enable modules sequentially and verify
|
||||
// each setting was saved before proceeding.
|
||||
|
||||
// Enable Cerberus first (master toggle) and verify
|
||||
await setSecurityModuleEnabled(requestContext, 'cerberus', true);
|
||||
|
||||
// Wait for Cerberus to be enabled before enabling sub-modules
|
||||
let status = await getSecurityStatus(requestContext);
|
||||
let cerberusRetries = 5;
|
||||
while (!status.cerberus.enabled && cerberusRetries > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
status = await getSecurityStatus(requestContext);
|
||||
cerberusRetries--;
|
||||
}
|
||||
|
||||
// If Cerberus still not enabled after retries, test environment may have
|
||||
// shared state issues (parallel tests resetting security settings).
|
||||
// Skip the dependent assertions rather than fail flakily.
|
||||
if (!status.cerberus.enabled) {
|
||||
console.log('⚠ Cerberus could not be enabled - possible test isolation issue in parallel execution');
|
||||
test.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable all sub-modules
|
||||
await setSecurityModuleEnabled(requestContext, 'acl', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'waf', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'rateLimit', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'crowdsec', true);
|
||||
|
||||
// Verify all are enabled with retry logic for timing tolerance
|
||||
const allModulesEnabled = (s: SecurityStatus) =>
|
||||
s.cerberus.enabled && s.acl.enabled && s.waf.enabled &&
|
||||
s.rate_limit.enabled && s.crowdsec.enabled;
|
||||
|
||||
status = await getSecurityStatus(requestContext);
|
||||
let retries = 5;
|
||||
while (!allModulesEnabled(status) && retries > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
status = await getSecurityStatus(requestContext);
|
||||
retries--;
|
||||
}
|
||||
|
||||
expect(status.cerberus.enabled).toBe(true);
|
||||
expect(status.acl.enabled).toBe(true);
|
||||
expect(status.waf.enabled).toBe(true);
|
||||
expect(status.rate_limit.enabled).toBe(true);
|
||||
expect(status.crowdsec.enabled).toBe(true);
|
||||
|
||||
console.log('✓ All security modules enabled simultaneously');
|
||||
});
|
||||
|
||||
test('should log security events to audit log', async () => {
|
||||
// Make a settings change to trigger audit log entry
|
||||
await setSecurityModuleEnabled(requestContext, 'cerberus', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'acl', true);
|
||||
|
||||
// Wait a moment for audit log to be written
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
// Fetch audit logs
|
||||
const response = await requestContext.get('/api/v1/security/audit-logs');
|
||||
|
||||
if (response.ok()) {
|
||||
const logs = await response.json();
|
||||
expect(Array.isArray(logs) || logs.items !== undefined).toBe(true);
|
||||
|
||||
// Verify structure (may be empty if audit logging not configured)
|
||||
console.log(`✓ Audit log endpoint accessible, ${Array.isArray(logs) ? logs.length : logs.items?.length || 0} entries`);
|
||||
} else {
|
||||
// Audit logs may require additional configuration
|
||||
console.log(`Audit logs endpoint returned ${response.status()}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle rapid module toggle without race conditions', async () => {
|
||||
// Enable Cerberus first
|
||||
await setSecurityModuleEnabled(requestContext, 'cerberus', true);
|
||||
|
||||
// Rapidly toggle ACL on/off
|
||||
const toggles = 5;
|
||||
for (let i = 0; i < toggles; i++) {
|
||||
await requestContext.post('/api/v1/settings', {
|
||||
data: { key: 'security.acl.enabled', value: i % 2 === 0 ? 'true' : 'false' },
|
||||
});
|
||||
}
|
||||
|
||||
// Final toggle leaves ACL in known state (i=4 sets 'true')
|
||||
// Wait with retry for state to propagate
|
||||
let status = await getSecurityStatus(requestContext);
|
||||
let retries = 5;
|
||||
while (!status.acl.enabled && retries > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
status = await getSecurityStatus(requestContext);
|
||||
retries--;
|
||||
}
|
||||
|
||||
// After 5 toggles (0,1,2,3,4), final state is i=4 which sets 'true'
|
||||
expect(status.acl.enabled).toBe(true);
|
||||
|
||||
console.log('✓ Rapid toggle completed without race conditions');
|
||||
});
|
||||
|
||||
test('should persist settings across API calls', async () => {
|
||||
// Enable a specific configuration
|
||||
await setSecurityModuleEnabled(requestContext, 'cerberus', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'waf', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'acl', false);
|
||||
|
||||
// Create a new request context to simulate fresh session
|
||||
const freshContext = await request.newContext({
|
||||
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8080',
|
||||
storageState: STORAGE_STATE,
|
||||
});
|
||||
|
||||
try {
|
||||
const status = await getSecurityStatus(freshContext);
|
||||
|
||||
expect(status.cerberus.enabled).toBe(true);
|
||||
expect(status.waf.enabled).toBe(true);
|
||||
expect(status.acl.enabled).toBe(false);
|
||||
|
||||
console.log('✓ Settings persisted across API calls');
|
||||
} finally {
|
||||
await freshContext.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
test('should enforce correct priority when multiple modules enabled', async () => {
|
||||
// Enable all modules
|
||||
await setSecurityModuleEnabled(requestContext, 'cerberus', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'acl', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'waf', true);
|
||||
await setSecurityModuleEnabled(requestContext, 'rateLimit', true);
|
||||
|
||||
// Verify security status shows all enabled
|
||||
const status = await getSecurityStatus(requestContext);
|
||||
|
||||
expect(status.cerberus.enabled).toBe(true);
|
||||
expect(status.acl.enabled).toBe(true);
|
||||
expect(status.waf.enabled).toBe(true);
|
||||
expect(status.rate_limit.enabled).toBe(true);
|
||||
|
||||
// The actual priority enforcement is:
|
||||
// Layer 1: CrowdSec (IP reputation/bans)
|
||||
// Layer 2: ACL (IP whitelist/blacklist)
|
||||
// Layer 3: WAF (attack patterns)
|
||||
// Layer 4: Rate Limiting (threshold enforcement)
|
||||
//
|
||||
// A blocked request at Layer 1 never reaches Layer 2-4
|
||||
// This is enforced at the Caddy/middleware level
|
||||
|
||||
console.log(
|
||||
'✓ Multiple modules enabled - priority enforcement is at middleware level'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user