aiwaf 0.1.8.7__tar.gz → 0.1.8.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aiwaf might be problematic. Click here for more details.
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/PKG-INFO +98 -3
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/README.md +97 -2
- aiwaf-0.1.8.9/aiwaf/management/commands/aiwaf_logging.py +166 -0
- aiwaf-0.1.8.9/aiwaf/middleware_logger.py +160 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/storage.py +74 -33
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/trainer.py +22 -7
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf.egg-info/PKG-INFO +98 -3
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf.egg-info/SOURCES.txt +2 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/pyproject.toml +1 -1
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/setup.py +1 -1
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/LICENSE +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/__init__.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/apps.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/blacklist_manager.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/decorators.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/management/__init__.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/management/commands/__init__.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/management/commands/add_ipexemption.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/management/commands/aiwaf_reset.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/management/commands/detect_and_train.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/middleware.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/models.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/resources/model.pkl +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/templatetags/__init__.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/templatetags/aiwaf_tags.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf/utils.py +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf.egg-info/dependency_links.txt +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf.egg-info/requires.txt +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/aiwaf.egg-info/top_level.txt +0 -0
- {aiwaf-0.1.8.7 → aiwaf-0.1.8.9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.8.
|
|
3
|
+
Version: 0.1.8.9
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -27,6 +27,18 @@ Dynamic: requires-python
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
## 🚀 Quick Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install aiwaf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**⚠️ Important:** Add `'aiwaf'` to your Django `INSTALLED_APPS` to avoid setup errors.
|
|
37
|
+
|
|
38
|
+
**📋 Complete Setup Guide:** See [INSTALLATION.md](INSTALLATION.md) for detailed installation instructions and troubleshooting.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
30
42
|
## System Requirements
|
|
31
43
|
|
|
32
44
|
No GPU needed—AI-WAF runs entirely on CPU with just Python 3.8+, Django 3.2+, a single vCPU and ~512 MB RAM for small sites; for moderate production traffic you can bump to 2–4 vCPUs and 2–4 GB RAM, offload the daily detect-and-train job to a worker, and rotate logs to keep memory use bounded.
|
|
@@ -83,7 +95,28 @@ aiwaf/
|
|
|
83
95
|
- Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
|
|
84
96
|
|
|
85
97
|
- **UUID Tampering Protection**
|
|
86
|
-
Blocks guessed or invalid UUIDs that don
|
|
98
|
+
Blocks guessed or invalid UUIDs that don't resolve to real models.
|
|
99
|
+
|
|
100
|
+
- **Built-in Request Logger**
|
|
101
|
+
Optional middleware logger that captures requests to CSV:
|
|
102
|
+
- **Automatic fallback** when main access logs unavailable
|
|
103
|
+
- **CSV format** for easy analysis and training
|
|
104
|
+
- **Captures response times** for better anomaly detection
|
|
105
|
+
- **Zero configuration** - works out of the box
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
**Exempt Path & IP Awareness**
|
|
109
|
+
|
|
110
|
+
**Exempt Paths:**
|
|
111
|
+
AI‑WAF automatically exempts common login paths (`/admin/`, `/login/`, `/accounts/login/`, etc.) from all blocking mechanisms. You can add additional exempt paths in your Django `settings.py`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
AIWAF_EXEMPT_PATHS = [
|
|
115
|
+
"/api/webhooks/",
|
|
116
|
+
"/health/",
|
|
117
|
+
"/special-endpoint/",
|
|
118
|
+
]
|
|
119
|
+
```
|
|
87
120
|
|
|
88
121
|
|
|
89
122
|
**Exempt Path & IP Awareness**
|
|
@@ -213,6 +246,42 @@ AIWAF_CSV_DATA_DIR = "aiwaf_data" # Directory for CSV files
|
|
|
213
246
|
|
|
214
247
|
---
|
|
215
248
|
|
|
249
|
+
### Built-in Request Logger (Optional)
|
|
250
|
+
|
|
251
|
+
Enable AI-WAF's built-in request logger as a fallback when main access logs aren't available:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
# Enable middleware logging
|
|
255
|
+
AIWAF_MIDDLEWARE_LOGGING = True # Enable/disable logging
|
|
256
|
+
AIWAF_MIDDLEWARE_LOG = "aiwaf_requests.log" # Log file path
|
|
257
|
+
AIWAF_MIDDLEWARE_CSV = True # Use CSV format (recommended)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Then add middleware to MIDDLEWARE list:**
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
MIDDLEWARE = [
|
|
264
|
+
# ... your existing middleware ...
|
|
265
|
+
'aiwaf.middleware_logger.AIWAFLoggerMiddleware', # Add near the end
|
|
266
|
+
]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Manage middleware logging:**
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
python manage.py aiwaf_logging --status # Check logging status
|
|
273
|
+
python manage.py aiwaf_logging --enable # Show setup instructions
|
|
274
|
+
python manage.py aiwaf_logging --clear # Clear log files
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Benefits:**
|
|
278
|
+
- **Automatic fallback** when `AIWAF_ACCESS_LOG` unavailable
|
|
279
|
+
- **CSV format** with precise timestamps and response times
|
|
280
|
+
- **Zero configuration** - trainer automatically detects and uses CSV logs
|
|
281
|
+
- **Lightweight** - fails silently to avoid breaking your application
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
216
285
|
### Optional (defaults shown)
|
|
217
286
|
|
|
218
287
|
```python
|
|
@@ -244,14 +313,40 @@ Add in **this** order to your `MIDDLEWARE` list:
|
|
|
244
313
|
```python
|
|
245
314
|
MIDDLEWARE = [
|
|
246
315
|
"aiwaf.middleware.IPAndKeywordBlockMiddleware",
|
|
247
|
-
"aiwaf.middleware.RateLimitMiddleware",
|
|
316
|
+
"aiwaf.middleware.RateLimitMiddleware",
|
|
248
317
|
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
249
318
|
"aiwaf.middleware.HoneypotTimingMiddleware",
|
|
250
319
|
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
251
320
|
# ... other middleware ...
|
|
321
|
+
"aiwaf.middleware_logger.AIWAFLoggerMiddleware", # Optional: Add if using built-in logger
|
|
252
322
|
]
|
|
253
323
|
```
|
|
254
324
|
|
|
325
|
+
> **⚠️ Order matters!** AI-WAF protection middleware should come early. The logger middleware should come near the end to capture final response data.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Running Detection & Training
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
python manage.py detect_and_train
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### What happens:
|
|
336
|
+
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware CSV logs
|
|
337
|
+
2. Auto‑block IPs with ≥ 6 total 404s
|
|
338
|
+
3. Extract features & train IsolationForest
|
|
339
|
+
4. Save `model.pkl`
|
|
340
|
+
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
341
|
+
6. Remove any keywords associated with newly exempt paths
|
|
342
|
+
|
|
343
|
+
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware CSV logs.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 🧠 How It Works
|
|
348
|
+
```
|
|
349
|
+
|
|
255
350
|
---
|
|
256
351
|
|
|
257
352
|
## Running Detection & Training
|
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 🚀 Quick Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install aiwaf
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**⚠️ Important:** Add `'aiwaf'` to your Django `INSTALLED_APPS` to avoid setup errors.
|
|
16
|
+
|
|
17
|
+
**📋 Complete Setup Guide:** See [INSTALLATION.md](INSTALLATION.md) for detailed installation instructions and troubleshooting.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
9
21
|
## System Requirements
|
|
10
22
|
|
|
11
23
|
No GPU needed—AI-WAF runs entirely on CPU with just Python 3.8+, Django 3.2+, a single vCPU and ~512 MB RAM for small sites; for moderate production traffic you can bump to 2–4 vCPUs and 2–4 GB RAM, offload the daily detect-and-train job to a worker, and rotate logs to keep memory use bounded.
|
|
@@ -62,7 +74,28 @@ aiwaf/
|
|
|
62
74
|
- Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
|
|
63
75
|
|
|
64
76
|
- **UUID Tampering Protection**
|
|
65
|
-
Blocks guessed or invalid UUIDs that don
|
|
77
|
+
Blocks guessed or invalid UUIDs that don't resolve to real models.
|
|
78
|
+
|
|
79
|
+
- **Built-in Request Logger**
|
|
80
|
+
Optional middleware logger that captures requests to CSV:
|
|
81
|
+
- **Automatic fallback** when main access logs unavailable
|
|
82
|
+
- **CSV format** for easy analysis and training
|
|
83
|
+
- **Captures response times** for better anomaly detection
|
|
84
|
+
- **Zero configuration** - works out of the box
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
**Exempt Path & IP Awareness**
|
|
88
|
+
|
|
89
|
+
**Exempt Paths:**
|
|
90
|
+
AI‑WAF automatically exempts common login paths (`/admin/`, `/login/`, `/accounts/login/`, etc.) from all blocking mechanisms. You can add additional exempt paths in your Django `settings.py`:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
AIWAF_EXEMPT_PATHS = [
|
|
94
|
+
"/api/webhooks/",
|
|
95
|
+
"/health/",
|
|
96
|
+
"/special-endpoint/",
|
|
97
|
+
]
|
|
98
|
+
```
|
|
66
99
|
|
|
67
100
|
|
|
68
101
|
**Exempt Path & IP Awareness**
|
|
@@ -192,6 +225,42 @@ AIWAF_CSV_DATA_DIR = "aiwaf_data" # Directory for CSV files
|
|
|
192
225
|
|
|
193
226
|
---
|
|
194
227
|
|
|
228
|
+
### Built-in Request Logger (Optional)
|
|
229
|
+
|
|
230
|
+
Enable AI-WAF's built-in request logger as a fallback when main access logs aren't available:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
# Enable middleware logging
|
|
234
|
+
AIWAF_MIDDLEWARE_LOGGING = True # Enable/disable logging
|
|
235
|
+
AIWAF_MIDDLEWARE_LOG = "aiwaf_requests.log" # Log file path
|
|
236
|
+
AIWAF_MIDDLEWARE_CSV = True # Use CSV format (recommended)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Then add middleware to MIDDLEWARE list:**
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
MIDDLEWARE = [
|
|
243
|
+
# ... your existing middleware ...
|
|
244
|
+
'aiwaf.middleware_logger.AIWAFLoggerMiddleware', # Add near the end
|
|
245
|
+
]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Manage middleware logging:**
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
python manage.py aiwaf_logging --status # Check logging status
|
|
252
|
+
python manage.py aiwaf_logging --enable # Show setup instructions
|
|
253
|
+
python manage.py aiwaf_logging --clear # Clear log files
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Benefits:**
|
|
257
|
+
- **Automatic fallback** when `AIWAF_ACCESS_LOG` unavailable
|
|
258
|
+
- **CSV format** with precise timestamps and response times
|
|
259
|
+
- **Zero configuration** - trainer automatically detects and uses CSV logs
|
|
260
|
+
- **Lightweight** - fails silently to avoid breaking your application
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
195
264
|
### Optional (defaults shown)
|
|
196
265
|
|
|
197
266
|
```python
|
|
@@ -223,14 +292,40 @@ Add in **this** order to your `MIDDLEWARE` list:
|
|
|
223
292
|
```python
|
|
224
293
|
MIDDLEWARE = [
|
|
225
294
|
"aiwaf.middleware.IPAndKeywordBlockMiddleware",
|
|
226
|
-
"aiwaf.middleware.RateLimitMiddleware",
|
|
295
|
+
"aiwaf.middleware.RateLimitMiddleware",
|
|
227
296
|
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
228
297
|
"aiwaf.middleware.HoneypotTimingMiddleware",
|
|
229
298
|
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
230
299
|
# ... other middleware ...
|
|
300
|
+
"aiwaf.middleware_logger.AIWAFLoggerMiddleware", # Optional: Add if using built-in logger
|
|
231
301
|
]
|
|
232
302
|
```
|
|
233
303
|
|
|
304
|
+
> **⚠️ Order matters!** AI-WAF protection middleware should come early. The logger middleware should come near the end to capture final response data.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Running Detection & Training
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
python manage.py detect_and_train
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### What happens:
|
|
315
|
+
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware CSV logs
|
|
316
|
+
2. Auto‑block IPs with ≥ 6 total 404s
|
|
317
|
+
3. Extract features & train IsolationForest
|
|
318
|
+
4. Save `model.pkl`
|
|
319
|
+
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
320
|
+
6. Remove any keywords associated with newly exempt paths
|
|
321
|
+
|
|
322
|
+
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware CSV logs.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 🧠 How It Works
|
|
327
|
+
```
|
|
328
|
+
|
|
234
329
|
---
|
|
235
330
|
|
|
236
331
|
## Running Detection & Training
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
class Command(BaseCommand):
|
|
6
|
+
help = 'Manage AI-WAF middleware logging settings and view log status'
|
|
7
|
+
|
|
8
|
+
def add_arguments(self, parser):
|
|
9
|
+
parser.add_argument(
|
|
10
|
+
'--enable',
|
|
11
|
+
action='store_true',
|
|
12
|
+
help='Enable middleware logging (shows settings to add)'
|
|
13
|
+
)
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
'--disable',
|
|
16
|
+
action='store_true',
|
|
17
|
+
help='Disable middleware logging (shows settings to remove)'
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
'--status',
|
|
21
|
+
action='store_true',
|
|
22
|
+
help='Show current middleware logging status'
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'--clear',
|
|
26
|
+
action='store_true',
|
|
27
|
+
help='Clear/delete middleware log files'
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def handle(self, *args, **options):
|
|
31
|
+
if options['enable']:
|
|
32
|
+
self._show_enable_instructions()
|
|
33
|
+
elif options['disable']:
|
|
34
|
+
self._show_disable_instructions()
|
|
35
|
+
elif options['clear']:
|
|
36
|
+
self._clear_logs()
|
|
37
|
+
else:
|
|
38
|
+
self._show_status()
|
|
39
|
+
|
|
40
|
+
def _show_status(self):
|
|
41
|
+
"""Show current middleware logging configuration"""
|
|
42
|
+
self.stdout.write(self.style.HTTP_INFO("🔍 AI-WAF Middleware Logging Status"))
|
|
43
|
+
self.stdout.write("")
|
|
44
|
+
|
|
45
|
+
# Check settings
|
|
46
|
+
logging_enabled = getattr(settings, 'AIWAF_MIDDLEWARE_LOGGING', False)
|
|
47
|
+
log_file = getattr(settings, 'AIWAF_MIDDLEWARE_LOG', 'aiwaf_requests.log')
|
|
48
|
+
csv_format = getattr(settings, 'AIWAF_MIDDLEWARE_CSV', True)
|
|
49
|
+
csv_file = log_file.replace('.log', '.csv') if csv_format else None
|
|
50
|
+
|
|
51
|
+
# Status
|
|
52
|
+
status_color = self.style.SUCCESS if logging_enabled else self.style.WARNING
|
|
53
|
+
self.stdout.write(f"Status: {status_color('ENABLED' if logging_enabled else 'DISABLED')}")
|
|
54
|
+
self.stdout.write(f"Log File: {log_file}")
|
|
55
|
+
if csv_format:
|
|
56
|
+
self.stdout.write(f"CSV File: {csv_file}")
|
|
57
|
+
self.stdout.write(f"Format: {'CSV' if csv_format else 'Text'}")
|
|
58
|
+
self.stdout.write("")
|
|
59
|
+
|
|
60
|
+
# File existence and sizes
|
|
61
|
+
if logging_enabled:
|
|
62
|
+
self.stdout.write("📁 Log Files:")
|
|
63
|
+
|
|
64
|
+
if csv_format and csv_file:
|
|
65
|
+
if os.path.exists(csv_file):
|
|
66
|
+
size = os.path.getsize(csv_file)
|
|
67
|
+
lines = self._count_csv_lines(csv_file)
|
|
68
|
+
self.stdout.write(f" ✅ {csv_file} ({size:,} bytes, {lines:,} entries)")
|
|
69
|
+
else:
|
|
70
|
+
self.stdout.write(f" ❌ {csv_file} (not found)")
|
|
71
|
+
|
|
72
|
+
if os.path.exists(log_file):
|
|
73
|
+
size = os.path.getsize(log_file)
|
|
74
|
+
self.stdout.write(f" ✅ {log_file} ({size:,} bytes)")
|
|
75
|
+
else:
|
|
76
|
+
self.stdout.write(f" ❌ {log_file} (not found)")
|
|
77
|
+
|
|
78
|
+
# Middleware check
|
|
79
|
+
middleware_list = getattr(settings, 'MIDDLEWARE', [])
|
|
80
|
+
middleware_installed = 'aiwaf.middleware_logger.AIWAFLoggerMiddleware' in middleware_list
|
|
81
|
+
|
|
82
|
+
self.stdout.write("")
|
|
83
|
+
middleware_color = self.style.SUCCESS if middleware_installed else self.style.ERROR
|
|
84
|
+
self.stdout.write(f"Middleware: {middleware_color('INSTALLED' if middleware_installed else 'NOT INSTALLED')}")
|
|
85
|
+
|
|
86
|
+
if logging_enabled and not middleware_installed:
|
|
87
|
+
self.stdout.write(self.style.WARNING("⚠️ Logging is enabled but middleware is not installed!"))
|
|
88
|
+
|
|
89
|
+
def _show_enable_instructions(self):
|
|
90
|
+
"""Show instructions for enabling middleware logging"""
|
|
91
|
+
self.stdout.write(self.style.SUCCESS("🚀 Enable AI-WAF Middleware Logging"))
|
|
92
|
+
self.stdout.write("")
|
|
93
|
+
self.stdout.write("Add these settings to your Django settings.py:")
|
|
94
|
+
self.stdout.write("")
|
|
95
|
+
self.stdout.write(self.style.HTTP_INFO("# Enable AI-WAF middleware logging"))
|
|
96
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_LOGGING = True"))
|
|
97
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_LOG = 'aiwaf_requests.log' # Optional"))
|
|
98
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_CSV = True # Optional (default: True)"))
|
|
99
|
+
self.stdout.write("")
|
|
100
|
+
self.stdout.write("Add middleware to MIDDLEWARE list (preferably near the end):")
|
|
101
|
+
self.stdout.write("")
|
|
102
|
+
self.stdout.write(self.style.HTTP_INFO("MIDDLEWARE = ["))
|
|
103
|
+
self.stdout.write(self.style.HTTP_INFO(" # ... your existing middleware ..."))
|
|
104
|
+
self.stdout.write(self.style.HTTP_INFO(" 'aiwaf.middleware_logger.AIWAFLoggerMiddleware',"))
|
|
105
|
+
self.stdout.write(self.style.HTTP_INFO("]"))
|
|
106
|
+
self.stdout.write("")
|
|
107
|
+
self.stdout.write("Benefits:")
|
|
108
|
+
self.stdout.write(" ✅ Fallback when main access logs unavailable")
|
|
109
|
+
self.stdout.write(" ✅ CSV format for easy analysis")
|
|
110
|
+
self.stdout.write(" ✅ Automatic integration with AI-WAF trainer")
|
|
111
|
+
self.stdout.write(" ✅ Captures response times for better detection")
|
|
112
|
+
|
|
113
|
+
def _show_disable_instructions(self):
|
|
114
|
+
"""Show instructions for disabling middleware logging"""
|
|
115
|
+
self.stdout.write(self.style.WARNING("⏹️ Disable AI-WAF Middleware Logging"))
|
|
116
|
+
self.stdout.write("")
|
|
117
|
+
self.stdout.write("To disable, update your Django settings.py:")
|
|
118
|
+
self.stdout.write("")
|
|
119
|
+
self.stdout.write(self.style.HTTP_INFO("# Disable AI-WAF middleware logging"))
|
|
120
|
+
self.stdout.write(self.style.HTTP_INFO("AIWAF_MIDDLEWARE_LOGGING = False"))
|
|
121
|
+
self.stdout.write("")
|
|
122
|
+
self.stdout.write("And remove from MIDDLEWARE list:")
|
|
123
|
+
self.stdout.write("")
|
|
124
|
+
self.stdout.write(self.style.HTTP_INFO("MIDDLEWARE = ["))
|
|
125
|
+
self.stdout.write(self.style.HTTP_INFO(" # ... your existing middleware ..."))
|
|
126
|
+
self.stdout.write(self.style.HTTP_INFO(" # 'aiwaf.middleware_logger.AIWAFLoggerMiddleware', # Remove this line"))
|
|
127
|
+
self.stdout.write(self.style.HTTP_INFO("]"))
|
|
128
|
+
|
|
129
|
+
def _clear_logs(self):
|
|
130
|
+
"""Clear/delete middleware log files"""
|
|
131
|
+
log_file = getattr(settings, 'AIWAF_MIDDLEWARE_LOG', 'aiwaf_requests.log')
|
|
132
|
+
csv_format = getattr(settings, 'AIWAF_MIDDLEWARE_CSV', True)
|
|
133
|
+
csv_file = log_file.replace('.log', '.csv') if csv_format else None
|
|
134
|
+
|
|
135
|
+
files_deleted = 0
|
|
136
|
+
|
|
137
|
+
# Delete CSV file
|
|
138
|
+
if csv_file and os.path.exists(csv_file):
|
|
139
|
+
try:
|
|
140
|
+
os.remove(csv_file)
|
|
141
|
+
self.stdout.write(self.style.SUCCESS(f"✅ Deleted {csv_file}"))
|
|
142
|
+
files_deleted += 1
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self.stdout.write(self.style.ERROR(f"❌ Failed to delete {csv_file}: {e}"))
|
|
145
|
+
|
|
146
|
+
# Delete text log file
|
|
147
|
+
if os.path.exists(log_file):
|
|
148
|
+
try:
|
|
149
|
+
os.remove(log_file)
|
|
150
|
+
self.stdout.write(self.style.SUCCESS(f"✅ Deleted {log_file}"))
|
|
151
|
+
files_deleted += 1
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.stdout.write(self.style.ERROR(f"❌ Failed to delete {log_file}: {e}"))
|
|
154
|
+
|
|
155
|
+
if files_deleted == 0:
|
|
156
|
+
self.stdout.write(self.style.WARNING("ℹ️ No log files found to delete"))
|
|
157
|
+
else:
|
|
158
|
+
self.stdout.write(self.style.SUCCESS(f"🗑️ Deleted {files_deleted} log file(s)"))
|
|
159
|
+
|
|
160
|
+
def _count_csv_lines(self, csv_file):
|
|
161
|
+
"""Count lines in CSV file (excluding header)"""
|
|
162
|
+
try:
|
|
163
|
+
with open(csv_file, 'r', encoding='utf-8') as f:
|
|
164
|
+
return sum(1 for line in f) - 1 # Subtract header
|
|
165
|
+
except:
|
|
166
|
+
return 0
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# aiwaf/middleware_logger.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import csv
|
|
5
|
+
import time
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
9
|
+
from .utils import get_ip
|
|
10
|
+
|
|
11
|
+
class AIWAFLoggerMiddleware(MiddlewareMixin):
|
|
12
|
+
"""
|
|
13
|
+
Middleware that logs requests to a CSV file for AI-WAF training.
|
|
14
|
+
Acts as a fallback when main access logs are unavailable.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, get_response):
|
|
18
|
+
super().__init__(get_response)
|
|
19
|
+
self.log_file = getattr(settings, "AIWAF_MIDDLEWARE_LOG", "aiwaf_requests.log")
|
|
20
|
+
self.csv_format = getattr(settings, "AIWAF_MIDDLEWARE_CSV", True)
|
|
21
|
+
self.log_enabled = getattr(settings, "AIWAF_MIDDLEWARE_LOGGING", False)
|
|
22
|
+
|
|
23
|
+
# CSV file path (if using CSV format)
|
|
24
|
+
if self.csv_format and self.log_enabled:
|
|
25
|
+
self.csv_file = self.log_file.replace('.log', '.csv')
|
|
26
|
+
self._ensure_csv_header()
|
|
27
|
+
|
|
28
|
+
def _ensure_csv_header(self):
|
|
29
|
+
"""Ensure CSV file has proper header row"""
|
|
30
|
+
if not os.path.exists(self.csv_file):
|
|
31
|
+
os.makedirs(os.path.dirname(self.csv_file), exist_ok=True) if os.path.dirname(self.csv_file) else None
|
|
32
|
+
with open(self.csv_file, 'w', newline='', encoding='utf-8') as f:
|
|
33
|
+
writer = csv.writer(f)
|
|
34
|
+
writer.writerow([
|
|
35
|
+
'timestamp', 'ip_address', 'method', 'path', 'status_code',
|
|
36
|
+
'response_time', 'user_agent', 'referer', 'content_length'
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
def process_request(self, request):
|
|
40
|
+
"""Store request start time"""
|
|
41
|
+
request._aiwaf_start_time = time.time()
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
def process_response(self, request, response):
|
|
45
|
+
"""Log the completed request"""
|
|
46
|
+
if not self.log_enabled:
|
|
47
|
+
return response
|
|
48
|
+
|
|
49
|
+
# Calculate response time
|
|
50
|
+
start_time = getattr(request, '_aiwaf_start_time', time.time())
|
|
51
|
+
response_time = time.time() - start_time
|
|
52
|
+
|
|
53
|
+
# Extract request data
|
|
54
|
+
log_data = {
|
|
55
|
+
'timestamp': datetime.now().strftime('%d/%b/%Y:%H:%M:%S +0000'),
|
|
56
|
+
'ip_address': get_ip(request),
|
|
57
|
+
'method': request.method,
|
|
58
|
+
'path': request.path,
|
|
59
|
+
'status_code': response.status_code,
|
|
60
|
+
'response_time': f"{response_time:.3f}",
|
|
61
|
+
'user_agent': request.META.get('HTTP_USER_AGENT', '-'),
|
|
62
|
+
'referer': request.META.get('HTTP_REFERER', '-'),
|
|
63
|
+
'content_length': response.get('Content-Length', '-')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if self.csv_format:
|
|
67
|
+
self._log_to_csv(log_data)
|
|
68
|
+
else:
|
|
69
|
+
self._log_to_text(log_data)
|
|
70
|
+
|
|
71
|
+
return response
|
|
72
|
+
|
|
73
|
+
def _log_to_csv(self, data):
|
|
74
|
+
"""Write log entry to CSV file"""
|
|
75
|
+
try:
|
|
76
|
+
with open(self.csv_file, 'a', newline='', encoding='utf-8') as f:
|
|
77
|
+
writer = csv.writer(f)
|
|
78
|
+
writer.writerow([
|
|
79
|
+
data['timestamp'], data['ip_address'], data['method'],
|
|
80
|
+
data['path'], data['status_code'], data['response_time'],
|
|
81
|
+
data['user_agent'], data['referer'], data['content_length']
|
|
82
|
+
])
|
|
83
|
+
except Exception as e:
|
|
84
|
+
# Fail silently to avoid breaking the application
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
def _log_to_text(self, data):
|
|
88
|
+
"""Write log entry in common log format"""
|
|
89
|
+
try:
|
|
90
|
+
# Common Log Format with response time
|
|
91
|
+
log_line = f'{data["ip_address"]} - - [{data["timestamp"]}] "{data["method"]} {data["path"]} HTTP/1.1" {data["status_code"]} {data["content_length"]} "{data["referer"]}" "{data["user_agent"]}" response-time={data["response_time"]}\n'
|
|
92
|
+
|
|
93
|
+
with open(self.log_file, 'a', encoding='utf-8') as f:
|
|
94
|
+
f.write(log_line)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
# Fail silently to avoid breaking the application
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AIWAFCSVLogParser:
|
|
101
|
+
"""
|
|
102
|
+
Parser for AI-WAF CSV logs that converts them to the format expected by trainer.py
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def parse_csv_log(csv_file_path):
|
|
107
|
+
"""
|
|
108
|
+
Parse CSV log file and return records in the format expected by trainer.py
|
|
109
|
+
Returns list of dictionaries with keys: ip, timestamp, path, status, referer, user_agent, response_time
|
|
110
|
+
"""
|
|
111
|
+
records = []
|
|
112
|
+
|
|
113
|
+
if not os.path.exists(csv_file_path):
|
|
114
|
+
return records
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
with open(csv_file_path, 'r', newline='', encoding='utf-8') as f:
|
|
118
|
+
reader = csv.DictReader(f)
|
|
119
|
+
for row in reader:
|
|
120
|
+
try:
|
|
121
|
+
# Convert timestamp to datetime object
|
|
122
|
+
timestamp = datetime.strptime(row['timestamp'], '%d/%b/%Y:%H:%M:%S +0000')
|
|
123
|
+
|
|
124
|
+
record = {
|
|
125
|
+
'ip': row['ip_address'],
|
|
126
|
+
'timestamp': timestamp,
|
|
127
|
+
'path': row['path'],
|
|
128
|
+
'status': row['status_code'],
|
|
129
|
+
'referer': row['referer'],
|
|
130
|
+
'user_agent': row['user_agent'],
|
|
131
|
+
'response_time': float(row['response_time'])
|
|
132
|
+
}
|
|
133
|
+
records.append(record)
|
|
134
|
+
except (ValueError, KeyError) as e:
|
|
135
|
+
# Skip malformed rows
|
|
136
|
+
continue
|
|
137
|
+
except Exception as e:
|
|
138
|
+
# Return empty list if file can't be read
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
return records
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def get_log_lines_for_trainer(csv_file_path):
|
|
145
|
+
"""
|
|
146
|
+
Convert CSV log to format compatible with trainer.py's _read_all_logs()
|
|
147
|
+
Returns list of log line strings
|
|
148
|
+
"""
|
|
149
|
+
records = AIWAFCSVLogParser.parse_csv_log(csv_file_path)
|
|
150
|
+
log_lines = []
|
|
151
|
+
|
|
152
|
+
for record in records:
|
|
153
|
+
# Convert back to common log format that trainer.py expects
|
|
154
|
+
timestamp_str = record['timestamp'].strftime('%d/%b/%Y:%H:%M:%S +0000')
|
|
155
|
+
content_length = '-' # We don't track this in our format
|
|
156
|
+
|
|
157
|
+
log_line = f'{record["ip"]} - - [{timestamp_str}] "GET {record["path"]} HTTP/1.1" {record["status"]} {content_length} "{record["referer"]}" "{record["user_agent"]}" response-time={record["response_time"]:.3f}'
|
|
158
|
+
log_lines.append(log_line)
|
|
159
|
+
|
|
160
|
+
return log_lines
|
|
@@ -3,7 +3,18 @@ import numpy as np
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
from django.conf import settings
|
|
5
5
|
from django.utils import timezone
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
# Only import models if aiwaf is in INSTALLED_APPS
|
|
8
|
+
try:
|
|
9
|
+
from django.apps import apps
|
|
10
|
+
if apps.is_installed('aiwaf'):
|
|
11
|
+
from .models import FeatureSample, BlacklistEntry, IPExemption, DynamicKeyword
|
|
12
|
+
else:
|
|
13
|
+
# Create dummy classes to avoid import errors
|
|
14
|
+
FeatureSample = BlacklistEntry = IPExemption = DynamicKeyword = None
|
|
15
|
+
except (ImportError, RuntimeError):
|
|
16
|
+
# Handle cases where Django isn't fully initialized yet
|
|
17
|
+
FeatureSample = BlacklistEntry = IPExemption = DynamicKeyword = None
|
|
7
18
|
|
|
8
19
|
# Configuration
|
|
9
20
|
STORAGE_MODE = getattr(settings, "AIWAF_STORAGE_MODE", "models") # "models" or "csv"
|
|
@@ -52,22 +63,25 @@ class CsvFeatureStore:
|
|
|
52
63
|
class DbFeatureStore:
|
|
53
64
|
@staticmethod
|
|
54
65
|
def persist_rows(rows):
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
if FeatureSample is not None:
|
|
67
|
+
objs = []
|
|
68
|
+
for ip,pl,kw,rt,si,bc,t404,label in rows:
|
|
69
|
+
objs.append(FeatureSample(
|
|
70
|
+
ip=ip, path_len=pl, kw_hits=kw,
|
|
71
|
+
resp_time=rt, status_idx=si,
|
|
72
|
+
burst_count=bc, total_404=t404,
|
|
73
|
+
label=label
|
|
74
|
+
))
|
|
75
|
+
FeatureSample.objects.bulk_create(objs, ignore_conflicts=True)
|
|
64
76
|
|
|
65
77
|
@staticmethod
|
|
66
78
|
def load_matrix():
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
if FeatureSample is not None:
|
|
80
|
+
qs = FeatureSample.objects.all().values_list(
|
|
81
|
+
"path_len","kw_hits","resp_time","status_idx","burst_count","total_404"
|
|
82
|
+
)
|
|
83
|
+
return np.array(list(qs), dtype=float)
|
|
84
|
+
return np.empty((0,6))
|
|
71
85
|
|
|
72
86
|
def get_store():
|
|
73
87
|
if getattr(settings, "AIWAF_FEATURE_STORE", "csv") == "db":
|
|
@@ -266,8 +280,12 @@ def get_blacklist_store():
|
|
|
266
280
|
if STORAGE_MODE == "csv":
|
|
267
281
|
return CsvBlacklistStore
|
|
268
282
|
else:
|
|
269
|
-
# Return a wrapper for Django models
|
|
270
|
-
|
|
283
|
+
# Return a wrapper for Django models (only if models are available)
|
|
284
|
+
if BlacklistEntry is not None:
|
|
285
|
+
return ModelBlacklistStore
|
|
286
|
+
else:
|
|
287
|
+
# Fallback to CSV if models aren't available
|
|
288
|
+
return CsvBlacklistStore
|
|
271
289
|
|
|
272
290
|
|
|
273
291
|
def get_exemption_store():
|
|
@@ -275,7 +293,10 @@ def get_exemption_store():
|
|
|
275
293
|
if STORAGE_MODE == "csv":
|
|
276
294
|
return CsvExemptionStore
|
|
277
295
|
else:
|
|
278
|
-
|
|
296
|
+
if IPExemption is not None:
|
|
297
|
+
return ModelExemptionStore
|
|
298
|
+
else:
|
|
299
|
+
return CsvExemptionStore
|
|
279
300
|
|
|
280
301
|
|
|
281
302
|
def get_keyword_store():
|
|
@@ -283,7 +304,10 @@ def get_keyword_store():
|
|
|
283
304
|
if STORAGE_MODE == "csv":
|
|
284
305
|
return CsvKeywordStore
|
|
285
306
|
else:
|
|
286
|
-
|
|
307
|
+
if DynamicKeyword is not None:
|
|
308
|
+
return ModelKeywordStore
|
|
309
|
+
else:
|
|
310
|
+
return CsvKeywordStore
|
|
287
311
|
|
|
288
312
|
|
|
289
313
|
# ============= Django Model Wrappers =============
|
|
@@ -293,19 +317,25 @@ class ModelBlacklistStore:
|
|
|
293
317
|
|
|
294
318
|
@staticmethod
|
|
295
319
|
def add_ip(ip_address, reason):
|
|
296
|
-
BlacklistEntry
|
|
320
|
+
if BlacklistEntry is not None:
|
|
321
|
+
BlacklistEntry.objects.get_or_create(ip_address=ip_address, defaults={"reason": reason})
|
|
297
322
|
|
|
298
323
|
@staticmethod
|
|
299
324
|
def is_blocked(ip_address):
|
|
300
|
-
|
|
325
|
+
if BlacklistEntry is not None:
|
|
326
|
+
return BlacklistEntry.objects.filter(ip_address=ip_address).exists()
|
|
327
|
+
return False
|
|
301
328
|
|
|
302
329
|
@staticmethod
|
|
303
330
|
def get_all():
|
|
304
|
-
|
|
331
|
+
if BlacklistEntry is not None:
|
|
332
|
+
return list(BlacklistEntry.objects.values("ip_address", "reason", "created_at"))
|
|
333
|
+
return []
|
|
305
334
|
|
|
306
335
|
@staticmethod
|
|
307
336
|
def remove_ip(ip_address):
|
|
308
|
-
BlacklistEntry
|
|
337
|
+
if BlacklistEntry is not None:
|
|
338
|
+
BlacklistEntry.objects.filter(ip_address=ip_address).delete()
|
|
309
339
|
|
|
310
340
|
|
|
311
341
|
class ModelExemptionStore:
|
|
@@ -313,19 +343,25 @@ class ModelExemptionStore:
|
|
|
313
343
|
|
|
314
344
|
@staticmethod
|
|
315
345
|
def add_ip(ip_address, reason=""):
|
|
316
|
-
IPExemption
|
|
346
|
+
if IPExemption is not None:
|
|
347
|
+
IPExemption.objects.get_or_create(ip_address=ip_address, defaults={"reason": reason})
|
|
317
348
|
|
|
318
349
|
@staticmethod
|
|
319
350
|
def is_exempted(ip_address):
|
|
320
|
-
|
|
351
|
+
if IPExemption is not None:
|
|
352
|
+
return IPExemption.objects.filter(ip_address=ip_address).exists()
|
|
353
|
+
return False
|
|
321
354
|
|
|
322
355
|
@staticmethod
|
|
323
356
|
def get_all():
|
|
324
|
-
|
|
357
|
+
if IPExemption is not None:
|
|
358
|
+
return list(IPExemption.objects.values("ip_address", "reason", "created_at"))
|
|
359
|
+
return []
|
|
325
360
|
|
|
326
361
|
@staticmethod
|
|
327
362
|
def remove_ip(ip_address):
|
|
328
|
-
IPExemption
|
|
363
|
+
if IPExemption is not None:
|
|
364
|
+
IPExemption.objects.filter(ip_address=ip_address).delete()
|
|
329
365
|
|
|
330
366
|
|
|
331
367
|
class ModelKeywordStore:
|
|
@@ -333,19 +369,24 @@ class ModelKeywordStore:
|
|
|
333
369
|
|
|
334
370
|
@staticmethod
|
|
335
371
|
def add_keyword(keyword, count=1):
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
372
|
+
if DynamicKeyword is not None:
|
|
373
|
+
obj, created = DynamicKeyword.objects.get_or_create(keyword=keyword, defaults={"count": count})
|
|
374
|
+
if not created:
|
|
375
|
+
obj.count += count
|
|
376
|
+
obj.save()
|
|
340
377
|
|
|
341
378
|
@staticmethod
|
|
342
379
|
def get_top_keywords(limit=10):
|
|
343
|
-
|
|
380
|
+
if DynamicKeyword is not None:
|
|
381
|
+
return list(DynamicKeyword.objects.order_by("-count").values_list("keyword", flat=True)[:limit])
|
|
382
|
+
return []
|
|
344
383
|
|
|
345
384
|
@staticmethod
|
|
346
385
|
def remove_keyword(keyword):
|
|
347
|
-
DynamicKeyword
|
|
386
|
+
if DynamicKeyword is not None:
|
|
387
|
+
DynamicKeyword.objects.filter(keyword=keyword).delete()
|
|
348
388
|
|
|
349
389
|
@staticmethod
|
|
350
390
|
def clear_all():
|
|
351
|
-
DynamicKeyword
|
|
391
|
+
if DynamicKeyword is not None:
|
|
392
|
+
DynamicKeyword.objects.all().delete()
|
|
@@ -65,16 +65,31 @@ def remove_exempt_keywords() -> None:
|
|
|
65
65
|
|
|
66
66
|
def _read_all_logs() -> list[str]:
|
|
67
67
|
lines = []
|
|
68
|
+
|
|
69
|
+
# First try to read from main access log
|
|
68
70
|
if LOG_PATH and os.path.exists(LOG_PATH):
|
|
69
71
|
with open(LOG_PATH, "r", errors="ignore") as f:
|
|
70
72
|
lines.extend(f.readlines())
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
for p in sorted(glob.glob(f"{LOG_PATH}.*")):
|
|
74
|
+
opener = gzip.open if p.endswith(".gz") else open
|
|
75
|
+
try:
|
|
76
|
+
with opener(p, "rt", errors="ignore") as f:
|
|
77
|
+
lines.extend(f.readlines())
|
|
78
|
+
except OSError:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
# If no lines found from main log, try AI-WAF middleware CSV log
|
|
82
|
+
if not lines:
|
|
83
|
+
middleware_csv = getattr(settings, "AIWAF_MIDDLEWARE_LOG", "aiwaf_requests.log").replace('.log', '.csv')
|
|
84
|
+
if os.path.exists(middleware_csv):
|
|
85
|
+
try:
|
|
86
|
+
from .middleware_logger import AIWAFCSVLogParser
|
|
87
|
+
csv_lines = AIWAFCSVLogParser.get_log_lines_for_trainer(middleware_csv)
|
|
88
|
+
lines.extend(csv_lines)
|
|
89
|
+
print(f"📋 Using AI-WAF middleware CSV log: {middleware_csv} ({len(csv_lines)} entries)")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"⚠️ Failed to read middleware CSV log: {e}")
|
|
92
|
+
|
|
78
93
|
return lines
|
|
79
94
|
|
|
80
95
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.8.
|
|
3
|
+
Version: 0.1.8.9
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -27,6 +27,18 @@ Dynamic: requires-python
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
## 🚀 Quick Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install aiwaf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**⚠️ Important:** Add `'aiwaf'` to your Django `INSTALLED_APPS` to avoid setup errors.
|
|
37
|
+
|
|
38
|
+
**📋 Complete Setup Guide:** See [INSTALLATION.md](INSTALLATION.md) for detailed installation instructions and troubleshooting.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
30
42
|
## System Requirements
|
|
31
43
|
|
|
32
44
|
No GPU needed—AI-WAF runs entirely on CPU with just Python 3.8+, Django 3.2+, a single vCPU and ~512 MB RAM for small sites; for moderate production traffic you can bump to 2–4 vCPUs and 2–4 GB RAM, offload the daily detect-and-train job to a worker, and rotate logs to keep memory use bounded.
|
|
@@ -83,7 +95,28 @@ aiwaf/
|
|
|
83
95
|
- Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
|
|
84
96
|
|
|
85
97
|
- **UUID Tampering Protection**
|
|
86
|
-
Blocks guessed or invalid UUIDs that don
|
|
98
|
+
Blocks guessed or invalid UUIDs that don't resolve to real models.
|
|
99
|
+
|
|
100
|
+
- **Built-in Request Logger**
|
|
101
|
+
Optional middleware logger that captures requests to CSV:
|
|
102
|
+
- **Automatic fallback** when main access logs unavailable
|
|
103
|
+
- **CSV format** for easy analysis and training
|
|
104
|
+
- **Captures response times** for better anomaly detection
|
|
105
|
+
- **Zero configuration** - works out of the box
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
**Exempt Path & IP Awareness**
|
|
109
|
+
|
|
110
|
+
**Exempt Paths:**
|
|
111
|
+
AI‑WAF automatically exempts common login paths (`/admin/`, `/login/`, `/accounts/login/`, etc.) from all blocking mechanisms. You can add additional exempt paths in your Django `settings.py`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
AIWAF_EXEMPT_PATHS = [
|
|
115
|
+
"/api/webhooks/",
|
|
116
|
+
"/health/",
|
|
117
|
+
"/special-endpoint/",
|
|
118
|
+
]
|
|
119
|
+
```
|
|
87
120
|
|
|
88
121
|
|
|
89
122
|
**Exempt Path & IP Awareness**
|
|
@@ -213,6 +246,42 @@ AIWAF_CSV_DATA_DIR = "aiwaf_data" # Directory for CSV files
|
|
|
213
246
|
|
|
214
247
|
---
|
|
215
248
|
|
|
249
|
+
### Built-in Request Logger (Optional)
|
|
250
|
+
|
|
251
|
+
Enable AI-WAF's built-in request logger as a fallback when main access logs aren't available:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
# Enable middleware logging
|
|
255
|
+
AIWAF_MIDDLEWARE_LOGGING = True # Enable/disable logging
|
|
256
|
+
AIWAF_MIDDLEWARE_LOG = "aiwaf_requests.log" # Log file path
|
|
257
|
+
AIWAF_MIDDLEWARE_CSV = True # Use CSV format (recommended)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Then add middleware to MIDDLEWARE list:**
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
MIDDLEWARE = [
|
|
264
|
+
# ... your existing middleware ...
|
|
265
|
+
'aiwaf.middleware_logger.AIWAFLoggerMiddleware', # Add near the end
|
|
266
|
+
]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Manage middleware logging:**
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
python manage.py aiwaf_logging --status # Check logging status
|
|
273
|
+
python manage.py aiwaf_logging --enable # Show setup instructions
|
|
274
|
+
python manage.py aiwaf_logging --clear # Clear log files
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Benefits:**
|
|
278
|
+
- **Automatic fallback** when `AIWAF_ACCESS_LOG` unavailable
|
|
279
|
+
- **CSV format** with precise timestamps and response times
|
|
280
|
+
- **Zero configuration** - trainer automatically detects and uses CSV logs
|
|
281
|
+
- **Lightweight** - fails silently to avoid breaking your application
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
216
285
|
### Optional (defaults shown)
|
|
217
286
|
|
|
218
287
|
```python
|
|
@@ -244,14 +313,40 @@ Add in **this** order to your `MIDDLEWARE` list:
|
|
|
244
313
|
```python
|
|
245
314
|
MIDDLEWARE = [
|
|
246
315
|
"aiwaf.middleware.IPAndKeywordBlockMiddleware",
|
|
247
|
-
"aiwaf.middleware.RateLimitMiddleware",
|
|
316
|
+
"aiwaf.middleware.RateLimitMiddleware",
|
|
248
317
|
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
249
318
|
"aiwaf.middleware.HoneypotTimingMiddleware",
|
|
250
319
|
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
251
320
|
# ... other middleware ...
|
|
321
|
+
"aiwaf.middleware_logger.AIWAFLoggerMiddleware", # Optional: Add if using built-in logger
|
|
252
322
|
]
|
|
253
323
|
```
|
|
254
324
|
|
|
325
|
+
> **⚠️ Order matters!** AI-WAF protection middleware should come early. The logger middleware should come near the end to capture final response data.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Running Detection & Training
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
python manage.py detect_and_train
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### What happens:
|
|
336
|
+
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware CSV logs
|
|
337
|
+
2. Auto‑block IPs with ≥ 6 total 404s
|
|
338
|
+
3. Extract features & train IsolationForest
|
|
339
|
+
4. Save `model.pkl`
|
|
340
|
+
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
341
|
+
6. Remove any keywords associated with newly exempt paths
|
|
342
|
+
|
|
343
|
+
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware CSV logs.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 🧠 How It Works
|
|
348
|
+
```
|
|
349
|
+
|
|
255
350
|
---
|
|
256
351
|
|
|
257
352
|
## Running Detection & Training
|
|
@@ -7,6 +7,7 @@ aiwaf/apps.py
|
|
|
7
7
|
aiwaf/blacklist_manager.py
|
|
8
8
|
aiwaf/decorators.py
|
|
9
9
|
aiwaf/middleware.py
|
|
10
|
+
aiwaf/middleware_logger.py
|
|
10
11
|
aiwaf/models.py
|
|
11
12
|
aiwaf/storage.py
|
|
12
13
|
aiwaf/trainer.py
|
|
@@ -19,6 +20,7 @@ aiwaf.egg-info/top_level.txt
|
|
|
19
20
|
aiwaf/management/__init__.py
|
|
20
21
|
aiwaf/management/commands/__init__.py
|
|
21
22
|
aiwaf/management/commands/add_ipexemption.py
|
|
23
|
+
aiwaf/management/commands/aiwaf_logging.py
|
|
22
24
|
aiwaf/management/commands/aiwaf_reset.py
|
|
23
25
|
aiwaf/management/commands/detect_and_train.py
|
|
24
26
|
aiwaf/resources/model.pkl
|
|
@@ -9,7 +9,7 @@ long_description = (HERE / "README.md").read_text(encoding="utf-8")
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="aiwaf",
|
|
12
|
-
version="0.1.8.
|
|
12
|
+
version="0.1.8.9",
|
|
13
13
|
description="AI‑driven, self‑learning Web Application Firewall for Django",
|
|
14
14
|
long_description=long_description,
|
|
15
15
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|