374 lines
14 KiB
HTML
374 lines
14 KiB
HTML
{{ define "threat_rules_title" }}Threat Rules{{ end }}
|
|
{{ define "threat_rules_content" }}
|
|
<div class="space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<h1 class="text-2xl font-semibold text-white">Threat Detection Rules</h1>
|
|
<button id="add-rule-btn" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded text-white">
|
|
Add New Rule
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Rules Table -->
|
|
<div class="bg-gray-800 border border-gray-700 rounded-lg">
|
|
<div class="p-4 border-b border-gray-700">
|
|
<h2 class="text-lg font-semibold text-white">Active Rules</h2>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-700">
|
|
<thead class="bg-gray-800">
|
|
<tr>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Name</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Service</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Condition</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Threshold</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Time Window</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Action</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Status</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="rules-table" class="bg-gray-900 divide-y divide-gray-800">
|
|
<!-- Dynamic content will be inserted here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rule Form Modal -->
|
|
<div id="rule-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="bg-gray-800 rounded-lg max-w-2xl w-full">
|
|
<div class="p-4 border-b border-gray-700 flex justify-between items-center">
|
|
<h3 id="modal-title" class="text-lg font-semibold text-white">Add New Rule</h3>
|
|
<button id="close-modal" class="text-gray-400 hover:text-white">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<form id="rule-form" class="p-4 space-y-4">
|
|
<input type="hidden" id="rule-id" name="id">
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm text-gray-300 mb-1">Rule Name</label>
|
|
<input type="text" id="rule-name" name="name" required
|
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100"
|
|
placeholder="e.g., SSH Brute Force Detection">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm text-gray-300 mb-1">Service</label>
|
|
<select id="rule-service" name="service" required
|
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100">
|
|
<option value="*">All Services</option>
|
|
<option value="ssh">SSH</option>
|
|
<option value="http">HTTP</option>
|
|
<option value="https">HTTPS</option>
|
|
<option value="ftp">FTP</option>
|
|
<option value="smtp">SMTP</option>
|
|
<option value="imap">IMAP</option>
|
|
<option value="telnet">Telnet</option>
|
|
<option value="mysql">MySQL</option>
|
|
<option value="postgresql">PostgreSQL</option>
|
|
<option value="mongodb">MongoDB</option>
|
|
<option value="rdp">RDP</option>
|
|
<option value="smb">SMB</option>
|
|
<option value="sip">SIP</option>
|
|
<option value="vnc">VNC</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm text-gray-300 mb-1">Description</label>
|
|
<textarea id="rule-description" name="description" rows="2"
|
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100"
|
|
placeholder="Describe what this rule detects..."></textarea>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-sm text-gray-300 mb-1">Condition</label>
|
|
<select id="rule-condition" name="condition" required
|
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100">
|
|
<option value="connection_count">Connection Count</option>
|
|
<option value="auth_attempts">Authentication Attempts</option>
|
|
<option value="service_diversity">Service Diversity</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm text-gray-300 mb-1">Threshold</label>
|
|
<input type="number" id="rule-threshold" name="threshold" required min="1"
|
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100"
|
|
placeholder="e.g., 10">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm text-gray-300 mb-1">Time Window (minutes)</label>
|
|
<input type="number" id="rule-time-window" name="time_window" required min="1"
|
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100"
|
|
placeholder="e.g., 60">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm text-gray-300 mb-1">Action</label>
|
|
<select id="rule-action" name="action" required
|
|
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-gray-100">
|
|
<option value="alert">Alert Only</option>
|
|
<option value="block">Block IP</option>
|
|
<option value="monitor">Monitor</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<label class="flex items-center text-sm text-gray-300">
|
|
<input type="checkbox" id="rule-enabled" name="enabled" checked
|
|
class="mr-2 h-4 w-4 text-primary-600 bg-gray-900 border-gray-700 rounded">
|
|
Rule Enabled
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-3 pt-4">
|
|
<button type="button" id="cancel-btn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Cancel
|
|
</button>
|
|
<button type="submit" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded text-white">
|
|
Save Rule
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentRules = [];
|
|
let editingRuleId = null;
|
|
|
|
// Load initial data
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadRules();
|
|
});
|
|
|
|
// Event listeners
|
|
document.getElementById('add-rule-btn').addEventListener('click', function() {
|
|
showRuleModal();
|
|
});
|
|
|
|
document.getElementById('close-modal').addEventListener('click', hideRuleModal);
|
|
document.getElementById('cancel-btn').addEventListener('click', hideRuleModal);
|
|
|
|
document.getElementById('rule-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
saveRule();
|
|
});
|
|
|
|
// Load threat rules
|
|
async function loadRules() {
|
|
try {
|
|
const response = await fetch('/api/threat/rules');
|
|
const data = await response.json();
|
|
currentRules = data.rules || [];
|
|
renderRulesTable(currentRules);
|
|
} catch (error) {
|
|
console.error('Failed to load rules:', error);
|
|
}
|
|
}
|
|
|
|
// Render rules table
|
|
function renderRulesTable(rules) {
|
|
const tbody = document.getElementById('rules-table');
|
|
tbody.innerHTML = '';
|
|
|
|
if (rules.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="8" class="px-4 py-4 text-center text-gray-400">No rules configured</td></tr>';
|
|
return;
|
|
}
|
|
|
|
rules.forEach(rule => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'hover:bg-gray-800';
|
|
|
|
const statusBadge = rule.enabled
|
|
? '<span class="px-2 py-1 text-xs bg-green-600 text-white rounded">Enabled</span>'
|
|
: '<span class="px-2 py-1 text-xs bg-gray-600 text-white rounded">Disabled</span>';
|
|
|
|
const actionBadge = getActionBadge(rule.action);
|
|
|
|
row.innerHTML = `
|
|
<td class="px-4 py-2 text-sm text-gray-300">
|
|
<div class="font-medium">${rule.name}</div>
|
|
<div class="text-xs text-gray-500">${rule.description || ''}</div>
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-gray-300">
|
|
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">${rule.service}</span>
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-gray-300">${rule.condition}</td>
|
|
<td class="px-4 py-2 text-sm text-gray-300">${rule.threshold}</td>
|
|
<td class="px-4 py-2 text-sm text-gray-300">${rule.time_window}m</td>
|
|
<td class="px-4 py-2 text-sm">${actionBadge}</td>
|
|
<td class="px-4 py-2 text-sm">${statusBadge}</td>
|
|
<td class="px-4 py-2 text-sm">
|
|
<div class="flex space-x-2">
|
|
<button onclick="editRule(${rule.id})" class="px-2 py-1 text-xs bg-blue-600 hover:bg-blue-500 text-white rounded">Edit</button>
|
|
<button onclick="toggleRule(${rule.id}, ${!rule.enabled})" class="px-2 py-1 text-xs ${rule.enabled ? 'bg-gray-600 hover:bg-gray-500' : 'bg-green-600 hover:bg-green-500'} text-white rounded">
|
|
${rule.enabled ? 'Disable' : 'Enable'}
|
|
</button>
|
|
<button onclick="deleteRule(${rule.id})" class="px-2 py-1 text-xs bg-red-600 hover:bg-red-500 text-white rounded">Delete</button>
|
|
</div>
|
|
</td>
|
|
`;
|
|
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// Get action badge HTML
|
|
function getActionBadge(action) {
|
|
switch (action) {
|
|
case 'block':
|
|
return '<span class="px-2 py-1 text-xs bg-red-600 text-white rounded">Block</span>';
|
|
case 'alert':
|
|
return '<span class="px-2 py-1 text-xs bg-yellow-600 text-white rounded">Alert</span>';
|
|
case 'monitor':
|
|
return '<span class="px-2 py-1 text-xs bg-blue-600 text-white rounded">Monitor</span>';
|
|
default:
|
|
return '<span class="px-2 py-1 text-xs bg-gray-600 text-white rounded">Unknown</span>';
|
|
}
|
|
}
|
|
|
|
// Show rule modal
|
|
function showRuleModal(rule = null) {
|
|
editingRuleId = rule ? rule.id : null;
|
|
|
|
if (rule) {
|
|
document.getElementById('modal-title').textContent = 'Edit Rule';
|
|
document.getElementById('rule-id').value = rule.id;
|
|
document.getElementById('rule-name').value = rule.name;
|
|
document.getElementById('rule-description').value = rule.description || '';
|
|
document.getElementById('rule-service').value = rule.service;
|
|
document.getElementById('rule-condition').value = rule.condition;
|
|
document.getElementById('rule-threshold').value = rule.threshold;
|
|
document.getElementById('rule-time-window').value = rule.time_window;
|
|
document.getElementById('rule-action').value = rule.action;
|
|
document.getElementById('rule-enabled').checked = rule.enabled;
|
|
} else {
|
|
document.getElementById('modal-title').textContent = 'Add New Rule';
|
|
document.getElementById('rule-form').reset();
|
|
document.getElementById('rule-enabled').checked = true;
|
|
}
|
|
|
|
document.getElementById('rule-modal').classList.remove('hidden');
|
|
}
|
|
|
|
// Hide rule modal
|
|
function hideRuleModal() {
|
|
document.getElementById('rule-modal').classList.add('hidden');
|
|
editingRuleId = null;
|
|
}
|
|
|
|
// Save rule
|
|
async function saveRule() {
|
|
const formData = new FormData(document.getElementById('rule-form'));
|
|
const ruleData = {
|
|
name: formData.get('name'),
|
|
description: formData.get('description'),
|
|
service: formData.get('service'),
|
|
condition: formData.get('condition'),
|
|
threshold: parseInt(formData.get('threshold')),
|
|
time_window: parseInt(formData.get('time_window')),
|
|
action: formData.get('action'),
|
|
enabled: formData.has('enabled')
|
|
};
|
|
|
|
try {
|
|
let response;
|
|
if (editingRuleId) {
|
|
// Update existing rule
|
|
response = await fetch(`/api/threat/rules/${editingRuleId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(ruleData)
|
|
});
|
|
} else {
|
|
// Create new rule
|
|
response = await fetch('/api/threat/rules', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(ruleData)
|
|
});
|
|
}
|
|
|
|
if (response.ok) {
|
|
hideRuleModal();
|
|
loadRules();
|
|
} else {
|
|
const error = await response.text();
|
|
alert(`Failed to save rule: ${error}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to save rule:', error);
|
|
alert('Failed to save rule');
|
|
}
|
|
}
|
|
|
|
// Edit rule
|
|
function editRule(ruleId) {
|
|
const rule = currentRules.find(r => r.id === ruleId);
|
|
if (rule) {
|
|
showRuleModal(rule);
|
|
}
|
|
}
|
|
|
|
// Toggle rule enabled/disabled
|
|
async function toggleRule(ruleId, enabled) {
|
|
const rule = currentRules.find(r => r.id === ruleId);
|
|
if (!rule) return;
|
|
|
|
const updatedRule = { ...rule, enabled: enabled };
|
|
|
|
try {
|
|
const response = await fetch(`/api/threat/rules/${ruleId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(updatedRule)
|
|
});
|
|
|
|
if (response.ok) {
|
|
loadRules();
|
|
} else {
|
|
alert('Failed to update rule');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to toggle rule:', error);
|
|
alert('Failed to update rule');
|
|
}
|
|
}
|
|
|
|
// Delete rule
|
|
async function deleteRule(ruleId) {
|
|
const rule = currentRules.find(r => r.id === ruleId);
|
|
if (!rule) return;
|
|
|
|
if (!confirm(`Are you sure you want to delete the rule "${rule.name}"?`)) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/threat/rules/${ruleId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (response.ok) {
|
|
loadRules();
|
|
} else {
|
|
alert('Failed to delete rule');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to delete rule:', error);
|
|
alert('Failed to delete rule');
|
|
}
|
|
}
|
|
</script>
|
|
{{ end }}
|