theghostcmd commited on
Commit
5765e13
·
verified ·
1 Parent(s): 758edb3

Upload 18 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ dashboard/static/logo.png filter=lfs diff=lfs merge=lfs -text
37
+ database/security_events.db filter=lfs diff=lfs merge=lfs -text
38
+ geoip/GeoLite2-Country.mmdb filter=lfs diff=lfs merge=lfs -text
dashboard/app.py ADDED
@@ -0,0 +1,600 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template_string, jsonify, request, send_file
2
+ import sys
3
+ import os
4
+ import io
5
+ from datetime import datetime
6
+ from collections import Counter
7
+ from reportlab.lib.pagesizes import letter
8
+ from reportlab.pdfgen import canvas
9
+ from reportlab.lib.utils import ImageReader
10
+ from reportlab.lib.units import inch
11
+ from scapy.utils import wrpcap
12
+ import tempfile
13
+
14
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
15
+
16
+ from database.db import Database
17
+ from response.block_ip import block_ip_windows, unblock_ip_windows
18
+ from geoip.blocker import GeoIPBlocker
19
+
20
+ app = Flask(__name__)
21
+ db = None
22
+ pcap_sniffer = None
23
+ geoip = None
24
+ geoip_available = False
25
+
26
+ # ---------- PDF Helpers ----------
27
+ def add_page_number(c, page_num):
28
+ c.saveState()
29
+ c.setFont("Helvetica", 8)
30
+ c.drawString(letter[0] - 80, 30, f"Page {page_num}")
31
+ c.restoreState()
32
+
33
+ def draw_header(c, title, subtitle=""):
34
+ c.setFont("Helvetica-Bold", 16)
35
+ c.drawString(50, letter[1] - 50, title)
36
+ c.setFont("Helvetica", 10)
37
+ c.drawString(50, letter[1] - 70, subtitle)
38
+ c.line(50, letter[1] - 80, letter[0] - 50, letter[1] - 80)
39
+
40
+ def draw_watermark(c, logo_path):
41
+ if os.path.exists(logo_path):
42
+ try:
43
+ img = ImageReader(logo_path)
44
+ c.saveState()
45
+ c.setFillAlpha(0.2)
46
+ c.drawImage(img, letter[0]/2 - 1.5*inch, letter[1]/2 - 1.5*inch,
47
+ width=3*inch, height=3*inch, mask='auto', preserveAspectRatio=True)
48
+ c.restoreState()
49
+ except:
50
+ pass
51
+
52
+ def generate_blocked_ips_pdf():
53
+ blocked = db.get_blocked_ips()
54
+ buffer = io.BytesIO()
55
+ c = canvas.Canvas(buffer, pagesize=letter)
56
+ logo_path = os.path.join(os.path.dirname(__file__), 'static', 'logo.png')
57
+ page_num = 1
58
+
59
+ draw_watermark(c, logo_path)
60
+ draw_header(c, "MayOne Security Framework", "Blocked IP Addresses Report")
61
+ y = letter[1] - 110
62
+ c.drawString(50, y, f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
63
+ y -= 20
64
+ c.drawString(50, y, f"Total Blocked IPs: {len(blocked)}")
65
+ y -= 40
66
+
67
+ c.setFont("Helvetica-Bold", 10)
68
+ c.drawString(50, y, "IP Address")
69
+ c.drawString(160, y, "Blocked Since")
70
+ c.drawString(300, y, "Reason")
71
+ c.setFont("Helvetica", 10)
72
+ y -= 20
73
+
74
+ for ip, block_time, reason in blocked:
75
+ if y < 80:
76
+ draw_watermark(c, logo_path)
77
+ add_page_number(c, page_num)
78
+ c.showPage()
79
+ page_num += 1
80
+ draw_header(c, "MayOne Security Framework (cont.)", "Blocked IPs")
81
+ y = letter[1] - 110
82
+ c.setFont("Helvetica-Bold", 10)
83
+ c.drawString(50, y, "IP Address")
84
+ c.drawString(160, y, "Blocked Since")
85
+ c.drawString(300, y, "Reason")
86
+ c.setFont("Helvetica", 10)
87
+ y -= 20
88
+ reason_short = reason[:60] + "..." if len(reason) > 60 else reason
89
+ c.drawString(50, y, ip)
90
+ c.drawString(160, y, block_time[:19])
91
+ c.drawString(300, y, reason_short)
92
+ y -= 20
93
+
94
+ add_page_number(c, page_num)
95
+ c.save()
96
+ buffer.seek(0)
97
+ return buffer
98
+
99
+ def generate_all_ips_pdf():
100
+ events = db.get_recent_events(limit=1000)
101
+ buffer = io.BytesIO()
102
+ c = canvas.Canvas(buffer, pagesize=letter)
103
+ logo_path = os.path.join(os.path.dirname(__file__), 'static', 'logo.png')
104
+ page_num = 1
105
+
106
+ draw_watermark(c, logo_path)
107
+ draw_header(c, "MayOne Security Framework", "Complete IP Traffic Log")
108
+ y = letter[1] - 110
109
+ c.drawString(50, y, f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
110
+ y -= 20
111
+ c.drawString(50, y, f"Total Events: {len(events)}")
112
+ y -= 40
113
+
114
+ headers = ["Timestamp", "Src IP", "Dst IP", "Proto", "Port", "Size", "Threat", "Risk"]
115
+ col_widths = [100, 80, 80, 40, 40, 50, 70, 40]
116
+ c.setFont("Helvetica-Bold", 8)
117
+ x = 50
118
+ for i, h in enumerate(headers):
119
+ c.drawString(x, y, h)
120
+ x += col_widths[i]
121
+ c.setFont("Helvetica", 8)
122
+ y -= 15
123
+
124
+ for ev in events:
125
+ if y < 80:
126
+ draw_watermark(c, logo_path)
127
+ add_page_number(c, page_num)
128
+ c.showPage()
129
+ page_num += 1
130
+ draw_header(c, "MayOne Security Framework (cont.)", "IP Traffic Log")
131
+ y = letter[1] - 110
132
+ c.setFont("Helvetica-Bold", 8)
133
+ x = 50
134
+ for i, h in enumerate(headers):
135
+ c.drawString(x, y, h)
136
+ x += col_widths[i]
137
+ c.setFont("Helvetica", 8)
138
+ y -= 15
139
+ ts = ev[1][:19] if len(ev[1]) > 19 else ev[1]
140
+ src = ev[2][:15]
141
+ dst = ev[3][:15]
142
+ proto = ev[4][:4]
143
+ port = str(ev[5]) if ev[5] else ""
144
+ size = str(ev[6])
145
+ threat = ev[7] or "Normal"
146
+ risk = str(ev[8]) if ev[8] else "0"
147
+ x = 50
148
+ c.drawString(x, y, ts); x += col_widths[0]
149
+ c.drawString(x, y, src); x += col_widths[1]
150
+ c.drawString(x, y, dst); x += col_widths[2]
151
+ c.drawString(x, y, proto); x += col_widths[3]
152
+ c.drawString(x, y, port); x += col_widths[4]
153
+ c.drawString(x, y, size); x += col_widths[5]
154
+ c.drawString(x, y, threat); x += col_widths[6]
155
+ c.drawString(x, y, risk)
156
+ y -= 12
157
+
158
+ add_page_number(c, page_num)
159
+ c.save()
160
+ buffer.seek(0)
161
+ return buffer
162
+
163
+ # ---------- HTML Template (with GeoIP warning and conditional toggle) ----------
164
+ HTML_TEMPLATE = '''
165
+ <!DOCTYPE html>
166
+ <html>
167
+ <head>
168
+ <title>MayOne Security Framework</title>
169
+ <meta charset="UTF-8">
170
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
171
+ <style>
172
+ * { box-sizing: border-box; }
173
+ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; background: #0a0f1e; color: #eef4ff; padding: 20px; }
174
+ .container { max-width: 1400px; margin: 0 auto; }
175
+ .header { display: flex; align-items: center; gap: 20px; margin-bottom: 30px; border-bottom: 2px solid #2a3f6e; padding-bottom: 15px; }
176
+ .logo { height: 60px; width: auto; }
177
+ h1 { margin: 0; font-size: 2rem; background: linear-gradient(135deg, #ff6b6b, #4ecdc4); -webkit-background-clip: text; background-clip: text; color: transparent; }
178
+ .subtitle { color: #8e9aaf; margin-top: 5px; }
179
+ .card { background: #141b2b; border-radius: 16px; padding: 20px; margin-bottom: 25px; box-shadow: 0 8px 20px rgba(0,0,0,0.3); border: 1px solid #2a3f6e; }
180
+ .card h2 { margin-top: 0; color: #4ecdc4; font-size: 1.5rem; }
181
+ .stats-grid { display: flex; gap: 20px; flex-wrap: wrap; }
182
+ .stat-box { background: #0f172a; padding: 15px 25px; border-radius: 12px; flex: 1; min-width: 150px; text-align: center; border-left: 4px solid #4ecdc4; }
183
+ .stat-number { font-size: 2rem; font-weight: bold; color: #ffd966; }
184
+ .chart-container { display: flex; gap: 20px; flex-wrap: wrap; }
185
+ .chart-box { flex: 1; min-width: 250px; background: #0f172a; border-radius: 12px; padding: 15px; }
186
+ canvas { max-height: 300px; width: 100%; }
187
+ table { width: 100%; border-collapse: collapse; }
188
+ th, td { text-align: left; padding: 12px; border-bottom: 1px solid #2a3f6e; }
189
+ th { background: #1e2a3a; color: #4ecdc4; }
190
+ .critical { color: #ff6b6b; font-weight: bold; }
191
+ .high { color: #ffa64d; }
192
+ .medium { color: #ffd966; }
193
+ .form-group { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
194
+ .form-field { flex: 1; }
195
+ label { display: block; font-size: 0.8rem; margin-bottom: 5px; color: #8e9aaf; }
196
+ input, textarea { width: 100%; padding: 10px; background: #0f172a; border: 1px solid #2a3f6e; border-radius: 8px; color: #eef4ff; font-size: 0.9rem; }
197
+ button { background: #4ecdc4; color: #0a0f1e; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; transition: 0.2s; }
198
+ button.danger { background: #ff6b6b; }
199
+ button.secondary { background: #2a3f6e; color: white; }
200
+ button:hover { opacity: 0.85; transform: translateY(-2px); }
201
+ .alert { padding: 10px; border-radius: 8px; margin-bottom: 15px; display: none; }
202
+ .alert-success { background: #2e7d64; color: white; }
203
+ .alert-error { background: #b91c1c; color: white; }
204
+ .toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; }
205
+ .warning { background: #ffaa44; color: #0a0f1e; padding: 10px 15px; border-radius: 8px; margin-bottom: 20px; font-weight: bold; }
206
+ footer { text-align: center; margin-top: 30px; font-size: 0.8rem; color: #5a6e8a; }
207
+ </style>
208
+ </head>
209
+ <body>
210
+ <div class="container">
211
+ <div class="header">
212
+ <img src="/static/logo.png" alt="MayOne Logo" class="logo" onerror="this.style.display='none'">
213
+ <div>
214
+ <h1>🛡️ MayOne Security Framework</h1>
215
+ <div class="subtitle">AI‑powered Intrusion Detection & Response</div>
216
+ </div>
217
+ </div>
218
+ <div id="alertBox" class="alert"></div>
219
+ <div id="geoipWarning" class="warning" style="display: none;">
220
+ ⚠️ GeoIP database not found. Download GeoLite2-Country.mmdb and place it in the 'geoip/' folder to enable country lookup and automatic blocking.
221
+ </div>
222
+ <div class="card">
223
+ <h2>📊 Live Statistics</h2>
224
+ <div class="stats-grid">
225
+ <div class="stat-box"><div class="stat-number" id="totalEvents">0</div><div>Total Events</div></div>
226
+ <div class="stat-box"><div class="stat-number" id="uniqueSrc">0</div><div>Unique Sources</div></div>
227
+ <div class="stat-box"><div class="stat-number" id="blockedCount">0</div><div>Blocked IPs</div></div>
228
+ </div>
229
+ </div>
230
+ <div class="card">
231
+ <h2>📡 Traffic Analysis</h2>
232
+ <div class="chart-container">
233
+ <div class="chart-box"><h3>Protocol Distribution</h3><canvas id="protocolChart"></canvas></div>
234
+ <div class="chart-box"><h3>Top 5 Ports</h3><canvas id="portChart"></canvas></div>
235
+ </div>
236
+ </div>
237
+ <div class="card">
238
+ <h2>🚨 Recent Threats</h2>
239
+ <div style="overflow-x: auto;">
240
+ <table id="threatsTable">
241
+ <thead>
242
+ <tr>
243
+ <th>Time</th>
244
+ <th>Source IP</th>
245
+ <th>Country</th>
246
+ <th>Protocol</th>
247
+ <th>Dest Port</th>
248
+ <th>Threat Type</th>
249
+ <th>Risk</th>
250
+ <th>Action</th>
251
+ </tr>
252
+ </thead>
253
+ <tbody id="threatsBody">
254
+ <tr><td colspan="8">Loading...</td></tr>
255
+ </tbody>
256
+ </table>
257
+ </div>
258
+ </div>
259
+ <div class="card">
260
+ <div class="toolbar">
261
+ <h2>🚫 Blocked IPs & Manual Control</h2>
262
+ <div style="display: flex; gap: 15px; align-items: center;">
263
+ <label id="geoipLabel" style="display: inline-flex; align-items: center; gap: 8px; background: #1e2a3a; padding: 5px 12px; border-radius: 20px;">
264
+ 🌍 GeoIP Blocking
265
+ <input type="checkbox" id="geoipToggle" style="width: 18px; height: 18px; margin: 0;">
266
+ </label>
267
+ <button id="downloadBlockedPdfBtn" class="secondary">📄 Blocked IPs (PDF)</button>
268
+ <button id="downloadAllPdfBtn" class="secondary">📊 All IP Logs (PDF)</button>
269
+ <button id="downloadPcapBtn" class="secondary">📦 Download PCAP</button>
270
+ </div>
271
+ </div>
272
+ <div class="form-group" style="margin-bottom: 25px;">
273
+ <div class="form-field"><label>IP Address to Block</label><input type="text" id="blockIp" placeholder="e.g., 203.0.113.5"></div>
274
+ <div class="form-field"><label>Reason (optional)</label><input type="text" id="blockReason" placeholder="Manual block"></div>
275
+ <button id="blockBtn">➕ Block IP</button>
276
+ </div>
277
+ <div style="overflow-x: auto;">
278
+ <table id="blockedTable">
279
+ <thead>
280
+ <tr><th>IP Address</th><th>Blocked Since</th><th>Reason</th><th>Action</th></tr>
281
+ </thead>
282
+ <tbody id="blockedBody">
283
+ <tr><td colspan="4">Loading...</td></tr>
284
+ </tbody>
285
+ </table>
286
+ </div>
287
+ </div>
288
+ <footer>MayOne Security Framework — Real‑time monitoring | Auto‑refresh every 3s</footer>
289
+ </div>
290
+
291
+ <script>
292
+ let protocolChart, portChart;
293
+ let geoipAvailable = {{ geoip_available|tojson }};
294
+
295
+ if (!geoipAvailable) {
296
+ document.getElementById('geoipWarning').style.display = 'block';
297
+ document.getElementById('geoipToggle').disabled = true;
298
+ document.getElementById('geoipLabel').style.opacity = '0.6';
299
+ }
300
+
301
+ function showAlert(message, type) {
302
+ const alertDiv = document.getElementById('alertBox');
303
+ alertDiv.textContent = message;
304
+ alertDiv.className = `alert alert-${type}`;
305
+ alertDiv.style.display = 'block';
306
+ setTimeout(() => { alertDiv.style.display = 'none'; }, 4000);
307
+ }
308
+
309
+ async function fetchStats() {
310
+ try {
311
+ const res = await fetch('/api/stats');
312
+ const data = await res.json();
313
+ document.getElementById('totalEvents').innerText = data.total_events;
314
+ document.getElementById('uniqueSrc').innerText = data.unique_src;
315
+ document.getElementById('blockedCount').innerText = data.blocked_count;
316
+ } catch(e) { console.error('Stats error', e); }
317
+ }
318
+
319
+ async function fetchTrafficStats() {
320
+ try {
321
+ const res = await fetch('/api/traffic_stats');
322
+ const data = await res.json();
323
+ if (protocolChart) protocolChart.destroy();
324
+ const protoCtx = document.getElementById('protocolChart').getContext('2d');
325
+ protocolChart = new Chart(protoCtx, {
326
+ type: 'pie',
327
+ data: { labels: Object.keys(data.protocols), datasets: [{ data: Object.values(data.protocols), backgroundColor: ['#4ecdc4', '#ff6b6b', '#ffd966', '#a78bfa'] }] },
328
+ options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { labels: { color: '#eef4ff' } } } }
329
+ });
330
+ if (portChart) portChart.destroy();
331
+ const portCtx = document.getElementById('portChart').getContext('2d');
332
+ const portLabels = Object.keys(data.top_ports);
333
+ const portValues = Object.values(data.top_ports);
334
+ portChart = new Chart(portCtx, {
335
+ type: 'bar',
336
+ data: { labels: portLabels, datasets: [{ label: 'Packets', data: portValues, backgroundColor: '#4ecdc4' }] },
337
+ options: { responsive: true, maintainAspectRatio: true, scales: { y: { beginAtZero: true, ticks: { color: '#eef4ff' } }, x: { ticks: { color: '#eef4ff' } } }, plugins: { legend: { labels: { color: '#eef4ff' } } } }
338
+ });
339
+ } catch(e) { console.error('Traffic stats error', e); }
340
+ }
341
+
342
+ async function fetchThreats() {
343
+ try {
344
+ const res = await fetch('/api/threats');
345
+ const threats = await res.json();
346
+ const tbody = document.getElementById('threatsBody');
347
+ if(threats.length === 0) { tbody.innerHTML = '<tr><td colspan="8">No threats detected</td></tr>'; return; }
348
+ tbody.innerHTML = threats.map(t => `
349
+ <tr>
350
+ <td>${t.time.slice(0,19)}</td>
351
+ <td>${t.src_ip}</td>
352
+ <td>${t.country || '-'}</td>
353
+ <td>${t.protocol || '-'}</td>
354
+ <td>${t.port || '-'}</td>
355
+ <td>${t.threat_type || 'ANOMALY'}</td>
356
+ <td class="${t.risk >= 80 ? 'critical' : (t.risk >= 60 ? 'high' : (t.risk >= 30 ? 'medium' : ''))}">${t.risk}</td>
357
+ <td>${t.action || '-'}</td>
358
+ </tr>
359
+ `).join('');
360
+ } catch(e) { console.error('Threats error', e); }
361
+ }
362
+
363
+ async function fetchBlockedIPs() {
364
+ try {
365
+ const res = await fetch('/api/blocked_ips');
366
+ const blocked = await res.json();
367
+ const tbody = document.getElementById('blockedBody');
368
+ if(blocked.length === 0) { tbody.innerHTML = '<tr><td colspan="4">No IPs blocked</td></tr>'; return; }
369
+ tbody.innerHTML = blocked.map(b => `
370
+ <tr>
371
+ <td>${b.ip}</td>
372
+ <td>${b.time.slice(0,19)}</td>
373
+ <td>${b.reason}</td>
374
+ <td><button class="danger" onclick="unblockIP('${b.ip}')">Unblock</button></td>
375
+ </tr>
376
+ `).join('');
377
+ } catch(e) { console.error('Blocked IPs error', e); }
378
+ }
379
+
380
+ async function fetchGeoIPStatus() {
381
+ if (!geoipAvailable) return;
382
+ try {
383
+ const res = await fetch('/api/geoip_status');
384
+ const data = await res.json();
385
+ document.getElementById('geoipToggle').checked = data.enabled;
386
+ } catch(e) { console.error('GeoIP status error', e); }
387
+ }
388
+
389
+ async function blockIP() {
390
+ const ip = document.getElementById('blockIp').value.trim();
391
+ if(!ip) { showAlert('Please enter an IP address', 'error'); return; }
392
+ const reason = document.getElementById('blockReason').value.trim() || 'Manual block from dashboard';
393
+ try {
394
+ const res = await fetch('/api/block', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ip: ip, reason: reason}) });
395
+ const data = await res.json();
396
+ if(data.success) {
397
+ showAlert(`Blocked ${ip} successfully`, 'success');
398
+ document.getElementById('blockIp').value = '';
399
+ document.getElementById('blockReason').value = '';
400
+ fetchBlockedIPs(); fetchStats();
401
+ } else { showAlert(`Failed: ${data.error}`, 'error'); }
402
+ } catch(e) { showAlert('Network error', 'error'); }
403
+ }
404
+
405
+ window.unblockIP = async function(ip) {
406
+ if(!confirm(`Unblock ${ip}?`)) return;
407
+ try {
408
+ const res = await fetch('/api/unblock', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ip: ip}) });
409
+ const data = await res.json();
410
+ if(data.success) {
411
+ showAlert(`Unblocked ${ip}`, 'success');
412
+ fetchBlockedIPs(); fetchStats();
413
+ } else { showAlert(`Failed: ${data.error}`, 'error'); }
414
+ } catch(e) { showAlert('Network error', 'error'); }
415
+ };
416
+
417
+ document.getElementById('downloadBlockedPdfBtn').addEventListener('click', () => window.location.href = '/api/download_blocked_ips_pdf');
418
+ document.getElementById('downloadAllPdfBtn').addEventListener('click', () => window.location.href = '/api/download_all_ips_pdf');
419
+ document.getElementById('downloadPcapBtn').addEventListener('click', () => window.location.href = '/api/download_pcap');
420
+
421
+ if (geoipAvailable) {
422
+ document.getElementById('geoipToggle').addEventListener('change', async (e) => {
423
+ const enabled = e.target.checked;
424
+ try {
425
+ const res = await fetch('/api/geoip_toggle', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({enabled: enabled}) });
426
+ const data = await res.json();
427
+ if(data.success) showAlert(`GeoIP blocking ${enabled ? 'enabled' : 'disabled'}`, 'success');
428
+ else showAlert('Failed to toggle GeoIP', 'error');
429
+ } catch(e) { showAlert('Network error', 'error'); }
430
+ });
431
+ }
432
+
433
+ function refreshAll() {
434
+ fetchStats();
435
+ fetchTrafficStats();
436
+ fetchThreats();
437
+ fetchBlockedIPs();
438
+ if (geoipAvailable) fetchGeoIPStatus();
439
+ }
440
+ setInterval(refreshAll, 3000);
441
+ refreshAll();
442
+ document.getElementById('blockBtn').addEventListener('click', blockIP);
443
+ </script>
444
+ </body>
445
+ </html>
446
+ '''
447
+
448
+ # ---------- Flask Routes ----------
449
+ @app.route('/')
450
+ def dashboard():
451
+ return render_template_string(HTML_TEMPLATE, geoip_available=geoip_available)
452
+
453
+ @app.route('/api/stats')
454
+ def api_stats():
455
+ total_events = db.get_total_event_count()
456
+ conn, cursor = db._get_connection()
457
+ cursor.execute('SELECT DISTINCT src_ip FROM events')
458
+ unique_src = len(cursor.fetchall())
459
+ blocked = db.get_blocked_ips()
460
+ return jsonify({
461
+ 'total_events': total_events,
462
+ 'unique_src': unique_src,
463
+ 'blocked_count': len(blocked)
464
+ })
465
+
466
+ @app.route('/api/traffic_stats')
467
+ def api_traffic_stats():
468
+ events = db.get_recent_events(limit=2000)
469
+ protocol_counter = Counter()
470
+ port_counter = Counter()
471
+ for ev in events:
472
+ proto = ev[4]
473
+ port = ev[5]
474
+ protocol_counter[proto] += 1
475
+ if port:
476
+ port_counter[port] += 1
477
+ top_ports = dict(port_counter.most_common(5))
478
+ return jsonify({
479
+ 'protocols': dict(protocol_counter),
480
+ 'top_ports': top_ports
481
+ })
482
+
483
+ @app.route('/api/threats')
484
+ def api_threats():
485
+ events = db.get_recent_events(500)
486
+ threats = []
487
+ for e in events:
488
+ if e[7] or e[8] > 0:
489
+ src_ip = e[2]
490
+ country = None
491
+ if geoip and geoip.reader:
492
+ country = geoip.get_country_code(src_ip)
493
+ threats.append({
494
+ 'time': e[1],
495
+ 'src_ip': src_ip,
496
+ 'country': country,
497
+ 'protocol': e[4],
498
+ 'port': e[5] if e[5] else None,
499
+ 'threat_type': e[7] or 'MONITORED',
500
+ 'risk': e[8],
501
+ 'action': e[9]
502
+ })
503
+ return jsonify(threats[:100])
504
+
505
+ @app.route('/api/blocked_ips')
506
+ def api_blocked_ips():
507
+ blocked = db.get_blocked_ips()
508
+ return jsonify([{'ip': b[0], 'time': b[1], 'reason': b[2]} for b in blocked])
509
+
510
+ @app.route('/api/block', methods=['POST'])
511
+ def api_block():
512
+ data = request.get_json()
513
+ ip = data.get('ip', '').strip()
514
+ reason = data.get('reason', 'Manual block from dashboard')
515
+ if not ip:
516
+ return jsonify({'success': False, 'error': 'IP required'}), 400
517
+ success = block_ip_windows(ip, reason)
518
+ if success:
519
+ db.insert_blocked_ip(ip, reason)
520
+ return jsonify({'success': True})
521
+ else:
522
+ return jsonify({'success': False, 'error': 'Firewall rule failed'}), 500
523
+
524
+ @app.route('/api/unblock', methods=['POST'])
525
+ def api_unblock():
526
+ data = request.get_json()
527
+ ip = data.get('ip', '').strip()
528
+ if not ip:
529
+ return jsonify({'success': False, 'error': 'IP required'}), 400
530
+ success = unblock_ip_windows(ip)
531
+ if success:
532
+ conn, cursor = db._get_connection()
533
+ cursor.execute('DELETE FROM blocked_ips WHERE ip = ?', (ip,))
534
+ conn.commit()
535
+ return jsonify({'success': True})
536
+ else:
537
+ return jsonify({'success': False, 'error': 'Unblock failed'}), 500
538
+
539
+ @app.route('/api/download_blocked_ips_pdf')
540
+ def download_blocked_ips_pdf():
541
+ pdf_buffer = generate_blocked_ips_pdf()
542
+ return send_file(pdf_buffer, as_attachment=True, download_name='blocked_ips_log.pdf', mimetype='application/pdf')
543
+
544
+ @app.route('/api/download_all_ips_pdf')
545
+ def download_all_ips_pdf():
546
+ pdf_buffer = generate_all_ips_pdf()
547
+ return send_file(pdf_buffer, as_attachment=True, download_name='all_ip_traffic_log.pdf', mimetype='application/pdf')
548
+
549
+ @app.route('/api/download_pcap')
550
+ def download_pcap():
551
+ if not pcap_sniffer:
552
+ return jsonify({'error': 'PCAP capture not available'}), 500
553
+ packets = pcap_sniffer.get_pcap_buffer()
554
+ if not packets:
555
+ return jsonify({'error': 'No packets captured yet'}), 404
556
+ try:
557
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.pcap') as tmp:
558
+ wrpcap(tmp.name, packets)
559
+ tmp_path = tmp.name
560
+ with open(tmp_path, 'rb') as f:
561
+ pcap_data = f.read()
562
+ os.unlink(tmp_path)
563
+ return send_file(io.BytesIO(pcap_data), as_attachment=True, download_name='capture.pcap', mimetype='application/vnd.tcpdump.pcap')
564
+ except Exception as e:
565
+ app.logger.error(f"PCAP export failed: {e}")
566
+ return jsonify({'error': str(e)}), 500
567
+
568
+ @app.route('/api/geoip_status')
569
+ def geoip_status():
570
+ import main
571
+ return jsonify({'enabled': getattr(main, 'geoip_enabled', False)})
572
+
573
+ @app.route('/api/geoip_toggle', methods=['POST'])
574
+ def geoip_toggle():
575
+ import main
576
+ data = request.get_json()
577
+ main.geoip_enabled = data.get('enabled', False)
578
+ return jsonify({'success': True})
579
+
580
+ @app.route('/static/<path:filename>')
581
+ def serve_static(filename):
582
+ from flask import send_from_directory
583
+ static_dir = os.path.join(os.path.dirname(__file__), 'static')
584
+ return send_from_directory(static_dir, filename)
585
+
586
+ # ---------- Startup ----------
587
+ def run_dashboard(host='127.0.0.1', port=5000, sniffer=None):
588
+ global db, pcap_sniffer, geoip, geoip_available
589
+ db = Database()
590
+ pcap_sniffer = sniffer
591
+ geoip = GeoIPBlocker()
592
+ geoip_available = geoip.reader is not None
593
+ if not geoip_available:
594
+ print("[Dashboard] GeoIP database not found. Country lookup and blocking disabled.")
595
+ static_folder = os.path.join(os.path.dirname(__file__), 'static')
596
+ os.makedirs(static_folder, exist_ok=True)
597
+ logo_path = os.path.join(static_folder, 'logo.png')
598
+ if not os.path.exists(logo_path):
599
+ print("[Dashboard] No logo.png found in dashboard/static/. Please add your logo for watermark.")
600
+ app.run(host=host, port=port, debug=False, use_reloader=False)
dashboard/static/logo.png ADDED

Git LFS Details

  • SHA256: f8e30ba053d5794ec74ecd85d3b615c76ca1c32518df7dfd2b971867e5f88b13
  • Pointer size: 132 Bytes
  • Size of remote file: 1.32 MB
database/__pycache__/db.cpython-310.pyc ADDED
Binary file (5.02 kB). View file
 
database/db.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import threading
3
+ from datetime import datetime
4
+ import os
5
+ from config import DB_PATH
6
+
7
+ class Database:
8
+ _local = threading.local()
9
+
10
+ def __init__(self):
11
+ self._ensure_db_dir()
12
+ self._get_connection() # create tables for the calling thread
13
+
14
+ def _ensure_db_dir(self):
15
+ os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
16
+
17
+ def _get_connection(self):
18
+ if not hasattr(self._local, 'conn'):
19
+ self._local.conn = sqlite3.connect(DB_PATH, check_same_thread=False)
20
+ self._local.cursor = self._local.conn.cursor()
21
+ self._create_tables(self._local.cursor)
22
+ return self._local.conn, self._local.cursor
23
+
24
+ def _create_tables(self, cursor):
25
+ cursor.execute('''
26
+ CREATE TABLE IF NOT EXISTS events (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ timestamp TEXT,
29
+ src_ip TEXT,
30
+ dst_ip TEXT,
31
+ protocol TEXT,
32
+ port INTEGER,
33
+ packet_size INTEGER,
34
+ threat_type TEXT,
35
+ risk_score INTEGER,
36
+ action TEXT
37
+ )
38
+ ''')
39
+ cursor.execute('''
40
+ CREATE TABLE IF NOT EXISTS threats (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ timestamp TEXT,
43
+ src_ip TEXT,
44
+ threat_type TEXT,
45
+ risk_score INTEGER,
46
+ details TEXT
47
+ )
48
+ ''')
49
+ cursor.execute('''
50
+ CREATE TABLE IF NOT EXISTS blocked_ips (
51
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
52
+ ip TEXT UNIQUE,
53
+ block_time TEXT,
54
+ reason TEXT
55
+ )
56
+ ''')
57
+ cursor.execute('''
58
+ CREATE TABLE IF NOT EXISTS reports (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ timestamp TEXT,
61
+ report_path TEXT,
62
+ type TEXT
63
+ )
64
+ ''')
65
+ self._local.conn.commit()
66
+
67
+ def insert_event(self, src_ip, dst_ip, protocol, port, pkt_size, threat_type, risk_score, action):
68
+ conn, cursor = self._get_connection()
69
+ ts = datetime.now().isoformat()
70
+ cursor.execute('''
71
+ INSERT INTO events (timestamp, src_ip, dst_ip, protocol, port, packet_size, threat_type, risk_score, action)
72
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
73
+ ''', (ts, src_ip, dst_ip, protocol, port, pkt_size, threat_type, risk_score, action))
74
+ conn.commit()
75
+
76
+ def insert_threat(self, src_ip, threat_type, risk_score, details=""):
77
+ conn, cursor = self._get_connection()
78
+ ts = datetime.now().isoformat()
79
+ cursor.execute('''
80
+ INSERT INTO threats (timestamp, src_ip, threat_type, risk_score, details)
81
+ VALUES (?, ?, ?, ?, ?)
82
+ ''', (ts, src_ip, threat_type, risk_score, details))
83
+ conn.commit()
84
+
85
+ def insert_blocked_ip(self, ip, reason):
86
+ conn, cursor = self._get_connection()
87
+ ts = datetime.now().isoformat()
88
+ try:
89
+ cursor.execute('''
90
+ INSERT INTO blocked_ips (ip, block_time, reason)
91
+ VALUES (?, ?, ?)
92
+ ''', (ip, ts, reason))
93
+ conn.commit()
94
+ except sqlite3.IntegrityError:
95
+ pass
96
+
97
+ def get_recent_events(self, limit=100):
98
+ _, cursor = self._get_connection()
99
+ cursor.execute('SELECT * FROM events ORDER BY timestamp DESC LIMIT ?', (limit,))
100
+ return cursor.fetchall()
101
+
102
+ def get_threat_summary(self, hours=24):
103
+ _, cursor = self._get_connection()
104
+ cursor.execute('''
105
+ SELECT threat_type, COUNT(*), AVG(risk_score) FROM threats
106
+ WHERE timestamp > datetime('now', '-' || ? || ' hours')
107
+ GROUP BY threat_type
108
+ ''', (hours,))
109
+ return cursor.fetchall()
110
+
111
+ def get_blocked_ips(self):
112
+ _, cursor = self._get_connection()
113
+ cursor.execute('SELECT ip, block_time, reason FROM blocked_ips ORDER BY block_time DESC')
114
+ return cursor.fetchall()
115
+
116
+ def get_total_event_count(self):
117
+ _, cursor = self._get_connection()
118
+ cursor.execute('SELECT COUNT(*) FROM events')
119
+ return cursor.fetchone()[0]
120
+
121
+ def close(self):
122
+ if hasattr(self._local, 'conn'):
123
+ self._local.conn.close()
124
+ del self._local.conn
125
+ del self._local.cursor
database/security_events.db ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:af050a8eff07647ff4d84b9423ebbf14d368a1273bee669ef4125852ce1bfc90
3
+ size 16191488
detection/__pycache__/threat_detector.cpython-310.pyc ADDED
Binary file (2.52 kB). View file
 
detection/threat_detector.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from collections import defaultdict, deque
3
+ import threading
4
+
5
+ class ThreatDetector:
6
+ def __init__(self, time_window=10, port_scan_th=20, brute_th=10, ddos_th=100, burst_th=50):
7
+ self.time_window = time_window
8
+ self.port_scan_th = port_scan_th
9
+ self.brute_th = brute_th
10
+ self.ddos_th = ddos_th
11
+ self.burst_th = burst_th
12
+ self.src_ports = defaultdict(lambda: deque())
13
+ self.src_packets = defaultdict(lambda: deque())
14
+ self.lock = threading.Lock()
15
+
16
+ def _clean_old(self, src_ip, current_time):
17
+ while self.src_ports[src_ip] and self.src_ports[src_ip][0][0] < current_time - self.time_window:
18
+ self.src_ports[src_ip].popleft()
19
+ while self.src_packets[src_ip] and self.src_packets[src_ip][0][0] < current_time - self.time_window:
20
+ self.src_packets[src_ip].popleft()
21
+
22
+ def detect(self, packet_info):
23
+ src = packet_info['src_ip']
24
+ port = packet_info['port']
25
+ ts = packet_info['timestamp']
26
+ threats = []
27
+
28
+ with self.lock:
29
+ if port:
30
+ self.src_ports[src].append((ts, port))
31
+ self.src_packets[src].append((ts, packet_info['size']))
32
+ self._clean_old(src, ts)
33
+
34
+ unique_ports = {p for _, p in self.src_ports[src]}
35
+ if len(unique_ports) >= self.port_scan_th:
36
+ threats.append(('PORT_SCAN', min(100, 60 + len(unique_ports))))
37
+
38
+ if port in [22, 23, 3389, 5900, 21] and len(self.src_packets[src]) >= self.brute_th:
39
+ threats.append(('BRUTE_FORCE', min(100, 70 + len(self.src_packets[src]))))
40
+
41
+ pkt_rate = len(self.src_packets[src]) / self.time_window
42
+ if pkt_rate > self.ddos_th:
43
+ threats.append(('DDoS_FLOOD', min(100, 80 + int(pkt_rate - self.ddos_th))))
44
+
45
+ recent_sizes = [size for t, size in self.src_packets[src] if t > ts - 1]
46
+ if len(recent_sizes) >= self.burst_th:
47
+ threats.append(('BURST', min(100, 65 + len(recent_sizes))))
48
+
49
+ return threats
doc/dashboard.png ADDED
geoip/GeoLite2-Country.mmdb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f5e80a9a3129d46e75c8cccd66bfac725b0449a6c89ba5093a16561d58f20bda
3
+ size 9536434
geoip/__init__.py ADDED
File without changes
geoip/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (129 Bytes). View file
 
geoip/__pycache__/blocker.cpython-310.pyc ADDED
Binary file (1.3 kB). View file
 
geoip/blocker.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import geoip2.database
2
+ import os
3
+ from config import GEOIP_DB_PATH, HIGH_RISK_COUNTRIES
4
+
5
+ class GeoIPBlocker:
6
+ def __init__(self):
7
+ self.reader = None
8
+ if os.path.exists(GEOIP_DB_PATH):
9
+ try:
10
+ self.reader = geoip2.database.Reader(GEOIP_DB_PATH)
11
+ print("[GeoIP] Database loaded successfully.")
12
+ except Exception as e:
13
+ print(f"[GeoIP] Failed to load database: {e}")
14
+ else:
15
+ print(f"[GeoIP] Database not found at {GEOIP_DB_PATH}. GeoIP blocking disabled.")
16
+
17
+ def get_country_code(self, ip):
18
+ if not self.reader:
19
+ return None
20
+ try:
21
+ response = self.reader.country(ip)
22
+ return response.country.iso_code
23
+ except:
24
+ return None
25
+
26
+ def is_high_risk(self, ip):
27
+ if not self.reader:
28
+ return False
29
+ code = self.get_country_code(ip)
30
+ return code in HIGH_RISK_COUNTRIES if code else False
logs/security.log ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+
3
+ logging.basicConfig(
4
+ filename="logs/security.log",
5
+ level=logging.INFO,
6
+ format="%(asctime)s - %(message)s"
7
+ )
8
+
9
+ def log_event(message):
10
+
11
+ logging.info(message)
logs/sniffer.log ADDED
The diff for this file is too large to render. See raw diff
 
monitor/packet_sniffer.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import queue
3
+ import time
4
+ from scapy.all import sniff, IP, TCP, UDP, ICMP
5
+ from collections import deque
6
+ import logging
7
+
8
+ logging.basicConfig(filename='logs/sniffer.log', level=logging.ERROR,
9
+ format='%(asctime)s - %(levelname)s - %(message)s')
10
+
11
+ class PacketSniffer(threading.Thread):
12
+ def __init__(self, packet_queue, interface=None, pcap_buffer_size=10000):
13
+ super().__init__()
14
+ self.packet_queue = packet_queue
15
+ self.interface = interface
16
+ self.running = True
17
+ self.daemon = True
18
+ self.raw_packets = deque(maxlen=pcap_buffer_size) # store raw packets for PCAP
19
+ self.lock = threading.Lock()
20
+
21
+ def run(self):
22
+ try:
23
+ sniff(iface=self.interface, prn=self._process_packet, store=0, stop_filter=self._stop_filter)
24
+ except Exception as e:
25
+ logging.error(f"Sniffer error: {e}")
26
+
27
+ def _process_packet(self, packet):
28
+ if not self.running:
29
+ return
30
+ # Store raw packet for PCAP export
31
+ with self.lock:
32
+ self.raw_packets.append(packet)
33
+ try:
34
+ if IP in packet:
35
+ src = packet[IP].src
36
+ dst = packet[IP].dst
37
+ proto = packet[IP].proto
38
+ size = len(packet)
39
+ ts = time.time()
40
+ port = None
41
+ if TCP in packet:
42
+ port = packet[TCP].dport
43
+ proto_name = "TCP"
44
+ elif UDP in packet:
45
+ port = packet[UDP].dport
46
+ proto_name = "UDP"
47
+ elif ICMP in packet:
48
+ proto_name = "ICMP"
49
+ else:
50
+ proto_name = "OTHER"
51
+
52
+ packet_info = {
53
+ 'timestamp': ts,
54
+ 'src_ip': src,
55
+ 'dst_ip': dst,
56
+ 'protocol': proto_name,
57
+ 'port': port,
58
+ 'size': size
59
+ }
60
+ self.packet_queue.put(packet_info)
61
+ except Exception as e:
62
+ logging.error(f"Error processing packet: {e}")
63
+
64
+ def _stop_filter(self, packet):
65
+ return not self.running
66
+
67
+ def get_pcap_buffer(self):
68
+ with self.lock:
69
+ return list(self.raw_packets)
70
+
71
+ def stop(self):
72
+ self.running = False
reports/report_generator.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from datetime import datetime
4
+ from reportlab.lib.pagesizes import letter
5
+ from reportlab.pdfgen import canvas
6
+ from reportlab.lib.utils import simpleSplit
7
+
8
+ class ReportGenerator:
9
+ def __init__(self, db):
10
+ self.db = db
11
+ self.report_dir = "reports"
12
+ os.makedirs(self.report_dir, exist_ok=True)
13
+
14
+ def _get_cursor(self):
15
+ _, cursor = self.db._get_connection()
16
+ return cursor
17
+
18
+ def generate_report(self, reason="scheduled"):
19
+ ts = datetime.now()
20
+ base_name = f"security_report_{ts.strftime('%Y%m%d_%H%M%S')}"
21
+ data = self._collect_data(ts)
22
+
23
+ json_path = os.path.join(self.report_dir, base_name + ".json")
24
+ with open(json_path, 'w') as f:
25
+ json.dump(data, f, indent=2)
26
+
27
+ txt_path = os.path.join(self.report_dir, base_name + ".txt")
28
+ with open(txt_path, 'w') as f:
29
+ f.write(self._format_text(data))
30
+
31
+ pdf_path = os.path.join(self.report_dir, base_name + ".pdf")
32
+ self._generate_pdf(data, pdf_path)
33
+
34
+ cursor = self._get_cursor()
35
+ cursor.execute('INSERT INTO reports (timestamp, report_path, type) VALUES (?, ?, ?)',
36
+ (ts.isoformat(), json_path, reason))
37
+ self.db._get_connection()[0].commit()
38
+ return json_path, txt_path, pdf_path
39
+
40
+ def _collect_data(self, ts):
41
+ events = self.db.get_recent_events(200)
42
+ threats = self.db.get_threat_summary(24)
43
+ blocked = self.db.get_blocked_ips()
44
+ total_packets = len(events)
45
+ unique_src = set(e[2] for e in events)
46
+ return {
47
+ "report_time": ts.isoformat(),
48
+ "summary": {
49
+ "total_events": total_packets,
50
+ "unique_sources": len(unique_src),
51
+ "threats_detected": len([e for e in events if e[7] is not None]),
52
+ "blocked_ips": len(blocked)
53
+ },
54
+ "threat_breakdown": [{"type": t[0], "count": t[1], "avg_risk": t[2]} for t in threats],
55
+ "blocked_ips_list": [{"ip": b[0], "time": b[1], "reason": b[2]} for b in blocked],
56
+ "top_suspicious": self._get_top_ips(events),
57
+ "recommendations": self._generate_recs(threats, len(blocked))
58
+ }
59
+
60
+ def _get_top_ips(self, events, n=5):
61
+ freq = {}
62
+ for e in events:
63
+ ip = e[2]
64
+ freq[ip] = freq.get(ip, 0) + 1
65
+ sorted_ips = sorted(freq.items(), key=lambda x: x[1], reverse=True)[:n]
66
+ return [{"ip": ip, "packets": cnt} for ip, cnt in sorted_ips]
67
+
68
+ def _generate_recs(self, threats, blocked_count):
69
+ recs = []
70
+ if any(t[0] == "PORT_SCAN" for t in threats):
71
+ recs.append("Enable port knocking or move SSH/RDP to non-standard ports.")
72
+ if any(t[0] == "BRUTE_FORCE" for t in threats):
73
+ recs.append("Enforce strong passwords and consider account lockout policies.")
74
+ if blocked_count > 10:
75
+ recs.append("Review blocked IP list; consider using an IP reputation feed.")
76
+ if not recs:
77
+ recs.append("No immediate action required. Continue monitoring.")
78
+ return recs
79
+
80
+ def _format_text(self, data):
81
+ lines = []
82
+ lines.append("="*60)
83
+ lines.append(f"MayOne Security Report - {data['report_time']}")
84
+ lines.append("="*60)
85
+ lines.append("\nSUMMARY")
86
+ for k,v in data['summary'].items():
87
+ lines.append(f" {k}: {v}")
88
+ lines.append("\nTHREAT BREAKDOWN")
89
+ for t in data['threat_breakdown']:
90
+ lines.append(f" {t['type']}: {t['count']} events, avg risk {t['avg_risk']:.1f}")
91
+ lines.append("\nBLOCKED IPs")
92
+ for b in data['blocked_ips_list'][:10]:
93
+ lines.append(f" {b['ip']} - {b['reason']} (since {b['time']})")
94
+ lines.append("\nRECOMMENDATIONS")
95
+ for r in data['recommendations']:
96
+ lines.append(f" - {r}")
97
+ return "\n".join(lines)
98
+
99
+ def _generate_pdf(self, data, path):
100
+ c = canvas.Canvas(path, pagesize=letter)
101
+ width, height = letter
102
+ y = height - 50
103
+ c.drawString(50, y, f"MayOne Security Report - {data['report_time']}")
104
+ y -= 30
105
+ c.drawString(50, y, "Summary")
106
+ y -= 20
107
+ for k,v in data['summary'].items():
108
+ c.drawString(70, y, f"{k}: {v}")
109
+ y -= 15
110
+ y -= 10
111
+ c.drawString(50, y, "Top Recommendations")
112
+ y -= 20
113
+ for rec in data['recommendations'][:3]:
114
+ lines = simpleSplit(rec, "Helvetica", 12, width-100)
115
+ for line in lines:
116
+ c.drawString(70, y, f"- {line}")
117
+ y -= 15
118
+ c.save()
response/block_ip.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import logging
3
+
4
+ logging.basicConfig(filename='logs/response.log', level=logging.INFO)
5
+
6
+ def block_ip_windows(ip, reason="Malicious activity"):
7
+ try:
8
+ rule_name_in = f"Block_IP_{ip.replace('.', '_')}_in"
9
+ rule_name_out = f"Block_IP_{ip.replace('.', '_')}_out"
10
+
11
+ check_in = subprocess.run(f'netsh advfirewall firewall show rule name="{rule_name_in}"', shell=True, capture_output=True, text=True)
12
+ check_out = subprocess.run(f'netsh advfirewall firewall show rule name="{rule_name_out}"', shell=True, capture_output=True, text=True)
13
+
14
+ if "No rules match" in check_in.stdout:
15
+ cmd_in = f'netsh advfirewall firewall add rule name="{rule_name_in}" dir=in action=block remoteip={ip}'
16
+ proc_in = subprocess.run(cmd_in, shell=True, capture_output=True, text=True)
17
+ if proc_in.returncode != 0:
18
+ logging.error(f"Failed to add inbound rule for {ip}: {proc_in.stderr}")
19
+ return False
20
+
21
+ if "No rules match" in check_out.stdout:
22
+ cmd_out = f'netsh advfirewall firewall add rule name="{rule_name_out}" dir=out action=block remoteip={ip}'
23
+ proc_out = subprocess.run(cmd_out, shell=True, capture_output=True, text=True)
24
+ if proc_out.returncode != 0:
25
+ logging.error(f"Failed to add outbound rule for {ip}: {proc_out.stderr}")
26
+ return False
27
+
28
+ logging.info(f"Blocked IP {ip} (inbound+outbound) - {reason}")
29
+ return True
30
+ except Exception as e:
31
+ logging.error(f"Exception blocking {ip}: {e}")
32
+ return False
33
+
34
+ def unblock_ip_windows(ip):
35
+ try:
36
+ rule_name_in = f"Block_IP_{ip.replace('.', '_')}_in"
37
+ rule_name_out = f"Block_IP_{ip.replace('.', '_')}_out"
38
+ subprocess.run(f'netsh advfirewall firewall delete rule name="{rule_name_in}"', shell=True, capture_output=True)
39
+ subprocess.run(f'netsh advfirewall firewall delete rule name="{rule_name_out}"', shell=True, capture_output=True)
40
+ logging.info(f"Unblocked IP {ip}")
41
+ return True
42
+ except Exception as e:
43
+ logging.error(f"Exception unblocking {ip}: {e}")
44
+ return False