Add ModSecurity whitelist rules

This commit is contained in:
root
2026-01-30 21:36:58 +02:00
parent 4f8639d77c
commit f55ffdc263
3 changed files with 127 additions and 2 deletions

View File

@@ -8,7 +8,9 @@ use App\Models\Setting;
use App\Services\Agent\AgentClient;
use BackedEnum;
use Exception;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
@@ -47,10 +49,16 @@ class Waf extends Page implements HasForms
public function mount(): void
{
$this->wafInstalled = $this->detectWaf();
$whitelistRaw = Setting::get('waf_whitelist_rules', '[]');
$whitelistRules = json_decode($whitelistRaw, true);
if (! is_array($whitelistRules)) {
$whitelistRules = [];
}
$this->wafFormData = [
'enabled' => Setting::get('waf_enabled', '0') === '1',
'paranoia' => Setting::get('waf_paranoia', '1'),
'audit_log' => Setting::get('waf_audit_log', '1') === '1',
'whitelist_rules' => $whitelistRules,
];
}
@@ -101,6 +109,44 @@ class Waf extends Page implements HasForms
->label(__('Enable Audit Log')),
])
->columns(2),
Section::make(__('Whitelist Rules'))
->description(__('Exclude trusted traffic from specific ModSecurity rule IDs.'))
->schema([
Repeater::make('whitelist_rules')
->label(__('Whitelist Entries'))
->schema([
TextInput::make('label')
->label(__('Name'))
->placeholder(__('Admin API allowlist'))
->maxLength(80),
Select::make('match_type')
->label(__('Match Type'))
->options([
'ip' => __('IP Address or CIDR'),
'uri_exact' => __('Exact URI'),
'uri_prefix' => __('URI Prefix'),
'host' => __('Host Header'),
])
->required(),
TextInput::make('match_value')
->label(__('Match Value'))
->placeholder(__('Example: 203.0.113.10 or /wp-admin/admin-ajax.php'))
->required(),
TextInput::make('rule_ids')
->label(__('Rule IDs'))
->placeholder(__('Example: 942100,949110'))
->helperText(__('Comma-separated ModSecurity rule IDs to disable for matches.'))
->required(),
Toggle::make('enabled')
->label(__('Enabled'))
->default(true),
])
->itemLabel(fn (array $state): ?string => $state['label'] ?? $state['match_value'] ?? null)
->addActionLabel(__('Add Whitelist Rule'))
->collapsible()
->columns(['default' => 2, 'md' => 2]),
])
->columns(1),
]);
}
@@ -115,13 +161,15 @@ class Waf extends Page implements HasForms
Setting::set('waf_enabled', $requestedEnabled ? '1' : '0');
Setting::set('waf_paranoia', (string) ($data['paranoia'] ?? '1'));
Setting::set('waf_audit_log', ! empty($data['audit_log']) ? '1' : '0');
Setting::set('waf_whitelist_rules', json_encode(array_values($data['whitelist_rules'] ?? []), JSON_UNESCAPED_SLASHES));
try {
$agent = new AgentClient;
$agent->wafApplySettings(
$requestedEnabled,
(string) ($data['paranoia'] ?? '1'),
! empty($data['audit_log'])
! empty($data['audit_log']),
$data['whitelist_rules'] ?? []
);
if (! $this->wafInstalled && ! empty($data['enabled'])) {

View File

@@ -1325,12 +1325,13 @@ class AgentClient
}
// WAF / Geo
public function wafApplySettings(bool $enabled, string $paranoia, bool $auditLog): array
public function wafApplySettings(bool $enabled, string $paranoia, bool $auditLog, array $whitelistRules = []): array
{
return $this->send('waf.apply', [
'enabled' => $enabled,
'paranoia' => $paranoia,
'audit_log' => $auditLog,
'whitelist_rules' => $whitelistRules,
]);
}

View File

@@ -3078,6 +3078,7 @@ function wafApplySettings(array $params): array
$paranoia = (int) ($params['paranoia'] ?? 1);
$paranoia = max(1, min(4, $paranoia));
$auditLog = !empty($params['audit_log']);
$whitelistRules = $params['whitelist_rules'] ?? [];
ensureJabaliNginxIncludeFiles();
@@ -3108,6 +3109,11 @@ function wafApplySettings(array $params): array
'SecAction "id:900110,phase:1,t:none,pass,setvar:tx.executing_paranoia_level=' . $paranoia . '"',
];
$whitelistLines = buildWafWhitelistRules($whitelistRules);
if (!empty($whitelistLines)) {
$rules = array_merge($rules, $whitelistLines);
}
file_put_contents(JABALI_WAF_RULES, implode("\n", $rules) . "\n");
$include = [
@@ -3149,6 +3155,76 @@ function wafApplySettings(array $params): array
return ['success' => true, 'enabled' => $enabled, 'paranoia' => $paranoia, 'audit_log' => $auditLog];
}
function buildWafWhitelistRules(array $rules): array
{
$lines = [];
$ruleBaseId = 120000;
$index = 0;
$matchMap = [
'ip' => ['REMOTE_ADDR', '@ipMatch'],
'uri_exact' => ['REQUEST_URI', '@streq'],
'uri_prefix' => ['REQUEST_URI', '@beginsWith'],
'host' => ['REQUEST_HEADERS:Host', '@streq'],
];
foreach ($rules as $rule) {
if (!is_array($rule)) {
continue;
}
if (isset($rule['enabled']) && !$rule['enabled']) {
continue;
}
$matchType = (string) ($rule['match_type'] ?? '');
$matchValue = trim((string) ($rule['match_value'] ?? ''));
$idsRaw = (string) ($rule['rule_ids'] ?? '');
if ($matchValue === '' || $idsRaw === '') {
continue;
}
if (!isset($matchMap[$matchType])) {
continue;
}
$ids = preg_split('/[,\s]+/', $idsRaw, -1, PREG_SPLIT_NO_EMPTY) ?: [];
$ids = array_values(array_filter(array_map('trim', $ids), function ($id) {
return ctype_digit($id);
}));
if (empty($ids)) {
continue;
}
[$variable, $operator] = $matchMap[$matchType];
$ruleId = $ruleBaseId + $index;
$index++;
$ctlParts = [];
foreach ($ids as $id) {
$ctlParts[] = 'ctl:ruleRemoveById=' . $id;
}
$value = str_replace('"', '\\"', $matchValue);
$lines[] = sprintf(
'SecRule %s "%s %s" "id:%d,phase:1,pass,nolog,%s"',
$variable,
$operator,
$value,
$ruleId,
implode(',', $ctlParts)
);
}
if (!empty($lines)) {
array_unshift($lines, '# Whitelist rules (managed by Jabali)');
}
return $lines;
}
function geoUpdateDatabase(array $params): array
{
$accountId = trim((string) ($params['account_id'] ?? ''));