aiwaf 0.1.9.0.5__py3-none-any.whl → 0.1.9.0.7__py3-none-any.whl
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/__init__.py +1 -1
- aiwaf/blacklist_manager.py +3 -3
- aiwaf/management/commands/add_exemption.py +30 -0
- aiwaf/management/commands/add_ipexemption.py +1 -1
- aiwaf/management/commands/clear_cache.py +18 -0
- aiwaf/management/commands/debug_csv.py +1 -1
- aiwaf/management/commands/diagnose_blocking.py +96 -0
- aiwaf/management/commands/setup_models.py +35 -0
- aiwaf/management/commands/test_exemption.py +1 -1
- aiwaf/middleware_logger.py +66 -106
- aiwaf/models.py +28 -1
- aiwaf/storage.py +166 -381
- aiwaf/trainer.py +0 -12
- {aiwaf-0.1.9.0.5.dist-info → aiwaf-0.1.9.0.7.dist-info}/METADATA +30 -27
- aiwaf-0.1.9.0.7.dist-info/RECORD +33 -0
- aiwaf-0.1.9.0.5.dist-info/RECORD +0 -29
- {aiwaf-0.1.9.0.5.dist-info → aiwaf-0.1.9.0.7.dist-info}/WHEEL +0 -0
- {aiwaf-0.1.9.0.5.dist-info → aiwaf-0.1.9.0.7.dist-info}/licenses/LICENSE +0 -0
- {aiwaf-0.1.9.0.5.dist-info → aiwaf-0.1.9.0.7.dist-info}/top_level.txt +0 -0
aiwaf/__init__.py
CHANGED
aiwaf/blacklist_manager.py
CHANGED
|
@@ -12,7 +12,7 @@ class BlacklistManager:
|
|
|
12
12
|
return # Don't block exempted IPs
|
|
13
13
|
|
|
14
14
|
store = get_blacklist_store()
|
|
15
|
-
store.
|
|
15
|
+
store.block_ip(ip, reason)
|
|
16
16
|
|
|
17
17
|
@staticmethod
|
|
18
18
|
def is_blocked(ip):
|
|
@@ -29,9 +29,9 @@ class BlacklistManager:
|
|
|
29
29
|
@staticmethod
|
|
30
30
|
def all_blocked():
|
|
31
31
|
store = get_blacklist_store()
|
|
32
|
-
return store.
|
|
32
|
+
return store.get_all_blocked_ips()
|
|
33
33
|
|
|
34
34
|
@staticmethod
|
|
35
35
|
def unblock(ip):
|
|
36
36
|
store = get_blacklist_store()
|
|
37
|
-
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}")
|
|
@@ -17,7 +17,7 @@ class Command(BaseCommand):
|
|
|
17
17
|
if store.is_exempted(ip):
|
|
18
18
|
self.stdout.write(self.style.WARNING(f'IP {ip} is already exempted.'))
|
|
19
19
|
else:
|
|
20
|
-
store.
|
|
20
|
+
store.add_exemption(ip, reason)
|
|
21
21
|
self.stdout.write(self.style.SUCCESS(f'IP {ip} added to exemption list.'))
|
|
22
22
|
if reason:
|
|
23
23
|
self.stdout.write(self.style.SUCCESS(f'Reason: {reason}'))
|
|
@@ -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")
|
|
@@ -101,7 +101,7 @@ class Command(BaseCommand):
|
|
|
101
101
|
self.stdout.write(f"Before: IP {test_ip} exempted = {is_exempted_before}")
|
|
102
102
|
|
|
103
103
|
# Add to exemption
|
|
104
|
-
exemption_store.
|
|
104
|
+
exemption_store.add_exemption(test_ip, "Test exemption from debug command")
|
|
105
105
|
self.stdout.write(f"✅ Added {test_ip} to exemption list")
|
|
106
106
|
|
|
107
107
|
# Check if now exempted
|
|
@@ -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")
|
|
@@ -63,7 +63,7 @@ class Command(BaseCommand):
|
|
|
63
63
|
|
|
64
64
|
# Create the exemption
|
|
65
65
|
try:
|
|
66
|
-
exemption_store.
|
|
66
|
+
exemption_store.add_exemption(test_ip, "Test exemption from debug")
|
|
67
67
|
self.stdout.write(self.style.SUCCESS("✅ Created test exemption"))
|
|
68
68
|
except Exception as e:
|
|
69
69
|
self.stdout.write(self.style.ERROR(f"❌ Failed to create exemption: {e}"))
|
aiwaf/middleware_logger.py
CHANGED
|
@@ -1,44 +1,39 @@
|
|
|
1
1
|
# aiwaf/middleware_logger.py
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
|
-
import csv
|
|
5
3
|
import time
|
|
6
4
|
from datetime import datetime
|
|
7
5
|
from django.conf import settings
|
|
8
6
|
from django.utils.deprecation import MiddlewareMixin
|
|
7
|
+
from django.utils import timezone
|
|
9
8
|
from .utils import get_ip
|
|
10
9
|
|
|
10
|
+
# Defer model imports to avoid AppRegistryNotReady during Django app loading
|
|
11
|
+
RequestLog = None
|
|
12
|
+
|
|
13
|
+
def _import_models():
|
|
14
|
+
"""Import Django models only when needed and apps are ready."""
|
|
15
|
+
global RequestLog
|
|
16
|
+
|
|
17
|
+
if RequestLog is not None:
|
|
18
|
+
return # Already imported
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from django.apps import apps
|
|
22
|
+
if apps.ready and apps.is_installed('aiwaf'):
|
|
23
|
+
from .models import RequestLog
|
|
24
|
+
except (ImportError, RuntimeError, Exception):
|
|
25
|
+
# Keep models as None if can't import
|
|
26
|
+
pass
|
|
27
|
+
|
|
11
28
|
class AIWAFLoggerMiddleware(MiddlewareMixin):
|
|
12
29
|
"""
|
|
13
|
-
Middleware that logs requests to
|
|
30
|
+
Middleware that logs requests to Django models for AI-WAF training.
|
|
14
31
|
Acts as a fallback when main access logs are unavailable.
|
|
15
32
|
"""
|
|
16
33
|
|
|
17
34
|
def __init__(self, get_response):
|
|
18
35
|
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
36
|
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
|
-
# Create directory if it doesn't exist
|
|
32
|
-
csv_dir = os.path.dirname(self.csv_file)
|
|
33
|
-
if csv_dir and not os.path.exists(csv_dir):
|
|
34
|
-
os.makedirs(csv_dir, exist_ok=True)
|
|
35
|
-
|
|
36
|
-
with open(self.csv_file, 'w', newline='', encoding='utf-8') as f:
|
|
37
|
-
writer = csv.writer(f)
|
|
38
|
-
writer.writerow([
|
|
39
|
-
'timestamp', 'ip_address', 'method', 'path', 'status_code',
|
|
40
|
-
'response_time', 'user_agent', 'referer', 'content_length'
|
|
41
|
-
])
|
|
42
37
|
|
|
43
38
|
def process_request(self, request):
|
|
44
39
|
"""Store request start time"""
|
|
@@ -46,7 +41,7 @@ class AIWAFLoggerMiddleware(MiddlewareMixin):
|
|
|
46
41
|
return None
|
|
47
42
|
|
|
48
43
|
def process_response(self, request, response):
|
|
49
|
-
"""Log the completed request"""
|
|
44
|
+
"""Log the completed request to Django model"""
|
|
50
45
|
if not self.log_enabled:
|
|
51
46
|
return response
|
|
52
47
|
|
|
@@ -54,116 +49,81 @@ class AIWAFLoggerMiddleware(MiddlewareMixin):
|
|
|
54
49
|
start_time = getattr(request, '_aiwaf_start_time', time.time())
|
|
55
50
|
response_time = time.time() - start_time
|
|
56
51
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
52
|
+
# Import models and log to database
|
|
53
|
+
_import_models()
|
|
54
|
+
if RequestLog is not None:
|
|
55
|
+
try:
|
|
56
|
+
RequestLog.objects.create(
|
|
57
|
+
ip_address=get_ip(request),
|
|
58
|
+
method=request.method,
|
|
59
|
+
path=request.path[:500], # Truncate long paths
|
|
60
|
+
status_code=response.status_code,
|
|
61
|
+
response_time=response_time,
|
|
62
|
+
user_agent=request.META.get('HTTP_USER_AGENT', '')[:2000], # Truncate long user agents
|
|
63
|
+
referer=request.META.get('HTTP_REFERER', '')[:500], # Truncate long referers
|
|
64
|
+
content_length=response.get('Content-Length', '-'),
|
|
65
|
+
timestamp=timezone.now()
|
|
66
|
+
)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
# Fail silently to avoid breaking the application
|
|
69
|
+
pass
|
|
74
70
|
|
|
75
71
|
return response
|
|
76
|
-
|
|
77
|
-
def _log_to_csv(self, data):
|
|
78
|
-
"""Write log entry to CSV file"""
|
|
79
|
-
try:
|
|
80
|
-
# Ensure directory exists before writing
|
|
81
|
-
csv_dir = os.path.dirname(self.csv_file)
|
|
82
|
-
if csv_dir and not os.path.exists(csv_dir):
|
|
83
|
-
os.makedirs(csv_dir, exist_ok=True)
|
|
84
|
-
|
|
85
|
-
with open(self.csv_file, 'a', newline='', encoding='utf-8') as f:
|
|
86
|
-
writer = csv.writer(f)
|
|
87
|
-
writer.writerow([
|
|
88
|
-
data['timestamp'], data['ip_address'], data['method'],
|
|
89
|
-
data['path'], data['status_code'], data['response_time'],
|
|
90
|
-
data['user_agent'], data['referer'], data['content_length']
|
|
91
|
-
])
|
|
92
|
-
except Exception as e:
|
|
93
|
-
# Fail silently to avoid breaking the application
|
|
94
|
-
pass
|
|
95
|
-
|
|
96
|
-
def _log_to_text(self, data):
|
|
97
|
-
"""Write log entry in common log format"""
|
|
98
|
-
try:
|
|
99
|
-
# Common Log Format with response time
|
|
100
|
-
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'
|
|
101
|
-
|
|
102
|
-
with open(self.log_file, 'a', encoding='utf-8') as f:
|
|
103
|
-
f.write(log_line)
|
|
104
|
-
except Exception as e:
|
|
105
|
-
# Fail silently to avoid breaking the application
|
|
106
|
-
pass
|
|
107
72
|
|
|
108
73
|
|
|
109
|
-
class
|
|
74
|
+
class AIWAFModelLogParser:
|
|
110
75
|
"""
|
|
111
|
-
Parser for AI-WAF
|
|
76
|
+
Parser for AI-WAF Django model logs that converts them to the format expected by trainer.py
|
|
112
77
|
"""
|
|
113
78
|
|
|
114
79
|
@staticmethod
|
|
115
|
-
def
|
|
80
|
+
def parse_model_logs():
|
|
116
81
|
"""
|
|
117
|
-
Parse
|
|
82
|
+
Parse Django model logs and return records in the format expected by trainer.py
|
|
118
83
|
Returns list of dictionaries with keys: ip, timestamp, path, status, referer, user_agent, response_time
|
|
119
84
|
"""
|
|
120
85
|
records = []
|
|
121
86
|
|
|
122
|
-
|
|
87
|
+
_import_models()
|
|
88
|
+
if RequestLog is None:
|
|
123
89
|
return records
|
|
124
90
|
|
|
125
91
|
try:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
'response_time': float(row['response_time'])
|
|
141
|
-
}
|
|
142
|
-
records.append(record)
|
|
143
|
-
except (ValueError, KeyError) as e:
|
|
144
|
-
# Skip malformed rows
|
|
145
|
-
continue
|
|
92
|
+
# Get all request logs
|
|
93
|
+
logs = RequestLog.objects.all().order_by('-timestamp')
|
|
94
|
+
|
|
95
|
+
for log in logs:
|
|
96
|
+
record = {
|
|
97
|
+
'ip': str(log.ip_address),
|
|
98
|
+
'timestamp': log.timestamp,
|
|
99
|
+
'path': log.path,
|
|
100
|
+
'status': str(log.status_code),
|
|
101
|
+
'referer': log.referer if log.referer else '-',
|
|
102
|
+
'user_agent': log.user_agent if log.user_agent else '-',
|
|
103
|
+
'response_time': log.response_time
|
|
104
|
+
}
|
|
105
|
+
records.append(record)
|
|
146
106
|
except Exception as e:
|
|
147
|
-
# Return empty list if
|
|
107
|
+
# Return empty list if models can't be accessed
|
|
148
108
|
pass
|
|
149
109
|
|
|
150
110
|
return records
|
|
151
111
|
|
|
152
112
|
@staticmethod
|
|
153
|
-
def get_log_lines_for_trainer(
|
|
113
|
+
def get_log_lines_for_trainer():
|
|
154
114
|
"""
|
|
155
|
-
Convert
|
|
115
|
+
Convert Django model logs to format compatible with trainer.py's _read_all_logs()
|
|
156
116
|
Returns list of log line strings
|
|
157
117
|
"""
|
|
158
|
-
records =
|
|
118
|
+
records = AIWAFModelLogParser.parse_model_logs()
|
|
159
119
|
log_lines = []
|
|
160
120
|
|
|
161
121
|
for record in records:
|
|
162
|
-
# Convert
|
|
122
|
+
# Convert to common log format that trainer.py expects
|
|
163
123
|
timestamp_str = record['timestamp'].strftime('%d/%b/%Y:%H:%M:%S +0000')
|
|
164
|
-
content_length = '-' # We don't track this in
|
|
124
|
+
content_length = '-' # We don't track this in detail
|
|
165
125
|
|
|
166
|
-
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}'
|
|
126
|
+
log_line = f'{record["ip"]} - - [{timestamp_str}] "{record.get("method", "GET")} {record["path"]} HTTP/1.1" {record["status"]} {content_length} "{record["referer"]}" "{record["user_agent"]}" response-time={record["response_time"]:.3f}'
|
|
167
127
|
log_lines.append(log_line)
|
|
168
128
|
|
|
169
129
|
return log_lines
|
aiwaf/models.py
CHANGED
|
@@ -43,4 +43,31 @@ class IPExemption(models.Model):
|
|
|
43
43
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
44
44
|
|
|
45
45
|
def __str__(self):
|
|
46
|
-
return f"{self.ip_address} (Exempted: {self.reason})"
|
|
46
|
+
return f"{self.ip_address} (Exempted: {self.reason})"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Model to store request logs for AI-WAF training
|
|
50
|
+
class RequestLog(models.Model):
|
|
51
|
+
ip_address = models.GenericIPAddressField(db_index=True)
|
|
52
|
+
method = models.CharField(max_length=10)
|
|
53
|
+
path = models.CharField(max_length=500)
|
|
54
|
+
status_code = models.IntegerField()
|
|
55
|
+
response_time = models.FloatField()
|
|
56
|
+
user_agent = models.TextField(blank=True, default="")
|
|
57
|
+
referer = models.CharField(max_length=500, blank=True, default="")
|
|
58
|
+
content_length = models.CharField(max_length=20, blank=True, default="-")
|
|
59
|
+
timestamp = models.DateTimeField(auto_now_add=True)
|
|
60
|
+
|
|
61
|
+
class Meta:
|
|
62
|
+
verbose_name = "Request Log"
|
|
63
|
+
verbose_name_plural = "Request Logs"
|
|
64
|
+
indexes = [
|
|
65
|
+
models.Index(fields=["ip_address"]),
|
|
66
|
+
models.Index(fields=["timestamp"]),
|
|
67
|
+
models.Index(fields=["status_code"]),
|
|
68
|
+
models.Index(fields=["method"]),
|
|
69
|
+
]
|
|
70
|
+
ordering = ['-timestamp']
|
|
71
|
+
|
|
72
|
+
def __str__(self):
|
|
73
|
+
return f"{self.ip_address} {self.method} {self.path} - {self.status_code}"
|