aiwaf 0.1.9.0.4__tar.gz → 0.1.9.0.6__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.9.0.4 → aiwaf-0.1.9.0.6}/PKG-INFO +30 -27
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/README.md +29 -26
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/__init__.py +1 -1
- aiwaf-0.1.9.0.6/aiwaf/blacklist_manager.py +37 -0
- aiwaf-0.1.9.0.6/aiwaf/management/commands/add_exemption.py +30 -0
- aiwaf-0.1.9.0.6/aiwaf/management/commands/clear_cache.py +18 -0
- aiwaf-0.1.9.0.6/aiwaf/management/commands/diagnose_blocking.py +96 -0
- aiwaf-0.1.9.0.6/aiwaf/management/commands/setup_models.py +35 -0
- aiwaf-0.1.9.0.6/aiwaf/management/commands/test_exemption.py +120 -0
- aiwaf-0.1.9.0.6/aiwaf/management/commands/test_exemption_fix.py +54 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/middleware.py +28 -16
- aiwaf-0.1.9.0.6/aiwaf/middleware_logger.py +129 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/models.py +28 -1
- aiwaf-0.1.9.0.6/aiwaf/storage.py +229 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/trainer.py +0 -12
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf.egg-info/PKG-INFO +30 -27
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf.egg-info/SOURCES.txt +6 -1
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/pyproject.toml +1 -1
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/setup.py +1 -1
- aiwaf-0.1.9.0.4/aiwaf/blacklist_manager.py +0 -24
- aiwaf-0.1.9.0.4/aiwaf/management/commands/debug_csv.py +0 -155
- aiwaf-0.1.9.0.4/aiwaf/middleware_logger.py +0 -169
- aiwaf-0.1.9.0.4/aiwaf/storage.py +0 -423
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/LICENSE +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/apps.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/decorators.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/__init__.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/commands/__init__.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/commands/add_ipexemption.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/commands/aiwaf_diagnose.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/commands/aiwaf_logging.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/commands/aiwaf_reset.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/commands/detect_and_train.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/management/commands/regenerate_model.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/resources/model.pkl +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/templatetags/__init__.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/templatetags/aiwaf_tags.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf/utils.py +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf.egg-info/dependency_links.txt +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf.egg-info/requires.txt +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/aiwaf.egg-info/top_level.txt +0 -0
- {aiwaf-0.1.9.0.4 → aiwaf-0.1.9.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.9.0.
|
|
3
|
+
Version: 0.1.9.0.6
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -68,7 +68,7 @@ aiwaf/
|
|
|
68
68
|
## 🚀 Features
|
|
69
69
|
|
|
70
70
|
- **IP Blocklist**
|
|
71
|
-
Instantly blocks suspicious IPs
|
|
71
|
+
Instantly blocks suspicious IPs using Django models with real-time performance.
|
|
72
72
|
|
|
73
73
|
- **Rate Limiting**
|
|
74
74
|
Sliding‑window blocks flooders (> `AIWAF_RATE_MAX` per `AIWAF_RATE_WINDOW`), then blacklists them.
|
|
@@ -98,9 +98,9 @@ aiwaf/
|
|
|
98
98
|
Blocks guessed or invalid UUIDs that don't resolve to real models.
|
|
99
99
|
|
|
100
100
|
- **Built-in Request Logger**
|
|
101
|
-
Optional middleware logger that captures requests to
|
|
101
|
+
Optional middleware logger that captures requests to Django models:
|
|
102
102
|
- **Automatic fallback** when main access logs unavailable
|
|
103
|
-
- **
|
|
103
|
+
- **Real-time storage** in database for instant access
|
|
104
104
|
- **Captures response times** for better anomaly detection
|
|
105
105
|
- **Zero configuration** - works out of the box
|
|
106
106
|
|
|
@@ -221,28 +221,33 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
|
221
221
|
|
|
222
222
|
---
|
|
223
223
|
|
|
224
|
-
###
|
|
224
|
+
### Database Models
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
AI-WAF uses Django models for real-time, high-performance storage:
|
|
227
227
|
|
|
228
228
|
```python
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
|
|
229
|
+
# All data is stored in Django models - no configuration needed
|
|
230
|
+
# Tables created automatically with migrations:
|
|
231
|
+
# - aiwaf_blacklistentry # Blocked IP addresses
|
|
232
|
+
# - aiwaf_ipexemption # Exempt IP addresses
|
|
233
|
+
# - aiwaf_dynamickeyword # Dynamic keywords with counts
|
|
234
|
+
# - aiwaf_featuresample # Feature samples for ML training
|
|
235
|
+
# - aiwaf_requestlog # Request logs (if middleware logging enabled)
|
|
235
236
|
```
|
|
236
237
|
|
|
237
|
-
**
|
|
238
|
-
- No
|
|
239
|
-
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
238
|
+
**Benefits of Django Models:**
|
|
239
|
+
- ⚡ **Real-time performance** - No file I/O bottlenecks
|
|
240
|
+
- 🔄 **Instant updates** - Changes visible immediately across all processes
|
|
241
|
+
- 🚀 **Better concurrency** - No file locking issues
|
|
242
|
+
- 📊 **Rich querying** - Use Django ORM for complex operations
|
|
243
|
+
- 🔍 **Admin integration** - View/manage data through Django admin
|
|
244
|
+
|
|
245
|
+
**Database Setup:**
|
|
246
|
+
```bash
|
|
247
|
+
# Create and apply migrations
|
|
248
|
+
python manage.py makemigrations aiwaf
|
|
249
|
+
python manage.py migrate aiwaf
|
|
250
|
+
```
|
|
246
251
|
|
|
247
252
|
---
|
|
248
253
|
|
|
@@ -253,8 +258,6 @@ Enable AI-WAF's built-in request logger as a fallback when main access logs aren
|
|
|
253
258
|
```python
|
|
254
259
|
# Enable middleware logging
|
|
255
260
|
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
261
|
```
|
|
259
262
|
|
|
260
263
|
**Then add middleware to MIDDLEWARE list:**
|
|
@@ -276,8 +279,8 @@ python manage.py aiwaf_logging --clear # Clear log files
|
|
|
276
279
|
|
|
277
280
|
**Benefits:**
|
|
278
281
|
- **Automatic fallback** when `AIWAF_ACCESS_LOG` unavailable
|
|
279
|
-
- **
|
|
280
|
-
- **Zero configuration** - trainer automatically detects and uses
|
|
282
|
+
- **Database storage** with precise timestamps and response times
|
|
283
|
+
- **Zero configuration** - trainer automatically detects and uses model logs
|
|
281
284
|
- **Lightweight** - fails silently to avoid breaking your application
|
|
282
285
|
|
|
283
286
|
---
|
|
@@ -386,7 +389,7 @@ python manage.py detect_and_train
|
|
|
386
389
|
```
|
|
387
390
|
|
|
388
391
|
### What happens:
|
|
389
|
-
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware
|
|
392
|
+
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware model logs
|
|
390
393
|
2. Auto‑block IPs with ≥ 6 total 404s
|
|
391
394
|
3. Extract features & train IsolationForest
|
|
392
395
|
4. Save `model.pkl` with current scikit-learn version
|
|
@@ -411,7 +414,7 @@ python manage.py detect_and_train
|
|
|
411
414
|
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
412
415
|
6. Remove any keywords associated with newly exempt paths
|
|
413
416
|
|
|
414
|
-
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware
|
|
417
|
+
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware model logs.
|
|
415
418
|
|
|
416
419
|
---
|
|
417
420
|
|
|
@@ -47,7 +47,7 @@ aiwaf/
|
|
|
47
47
|
## 🚀 Features
|
|
48
48
|
|
|
49
49
|
- **IP Blocklist**
|
|
50
|
-
Instantly blocks suspicious IPs
|
|
50
|
+
Instantly blocks suspicious IPs using Django models with real-time performance.
|
|
51
51
|
|
|
52
52
|
- **Rate Limiting**
|
|
53
53
|
Sliding‑window blocks flooders (> `AIWAF_RATE_MAX` per `AIWAF_RATE_WINDOW`), then blacklists them.
|
|
@@ -77,9 +77,9 @@ aiwaf/
|
|
|
77
77
|
Blocks guessed or invalid UUIDs that don't resolve to real models.
|
|
78
78
|
|
|
79
79
|
- **Built-in Request Logger**
|
|
80
|
-
Optional middleware logger that captures requests to
|
|
80
|
+
Optional middleware logger that captures requests to Django models:
|
|
81
81
|
- **Automatic fallback** when main access logs unavailable
|
|
82
|
-
- **
|
|
82
|
+
- **Real-time storage** in database for instant access
|
|
83
83
|
- **Captures response times** for better anomaly detection
|
|
84
84
|
- **Zero configuration** - works out of the box
|
|
85
85
|
|
|
@@ -200,28 +200,33 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
|
200
200
|
|
|
201
201
|
---
|
|
202
202
|
|
|
203
|
-
###
|
|
203
|
+
### Database Models
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
AI-WAF uses Django models for real-time, high-performance storage:
|
|
206
206
|
|
|
207
207
|
```python
|
|
208
|
-
#
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
#
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
# All data is stored in Django models - no configuration needed
|
|
209
|
+
# Tables created automatically with migrations:
|
|
210
|
+
# - aiwaf_blacklistentry # Blocked IP addresses
|
|
211
|
+
# - aiwaf_ipexemption # Exempt IP addresses
|
|
212
|
+
# - aiwaf_dynamickeyword # Dynamic keywords with counts
|
|
213
|
+
# - aiwaf_featuresample # Feature samples for ML training
|
|
214
|
+
# - aiwaf_requestlog # Request logs (if middleware logging enabled)
|
|
214
215
|
```
|
|
215
216
|
|
|
216
|
-
**
|
|
217
|
-
- No
|
|
218
|
-
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
217
|
+
**Benefits of Django Models:**
|
|
218
|
+
- ⚡ **Real-time performance** - No file I/O bottlenecks
|
|
219
|
+
- 🔄 **Instant updates** - Changes visible immediately across all processes
|
|
220
|
+
- 🚀 **Better concurrency** - No file locking issues
|
|
221
|
+
- 📊 **Rich querying** - Use Django ORM for complex operations
|
|
222
|
+
- 🔍 **Admin integration** - View/manage data through Django admin
|
|
223
|
+
|
|
224
|
+
**Database Setup:**
|
|
225
|
+
```bash
|
|
226
|
+
# Create and apply migrations
|
|
227
|
+
python manage.py makemigrations aiwaf
|
|
228
|
+
python manage.py migrate aiwaf
|
|
229
|
+
```
|
|
225
230
|
|
|
226
231
|
---
|
|
227
232
|
|
|
@@ -232,8 +237,6 @@ Enable AI-WAF's built-in request logger as a fallback when main access logs aren
|
|
|
232
237
|
```python
|
|
233
238
|
# Enable middleware logging
|
|
234
239
|
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
240
|
```
|
|
238
241
|
|
|
239
242
|
**Then add middleware to MIDDLEWARE list:**
|
|
@@ -255,8 +258,8 @@ python manage.py aiwaf_logging --clear # Clear log files
|
|
|
255
258
|
|
|
256
259
|
**Benefits:**
|
|
257
260
|
- **Automatic fallback** when `AIWAF_ACCESS_LOG` unavailable
|
|
258
|
-
- **
|
|
259
|
-
- **Zero configuration** - trainer automatically detects and uses
|
|
261
|
+
- **Database storage** with precise timestamps and response times
|
|
262
|
+
- **Zero configuration** - trainer automatically detects and uses model logs
|
|
260
263
|
- **Lightweight** - fails silently to avoid breaking your application
|
|
261
264
|
|
|
262
265
|
---
|
|
@@ -365,7 +368,7 @@ python manage.py detect_and_train
|
|
|
365
368
|
```
|
|
366
369
|
|
|
367
370
|
### What happens:
|
|
368
|
-
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware
|
|
371
|
+
1. Read access logs (incl. rotated or gzipped) **OR** AI-WAF middleware model logs
|
|
369
372
|
2. Auto‑block IPs with ≥ 6 total 404s
|
|
370
373
|
3. Extract features & train IsolationForest
|
|
371
374
|
4. Save `model.pkl` with current scikit-learn version
|
|
@@ -390,7 +393,7 @@ python manage.py detect_and_train
|
|
|
390
393
|
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
391
394
|
6. Remove any keywords associated with newly exempt paths
|
|
392
395
|
|
|
393
|
-
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware
|
|
396
|
+
**Note:** If main access log (`AIWAF_ACCESS_LOG`) is unavailable, trainer automatically falls back to AI-WAF middleware model logs.
|
|
394
397
|
|
|
395
398
|
---
|
|
396
399
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# aiwaf/blacklist_manager.py
|
|
2
|
+
|
|
3
|
+
from .storage import get_blacklist_store, get_exemption_store
|
|
4
|
+
|
|
5
|
+
class BlacklistManager:
|
|
6
|
+
@staticmethod
|
|
7
|
+
def block(ip, reason):
|
|
8
|
+
"""Add IP to blacklist, but only if it's not exempted"""
|
|
9
|
+
# Check if IP is exempted before blocking
|
|
10
|
+
exemption_store = get_exemption_store()
|
|
11
|
+
if exemption_store.is_exempted(ip):
|
|
12
|
+
return # Don't block exempted IPs
|
|
13
|
+
|
|
14
|
+
store = get_blacklist_store()
|
|
15
|
+
store.block_ip(ip, reason)
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def is_blocked(ip):
|
|
19
|
+
"""Check if IP is blocked, but respect exemptions"""
|
|
20
|
+
# First check if IP is exempted - exemptions override blacklist
|
|
21
|
+
exemption_store = get_exemption_store()
|
|
22
|
+
if exemption_store.is_exempted(ip):
|
|
23
|
+
return False # Exempted IPs are never considered blocked
|
|
24
|
+
|
|
25
|
+
# If not exempted, check blacklist
|
|
26
|
+
store = get_blacklist_store()
|
|
27
|
+
return store.is_blocked(ip)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def all_blocked():
|
|
31
|
+
store = get_blacklist_store()
|
|
32
|
+
return store.get_all_blocked_ips()
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def unblock(ip):
|
|
36
|
+
store = get_blacklist_store()
|
|
37
|
+
store.unblock_ip(ip)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
from aiwaf.storage import get_exemption_store
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
help = 'Add IP to exemption list using Django models'
|
|
8
|
+
|
|
9
|
+
def add_arguments(self, parser):
|
|
10
|
+
parser.add_argument('ip', help='IP address to exempt')
|
|
11
|
+
parser.add_argument('--reason', default='Manual exemption', help='Reason for exemption')
|
|
12
|
+
|
|
13
|
+
def handle(self, *args, **options):
|
|
14
|
+
ip = options['ip']
|
|
15
|
+
reason = options['reason']
|
|
16
|
+
|
|
17
|
+
self.stdout.write(f"Adding IP {ip} to exemption list...")
|
|
18
|
+
|
|
19
|
+
exemption_store = get_exemption_store()
|
|
20
|
+
exemption_store.add_exemption(ip, reason)
|
|
21
|
+
|
|
22
|
+
# Verify it was added
|
|
23
|
+
if exemption_store.is_exempted(ip):
|
|
24
|
+
self.stdout.write(self.style.SUCCESS(f"✅ Successfully exempted IP: {ip}"))
|
|
25
|
+
else:
|
|
26
|
+
self.stdout.write(self.style.ERROR(f"❌ Failed to exempt IP: {ip}"))
|
|
27
|
+
|
|
28
|
+
# Show all exempted IPs
|
|
29
|
+
all_exempted = exemption_store.get_all_exempted_ips()
|
|
30
|
+
self.stdout.write(f"\nAll exempted IPs: {all_exempted}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
from django.core.cache import cache
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
help = 'Clear Django cache'
|
|
8
|
+
|
|
9
|
+
def handle(self, *args, **options):
|
|
10
|
+
cache.clear()
|
|
11
|
+
self.stdout.write(self.style.SUCCESS("✅ Django cache cleared successfully!"))
|
|
12
|
+
|
|
13
|
+
# Also show what was cleared
|
|
14
|
+
self.stdout.write("🧹 Cleared all cached data including:")
|
|
15
|
+
self.stdout.write(" - Rate limiting data")
|
|
16
|
+
self.stdout.write(" - Blacklist cache")
|
|
17
|
+
self.stdout.write(" - AI anomaly data")
|
|
18
|
+
self.stdout.write(" - Honeypot timing data")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
from django.core.cache import cache
|
|
5
|
+
from aiwaf.blacklist_manager import BlacklistManager
|
|
6
|
+
from aiwaf.storage import get_exemption_store, get_blacklist_store
|
|
7
|
+
from aiwaf.utils import get_ip
|
|
8
|
+
from django.test import RequestFactory
|
|
9
|
+
|
|
10
|
+
class Command(BaseCommand):
|
|
11
|
+
help = 'Comprehensive diagnosis of blocking issues'
|
|
12
|
+
|
|
13
|
+
def add_arguments(self, parser):
|
|
14
|
+
parser.add_argument('--ip', default='97.187.30.95', help='IP address to test')
|
|
15
|
+
parser.add_argument('--clear-cache', action='store_true', help='Clear Django cache')
|
|
16
|
+
|
|
17
|
+
def handle(self, *args, **options):
|
|
18
|
+
test_ip = options['ip']
|
|
19
|
+
|
|
20
|
+
self.stdout.write(f"\n🔍 Comprehensive Blocking Diagnosis for IP: {test_ip}")
|
|
21
|
+
self.stdout.write("=" * 60)
|
|
22
|
+
|
|
23
|
+
if options['clear_cache']:
|
|
24
|
+
cache.clear()
|
|
25
|
+
self.stdout.write("🧹 Cleared Django cache")
|
|
26
|
+
|
|
27
|
+
# 1. Check exemption status
|
|
28
|
+
exemption_store = get_exemption_store()
|
|
29
|
+
is_exempted = exemption_store.is_exempted(test_ip)
|
|
30
|
+
self.stdout.write(f"1. ✅ IP exempted in storage: {is_exempted}")
|
|
31
|
+
|
|
32
|
+
# 2. Check blacklist status
|
|
33
|
+
blacklist_store = get_blacklist_store()
|
|
34
|
+
is_in_blacklist = blacklist_store.is_blocked(test_ip)
|
|
35
|
+
self.stdout.write(f"2. 🚫 IP in blacklist storage: {is_in_blacklist}")
|
|
36
|
+
|
|
37
|
+
# 3. Check BlacklistManager final decision
|
|
38
|
+
manager_blocked = BlacklistManager.is_blocked(test_ip)
|
|
39
|
+
self.stdout.write(f"3. 🎯 BlacklistManager says blocked: {manager_blocked}")
|
|
40
|
+
|
|
41
|
+
# 4. Check Django cache for blacklist entries
|
|
42
|
+
cache_key = f"blacklist:{test_ip}"
|
|
43
|
+
cached_value = cache.get(cache_key)
|
|
44
|
+
self.stdout.write(f"4. 💾 Cache value for blacklist:{test_ip}: {cached_value}")
|
|
45
|
+
|
|
46
|
+
# 5. Test what IP would be detected from a request
|
|
47
|
+
factory = RequestFactory()
|
|
48
|
+
|
|
49
|
+
# Test different scenarios
|
|
50
|
+
scenarios = [
|
|
51
|
+
("Direct IP", {'REMOTE_ADDR': test_ip}),
|
|
52
|
+
("X-Forwarded-For", {'HTTP_X_FORWARDED_FOR': test_ip}),
|
|
53
|
+
("X-Real-IP", {'HTTP_X_REAL_IP': test_ip}),
|
|
54
|
+
("CloudFlare", {'HTTP_CF_CONNECTING_IP': test_ip}),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
self.stdout.write(f"\n5. 🌐 IP Detection Tests:")
|
|
58
|
+
for name, meta in scenarios:
|
|
59
|
+
request = factory.get('/', **meta)
|
|
60
|
+
detected_ip = get_ip(request)
|
|
61
|
+
self.stdout.write(f" {name}: {detected_ip}")
|
|
62
|
+
if detected_ip == test_ip:
|
|
63
|
+
self.stdout.write(f" ✅ Match!")
|
|
64
|
+
|
|
65
|
+
# 6. Check rate limiting cache entries
|
|
66
|
+
self.stdout.write(f"\n6. 🚦 Rate Limiting Cache Entries:")
|
|
67
|
+
rate_keys = [
|
|
68
|
+
f"ratelimit:{test_ip}",
|
|
69
|
+
f"aiwaf:{test_ip}",
|
|
70
|
+
f"honeypot_get:{test_ip}"
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
for key in rate_keys:
|
|
74
|
+
value = cache.get(key)
|
|
75
|
+
if value:
|
|
76
|
+
self.stdout.write(f" {key}: {value}")
|
|
77
|
+
else:
|
|
78
|
+
self.stdout.write(f" {key}: None")
|
|
79
|
+
|
|
80
|
+
# 7. Summary
|
|
81
|
+
self.stdout.write(f"\n📋 SUMMARY:")
|
|
82
|
+
if is_exempted and not manager_blocked:
|
|
83
|
+
self.stdout.write(self.style.SUCCESS("✅ IP should NOT be blocked"))
|
|
84
|
+
if options.get('still_blocked'):
|
|
85
|
+
self.stdout.write(self.style.WARNING("⚠️ If still blocked, check:"))
|
|
86
|
+
self.stdout.write(" - Web server logs (nginx, apache)")
|
|
87
|
+
self.stdout.write(" - Other middleware or security software")
|
|
88
|
+
self.stdout.write(" - Browser cache/cookies")
|
|
89
|
+
elif not is_exempted:
|
|
90
|
+
self.stdout.write(self.style.WARNING(f"⚠️ IP {test_ip} is NOT exempted"))
|
|
91
|
+
elif manager_blocked:
|
|
92
|
+
self.stdout.write(self.style.ERROR(f"❌ IP is being blocked despite exemption"))
|
|
93
|
+
|
|
94
|
+
self.stdout.write(f"\n💡 To clear all caches and reset:")
|
|
95
|
+
self.stdout.write(f" python manage.py shell -c \"from django.core.cache import cache; cache.clear()\"")
|
|
96
|
+
self.stdout.write(f"=" * 60)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
from django.db import transaction
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
help = 'Create Django database migrations for AI-WAF models (after removing CSV support)'
|
|
8
|
+
|
|
9
|
+
def handle(self, *args, **options):
|
|
10
|
+
self.stdout.write("🔄 Creating AI-WAF database migrations...")
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# Import the management command functions
|
|
14
|
+
from django.core.management import call_command
|
|
15
|
+
|
|
16
|
+
# Create migrations for aiwaf app
|
|
17
|
+
self.stdout.write("Creating migrations for aiwaf models...")
|
|
18
|
+
call_command('makemigrations', 'aiwaf', verbosity=2)
|
|
19
|
+
|
|
20
|
+
# Apply migrations
|
|
21
|
+
self.stdout.write("Applying migrations...")
|
|
22
|
+
call_command('migrate', 'aiwaf', verbosity=2)
|
|
23
|
+
|
|
24
|
+
self.stdout.write(self.style.SUCCESS("✅ Successfully created and applied AI-WAF migrations!"))
|
|
25
|
+
self.stdout.write("")
|
|
26
|
+
self.stdout.write("🎯 Next steps:")
|
|
27
|
+
self.stdout.write("1. Add your IP to exemptions: python manage.py add_exemption YOUR_IP")
|
|
28
|
+
self.stdout.write("2. Test the system: python manage.py diagnose_blocking --ip YOUR_IP")
|
|
29
|
+
self.stdout.write("3. Clear any old cache: python manage.py clear_cache")
|
|
30
|
+
|
|
31
|
+
except Exception as e:
|
|
32
|
+
self.stdout.write(self.style.ERROR(f"❌ Error during migration: {e}"))
|
|
33
|
+
self.stdout.write("You may need to run these commands manually:")
|
|
34
|
+
self.stdout.write(" python manage.py makemigrations aiwaf")
|
|
35
|
+
self.stdout.write(" python manage.py migrate aiwaf")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
class Command(BaseCommand):
|
|
5
|
+
help = 'Test AI-WAF exemption functionality step by step'
|
|
6
|
+
|
|
7
|
+
def add_arguments(self, parser):
|
|
8
|
+
parser.add_argument(
|
|
9
|
+
'test_ip',
|
|
10
|
+
type=str,
|
|
11
|
+
help='IP address to test exemption for'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
def handle(self, *args, **options):
|
|
15
|
+
test_ip = options['test_ip']
|
|
16
|
+
|
|
17
|
+
self.stdout.write(self.style.HTTP_INFO(f"🧪 Testing Exemption for IP: {test_ip}"))
|
|
18
|
+
self.stdout.write("=" * 50)
|
|
19
|
+
|
|
20
|
+
# Step 1: Check settings
|
|
21
|
+
from django.conf import settings
|
|
22
|
+
storage_mode = getattr(settings, 'AIWAF_STORAGE_MODE', 'models')
|
|
23
|
+
csv_dir = getattr(settings, 'AIWAF_CSV_DATA_DIR', 'aiwaf_data')
|
|
24
|
+
|
|
25
|
+
self.stdout.write(f"Storage Mode: {storage_mode}")
|
|
26
|
+
self.stdout.write(f"CSV Directory: {csv_dir}")
|
|
27
|
+
self.stdout.write("")
|
|
28
|
+
|
|
29
|
+
# Step 2: Check storage factory
|
|
30
|
+
try:
|
|
31
|
+
from aiwaf.storage import get_exemption_store, EXEMPTION_CSV, CSV_DATA_DIR, STORAGE_MODE
|
|
32
|
+
exemption_store = get_exemption_store()
|
|
33
|
+
|
|
34
|
+
self.stdout.write(f"Exemption Store Class: {exemption_store.__name__}")
|
|
35
|
+
self.stdout.write(f"Expected CSV File: {EXEMPTION_CSV}")
|
|
36
|
+
self.stdout.write(f"CSV Directory: {CSV_DATA_DIR}")
|
|
37
|
+
self.stdout.write(f"Storage Mode from storage.py: {STORAGE_MODE}")
|
|
38
|
+
self.stdout.write("")
|
|
39
|
+
|
|
40
|
+
except Exception as e:
|
|
41
|
+
self.stdout.write(self.style.ERROR(f"❌ Storage import failed: {e}"))
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Step 3: Check file existence
|
|
45
|
+
if os.path.exists(EXEMPTION_CSV):
|
|
46
|
+
self.stdout.write(self.style.SUCCESS(f"✅ Exemption CSV exists: {EXEMPTION_CSV}"))
|
|
47
|
+
|
|
48
|
+
# Read and display file contents
|
|
49
|
+
try:
|
|
50
|
+
with open(EXEMPTION_CSV, 'r', encoding='utf-8') as f:
|
|
51
|
+
content = f.read().strip()
|
|
52
|
+
if content:
|
|
53
|
+
self.stdout.write(f"📄 File contents:\n{content}")
|
|
54
|
+
self.stdout.write("")
|
|
55
|
+
else:
|
|
56
|
+
self.stdout.write("📄 File is empty")
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
self.stdout.write(self.style.ERROR(f"❌ Could not read file: {e}"))
|
|
60
|
+
else:
|
|
61
|
+
self.stdout.write(self.style.ERROR(f"❌ Exemption CSV not found: {EXEMPTION_CSV}"))
|
|
62
|
+
self.stdout.write("Creating test exemption...")
|
|
63
|
+
|
|
64
|
+
# Create the exemption
|
|
65
|
+
try:
|
|
66
|
+
exemption_store.add_ip(test_ip, "Test exemption from debug")
|
|
67
|
+
self.stdout.write(self.style.SUCCESS("✅ Created test exemption"))
|
|
68
|
+
except Exception as e:
|
|
69
|
+
self.stdout.write(self.style.ERROR(f"❌ Failed to create exemption: {e}"))
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
# Step 4: Test exemption check via storage
|
|
73
|
+
try:
|
|
74
|
+
is_exempted_storage = exemption_store.is_exempted(test_ip)
|
|
75
|
+
self.stdout.write(f"Direct storage check: {test_ip} exempted = {is_exempted_storage}")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self.stdout.write(self.style.ERROR(f"❌ Storage exemption check failed: {e}"))
|
|
78
|
+
|
|
79
|
+
# Step 5: Test exemption check via utils function
|
|
80
|
+
try:
|
|
81
|
+
from aiwaf.utils import is_ip_exempted
|
|
82
|
+
is_exempted_utils = is_ip_exempted(test_ip)
|
|
83
|
+
self.stdout.write(f"Utils function check: {test_ip} exempted = {is_exempted_utils}")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
self.stdout.write(self.style.ERROR(f"❌ Utils exemption check failed: {e}"))
|
|
86
|
+
|
|
87
|
+
# Step 6: Test middleware import
|
|
88
|
+
try:
|
|
89
|
+
from aiwaf.middleware import IPAndKeywordBlockMiddleware
|
|
90
|
+
self.stdout.write("✅ Middleware import successful")
|
|
91
|
+
except Exception as e:
|
|
92
|
+
self.stdout.write(self.style.ERROR(f"❌ Middleware import failed: {e}"))
|
|
93
|
+
|
|
94
|
+
# Step 7: Test CSV reading manually
|
|
95
|
+
if os.path.exists(EXEMPTION_CSV):
|
|
96
|
+
try:
|
|
97
|
+
import csv
|
|
98
|
+
self.stdout.write("\n📋 Manual CSV parsing:")
|
|
99
|
+
with open(EXEMPTION_CSV, 'r', newline='', encoding='utf-8') as f:
|
|
100
|
+
reader = csv.DictReader(f)
|
|
101
|
+
found = False
|
|
102
|
+
for i, row in enumerate(reader):
|
|
103
|
+
ip_in_row = row.get('ip_address', 'N/A')
|
|
104
|
+
self.stdout.write(f" Row {i}: ip_address = '{ip_in_row}'")
|
|
105
|
+
if ip_in_row == test_ip:
|
|
106
|
+
found = True
|
|
107
|
+
self.stdout.write(f" ✅ Found match for {test_ip}")
|
|
108
|
+
|
|
109
|
+
if not found:
|
|
110
|
+
self.stdout.write(f" ❌ No match found for {test_ip}")
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
self.stdout.write(self.style.ERROR(f"❌ Manual CSV parsing failed: {e}"))
|
|
114
|
+
|
|
115
|
+
self.stdout.write("")
|
|
116
|
+
self.stdout.write(self.style.HTTP_INFO("💡 Debugging Tips:"))
|
|
117
|
+
self.stdout.write("1. Check that AIWAF_STORAGE_MODE = 'csv' in settings.py")
|
|
118
|
+
self.stdout.write("2. Ensure the CSV file has proper headers: ip_address,reason,created_at")
|
|
119
|
+
self.stdout.write("3. Check file permissions on the CSV directory")
|
|
120
|
+
self.stdout.write("4. Verify no trailing/leading spaces in IP addresses")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
from aiwaf.blacklist_manager import BlacklistManager
|
|
5
|
+
from aiwaf.storage import get_exemption_store
|
|
6
|
+
|
|
7
|
+
class Command(BaseCommand):
|
|
8
|
+
help = 'Test that exempted IPs are properly honored by BlacklistManager'
|
|
9
|
+
|
|
10
|
+
def add_arguments(self, parser):
|
|
11
|
+
parser.add_argument('--ip', default='97.187.30.95', help='IP address to test')
|
|
12
|
+
|
|
13
|
+
def handle(self, *args, **options):
|
|
14
|
+
test_ip = options['ip']
|
|
15
|
+
|
|
16
|
+
self.stdout.write(f"\n=== Testing Exemption Fix for IP: {test_ip} ===")
|
|
17
|
+
|
|
18
|
+
# Check exemption store
|
|
19
|
+
exemption_store = get_exemption_store()
|
|
20
|
+
is_exempted = exemption_store.is_exempted(test_ip)
|
|
21
|
+
self.stdout.write(f"1. Is IP exempted in storage? {is_exempted}")
|
|
22
|
+
|
|
23
|
+
# Test BlacklistManager.block() - should not block exempted IPs
|
|
24
|
+
self.stdout.write(f"\n2. Testing BlacklistManager.block() on exempted IP...")
|
|
25
|
+
BlacklistManager.block(test_ip, "Test block attempt")
|
|
26
|
+
|
|
27
|
+
# Check if actually blocked
|
|
28
|
+
is_blocked = BlacklistManager.is_blocked(test_ip)
|
|
29
|
+
self.stdout.write(f"3. Is IP blocked after block attempt? {is_blocked}")
|
|
30
|
+
|
|
31
|
+
if is_exempted and not is_blocked:
|
|
32
|
+
self.stdout.write(self.style.SUCCESS("✅ PASS: Exempted IP was NOT blocked"))
|
|
33
|
+
elif is_exempted and is_blocked:
|
|
34
|
+
self.stdout.write(self.style.ERROR("❌ FAIL: Exempted IP was blocked (this should not happen)"))
|
|
35
|
+
elif not is_exempted:
|
|
36
|
+
self.stdout.write(self.style.WARNING("⚠️ IP is not exempted, blocking behavior is normal"))
|
|
37
|
+
|
|
38
|
+
# Test with a non-exempted IP to verify blocking still works
|
|
39
|
+
test_non_exempted = "1.2.3.4"
|
|
40
|
+
self.stdout.write(f"\n4. Testing with non-exempted IP: {test_non_exempted}")
|
|
41
|
+
|
|
42
|
+
is_exempted_2 = exemption_store.is_exempted(test_non_exempted)
|
|
43
|
+
self.stdout.write(f" Is non-exempted IP exempted? {is_exempted_2}")
|
|
44
|
+
|
|
45
|
+
BlacklistManager.block(test_non_exempted, "Test block non-exempted")
|
|
46
|
+
is_blocked_2 = BlacklistManager.is_blocked(test_non_exempted)
|
|
47
|
+
self.stdout.write(f" Is non-exempted IP blocked? {is_blocked_2}")
|
|
48
|
+
|
|
49
|
+
if not is_exempted_2 and is_blocked_2:
|
|
50
|
+
self.stdout.write(self.style.SUCCESS("✅ PASS: Non-exempted IP was properly blocked"))
|
|
51
|
+
else:
|
|
52
|
+
self.stdout.write(self.style.ERROR("❌ FAIL: Non-exempted IP blocking failed"))
|
|
53
|
+
|
|
54
|
+
self.stdout.write(f"\n=== Test Complete ===")
|